mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-22 08:42:38 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dd122631c |
No files matched your search
+12
-59
@@ -12,23 +12,17 @@ jobs:
|
|||||||
config:
|
config:
|
||||||
- name: Release
|
- name: Release
|
||||||
lower: release
|
lower: release
|
||||||
flavor: standard
|
|
||||||
|
|
||||||
- name: Debug
|
- name: Debug
|
||||||
lower: debug
|
lower: debug
|
||||||
flavor: standard
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
lower: release
|
|
||||||
flavor: googlePlay
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Java JDK
|
- name: Setup Java JDK
|
||||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 21
|
java-version: 21
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
@@ -40,19 +34,9 @@ jobs:
|
|||||||
- name: Fix gradle permissions
|
- name: Fix gradle permissions
|
||||||
run: chmod +x ./gradlew
|
run: chmod +x ./gradlew
|
||||||
|
|
||||||
- name: Build project (standard)
|
- name: Build project
|
||||||
if: ${{ matrix.config.flavor == 'standard' }}
|
|
||||||
run: |
|
run: |
|
||||||
./gradlew app:assembleStandard${{ matrix.config.name }} \
|
./gradlew app:assemble${{ matrix.config.name }} \
|
||||||
-Pandroid.injected.signing.store.file="$HOME/release.keystore" \
|
|
||||||
-Pandroid.injected.signing.store.password="${{secrets.KEYSTORE_PASSWORD}}" \
|
|
||||||
-Pandroid.injected.signing.key.alias="${{secrets.KEY_ALIAS}}" \
|
|
||||||
-Pandroid.injected.signing.key.password="${{secrets.KEY_PASSWORD}}"
|
|
||||||
|
|
||||||
- name: Build project (Google Play)
|
|
||||||
if: ${{ matrix.config.flavor == 'googlePlay' }}
|
|
||||||
run: |
|
|
||||||
./gradlew app:bundleGooglePlayRelease -PdisableAbiSplits \
|
|
||||||
-Pandroid.injected.signing.store.file="$HOME/release.keystore" \
|
-Pandroid.injected.signing.store.file="$HOME/release.keystore" \
|
||||||
-Pandroid.injected.signing.store.password="${{secrets.KEYSTORE_PASSWORD}}" \
|
-Pandroid.injected.signing.store.password="${{secrets.KEYSTORE_PASSWORD}}" \
|
||||||
-Pandroid.injected.signing.key.alias="${{secrets.KEY_ALIAS}}" \
|
-Pandroid.injected.signing.key.alias="${{secrets.KEY_ALIAS}}" \
|
||||||
@@ -60,57 +44,35 @@ jobs:
|
|||||||
|
|
||||||
- name: Rename universal apk
|
- name: Rename universal apk
|
||||||
id: rename_apk_universal
|
id: rename_apk_universal
|
||||||
if: ${{ matrix.config.flavor == 'standard' }}
|
|
||||||
run: |
|
run: |
|
||||||
SHORT_SHA=${GITHUB_SHA::7}
|
SHORT_SHA=${GITHUB_SHA::7}
|
||||||
APK_PATH=${{github.workspace}}/app/build/outputs/apk/standard/${{ matrix.config.lower }}/app-standard-universal-${{ matrix.config.lower }}.apk
|
APK_PATH=${{github.workspace}}/app/build/outputs/apk/${{ matrix.config.lower }}/app-universal-${{ matrix.config.lower }}.apk
|
||||||
OUT_PATH=${{github.workspace}}/geode-launcher-${{ matrix.config.lower }}-$SHORT_SHA.apk
|
OUT_PATH=${{github.workspace}}/geode-launcher-${{ matrix.config.lower }}-$SHORT_SHA.apk
|
||||||
mv $APK_PATH $OUT_PATH
|
mv $APK_PATH $OUT_PATH
|
||||||
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Rename apk
|
- name: Rename apk
|
||||||
id: rename_apk
|
id: rename_apk
|
||||||
if: ${{ matrix.config.flavor == 'standard' }}
|
|
||||||
run: |
|
run: |
|
||||||
SHORT_SHA=${GITHUB_SHA::7}
|
SHORT_SHA=${GITHUB_SHA::7}
|
||||||
APK_PATH=${{github.workspace}}/app/build/outputs/apk/standard/${{ matrix.config.lower }}/app-standard-armeabi-v7a-${{ matrix.config.lower }}.apk
|
APK_PATH=${{github.workspace}}/app/build/outputs/apk/${{ matrix.config.lower }}/app-armeabi-v7a-${{ matrix.config.lower }}.apk
|
||||||
OUT_PATH=${{github.workspace}}/geode-launcher-${{ matrix.config.lower }}-android32-$SHORT_SHA.apk
|
OUT_PATH=${{github.workspace}}/geode-launcher-${{ matrix.config.lower }}-android32-$SHORT_SHA.apk
|
||||||
mv $APK_PATH $OUT_PATH
|
mv $APK_PATH $OUT_PATH
|
||||||
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Rename mappings
|
- name: Rename mappings
|
||||||
id: rename_mappings
|
id: rename_mappings
|
||||||
if: ${{ matrix.config.flavor == 'standard' && matrix.config.lower == 'release' }}
|
if: ${{ matrix.config.lower == 'release' }}
|
||||||
run: |
|
run: |
|
||||||
SHORT_SHA=${GITHUB_SHA::7}
|
SHORT_SHA=${GITHUB_SHA::7}
|
||||||
MAPPING_PATH=${{github.workspace}}/app/build/outputs/mapping/standardRelease/mapping.txt
|
MAPPING_PATH=${{github.workspace}}/app/build/outputs/mapping/${{ matrix.config.lower }}/mapping.txt
|
||||||
OUT_PATH=${{github.workspace}}/mapping-${{ matrix.config.lower }}-$SHORT_SHA.txt
|
|
||||||
mv $MAPPING_PATH $OUT_PATH
|
|
||||||
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Rename AAB
|
|
||||||
id: rename_aab
|
|
||||||
if: ${{ matrix.config.flavor == 'googlePlay' }}
|
|
||||||
run: |
|
|
||||||
SHORT_SHA=${GITHUB_SHA::7}
|
|
||||||
AAB_PATH=${{github.workspace}}/app/build/outputs/bundle/googlePlayRelease/app-googlePlay-release.aab
|
|
||||||
OUT_PATH=${{github.workspace}}/geode-launcher-${{ matrix.config.lower }}-$SHORT_SHA.aab
|
|
||||||
mv $AAB_PATH $OUT_PATH
|
|
||||||
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Rename AAB mappings
|
|
||||||
id: rename_aab_mappings
|
|
||||||
if: ${{ matrix.config.flavor == 'googlePlay' }}
|
|
||||||
run: |
|
|
||||||
SHORT_SHA=${GITHUB_SHA::7}
|
|
||||||
MAPPING_PATH=${{github.workspace}}/app/build/outputs/mapping/googlePlayRelease/mapping.txt
|
|
||||||
OUT_PATH=${{github.workspace}}/mapping-${{ matrix.config.lower }}-$SHORT_SHA.txt
|
OUT_PATH=${{github.workspace}}/mapping-${{ matrix.config.lower }}-$SHORT_SHA.txt
|
||||||
mv $MAPPING_PATH $OUT_PATH
|
mv $MAPPING_PATH $OUT_PATH
|
||||||
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload release build
|
- name: Upload release build
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ matrix.config.flavor == 'standard' && matrix.config.lower == 'release' }}
|
if: ${{ matrix.config.lower == 'release' }}
|
||||||
with:
|
with:
|
||||||
name: geode-launcher-${{ matrix.config.lower }}
|
name: geode-launcher-${{ matrix.config.lower }}
|
||||||
path: |
|
path: |
|
||||||
@@ -119,19 +81,10 @@ jobs:
|
|||||||
${{ steps.rename_mappings.outputs.path }}
|
${{ steps.rename_mappings.outputs.path }}
|
||||||
|
|
||||||
- name: Upload debug build
|
- name: Upload debug build
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ matrix.config.flavor == 'standard' && matrix.config.lower == 'debug' }}
|
if: ${{ matrix.config.lower == 'debug' }}
|
||||||
with:
|
with:
|
||||||
name: geode-launcher-${{ matrix.config.lower }}
|
name: geode-launcher-${{ matrix.config.lower }}
|
||||||
path: |
|
path: |
|
||||||
${{ steps.rename_apk_universal.outputs.path }}
|
${{ steps.rename_apk_universal.outputs.path }}
|
||||||
${{ steps.rename_apk.outputs.path }}
|
${{ steps.rename_apk.outputs.path }}
|
||||||
|
|
||||||
- name: Upload Google Play build
|
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
||||||
if: ${{ matrix.config.flavor == 'googlePlay' }}
|
|
||||||
with:
|
|
||||||
name: geode-launcher-google-play
|
|
||||||
path: |
|
|
||||||
${{ steps.rename_aab.outputs.path }}
|
|
||||||
${{ steps.rename_aab_mappings.outputs.path }}
|
|
||||||
+24
-287
@@ -1,8 +1,4 @@
|
|||||||
import groovy.json.JsonSlurper
|
val composeBOM: String by rootProject.extra
|
||||||
import java.io.IOException
|
|
||||||
import java.net.URI
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.util.zip.ZipInputStream
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
@@ -16,241 +12,15 @@ plugins {
|
|||||||
id("org.jetbrains.kotlin.plugin.compose")
|
id("org.jetbrains.kotlin.plugin.compose")
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentGeodeVersion = "5.10.1"
|
|
||||||
|
|
||||||
abstract class DownloadVersionMetadataTask : DefaultTask() {
|
|
||||||
@get:Input
|
|
||||||
abstract val geodeVersion: Property<String>
|
|
||||||
|
|
||||||
@get:OutputFile
|
|
||||||
abstract val targetFile: RegularFileProperty
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
fun execute() {
|
|
||||||
val downloadGeodeVersion = geodeVersion.get()
|
|
||||||
val destinationFile = targetFile.get().asFile
|
|
||||||
|
|
||||||
val releaseUrl = "https://api.geode-sdk.org/v1/loader/versions/$downloadGeodeVersion"
|
|
||||||
URI.create(releaseUrl).toURL().openStream().use { response ->
|
|
||||||
destinationFile.outputStream().use { destination ->
|
|
||||||
response.copyTo(destination)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class DownloadGeodeFileTask : DefaultTask() {
|
|
||||||
@get:Input
|
|
||||||
abstract val platform: Property<String>
|
|
||||||
|
|
||||||
@get:InputFile
|
|
||||||
abstract val metadata: RegularFileProperty
|
|
||||||
|
|
||||||
private fun downloadAndVerifyFile(filename: String, url: String, hash: String?): File {
|
|
||||||
logger.info("Downloading $filename from $url")
|
|
||||||
|
|
||||||
val tempFile = File(temporaryDir, filename)
|
|
||||||
|
|
||||||
URI.create(url).toURL().openStream().use { response ->
|
|
||||||
tempFile.outputStream().use { output ->
|
|
||||||
response.copyTo(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hash != null) {
|
|
||||||
val downloadedHash = MessageDigest.getInstance("SHA-256")
|
|
||||||
.digest(tempFile.readBytes())
|
|
||||||
.toHexString()
|
|
||||||
|
|
||||||
if (downloadedHash != hash) {
|
|
||||||
throw GradleException("Hash check failed for file $filename, expected $hash but found $downloadedHash.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tempFile
|
|
||||||
}
|
|
||||||
|
|
||||||
fun downloadZip(fallbackUrl: String?): File {
|
|
||||||
val downloadPlatform = platform.get()
|
|
||||||
|
|
||||||
val metadataFile = metadata.get().asFile
|
|
||||||
val json = JsonSlurper().parse(metadataFile) as Map<*, *>
|
|
||||||
|
|
||||||
val payload = json["payload"] as? Map<*, *>
|
|
||||||
|
|
||||||
val downloads = payload?.get("downloads") as? Map<*, *>
|
|
||||||
val platformInfo = downloads?.get(downloadPlatform) as? Map<*, *>
|
|
||||||
|
|
||||||
val downloadUrl = platformInfo?.get("url") as? String ?: fallbackUrl
|
|
||||||
if (downloadUrl == null) {
|
|
||||||
throw GradleException("Failed to fetch download url for platform $platform.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val expectedHash = platformInfo?.get("hash") as? String
|
|
||||||
|
|
||||||
return downloadAndVerifyFile("$downloadPlatform.zip", downloadUrl, expectedHash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class DownloadAndUnzipGeodeTask : DownloadGeodeFileTask() {
|
|
||||||
@get:Input
|
|
||||||
abstract val geodeVersion: Property<String>
|
|
||||||
|
|
||||||
@get:OutputFile
|
|
||||||
abstract val targetFile: RegularFileProperty
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
fun execute() {
|
|
||||||
val downloadGeodeVersion = geodeVersion.get()
|
|
||||||
val downloadPlatform = platform.get()
|
|
||||||
val destinationFile = targetFile.get().asFile
|
|
||||||
|
|
||||||
val tempOutput = this.downloadZip("https://github.com/geode-sdk/geode/releases/download/v$downloadGeodeVersion/geode-v$downloadGeodeVersion-$downloadPlatform.zip")
|
|
||||||
|
|
||||||
val expectedFile = "Geode.$downloadPlatform.so"
|
|
||||||
|
|
||||||
ZipInputStream(tempOutput.inputStream()).use { zip ->
|
|
||||||
var entry = zip.nextEntry
|
|
||||||
while (entry != null) {
|
|
||||||
if (entry.name == expectedFile) {
|
|
||||||
destinationFile.outputStream().use {
|
|
||||||
zip.copyTo(it)
|
|
||||||
}
|
|
||||||
return@execute
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = zip.nextEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
throw GradleException("Could not find Geode release.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class DownloadAndUnzipGeodeResourcesTask : DownloadGeodeFileTask() {
|
|
||||||
init {
|
|
||||||
platform.set("resources")
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:Input
|
|
||||||
abstract val geodeVersion: Property<String>
|
|
||||||
|
|
||||||
@get:OutputDirectory
|
|
||||||
abstract val outputDir: DirectoryProperty
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
fun execute() {
|
|
||||||
val downloadGeodeVersion = geodeVersion.get()
|
|
||||||
|
|
||||||
val tempOutput = this.downloadZip("https://github.com/geode-sdk/geode/releases/download/v$downloadGeodeVersion/resources.zip")
|
|
||||||
|
|
||||||
val baseOutput = outputDir.get().asFile
|
|
||||||
if (baseOutput.exists()) baseOutput.deleteRecursively()
|
|
||||||
|
|
||||||
val output = File(baseOutput, "geode.loader")
|
|
||||||
output.mkdirs()
|
|
||||||
|
|
||||||
val canonicalOutput = output.canonicalPath
|
|
||||||
|
|
||||||
ZipInputStream(tempOutput.inputStream()).use { zipStream ->
|
|
||||||
var entry = zipStream.nextEntry
|
|
||||||
while (entry != null) {
|
|
||||||
val destination = File(output, entry.name)
|
|
||||||
if (!destination.canonicalPath.startsWith(canonicalOutput)) {
|
|
||||||
throw GradleException("attempted copy to outside of output directory: ${entry.name}")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destination.isDirectory) {
|
|
||||||
destination.mkdirs()
|
|
||||||
} else {
|
|
||||||
destination.parentFile?.mkdirs()
|
|
||||||
|
|
||||||
destination.outputStream().use { destinationStream ->
|
|
||||||
zipStream.copyTo(destinationStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = zipStream.nextEntry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val fetchGeodeMetadataTask = tasks.register<DownloadVersionMetadataTask>("fetchGeodeMetadata") {
|
|
||||||
description = "Fetches and saves a Geode version's metadata"
|
|
||||||
inputs.property("geodeVersion", currentGeodeVersion)
|
|
||||||
|
|
||||||
geodeVersion = currentGeodeVersion
|
|
||||||
targetFile = temporaryDir.resolve("metadata.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
val downloadGeode32Task = tasks.register<DownloadAndUnzipGeodeTask>("downloadGeode32Binary") {
|
|
||||||
description = "Downloads the 32bit Geode release archive"
|
|
||||||
inputs.property("geodeVersion", currentGeodeVersion)
|
|
||||||
|
|
||||||
geodeVersion = currentGeodeVersion
|
|
||||||
platform = "android32"
|
|
||||||
metadata = fetchGeodeMetadataTask.flatMap { it.targetFile }
|
|
||||||
targetFile = temporaryDir.resolve("libgeode.so")
|
|
||||||
}
|
|
||||||
|
|
||||||
val downloadGeode64Task = tasks.register<DownloadAndUnzipGeodeTask>("downloadGeode64Binary") {
|
|
||||||
description = "Downloads the 64bit Geode release archive"
|
|
||||||
inputs.property("geodeVersion", currentGeodeVersion)
|
|
||||||
|
|
||||||
geodeVersion = currentGeodeVersion
|
|
||||||
platform = "android64"
|
|
||||||
metadata = fetchGeodeMetadataTask.flatMap { it.targetFile }
|
|
||||||
targetFile = temporaryDir.resolve("libgeode.so")
|
|
||||||
}
|
|
||||||
|
|
||||||
val downloadGeodeResourcesTask = tasks.register<DownloadAndUnzipGeodeResourcesTask>("downloadGeodeResources") {
|
|
||||||
description = "Downloads Geode resources"
|
|
||||||
inputs.property("geodeVersion", currentGeodeVersion)
|
|
||||||
|
|
||||||
geodeVersion = currentGeodeVersion
|
|
||||||
metadata = fetchGeodeMetadataTask.flatMap { it.targetFile }
|
|
||||||
outputDir = layout.buildDirectory.dir("geode-resources")
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class MergeGeodeFilesTask : DefaultTask() {
|
|
||||||
@get:InputFile
|
|
||||||
abstract val binary32: RegularFileProperty
|
|
||||||
|
|
||||||
@get:InputFile
|
|
||||||
abstract val binary64: RegularFileProperty
|
|
||||||
|
|
||||||
@get:OutputDirectory
|
|
||||||
abstract val outputDir: DirectoryProperty
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
fun execute() {
|
|
||||||
val out = outputDir.get().asFile
|
|
||||||
out.deleteRecursively()
|
|
||||||
out.mkdirs()
|
|
||||||
|
|
||||||
binary32.get().asFile.copyTo(outputDir.file("armeabi-v7a/libgeode.so").get().asFile)
|
|
||||||
binary64.get().asFile.copyTo(outputDir.file("arm64-v8a/libgeode.so").get().asFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val fullDownloadTask = tasks.register<MergeGeodeFilesTask>("downloadGeodeFull") {
|
|
||||||
description = "Downloads both Geode releases"
|
|
||||||
|
|
||||||
binary32 = downloadGeode32Task.flatMap { it.targetFile }
|
|
||||||
binary64 = downloadGeode64Task.flatMap { it.targetFile }
|
|
||||||
outputDir = layout.buildDirectory.dir("geode-prebuilt")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk = 37
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.geode.launcher"
|
applicationId = "com.geode.launcher"
|
||||||
minSdk = 23
|
minSdk = 23
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 30
|
versionCode = 26
|
||||||
versionName = "1.8.0"
|
versionName = "1.7.3"
|
||||||
|
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
@@ -269,7 +39,7 @@ android {
|
|||||||
|
|
||||||
splits {
|
splits {
|
||||||
abi {
|
abi {
|
||||||
isEnable = !project.hasProperty("disableAbiSplits")
|
isEnable = true
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
//noinspection ChromeOsAbiSupport. i'm sorry!
|
//noinspection ChromeOsAbiSupport. i'm sorry!
|
||||||
@@ -279,24 +49,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions += "distribution"
|
|
||||||
|
|
||||||
productFlavors {
|
|
||||||
create("standard") {
|
|
||||||
dimension = "distribution"
|
|
||||||
isDefault = true
|
|
||||||
buildConfigField("boolean", "GOOGLE_PLAY_BUILD", "false")
|
|
||||||
buildConfigField("String", "PREBUNDLED_GEODE", "null")
|
|
||||||
}
|
|
||||||
|
|
||||||
create("googlePlay") {
|
|
||||||
dimension = "distribution"
|
|
||||||
applicationId = "com.geode.launcher.play"
|
|
||||||
buildConfigField("boolean", "GOOGLE_PLAY_BUILD", "true")
|
|
||||||
buildConfigField("String", "PREBUNDLED_GEODE", "\"$currentGeodeVersion\"")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
@@ -312,13 +64,12 @@ android {
|
|||||||
// enables a polyfill for java Instant on api levels < 26 (used for updater)
|
// enables a polyfill for java Instant on api levels < 26 (used for updater)
|
||||||
isCoreLibraryDesugaringEnabled = true
|
isCoreLibraryDesugaringEnabled = true
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
prefab = true
|
|
||||||
}
|
}
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
@@ -327,7 +78,7 @@ android {
|
|||||||
}
|
}
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
version = "3.21.0+"
|
version = "4.0.0+"
|
||||||
path = file("src/main/cpp/CMakeLists.txt")
|
path = file("src/main/cpp/CMakeLists.txt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,44 +86,30 @@ android {
|
|||||||
ndkVersion = "29.0.14206865"
|
ndkVersion = "29.0.14206865"
|
||||||
}
|
}
|
||||||
|
|
||||||
androidComponents {
|
|
||||||
onVariants(selector().withFlavor("distribution" to "googlePlay")) { variant ->
|
|
||||||
variant.sources.jniLibs?.addGeneratedSourceDirectory(
|
|
||||||
fullDownloadTask,
|
|
||||||
MergeGeodeFilesTask::outputDir
|
|
||||||
)
|
|
||||||
variant.sources.assets?.addGeneratedSourceDirectory(
|
|
||||||
downloadGeodeResourcesTask,
|
|
||||||
DownloadAndUnzipGeodeResourcesTask::outputDir
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation (platform("androidx.compose:compose-bom:2026.08.00"))
|
implementation (platform("androidx.compose:compose-bom:$composeBOM"))
|
||||||
implementation ("androidx.core:core-ktx:1.19.0")
|
implementation ("androidx.core:core-ktx:1.17.0")
|
||||||
implementation ("androidx.compose.ui:ui")
|
implementation ("androidx.compose.ui:ui")
|
||||||
implementation ("androidx.compose.material3:material3")
|
implementation ("androidx.compose.material3:material3")
|
||||||
implementation ("androidx.compose.ui:ui-tooling-preview")
|
implementation ("androidx.compose.ui:ui-tooling-preview")
|
||||||
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.11.0")
|
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
|
||||||
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0")
|
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
|
||||||
implementation ("androidx.activity:activity-compose:1.13.0")
|
implementation ("androidx.activity:activity-compose:1.12.4")
|
||||||
implementation ("androidx.activity:activity-ktx:1.13.0")
|
implementation ("androidx.activity:activity-ktx:1.12.4")
|
||||||
implementation ("androidx.appcompat:appcompat:1.8.0")
|
implementation ("androidx.appcompat:appcompat:1.7.1")
|
||||||
implementation ("androidx.documentfile:documentfile:1.1.0")
|
implementation ("androidx.documentfile:documentfile:1.1.0")
|
||||||
implementation ("androidx.games:games-frame-pacing:2.1.3")
|
|
||||||
|
|
||||||
implementation ("com.squareup.okio:okio:3.18.2")
|
implementation ("com.squareup.okio:okio:3.16.4")
|
||||||
implementation ("com.squareup.okhttp3:okhttp:5.5.0")
|
implementation ("com.squareup.okhttp3:okhttp:5.3.2")
|
||||||
implementation ("com.squareup.okhttp3:okhttp-coroutines:5.5.0")
|
implementation ("com.squareup.okhttp3:okhttp-coroutines:5.3.2")
|
||||||
|
|
||||||
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
|
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
|
||||||
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.11.0")
|
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.10.0")
|
||||||
implementation ("org.jetbrains.kotlinx:kotlinx-datetime:0.8.0")
|
implementation ("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1")
|
||||||
|
|
||||||
implementation ("com.mikepenz:multiplatform-markdown-renderer-android:0.45.0")
|
implementation ("com.mikepenz:multiplatform-markdown-renderer-android:0.39.2")
|
||||||
implementation ("com.mikepenz:multiplatform-markdown-renderer-m3:0.45.0")
|
implementation ("com.mikepenz:multiplatform-markdown-renderer-m3:0.39.2")
|
||||||
implementation ("androidx.browser:browser:1.10.0")
|
implementation ("androidx.browser:browser:1.9.0")
|
||||||
debugImplementation ("androidx.compose.ui:ui-tooling")
|
debugImplementation ("androidx.compose.ui:ui-tooling")
|
||||||
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.5")
|
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||||
}
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<uses-permission
|
|
||||||
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
|
|
||||||
tools:node="remove" />
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
@@ -17,6 +17,8 @@
|
|||||||
android:maxSdkVersion="32" />
|
android:maxSdkVersion="32" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
@@ -101,7 +103,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".preferences.TextViewActivity"
|
android:name=".preferences.GeodeLogViewActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.GeodeLauncher">
|
android:theme="@style/Theme.GeodeLauncher">
|
||||||
</activity>
|
</activity>
|
||||||
@@ -125,7 +127,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".GeometryDashActivity"
|
android:name=".GeometryDashActivity"
|
||||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard"
|
android:configChanges="orientation|screenSize|smallestScreenSize"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:screenOrientation="landscape"
|
android:screenOrientation="landscape"
|
||||||
@@ -193,26 +195,6 @@
|
|||||||
<data android:scheme="geode-launcher" android:host="main" />
|
<data android:scheme="geode-launcher" android:host="main" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
<activity-alias
|
|
||||||
android:name=".MainActivitySapphire"
|
|
||||||
android:enabled="false"
|
|
||||||
android:exported="true"
|
|
||||||
android:icon="@mipmap/ic_launcher_sapphire"
|
|
||||||
android:label="@string/application_icon_sapphire"
|
|
||||||
android:targetActivity=".MainActivity">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="geode-launcher" android:host="main" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity-alias>
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
android:name=".MainActivityGeode"
|
android:name=".MainActivityGeode"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
@@ -236,7 +218,7 @@
|
|||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".UserDirectoryProvider"
|
android:name=".UserDirectoryProvider"
|
||||||
android:authorities="${applicationId}.user"
|
android:authorities="com.geode.launcher.user"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||||
@@ -246,7 +228,7 @@
|
|||||||
</provider>
|
</provider>
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="com.geode.launcher.fileprovider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
cmake_minimum_required(VERSION 3.21)
|
cmake_minimum_required(VERSION 4.1.2)
|
||||||
|
|
||||||
project(launcherfix)
|
project(launcherfix)
|
||||||
|
|
||||||
find_package(games-frame-pacing REQUIRED CONFIG)
|
add_library(launcherfix SHARED launcher-fix.cpp patcher.cpp)
|
||||||
|
|
||||||
add_library(launcherfix SHARED launcher-fix.cpp patcher.cpp pacing.cpp)
|
|
||||||
|
|
||||||
target_compile_features(launcherfix PUBLIC cxx_std_20)
|
target_compile_features(launcherfix PUBLIC cxx_std_20)
|
||||||
set_target_properties(launcherfix PROPERTIES CXX_EXTENSIONS OFF)
|
set_target_properties(launcherfix PROPERTIES CXX_EXTENSIONS OFF)
|
||||||
@@ -16,5 +14,4 @@ set_target_properties(launcherfix PROPERTIES
|
|||||||
CXX_VISIBILITY_PRESET hidden
|
CXX_VISIBILITY_PRESET hidden
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(launcherfix PRIVATE log android)
|
target_link_libraries(launcherfix PRIVATE log)
|
||||||
target_link_libraries(launcherfix PRIVATE games-frame-pacing::swappy_static)
|
|
||||||
@@ -15,7 +15,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// this is every function that i thought would be relevant
|
// this is every function that i thought would be relevant
|
||||||
constexpr std::array<const std::string_view, 10> remove_symbols{
|
constexpr std::array<const std::string_view, 12> remove_symbols{
|
||||||
"__gxx_personality_v0",
|
"__gxx_personality_v0",
|
||||||
"__cxa_throw",
|
"__cxa_throw",
|
||||||
"__cxa_rethrow",
|
"__cxa_rethrow",
|
||||||
@@ -25,10 +25,9 @@ constexpr std::array<const std::string_view, 10> remove_symbols{
|
|||||||
"__cxa_guard_abort",
|
"__cxa_guard_abort",
|
||||||
"__cxa_guard_acquire",
|
"__cxa_guard_acquire",
|
||||||
"__cxa_guard_release",
|
"__cxa_guard_release",
|
||||||
"__cxa_free_exception"
|
"__cxa_free_exception",
|
||||||
// conflicts with fix within geode on some devices
|
"__emutls_get_address", // added in 2.208
|
||||||
// "__emutls_get_address", // added in 2.208
|
"__cxa_thread_atexit"
|
||||||
// "__cxa_thread_atexit"
|
|
||||||
|
|
||||||
// android 10's libc doesn't have these symbols
|
// android 10's libc doesn't have these symbols
|
||||||
// and the ndk is forbidden from exporting them!! so keep it for old mods...
|
// and the ndk is forbidden from exporting them!! so keep it for old mods...
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
#include <swappy/swappyGL.h>
|
|
||||||
#include <swappy/swappyGL_extra.h>
|
|
||||||
#include <android/native_window_jni.h>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_initFramePacing(
|
|
||||||
JNIEnv *env,
|
|
||||||
jobject,
|
|
||||||
jobject activity
|
|
||||||
) {
|
|
||||||
SwappyGL_init(env, activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NativeWindowDeleter {
|
|
||||||
void operator()(ANativeWindow* p) const {
|
|
||||||
if (p != nullptr) ANativeWindow_release(p);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<ANativeWindow, NativeWindowDeleter> native_window = nullptr;
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_destroyFramePacing(JNIEnv*, jobject) {
|
|
||||||
SwappyGL_destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT jboolean JNICALL Java_com_geode_launcher_LauncherFix_swapFrame(
|
|
||||||
JNIEnv*,
|
|
||||||
jobject,
|
|
||||||
jlong display,
|
|
||||||
jlong surface
|
|
||||||
) {
|
|
||||||
return SwappyGL_swap(
|
|
||||||
reinterpret_cast<EGLDisplay>(display),
|
|
||||||
reinterpret_cast<EGLSurface>(surface)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setSurface(
|
|
||||||
JNIEnv* env,
|
|
||||||
jobject,
|
|
||||||
jobject surface
|
|
||||||
) {
|
|
||||||
if (surface == nullptr) {
|
|
||||||
native_window.reset();
|
|
||||||
SwappyGL_setWindow(nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
native_window.reset(ANativeWindow_fromSurface(env, surface));
|
|
||||||
SwappyGL_setWindow(native_window.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setSwapInterval(
|
|
||||||
JNIEnv* env,
|
|
||||||
jobject,
|
|
||||||
jlong interval_ns
|
|
||||||
) {
|
|
||||||
SwappyGL_setAutoSwapInterval(false);
|
|
||||||
SwappyGL_setSwapIntervalNS(interval_ns);
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -5,9 +5,7 @@ import android.content.Intent
|
|||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.opengl.EGL14
|
import android.opengl.GLSurfaceView
|
||||||
import android.opengl.EGLConfig
|
|
||||||
import android.opengl.EGLDisplay
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
@@ -26,8 +24,10 @@ import androidx.core.view.WindowCompat
|
|||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.customRobTop.BaseRobTopActivity
|
import com.customRobTop.BaseRobTopActivity
|
||||||
import com.customRobTop.JniToCpp
|
import com.customRobTop.JniToCpp
|
||||||
|
import com.geode.launcher.main.FullScreenLoadingIndicator
|
||||||
import com.geode.launcher.main.LaunchNotification
|
import com.geode.launcher.main.LaunchNotification
|
||||||
import com.geode.launcher.main.determineDisplayedCards
|
import com.geode.launcher.main.determineDisplayedCards
|
||||||
import com.geode.launcher.utils.Constants
|
import com.geode.launcher.utils.Constants
|
||||||
@@ -37,14 +37,18 @@ import com.geode.launcher.utils.GamePackageUtils
|
|||||||
import com.geode.launcher.utils.GeodeUtils
|
import com.geode.launcher.utils.GeodeUtils
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.cocos2dx.lib.Cocos2dxGLSurfaceView
|
import org.cocos2dx.lib.Cocos2dxGLSurfaceView
|
||||||
import org.cocos2dx.lib.Cocos2dxHelper
|
import org.cocos2dx.lib.Cocos2dxHelper
|
||||||
import org.cocos2dx.lib.Cocos2dxRenderer
|
import org.cocos2dx.lib.Cocos2dxRenderer
|
||||||
import org.fmod.FMOD
|
import org.fmod.FMOD
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import javax.microedition.khronos.egl.EGL10
|
||||||
|
import javax.microedition.khronos.egl.EGLConfig
|
||||||
|
import javax.microedition.khronos.egl.EGLDisplay
|
||||||
|
|
||||||
enum class DisplayMode {
|
enum class DisplayMode {
|
||||||
DEFAULT, LIMITED, FULLSCREEN;
|
DEFAULT, LIMITED, FULLSCREEN;
|
||||||
@@ -77,7 +81,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
private var mAspectRatio = 0.0f
|
private var mAspectRatio = 0.0f
|
||||||
private var mScreenZoom = 1.0f
|
private var mScreenZoom = 1.0f
|
||||||
private var mScreenZoomFit = false
|
private var mScreenZoomFit = false
|
||||||
private var mNativeFramePacing = false
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setupUIState()
|
setupUIState()
|
||||||
@@ -91,9 +94,46 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
val rootLayout = setupContext()
|
||||||
|
val loadingView = ComposeView(this).apply {
|
||||||
|
setContent {
|
||||||
|
FullScreenLoadingIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootLayout != null) {
|
||||||
|
rootLayout.addView(loadingView)
|
||||||
|
} else {
|
||||||
|
setContentView(loadingView)
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val error = preloadGame(rootLayout)
|
||||||
|
if (error != null) {
|
||||||
|
returnToMain(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootLayout?.removeView(loadingView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun preloadGame(rootLayout: FrameLayout?): LaunchUtils.FullLauncherError? = try {
|
||||||
createVersionFile()
|
createVersionFile()
|
||||||
tryLoadGame()
|
val loadFailed = tryLoadGame()
|
||||||
|
|
||||||
|
val baseView = createView()
|
||||||
|
|
||||||
|
if (rootLayout != null) {
|
||||||
|
rootLayout.addView(baseView)
|
||||||
|
} else {
|
||||||
|
setContentView(baseView)
|
||||||
|
}
|
||||||
|
|
||||||
|
mGLSurfaceView?.manualBackEvents = true
|
||||||
|
|
||||||
|
setupNotificationView(baseView, loadFailed)
|
||||||
|
|
||||||
|
null
|
||||||
} catch (e: UnsatisfiedLinkError) {
|
} catch (e: UnsatisfiedLinkError) {
|
||||||
Log.e("GeodeLauncher", "Library linkage failure", e)
|
Log.e("GeodeLauncher", "Library linkage failure", e)
|
||||||
|
|
||||||
@@ -110,82 +150,32 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
else -> LaunchUtils.LauncherError.LINKER_FAILED
|
else -> LaunchUtils.LauncherError.LINKER_FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
returnToMain(errorMessage, e.message, e.stackTraceToString())
|
LaunchUtils.FullLauncherError(
|
||||||
|
errorMessage,
|
||||||
return
|
e.message ?: "Unknown exception",
|
||||||
|
e.stackTraceToString()
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("GeodeLauncher", "Uncaught error during game load", e)
|
Log.e("GeodeLauncher", "Uncaught error during game load", e)
|
||||||
|
|
||||||
returnToMain(
|
LaunchUtils.FullLauncherError(
|
||||||
LaunchUtils.LauncherError.GENERIC,
|
LaunchUtils.LauncherError.GENERIC,
|
||||||
e.message ?: "Unknown Exception",
|
e.message ?: "Unknown Exception",
|
||||||
e.stackTraceToString()
|
e.stackTraceToString()
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupContext(): FrameLayout? {
|
||||||
|
Cocos2dxHelper.init(this, this)
|
||||||
|
|
||||||
|
GeodeUtils.setContext(this)
|
||||||
|
GeodeUtils.setCapabilityListener(this)
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
mGLSurfaceView?.sendKeyBack()
|
mGLSurfaceView?.sendKeyBack()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
mGLSurfaceView?.manualBackEvents = true
|
|
||||||
|
|
||||||
if (mNativeFramePacing) {
|
|
||||||
LauncherFix.initFramePacing(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createVersionFile() {
|
|
||||||
val versionPath = File(filesDir, "game_version.txt")
|
|
||||||
val gameVersion = GamePackageUtils.getGameVersionCode(packageManager)
|
|
||||||
|
|
||||||
versionPath.writeText("$gameVersion")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun returnToMain(
|
|
||||||
error: LaunchUtils.LauncherError? = null,
|
|
||||||
returnMessage: String? = null,
|
|
||||||
returnExtendedMessage: String? = null
|
|
||||||
) {
|
|
||||||
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)!!.apply {
|
|
||||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
|
|
||||||
if (error != null && !returnMessage.isNullOrEmpty()) {
|
|
||||||
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR, error)
|
|
||||||
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE, returnMessage)
|
|
||||||
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE, returnExtendedMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivity(launchIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tryLoadGame() {
|
|
||||||
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
|
|
||||||
|
|
||||||
setupRedirection(gdPackageInfo)
|
|
||||||
|
|
||||||
Cocos2dxHelper.init(this, this)
|
|
||||||
|
|
||||||
GeodeUtils.setContext(this)
|
|
||||||
GeodeUtils.setCapabilityListener(this)
|
|
||||||
|
|
||||||
tryLoadLibrary(gdPackageInfo, Constants.FMOD_LIB_NAME)
|
|
||||||
tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
|
|
||||||
|
|
||||||
if (GamePackageUtils.getGameVersionCode(packageManager) >= 39L) {
|
|
||||||
// this fix requires geode v3, which is 2.206+
|
|
||||||
// there is a short period in which 2.206 users will still have geode v2, but whatever. ig
|
|
||||||
LauncherFix.enableExceptionsRenaming()
|
|
||||||
}
|
|
||||||
|
|
||||||
LauncherFix.performPatches()
|
|
||||||
|
|
||||||
loadInternalMods()
|
|
||||||
|
|
||||||
val baseView = createView()
|
|
||||||
|
|
||||||
if (!GeodeUtils.handleSafeArea && Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
|
if (!GeodeUtils.handleSafeArea && Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
|
||||||
val frameLayoutParams = ViewGroup.LayoutParams(
|
val frameLayoutParams = ViewGroup.LayoutParams(
|
||||||
@@ -204,23 +194,65 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
right = displayInsets.safeInsetRight,
|
right = displayInsets.safeInsetRight,
|
||||||
bottom = displayInsets.safeInsetBottom
|
bottom = displayInsets.safeInsetBottom
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
v.updatePadding(
|
|
||||||
left = 0, top = 0, right = 0, bottom = 0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
WindowInsets.CONSUMED
|
WindowInsets.CONSUMED
|
||||||
}
|
}
|
||||||
|
|
||||||
frameLayout.addView(baseView)
|
|
||||||
setContentView(frameLayout)
|
setContentView(frameLayout)
|
||||||
} else {
|
return frameLayout
|
||||||
setContentView(baseView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun createVersionFile() = runCatching {
|
||||||
|
val gameVersion = GamePackageUtils.getGameVersionCode(packageManager)
|
||||||
|
|
||||||
|
val versionPath = File(filesDir, "game_version.txt")
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (versionPath.exists()) {
|
||||||
|
versionPath.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
versionPath.writeText("$gameVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun returnToMain(message: LaunchUtils.FullLauncherError? = null, ) {
|
||||||
|
val launchIntent = Intent(this, MainActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
|
||||||
|
if (message != null) {
|
||||||
|
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR, message.error)
|
||||||
|
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE, message.returnMessage)
|
||||||
|
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE, message.extendedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(launchIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun tryLoadGame(): Boolean {
|
||||||
|
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
|
||||||
|
setupRedirection(gdPackageInfo)
|
||||||
|
|
||||||
|
tryLoadLibrary(gdPackageInfo, Constants.FMOD_LIB_NAME)
|
||||||
|
tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
|
||||||
|
|
||||||
|
if (GamePackageUtils.getGameVersionCode(packageManager) >= 39L) {
|
||||||
|
// this fix requires geode v3, which is 2.206+
|
||||||
|
// there is a short period in which 2.206 users will still have geode v2, but whatever. ig
|
||||||
|
LauncherFix.enableExceptionsRenaming()
|
||||||
|
}
|
||||||
|
|
||||||
|
LauncherFix.performPatches()
|
||||||
|
|
||||||
|
loadInternalMods()
|
||||||
|
|
||||||
setupPostLibraryLoad(gdPackageInfo)
|
setupPostLibraryLoad(gdPackageInfo)
|
||||||
|
|
||||||
val geodeFailed = try {
|
return try {
|
||||||
loadGeodeLibrary()
|
loadGeodeLibrary()
|
||||||
false
|
false
|
||||||
} catch (e: UnsatisfiedLinkError) {
|
} catch (e: UnsatisfiedLinkError) {
|
||||||
@@ -230,8 +262,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
handleGeodeException(e)
|
handleGeodeException(e)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
setupNotificationView(baseView, geodeFailed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupNotificationView(baseView: FrameLayout, geodeFailed: Boolean) {
|
private fun setupNotificationView(baseView: FrameLayout, geodeFailed: Boolean) {
|
||||||
@@ -291,7 +321,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
private fun tryLoadLibrary(packageInfo: PackageInfo, libraryName: String) {
|
private suspend fun tryLoadLibrary(packageInfo: PackageInfo, libraryName: String) {
|
||||||
val nativeDir = getNativeLibraryDirectory(packageInfo.applicationInfo!!)
|
val nativeDir = getNativeLibraryDirectory(packageInfo.applicationInfo!!)
|
||||||
val libraryPath = if (nativeDir.endsWith('/')) "${nativeDir}lib$libraryName.so" else "$nativeDir/lib$libraryName.so"
|
val libraryPath = if (nativeDir.endsWith('/')) "${nativeDir}lib$libraryName.so" else "$nativeDir/lib$libraryName.so"
|
||||||
|
|
||||||
@@ -310,7 +340,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
private fun loadLibraryCopy(libraryName: String, libraryPath: String) {
|
private suspend fun loadLibraryCopy(libraryName: String, libraryPath: String) {
|
||||||
if (libraryPath.contains("!/")) {
|
if (libraryPath.contains("!/")) {
|
||||||
// library in apk can't be loaded directly
|
// library in apk can't be loaded directly
|
||||||
loadLibraryFromAssetsCopy(libraryName)
|
loadLibraryFromAssetsCopy(libraryName)
|
||||||
@@ -320,11 +350,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
val library = File(libraryPath)
|
val library = File(libraryPath)
|
||||||
val libraryCopy = File(cacheDir, "lib$libraryName.so")
|
val libraryCopy = File(cacheDir, "lib$libraryName.so")
|
||||||
|
|
||||||
libraryCopy.outputStream().use { libraryOutput ->
|
DownloadUtils.copyFile(library, libraryCopy)
|
||||||
library.inputStream().use { inputStream ->
|
|
||||||
DownloadUtils.copyFile(inputStream, libraryOutput)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.load(libraryCopy.path)
|
System.load(libraryCopy.path)
|
||||||
|
|
||||||
@@ -332,7 +358,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
private fun loadLibraryFromAssetsCopy(libraryName: String) {
|
private suspend fun loadLibraryFromAssetsCopy(libraryName: String) {
|
||||||
// loads a library loaded in assets
|
// loads a library loaded in assets
|
||||||
// this copies the library to a non-compressed directory
|
// this copies the library to a non-compressed directory
|
||||||
|
|
||||||
@@ -347,11 +373,13 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
// there doesn't seem to be a way to load a library from a file descriptor
|
// there doesn't seem to be a way to load a library from a file descriptor
|
||||||
val libraryCopy = File(cacheDir, "lib$libraryName.so")
|
val libraryCopy = File(cacheDir, "lib$libraryName.so")
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
libraryCopy.outputStream().use { libraryOutput ->
|
libraryCopy.outputStream().use { libraryOutput ->
|
||||||
libraryFd.createInputStream().use { inputStream ->
|
libraryFd.createInputStream().use { inputStream ->
|
||||||
DownloadUtils.copyFile(inputStream, libraryOutput)
|
DownloadUtils.copyFile(inputStream, libraryOutput)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
System.load(libraryCopy.path)
|
System.load(libraryCopy.path)
|
||||||
|
|
||||||
@@ -383,15 +411,8 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
return "$sourceDir!/lib/$architecture/"
|
return "$sourceDir!/lib/$architecture/"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadGeodeLibrary() {
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
private suspend fun loadGeodeLibrary() {
|
||||||
if (BuildConfig.PREBUNDLED_GEODE != null) {
|
|
||||||
// attempt to load an externally provided Geode first
|
|
||||||
if (!loadExternalGeode()) {
|
|
||||||
copyGeodeResources()
|
|
||||||
System.loadLibrary("geode")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Load Geode if exists
|
// Load Geode if exists
|
||||||
// bundling the object with the application allows for nicer backtraces
|
// bundling the object with the application allows for nicer backtraces
|
||||||
try {
|
try {
|
||||||
@@ -399,55 +420,43 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
System.loadLibrary("geode")
|
System.loadLibrary("geode")
|
||||||
return
|
return
|
||||||
} catch (e: UnsatisfiedLinkError) {
|
} catch (e: UnsatisfiedLinkError) {
|
||||||
if (!loadExternalGeode()) {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
|
||||||
private fun loadExternalGeode(): Boolean {
|
|
||||||
// but users may prefer it stored with data
|
// but users may prefer it stored with data
|
||||||
val geodeFilename = LaunchUtils.geodeFilename
|
val geodeFilename = LaunchUtils.geodeFilename
|
||||||
val geodePath = File(filesDir.path, "launcher/$geodeFilename")
|
val geodePath = File(filesDir.path, "launcher/$geodeFilename")
|
||||||
if (geodePath.exists()) {
|
if (geodePath.exists()) {
|
||||||
println("Loading Geode from ${geodePath.path}")
|
|
||||||
System.load(geodePath.path)
|
System.load(geodePath.path)
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// you know zmx i have 0 clue what this does so im
|
// you know zmx i have 0 clue what this does so im
|
||||||
// just gonna like copy the binary from external
|
// just gonna like copy the binary from external
|
||||||
// also i get 20 million permission denied errors
|
// also i get 20 million permission denied errors
|
||||||
val externalGeodePath = LaunchUtils.getInstalledGeodePath(this)
|
val externalGeodePath = LaunchUtils.getInstalledGeodePath(this)!!
|
||||||
?: return false
|
|
||||||
|
|
||||||
val copiedPath = File(filesDir.path, "copied")
|
val copiedPath = File(filesDir, "copied")
|
||||||
if (copiedPath.exists()) {
|
if (copiedPath.exists()) {
|
||||||
copiedPath.deleteRecursively()
|
copiedPath.deleteRecursively()
|
||||||
}
|
}
|
||||||
copiedPath.mkdir()
|
copiedPath.mkdir()
|
||||||
|
|
||||||
val copiedGeodePath = File(copiedPath.path, "Geode.so")
|
val copiedGeodePath = File(copiedPath, "Geode.so")
|
||||||
|
|
||||||
if (externalGeodePath.exists()) {
|
if (externalGeodePath.exists()) {
|
||||||
DownloadUtils.copyFile(
|
DownloadUtils.copyFile(
|
||||||
FileInputStream(externalGeodePath),
|
externalGeodePath,
|
||||||
FileOutputStream(copiedGeodePath)
|
copiedGeodePath
|
||||||
)
|
)
|
||||||
|
|
||||||
if (copiedGeodePath.exists()) {
|
if (copiedGeodePath.exists()) {
|
||||||
// android 17 fix
|
|
||||||
copiedGeodePath.setWritable(false)
|
|
||||||
|
|
||||||
println("Loading Geode from ${externalGeodePath.name}")
|
println("Loading Geode from ${externalGeodePath.name}")
|
||||||
|
|
||||||
System.load(copiedGeodePath.path)
|
System.load(copiedGeodePath.path)
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createView(): FrameLayout {
|
private fun createView(): FrameLayout {
|
||||||
@@ -495,11 +504,9 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
|
|
||||||
val frameRate = mLimitedRefreshRate
|
val frameRate = mLimitedRefreshRate
|
||||||
if (frameRate != null) {
|
if (frameRate != null) {
|
||||||
glSurfaceView.setMaxFrameRate(frameRate)
|
renderer.limitFrameRate(frameRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
glSurfaceView.nativeFramePacing = mNativeFramePacing
|
|
||||||
|
|
||||||
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||||
glSurfaceView.cocos2dxEditText = editText
|
glSurfaceView.cocos2dxEditText = editText
|
||||||
|
|
||||||
@@ -535,8 +542,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mNativeFramePacing = preferenceUtils.getBoolean(PreferenceUtils.Key.NATIVE_FRAME_PACING)
|
|
||||||
|
|
||||||
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
|
GeodeUtils.handleSafeArea = true
|
||||||
@@ -570,10 +575,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
FMOD.close()
|
FMOD.close()
|
||||||
|
|
||||||
if (mNativeFramePacing) {
|
|
||||||
LauncherFix.destroyFramePacing()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resumeGame() {
|
private fun resumeGame() {
|
||||||
@@ -655,10 +656,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
mGLSurfaceView?.enableControllerEvents()
|
mGLSurfaceView?.enableControllerEvents()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
GeodeUtils.CAPABILITY_RESIZE_CALLBACK -> {
|
|
||||||
mGLSurfaceView?.supportsResizeEvents = true
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -695,57 +692,11 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyGeodeResources() {
|
class EGLConfigChooser : GLSurfaceView.EGLConfigChooser {
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
// this comes from EGL14, but is unavailable on EGL10
|
||||||
if (BuildConfig.PREBUNDLED_GEODE == null) {
|
// also EGL14 is incompatible with EGL10. so whatever
|
||||||
return
|
private var EGL_OPENGL_ES2_BIT: Int = 0x04
|
||||||
}
|
|
||||||
|
|
||||||
val resourcesDirectory = LaunchUtils.getGeodeResourcesDirectory(this)
|
|
||||||
|
|
||||||
val pathBase = "geode.loader"
|
|
||||||
val versionFilename = "version"
|
|
||||||
|
|
||||||
val bundledVersion = try {
|
|
||||||
assets.open("$pathBase/$versionFilename").bufferedReader().use {
|
|
||||||
it.readText()
|
|
||||||
}.trimEnd()
|
|
||||||
} catch (_: IOException) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val versionFile = File(resourcesDirectory, versionFilename)
|
|
||||||
val resourcesVersion = if (versionFile.exists()) {
|
|
||||||
versionFile.readText().trimEnd()
|
|
||||||
} else null
|
|
||||||
|
|
||||||
if (bundledVersion == resourcesVersion) {
|
|
||||||
println("copyGeodeResources: found $resourcesVersion, expected $bundledVersion. No work needs to be done!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourcesDirectory.exists()) {
|
|
||||||
resourcesDirectory.deleteRecursively()
|
|
||||||
}
|
|
||||||
resourcesDirectory.mkdirs()
|
|
||||||
|
|
||||||
println("copyGeodeResources: found $resourcesVersion, expected $bundledVersion. Copying assets")
|
|
||||||
|
|
||||||
val fileListing = try {
|
|
||||||
assets.list(pathBase)
|
|
||||||
} catch (_: IOException) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fileListing?.forEach { fileName ->
|
|
||||||
val resourceOutput = File(resourcesDirectory, fileName)
|
|
||||||
|
|
||||||
val resource = assets.open("$pathBase/$fileName")
|
|
||||||
DownloadUtils.copyFile(resource, resourceOutput.outputStream())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EGLConfigChooser : ExplicitGLSurfaceView.EGLConfigChooser {
|
|
||||||
private data class ConfigValues(
|
private data class ConfigValues(
|
||||||
val redSize: Int,
|
val redSize: Int,
|
||||||
val greenSize: Int,
|
val greenSize: Int,
|
||||||
@@ -755,29 +706,29 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
val stencilSize: Int,
|
val stencilSize: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getAttribValue(display: EGLDisplay, config: EGLConfig, attrib: Int): Int {
|
private fun getAttribValue(egl: EGL10, display: EGLDisplay, config: EGLConfig, attrib: Int): Int {
|
||||||
val value = IntArray(1)
|
val value = IntArray(1)
|
||||||
EGL14.eglGetConfigAttrib(display, config, attrib, value, 0)
|
egl.eglGetConfigAttrib(display, config, attrib, value)
|
||||||
|
|
||||||
return value[0]
|
return value[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testConfig(display: EGLDisplay, attrib: ConfigValues): EGLConfig? {
|
private fun testConfig(egl: EGL10, display: EGLDisplay, attrib: ConfigValues): EGLConfig? {
|
||||||
val attribList = intArrayOf(
|
val attribList = intArrayOf(
|
||||||
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
|
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||||
EGL14.EGL_RED_SIZE, attrib.redSize,
|
EGL10.EGL_RED_SIZE, attrib.redSize,
|
||||||
EGL14.EGL_GREEN_SIZE, attrib.greenSize,
|
EGL10.EGL_GREEN_SIZE, attrib.greenSize,
|
||||||
EGL14.EGL_BLUE_SIZE, attrib.blueSize,
|
EGL10.EGL_BLUE_SIZE, attrib.blueSize,
|
||||||
EGL14.EGL_ALPHA_SIZE, attrib.alphaSize,
|
EGL10.EGL_ALPHA_SIZE, attrib.alphaSize,
|
||||||
EGL14.EGL_DEPTH_SIZE, attrib.depthSize,
|
EGL10.EGL_DEPTH_SIZE, attrib.depthSize,
|
||||||
EGL14.EGL_STENCIL_SIZE, attrib.stencilSize,
|
EGL10.EGL_STENCIL_SIZE, attrib.stencilSize,
|
||||||
EGL14.EGL_NONE
|
EGL10.EGL_NONE
|
||||||
)
|
)
|
||||||
|
|
||||||
val configs = arrayOfNulls<EGLConfig>(1)
|
val configs = arrayOfNulls<EGLConfig>(1)
|
||||||
val numConfigs = IntArray(1)
|
val numConfigs = IntArray(1)
|
||||||
|
|
||||||
val res = EGL14.eglChooseConfig(display, attribList, 0, configs, 0, configs.size, numConfigs, 0)
|
val res = egl.eglChooseConfig(display, attribList, configs, configs.size, numConfigs)
|
||||||
if (res && numConfigs[0] > 0 && configs[0] != null) {
|
if (res && numConfigs[0] > 0 && configs[0] != null) {
|
||||||
return configs[0]
|
return configs[0]
|
||||||
}
|
}
|
||||||
@@ -785,18 +736,18 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun printConfig(display: EGLDisplay, config: EGLConfig) {
|
private fun printConfig(egl: EGL10, display: EGLDisplay, config: EGLConfig) {
|
||||||
val id = getAttribValue(display, config, EGL14.EGL_CONFIG_ID)
|
val id = getAttribValue(egl, display, config, EGL10.EGL_CONFIG_ID)
|
||||||
val red = getAttribValue(display, config, EGL14.EGL_RED_SIZE)
|
val red = getAttribValue(egl, display, config, EGL10.EGL_RED_SIZE)
|
||||||
val green = getAttribValue(display, config, EGL14.EGL_GREEN_SIZE)
|
val green = getAttribValue(egl, display, config, EGL10.EGL_GREEN_SIZE)
|
||||||
val blue = getAttribValue(display, config, EGL14.EGL_BLUE_SIZE)
|
val blue = getAttribValue(egl, display, config, EGL10.EGL_BLUE_SIZE)
|
||||||
val alpha = getAttribValue(display, config, EGL14.EGL_ALPHA_SIZE)
|
val alpha = getAttribValue(egl, display, config, EGL10.EGL_ALPHA_SIZE)
|
||||||
val depth = getAttribValue(display, config, EGL14.EGL_DEPTH_SIZE)
|
val depth = getAttribValue(egl, display, config, EGL10.EGL_DEPTH_SIZE)
|
||||||
val stencil = getAttribValue(display, config, EGL14.EGL_STENCIL_SIZE)
|
val stencil = getAttribValue(egl, display, config, EGL10.EGL_STENCIL_SIZE)
|
||||||
println("EGLConfig $id: (red = $red, green = $green, blue = $blue, alpha = $alpha, depth = $depth, stencil = $stencil)")
|
println("EGLConfig $id: (red = $red, green = $green, blue = $blue, alpha = $alpha, depth = $depth, stencil = $stencil)")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chooseConfig(display: EGLDisplay): EGLConfig {
|
override fun chooseConfig(egl: EGL10, display: EGLDisplay): EGLConfig {
|
||||||
val configs = listOf(
|
val configs = listOf(
|
||||||
ConfigValues(8, 8, 8, 8, 16, 8),
|
ConfigValues(8, 8, 8, 8, 16, 8),
|
||||||
ConfigValues(8, 8, 8, 0, 16, 8),
|
ConfigValues(8, 8, 8, 0, 16, 8),
|
||||||
@@ -805,9 +756,9 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
)
|
)
|
||||||
|
|
||||||
return configs.firstNotNullOf {
|
return configs.firstNotNullOf {
|
||||||
val config = testConfig(display, it)
|
val config = testConfig(egl, display, it)
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
printConfig(display, config)
|
printConfig(egl, display, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
config
|
config
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.geode.launcher
|
package com.geode.launcher
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.view.Surface
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import com.geode.launcher.utils.Constants
|
import com.geode.launcher.utils.Constants
|
||||||
|
|
||||||
@@ -18,10 +16,4 @@ object LauncherFix {
|
|||||||
external fun enableExceptionsRenaming()
|
external fun enableExceptionsRenaming()
|
||||||
|
|
||||||
external fun performPatches()
|
external fun performPatches()
|
||||||
|
|
||||||
external fun initFramePacing(activity: Activity)
|
|
||||||
external fun destroyFramePacing()
|
|
||||||
external fun swapFrame(display: Long, surface: Long): Boolean
|
|
||||||
external fun setSurface(surface: Surface?)
|
|
||||||
external fun setSwapInterval(intervalNs: Long)
|
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,6 @@ import android.text.format.Formatter
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
import androidx.compose.animation.core.LinearEasing
|
import androidx.compose.animation.core.LinearEasing
|
||||||
@@ -72,7 +70,6 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.platform.LocalResources
|
import androidx.compose.ui.platform.LocalResources
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -85,9 +82,7 @@ import com.geode.launcher.ui.theme.Theme
|
|||||||
import com.geode.launcher.ui.theme.Theme.DARK
|
import com.geode.launcher.ui.theme.Theme.DARK
|
||||||
import com.geode.launcher.ui.theme.Theme.LIGHT
|
import com.geode.launcher.ui.theme.Theme.LIGHT
|
||||||
import com.geode.launcher.ui.theme.launcherTitleStyle
|
import com.geode.launcher.ui.theme.launcherTitleStyle
|
||||||
import com.geode.launcher.ui.theme.sapphireTitleStyle
|
|
||||||
import com.geode.launcher.updater.ReleaseManager
|
import com.geode.launcher.updater.ReleaseManager
|
||||||
import com.geode.launcher.utils.ApplicationIcon
|
|
||||||
import com.geode.launcher.utils.Constants
|
import com.geode.launcher.utils.Constants
|
||||||
import com.geode.launcher.utils.GamePackageUtils
|
import com.geode.launcher.utils.GamePackageUtils
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
@@ -111,16 +106,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
clearDownloadedApks(this)
|
clearDownloadedApks(this)
|
||||||
|
|
||||||
// remove old Geode for the like 4 people that downloaded the Play Store build
|
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
|
||||||
if (BuildConfig.PREBUNDLED_GEODE != null) {
|
|
||||||
val preferences = PreferenceUtils.get(this)
|
|
||||||
if (!preferences.getBoolean(PreferenceUtils.Key.PREBUNDLED_GEODE_MIGRATION_PERFORMED)) {
|
|
||||||
LaunchUtils.getInstalledGeodePath(this)?.delete()
|
|
||||||
preferences.setBoolean(PreferenceUtils.Key.PREBUNDLED_GEODE_MIGRATION_PERFORMED, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val returnMessage = intent.extras?.getString(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE)
|
val returnMessage = intent.extras?.getString(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE)
|
||||||
val returnExtendedMessage = intent.extras?.getString(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE)
|
val returnExtendedMessage = intent.extras?.getString(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE)
|
||||||
|
|
||||||
@@ -217,7 +202,7 @@ fun mapCancelReasonToInfo(cancelReason: LaunchViewModel.LaunchCancelReason): Lau
|
|||||||
)
|
)
|
||||||
LaunchViewModel.LaunchCancelReason.GAME_OUTDATED -> LaunchStatusInfo(
|
LaunchViewModel.LaunchCancelReason.GAME_OUTDATED -> LaunchStatusInfo(
|
||||||
title = stringResource(R.string.launcher_cancelled_error),
|
title = stringResource(R.string.launcher_cancelled_error),
|
||||||
details = stringResource(R.string.launcher_cancelled_outdated, GamePackageUtils.getSupportedUnifiedVersion())
|
details = stringResource(R.string.launcher_cancelled_outdated)
|
||||||
)
|
)
|
||||||
LaunchViewModel.LaunchCancelReason.AUTOMATIC,
|
LaunchViewModel.LaunchCancelReason.AUTOMATIC,
|
||||||
LaunchViewModel.LaunchCancelReason.MANUAL-> LaunchStatusInfo(
|
LaunchViewModel.LaunchCancelReason.MANUAL-> LaunchStatusInfo(
|
||||||
@@ -266,7 +251,7 @@ fun mapLaunchStatusToInfo(state: LaunchViewModel.LaunchUIState, inSafeMode: Bool
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is LaunchViewModel.LaunchUIState.Cancelled -> {
|
is LaunchViewModel.LaunchUIState.Cancelled -> {
|
||||||
mapCancelReasonToInfo(state.reason)
|
return mapCancelReasonToInfo(state.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -467,76 +452,36 @@ fun ExtraOptions(onSettings: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ColorPalette(
|
val lightOuterColorGradient = arrayOf(
|
||||||
val outerColors: List<Pair<Float, Color>>,
|
|
||||||
val innerColors: List<Pair<Float, Color>>,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AnimatedIconSpec(
|
|
||||||
val lightPalette: ColorPalette,
|
|
||||||
val darkPalette: ColorPalette,
|
|
||||||
|
|
||||||
@param:DrawableRes val innerId: Int,
|
|
||||||
@param:DrawableRes val outerId: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class BrandPalette(
|
|
||||||
@param:DrawableRes val darkLogo: Int,
|
|
||||||
@param:DrawableRes val lightLogo: Int,
|
|
||||||
|
|
||||||
@param:StringRes val title: Int,
|
|
||||||
val titleFont: TextStyle,
|
|
||||||
val animatedIcon: AnimatedIconSpec? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
val geodeColorPalette = BrandPalette(
|
|
||||||
animatedIcon = AnimatedIconSpec(
|
|
||||||
lightPalette = ColorPalette(
|
|
||||||
outerColors = listOf(
|
|
||||||
0.0f to Color(0xfff5b11d),
|
0.0f to Color(0xfff5b11d),
|
||||||
0.10f to Color(0xfff0834d),
|
0.10f to Color(0xfff0834d),
|
||||||
0.24f to Color(0xffe85f6b),
|
0.24f to Color(0xffe85f6b),
|
||||||
0.49f to Color(0xffc9659e),
|
0.49f to Color(0xffc9659e),
|
||||||
1.0f to Color(0xffb9588f)
|
1.0f to Color(0xffb9588f)
|
||||||
),
|
)
|
||||||
innerColors = listOf(
|
|
||||||
|
val lightInnerColorGradient = arrayOf(
|
||||||
0.48f to Color(0xfff2ce00),
|
0.48f to Color(0xfff2ce00),
|
||||||
0.63f to Color(0xfff4b97e),
|
0.63f to Color(0xfff4b97e),
|
||||||
0.73f to Color(0xfff7b66a),
|
0.73f to Color(0xfff7b66a),
|
||||||
1.0f to Color(0xffeb8fac)
|
1.0f to Color(0xffeb8fac)
|
||||||
)
|
)
|
||||||
),
|
|
||||||
darkPalette = ColorPalette(
|
val darkOuterColorGradient = arrayOf(
|
||||||
outerColors = listOf(
|
|
||||||
0.0f to Color(0xfffffdff),
|
0.0f to Color(0xfffffdff),
|
||||||
0.08f to Color(0xffF2EAF5),
|
0.08f to Color(0xffF2EAF5),
|
||||||
0.12f to Color(0xffEDE5EF),
|
0.12f to Color(0xffEDE5EF),
|
||||||
0.31f to Color(0xffCDB5CD),
|
0.31f to Color(0xffCDB5CD),
|
||||||
0.49f to Color(0xffBA9BBC),
|
0.49f to Color(0xffBA9BBC),
|
||||||
1.0f to Color(0xff8D7ACF),
|
1.0f to Color(0xff8D7ACF),
|
||||||
),
|
)
|
||||||
innerColors = listOf(
|
|
||||||
|
val darkInnerColorGradient = arrayOf(
|
||||||
0.48f to Color(0xffffffff),
|
0.48f to Color(0xffffffff),
|
||||||
0.63f to Color(0xfff5e4c2),
|
0.63f to Color(0xfff5e4c2),
|
||||||
0.73f to Color(0xffe5c7ad),
|
0.73f to Color(0xffe5c7ad),
|
||||||
1.0f to Color(0xffb790a9),
|
1.0f to Color(0xffb790a9),
|
||||||
)
|
)
|
||||||
),
|
|
||||||
innerId = R.drawable.geode_monochrome_inner,
|
|
||||||
outerId = R.drawable.geode_monochrome_outer,
|
|
||||||
),
|
|
||||||
darkLogo = R.drawable.geode_base,
|
|
||||||
lightLogo = R.drawable.geode_base_light,
|
|
||||||
title = R.string.launcher_title,
|
|
||||||
titleFont = launcherTitleStyle
|
|
||||||
)
|
|
||||||
|
|
||||||
val sapphireColorPalette = BrandPalette(
|
|
||||||
darkLogo = R.drawable.sapphire_full,
|
|
||||||
lightLogo = R.drawable.sapphire_full,
|
|
||||||
title = R.string.application_icon_sapphire,
|
|
||||||
titleFont = sapphireTitleStyle
|
|
||||||
)
|
|
||||||
|
|
||||||
@Preview(name = "non-animated, light")
|
@Preview(name = "non-animated, light")
|
||||||
@Preview(name = "non-animated, dark", uiMode = UI_MODE_NIGHT_YES)
|
@Preview(name = "non-animated, dark", uiMode = UI_MODE_NIGHT_YES)
|
||||||
@@ -560,49 +505,22 @@ fun GeodeLogoAnimatedPreview() {
|
|||||||
CompositionLocalProvider(LocalTheme provides theme) {
|
CompositionLocalProvider(LocalTheme provides theme) {
|
||||||
GeodeLauncherTheme(theme = theme) {
|
GeodeLauncherTheme(theme = theme) {
|
||||||
Surface {
|
Surface {
|
||||||
GeodeLogo(shouldAnimate = true)
|
GeodeLogo(true)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(name = "sapphire, non-animated, light")
|
|
||||||
@Preview(name = "sapphire, non-animated, dark", uiMode = UI_MODE_NIGHT_YES)
|
|
||||||
@Composable
|
|
||||||
fun SapphireLogoPreview() {
|
|
||||||
val theme = if (isSystemInDarkTheme()) DARK else LIGHT
|
|
||||||
CompositionLocalProvider(LocalTheme provides theme) {
|
|
||||||
GeodeLauncherTheme(theme = theme) {
|
|
||||||
Surface {
|
|
||||||
GeodeLogo(shouldAnimate = false, basePalette = sapphireColorPalette)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(name = "sapphire, animated, light")
|
|
||||||
@Preview(name = "sapphire, animated, dark", uiMode = UI_MODE_NIGHT_YES)
|
|
||||||
@Composable
|
|
||||||
fun SapphireLogoAnimatedPreview() {
|
|
||||||
val theme = if (isSystemInDarkTheme()) DARK else LIGHT
|
|
||||||
CompositionLocalProvider(LocalTheme provides theme) {
|
|
||||||
GeodeLauncherTheme(theme = theme) {
|
|
||||||
Surface {
|
|
||||||
GeodeLogo(shouldAnimate = true, basePalette = sapphireColorPalette)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
fun AnimatedLogo(modifier: Modifier = Modifier) {
|
||||||
val theme = LocalTheme.current
|
val theme = LocalTheme.current
|
||||||
|
val innerColorPalette = remember(theme) {
|
||||||
|
if (theme == LIGHT) lightInnerColorGradient else darkInnerColorGradient
|
||||||
|
}
|
||||||
|
|
||||||
val colorPalette = if (theme == LIGHT) spec.lightPalette
|
val outerColorPalette = remember(theme) {
|
||||||
else spec.darkPalette
|
if (theme == LIGHT) lightOuterColorGradient else darkOuterColorGradient
|
||||||
|
}
|
||||||
val innerColorPalette = colorPalette.innerColors
|
|
||||||
val outerColorPalette = colorPalette.outerColors
|
|
||||||
|
|
||||||
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
|
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
|
||||||
val offset by infiniteTransition.animateFloat(
|
val offset by infiniteTransition.animateFloat(
|
||||||
@@ -616,14 +534,14 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val innerBrush = Brush.linearGradient(
|
val innerBrush = Brush.linearGradient(
|
||||||
colorStops = innerColorPalette.toTypedArray(),
|
colorStops = innerColorPalette,
|
||||||
start = Offset(0.0f, 0.0f + (165.06f * offset)),
|
start = Offset(0.0f, 0.0f + (165.06f * offset)),
|
||||||
end = Offset(0.0f, 165.06f + (165.06f * offset)),
|
end = Offset(0.0f, 165.06f + (165.06f * offset)),
|
||||||
tileMode = TileMode.Mirror
|
tileMode = TileMode.Mirror
|
||||||
)
|
)
|
||||||
|
|
||||||
val outerBrush = Brush.linearGradient(
|
val outerBrush = Brush.linearGradient(
|
||||||
colorStops = outerColorPalette.toTypedArray(),
|
colorStops = outerColorPalette,
|
||||||
start = Offset(0.0f + (84.69f * offset), 0.0f + (165.06f * offset)),
|
start = Offset(0.0f + (84.69f * offset), 0.0f + (165.06f * offset)),
|
||||||
end = Offset(84.69f + (84.69f * offset), 169.61f + (165.06f * offset)),
|
end = Offset(84.69f + (84.69f * offset), 169.61f + (165.06f * offset)),
|
||||||
tileMode = TileMode.Mirror
|
tileMode = TileMode.Mirror
|
||||||
@@ -631,7 +549,7 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
|||||||
|
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(spec.innerId),
|
painter = painterResource(id = R.drawable.geode_monochrome_inner),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
||||||
@@ -642,10 +560,9 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
|||||||
blendMode = BlendMode.SrcIn
|
blendMode = BlendMode.SrcIn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.size(64.dp, 64.dp)
|
|
||||||
)
|
)
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(spec.outerId),
|
painter = painterResource(id = R.drawable.geode_monochrome_outer),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
||||||
@@ -656,50 +573,39 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
|||||||
blendMode = BlendMode.SrcIn
|
blendMode = BlendMode.SrcIn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.size(64.dp, 64.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GeodeLogo(modifier: Modifier = Modifier, shouldAnimate: Boolean = false, basePalette: BrandPalette = geodeColorPalette) {
|
fun GeodeLogo(shouldAnimate: Boolean = false, modifier: Modifier = Modifier) {
|
||||||
FlowRow(
|
Row(
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
itemVerticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
val theme = LocalTheme.current
|
val theme = LocalTheme.current
|
||||||
|
|
||||||
if (basePalette.animatedIcon != null) {
|
|
||||||
Crossfade(
|
Crossfade(
|
||||||
targetState = shouldAnimate,
|
targetState = shouldAnimate,
|
||||||
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing),
|
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing),
|
||||||
label="logo fade"
|
label="logo fade"
|
||||||
) { screen ->
|
) { screen ->
|
||||||
when (screen) {
|
when (screen) {
|
||||||
true -> AnimatedLogo(modifier = Modifier.size(64.dp, 64.dp), spec = basePalette.animatedIcon)
|
true -> AnimatedLogo(modifier = Modifier.size(64.dp, 64.dp))
|
||||||
false -> Image(
|
false -> Image(
|
||||||
painterResource(if (theme == LIGHT)
|
painterResource(if (theme == LIGHT)
|
||||||
basePalette.lightLogo else basePalette.darkLogo
|
R.drawable.geode_base_light else R.drawable.geode_base
|
||||||
),
|
),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(64.dp, 64.dp)
|
modifier = Modifier.size(64.dp, 64.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Image(
|
|
||||||
painterResource(if (theme == LIGHT)
|
|
||||||
basePalette.lightLogo else basePalette.darkLogo
|
|
||||||
),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(84.dp, 84.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
stringResource(basePalette.title),
|
stringResource(R.string.launcher_title),
|
||||||
style = basePalette.titleFont,
|
style = launcherTitleStyle,
|
||||||
fontSize = 64.sp,
|
fontSize = 64.sp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
@@ -726,9 +632,6 @@ fun GeodeUpdateIndicator(snackbarHostState: SnackbarHostState, onRetry: () -> Un
|
|||||||
ReleaseManager.UpdateException.Reason.EXTERNAL_FILE_IN_USE -> stringResource(
|
ReleaseManager.UpdateException.Reason.EXTERNAL_FILE_IN_USE -> stringResource(
|
||||||
R.string.release_fetch_external_in_use_short
|
R.string.release_fetch_external_in_use_short
|
||||||
)
|
)
|
||||||
ReleaseManager.UpdateException.Reason.HASH_VALIDATION_FAILED -> stringResource(
|
|
||||||
R.string.release_fetch_hash_validation_failed_short
|
|
||||||
)
|
|
||||||
else -> stringResource(R.string.release_fetch_generic_short)
|
else -> stringResource(R.string.release_fetch_generic_short)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -790,14 +693,7 @@ fun AltMainScreen(
|
|||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
val sapphireLogo = remember {
|
GeodeLogo(shouldAnimate = launchUIState.isInProgress())
|
||||||
PreferenceUtils.get(context)
|
|
||||||
.getString(PreferenceUtils.Key.SELECTED_ICON) == ApplicationIcon.SAPPHIRE.toId()
|
|
||||||
}
|
|
||||||
|
|
||||||
val basePalette = if (sapphireLogo) sapphireColorPalette
|
|
||||||
else geodeColorPalette
|
|
||||||
GeodeLogo(shouldAnimate = launchUIState.isInProgress(), basePalette = basePalette)
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
@@ -838,11 +734,10 @@ fun AltMainScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// only show launcher update in a case where a user won't see it ingame
|
// only show launcher update in a case where a user won't see it ingame
|
||||||
val currentUIState = launchUIState
|
if (launchUIState is LaunchViewModel.LaunchUIState.Cancelled) {
|
||||||
if (currentUIState is LaunchViewModel.LaunchUIState.Cancelled) {
|
|
||||||
val gameVersion = remember { GamePackageUtils.getGameVersionCodeOrNull(context.packageManager) }
|
val gameVersion = remember { GamePackageUtils.getGameVersionCodeOrNull(context.packageManager) }
|
||||||
|
|
||||||
if (gameVersion != null && gameVersion < Constants.SUPPORTED_VERSION_CODE_MIN_WARNING && currentUIState.reason != LaunchViewModel.LaunchCancelReason.GAME_OUTDATED) {
|
if (gameVersion != null && gameVersion < Constants.SUPPORTED_VERSION_CODE_MIN_WARNING) {
|
||||||
UnsupportedVersionWarning()
|
UnsupportedVersionWarning()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ import kotlin.time.Instant
|
|||||||
|
|
||||||
data class CrashDump(
|
data class CrashDump(
|
||||||
val filename: String,
|
val filename: String,
|
||||||
val lastModified: Instant,
|
val lastModified: Instant
|
||||||
val fullPath: String,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(crashDirectory) {
|
class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(crashDirectory) {
|
||||||
@@ -42,19 +41,16 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
val children = crashDirectory.listFiles {
|
val children = crashDirectory.listFiles {
|
||||||
// ignore indicator files (including old file)
|
// ignore indicator files (including old file)
|
||||||
_, name -> !LaunchUtils.CRASH_INDICATOR_NAMES.contains(name)
|
_, name -> name != LaunchUtils.CRASH_INDICATOR_NAME && name != "last-pid"
|
||||||
} ?: return emptyList()
|
} ?: return emptyList()
|
||||||
|
|
||||||
hasIndicator = LaunchUtils.CRASH_INDICATOR_NAMES.any {
|
hasIndicator = File(crashDirectory, LaunchUtils.CRASH_INDICATOR_NAME).exists()
|
||||||
File(crashDirectory, it).exists()
|
|
||||||
}
|
|
||||||
|
|
||||||
return children
|
return children
|
||||||
.map {
|
.map {
|
||||||
CrashDump(
|
CrashDump(
|
||||||
filename = it.name,
|
filename = it.name,
|
||||||
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
lastModified = Instant.fromEpochMilliseconds(it.lastModified())
|
||||||
fullPath = it.path,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.sortedByDescending { it.lastModified }
|
.sortedByDescending { it.lastModified }
|
||||||
@@ -62,12 +58,7 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
fun clearIndicator() {
|
fun clearIndicator() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
LaunchUtils.CRASH_INDICATOR_NAMES.forEach {
|
File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAME).delete()
|
||||||
val crashFile = File(baseDirectory, it)
|
|
||||||
if (crashFile.exists()) {
|
|
||||||
crashFile.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasIndicator = false
|
hasIndicator = false
|
||||||
}
|
}
|
||||||
@@ -75,8 +66,7 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
fun createIndicator() {
|
fun createIndicator() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val crashFile = File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAMES.first())
|
File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAME).createNewFile()
|
||||||
crashFile.createNewFile()
|
|
||||||
|
|
||||||
hasIndicator = true
|
hasIndicator = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ data class GeodeLog(
|
|||||||
val filename: String,
|
val filename: String,
|
||||||
val lastModified: Instant,
|
val lastModified: Instant,
|
||||||
val fileSize: Long,
|
val fileSize: Long,
|
||||||
val fullPath: String,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog>(crashDirectory) {
|
class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog>(crashDirectory) {
|
||||||
@@ -44,8 +43,7 @@ class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog
|
|||||||
GeodeLog(
|
GeodeLog(
|
||||||
filename = it.name,
|
filename = it.name,
|
||||||
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
||||||
fileSize = it.length(),
|
fileSize = it.length()
|
||||||
fullPath = it.path,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.sortedByDescending { it.lastModified }
|
.sortedByDescending { it.lastModified }
|
||||||
|
|||||||
@@ -55,14 +55,11 @@ import com.geode.launcher.preferences.ApplicationLogsActivity
|
|||||||
import com.geode.launcher.BuildConfig
|
import com.geode.launcher.BuildConfig
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
import com.geode.launcher.UserDirectoryProvider
|
import com.geode.launcher.UserDirectoryProvider
|
||||||
import com.geode.launcher.preferences.GeodeLogsListingActivity
|
|
||||||
import com.geode.launcher.preferences.TextViewActivity
|
|
||||||
import com.geode.launcher.ui.theme.Typography
|
import com.geode.launcher.ui.theme.Typography
|
||||||
import com.geode.launcher.utils.GamePackageUtils
|
import com.geode.launcher.utils.GamePackageUtils
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
data class LoadFailureInfo(
|
data class LoadFailureInfo(
|
||||||
val title: LaunchUtils.LauncherError,
|
val title: LaunchUtils.LauncherError,
|
||||||
@@ -140,28 +137,14 @@ fun ErrorInfoTitle(failureReason: LaunchUtils.LauncherError) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findSomethingForCrash(context: Context): File? {
|
|
||||||
val lastCrash = LaunchUtils.getLastCrash(context)
|
|
||||||
if (lastCrash != null) {
|
|
||||||
return lastCrash
|
|
||||||
}
|
|
||||||
|
|
||||||
val logsDirectory = LaunchUtils.getGeodeLogsDirectory(context)
|
|
||||||
if (!logsDirectory.exists()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return logsDirectory.listFiles()?.maxByOrNull { it.lastModified() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onShareCrash(context: Context) {
|
fun onShareCrash(context: Context) {
|
||||||
val lastCrash = findSomethingForCrash(context)
|
val lastCrash = LaunchUtils.getLastCrash(context)
|
||||||
if (lastCrash == null) {
|
if (lastCrash == null) {
|
||||||
Toast.makeText(context, R.string.launcher_error_export_missing, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.launcher_error_export_missing, Toast.LENGTH_SHORT).show()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||||
|
|
||||||
val providerLocation = lastCrash.toRelativeString(baseDirectory)
|
val providerLocation = lastCrash.toRelativeString(baseDirectory)
|
||||||
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
|
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
|
||||||
@@ -180,19 +163,6 @@ fun onShareCrash(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openLastCrash(context: Context) {
|
|
||||||
val lastCrash = findSomethingForCrash(context)
|
|
||||||
if (lastCrash == null) {
|
|
||||||
Toast.makeText(context, R.string.launcher_error_export_missing, Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val launchIntent = Intent(context, TextViewActivity::class.java).run {
|
|
||||||
putExtra(TextViewActivity.EXTRA_LOG_VIEW_FILENAME, lastCrash.path)
|
|
||||||
}
|
|
||||||
context.startActivity(launchIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onShowLogs(context: Context) {
|
fun onShowLogs(context: Context) {
|
||||||
val launchIntent = Intent(context, ApplicationLogsActivity::class.java)
|
val launchIntent = Intent(context, ApplicationLogsActivity::class.java)
|
||||||
context.startActivity(launchIntent)
|
context.startActivity(launchIntent)
|
||||||
@@ -202,10 +172,6 @@ fun onShowLogs(context: Context) {
|
|||||||
fun ErrorInfoDescription(description: String, modifier: Modifier = Modifier) {
|
fun ErrorInfoDescription(description: String, modifier: Modifier = Modifier) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val currentRelease = remember {
|
val currentRelease = remember {
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
|
||||||
if (BuildConfig.PREBUNDLED_GEODE != null)
|
|
||||||
"${BuildConfig.PREBUNDLED_GEODE}*"
|
|
||||||
else
|
|
||||||
PreferenceUtils.get(context).getString(PreferenceUtils.Key.CURRENT_VERSION_TAG)
|
PreferenceUtils.get(context).getString(PreferenceUtils.Key.CURRENT_VERSION_TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,10 +255,6 @@ fun ErrorInfoActions(extraDetails: String?, modifier: Modifier = Modifier) {
|
|||||||
Text(stringResource(R.string.launcher_error_copy))
|
Text(stringResource(R.string.launcher_error_copy))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val crashFile = remember { findSomethingForCrash(context) }
|
|
||||||
val readableCrash = remember { crashFile?.extension == "log" }
|
|
||||||
|
|
||||||
if (crashFile != null) {
|
|
||||||
Button(onClick = { onShareCrash(context) }) {
|
Button(onClick = { onShareCrash(context) }) {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(R.drawable.icon_share),
|
painterResource(R.drawable.icon_share),
|
||||||
@@ -302,21 +264,11 @@ fun ErrorInfoActions(extraDetails: String?, modifier: Modifier = Modifier) {
|
|||||||
Text(stringResource(R.string.launcher_error_export_crash))
|
Text(stringResource(R.string.launcher_error_export_crash))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readableCrash) {
|
val developerSettingsEnabled = remember {
|
||||||
FilledTonalButton(onClick = { openLastCrash(context) }) {
|
PreferenceUtils.get(context).getBoolean(PreferenceUtils.Key.DEVELOPER_MODE)
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.icon_description),
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
|
||||||
Text(stringResource(R.string.launcher_error_view_details))
|
|
||||||
}
|
}
|
||||||
}
|
if (developerSettingsEnabled) {
|
||||||
} else {
|
FilledTonalButton(onClick = { onShowLogs(context) }) {
|
||||||
Button(onClick = {
|
|
||||||
val launchIntent = Intent(context, GeodeLogsListingActivity::class.java)
|
|
||||||
context.startActivity(launchIntent)
|
|
||||||
}) {
|
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(R.drawable.icon_description),
|
painterResource(R.drawable.icon_description),
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import com.geode.launcher.utils.GeodeUtils
|
|||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -143,7 +142,7 @@ fun onSettings(context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LongPressButton(onClick: () -> Unit, onLongPress: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, content: @Composable RowScope.() -> Unit) {
|
fun LongPressButton(onClick: () -> Unit, onLongPress: () -> Unit, enabled: Boolean = true, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit) {
|
||||||
// compose apis don't provide a good way of adding long press to a button
|
// compose apis don't provide a good way of adding long press to a button
|
||||||
var isLongPress by remember { mutableStateOf(false) }
|
var isLongPress by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -155,11 +154,11 @@ fun LongPressButton(onClick: () -> Unit, onLongPress: () -> Unit, modifier: Modi
|
|||||||
interactionSource.interactions.collectLatest { interaction ->
|
interactionSource.interactions.collectLatest { interaction ->
|
||||||
when (interaction) {
|
when (interaction) {
|
||||||
is PressInteraction.Press -> {
|
is PressInteraction.Press -> {
|
||||||
delay(viewConfiguration.longPressTimeoutMillis.milliseconds)
|
delay(viewConfiguration.longPressTimeoutMillis)
|
||||||
|
|
||||||
// perform a second delay to make the action more obvious
|
// perform a second delay to make the action more obvious
|
||||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
delay(viewConfiguration.longPressTimeoutMillis.milliseconds)
|
delay(viewConfiguration.longPressTimeoutMillis)
|
||||||
|
|
||||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
|
||||||
@@ -187,8 +186,6 @@ fun LongPressButton(onClick: () -> Unit, onLongPress: () -> Unit, modifier: Modi
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UnsupportedVersionWarning(modifier: Modifier = Modifier) {
|
fun UnsupportedVersionWarning(modifier: Modifier = Modifier) {
|
||||||
val gameVersion = GamePackageUtils.getSupportedUnifiedVersion()
|
|
||||||
|
|
||||||
InlineDialog(
|
InlineDialog(
|
||||||
headline = {
|
headline = {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -204,7 +201,7 @@ fun UnsupportedVersionWarning(modifier: Modifier = Modifier) {
|
|||||||
},
|
},
|
||||||
body = {
|
body = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.launcher_game_update_required, gameVersion),
|
stringResource(R.string.launcher_game_update_required),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
modifier = modifier.padding(8.dp)
|
modifier = modifier.padding(8.dp)
|
||||||
|
|||||||
@@ -10,13 +10,16 @@ import androidx.compose.animation.shrinkHorizontally
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.displayCutoutPadding
|
import androidx.compose.foundation.layout.displayCutoutPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedCard
|
import androidx.compose.material3.OutlinedCard
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
@@ -39,8 +42,6 @@ import com.geode.launcher.utils.GamePackageUtils
|
|||||||
import com.geode.launcher.utils.GeodeUtils
|
import com.geode.launcher.utils.GeodeUtils
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
enum class LaunchNotificationType {
|
enum class LaunchNotificationType {
|
||||||
GEODE_UPDATED, LAUNCHER_UPDATE_AVAILABLE, UPDATE_FAILED, UNSUPPORTED_VERSION, FAILED_LOAD;
|
GEODE_UPDATED, LAUNCHER_UPDATE_AVAILABLE, UPDATE_FAILED, UNSUPPORTED_VERSION, FAILED_LOAD;
|
||||||
@@ -89,7 +90,7 @@ fun NotificationCardFromType(type: LaunchNotificationType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AnimatedNotificationCard(
|
AnimatedNotificationCard(
|
||||||
displayLength = 5.seconds,
|
displayLength = 5000L,
|
||||||
onClick = {
|
onClick = {
|
||||||
showInfoDialog = true
|
showInfoDialog = true
|
||||||
}
|
}
|
||||||
@@ -122,9 +123,10 @@ fun LaunchNotification(cards: List<LaunchNotificationType>) {
|
|||||||
val theme = Theme.fromInt(themeOption)
|
val theme = Theme.fromInt(themeOption)
|
||||||
|
|
||||||
val backgroundOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.BLACK_BACKGROUND)
|
val backgroundOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.BLACK_BACKGROUND)
|
||||||
|
val dynamicColorOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DISABLE_USER_THEME)
|
||||||
|
|
||||||
CompositionLocalProvider(LocalTheme provides theme) {
|
CompositionLocalProvider(LocalTheme provides theme) {
|
||||||
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption) {
|
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption, dynamicColor = !dynamicColorOption) {
|
||||||
// surface is not in use, so this is unfortunately not provided
|
// surface is not in use, so this is unfortunately not provided
|
||||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||||
Column(
|
Column(
|
||||||
@@ -142,15 +144,15 @@ fun LaunchNotification(cards: List<LaunchNotificationType>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AnimatedNotificationCard(modifier: Modifier = Modifier, visibilityDelay: Duration = Duration.ZERO, displayLength: Duration = 3.seconds, onClick: (() -> Unit)? = null, contents: @Composable () -> Unit) {
|
fun AnimatedNotificationCard(modifier: Modifier = Modifier, visibilityDelay: Long = 0L, displayLength: Long = 3000L, onClick: (() -> Unit)? = null, contents: @Composable () -> Unit) {
|
||||||
val state = remember {
|
val state = remember {
|
||||||
MutableTransitionState(false).apply {
|
MutableTransitionState(false).apply {
|
||||||
targetState = visibilityDelay <= Duration.ZERO
|
targetState = visibilityDelay <= 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(true) {
|
LaunchedEffect(true) {
|
||||||
if (visibilityDelay > Duration.ZERO) {
|
if (visibilityDelay > 0) {
|
||||||
delay(visibilityDelay)
|
delay(visibilityDelay)
|
||||||
state.targetState = true
|
state.targetState = true
|
||||||
}
|
}
|
||||||
@@ -282,3 +284,26 @@ fun ForwardCompatibilityContent(modifier: Modifier = Modifier) {
|
|||||||
modifier = modifier.width(IntrinsicSize.Max)
|
modifier = modifier.width(IntrinsicSize.Max)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FullScreenLoadingIndicator() {
|
||||||
|
val theme = Theme.DARK
|
||||||
|
val dynamicColorOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DISABLE_USER_THEME)
|
||||||
|
|
||||||
|
CompositionLocalProvider(LocalTheme provides theme) {
|
||||||
|
GeodeLauncherTheme(theme = theme, blackBackground = true, dynamicColor = !dynamicColorOption) {
|
||||||
|
Surface(modifier = Modifier.fillMaxSize()) {
|
||||||
|
var showProgress by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(1000L)
|
||||||
|
showProgress = true
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(showProgress) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import com.geode.launcher.BuildConfig
|
|
||||||
import com.geode.launcher.updater.ReleaseManager
|
import com.geode.launcher.updater.ReleaseManager
|
||||||
import com.geode.launcher.utils.Constants
|
import com.geode.launcher.utils.Constants
|
||||||
import com.geode.launcher.utils.GamePackageUtils
|
import com.geode.launcher.utils.GamePackageUtils
|
||||||
@@ -183,9 +182,18 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// play store build only comes with the one build of geode
|
// last launch crashed, so cancel
|
||||||
val minVersionCode = if (BuildConfig.GOOGLE_PLAY_BUILD) Constants.SUPPORTED_VERSION_CODE else Constants.SUPPORTED_VERSION_CODE_MIN
|
if (loadFailure != null) {
|
||||||
if (GamePackageUtils.getGameVersionCode(packageManager) < minVersionCode) {
|
// show a different message if this is the first thing users see
|
||||||
|
val cancelType = if (_uiState.value == LaunchUIState.Initial) {
|
||||||
|
LaunchCancelReason.LAST_LAUNCH_CRASHED_AUTOMATIC
|
||||||
|
} else LaunchCancelReason.LAST_LAUNCH_CRASHED
|
||||||
|
|
||||||
|
_uiState.tryEmit(LaunchUIState.Cancelled(cancelType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GamePackageUtils.getGameVersionCode(packageManager) < Constants.SUPPORTED_VERSION_CODE_MIN) {
|
||||||
_uiState.tryEmit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_OUTDATED))
|
_uiState.tryEmit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_OUTDATED))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -197,18 +205,6 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
|||||||
|
|
||||||
// if forcing immediate launch, then act as if it's manually started (skips timers)
|
// if forcing immediate launch, then act as if it's manually started (skips timers)
|
||||||
val forceImmediate = launchArguments?.forceLaunch == true
|
val forceImmediate = launchArguments?.forceLaunch == true
|
||||||
|
|
||||||
// last launch crashed, so cancel
|
|
||||||
if (!forceImmediate && loadFailure != null) {
|
|
||||||
// show a different message if this is the first thing users see
|
|
||||||
val cancelType = if (_uiState.value == LaunchUIState.Initial) {
|
|
||||||
LaunchCancelReason.LAST_LAUNCH_CRASHED_AUTOMATIC
|
|
||||||
} else LaunchCancelReason.LAST_LAUNCH_CRASHED
|
|
||||||
|
|
||||||
_uiState.tryEmit(LaunchUIState.Cancelled(cancelType))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceImmediate) {
|
if (forceImmediate) {
|
||||||
hasManuallyStarted = true
|
hasManuallyStarted = true
|
||||||
}
|
}
|
||||||
@@ -251,7 +247,6 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
|||||||
initReadyTimer()
|
initReadyTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
val releaseManager = ReleaseManager.get(application)
|
val releaseManager = ReleaseManager.get(application)
|
||||||
|
|
||||||
val hasGeode = LaunchUtils.isGeodeInstalled(application)
|
val hasGeode = LaunchUtils.isGeodeInstalled(application)
|
||||||
@@ -266,16 +261,11 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
|||||||
if (isRestart) {
|
if (isRestart) {
|
||||||
releaseManager.dismissedLauncherUpdate = true
|
releaseManager.dismissedLauncherUpdate = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
preReadyCheck()
|
preReadyCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun retryUpdate() {
|
fun retryUpdate() {
|
||||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ReleaseManager.get(application).checkForUpdates(true)
|
ReleaseManager.get(application).checkForUpdates(true)
|
||||||
if (ReleaseManager.get(application).isInUpdate) {
|
if (ReleaseManager.get(application).isInUpdate) {
|
||||||
restoreUpdateState = _uiState.value
|
restoreUpdateState = _uiState.value
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.PendingIntent
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageInstaller
|
import android.content.pm.PackageInstaller
|
||||||
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ fun onSelectIcon(context: Context, selectedIcon: ApplicationIconDetails) {
|
|||||||
val iconData = IconUtils.getIconDetails(it)
|
val iconData = IconUtils.getIconDetails(it)
|
||||||
|
|
||||||
packageManager.setComponentEnabledSetting(
|
packageManager.setComponentEnabledSetting(
|
||||||
ComponentName(context, "${context.packageName.removeSuffix(".play")}.${iconData.component}"),
|
ComponentName(context, "${context.packageName}.${iconData.component}"),
|
||||||
if (iconData.id == selectedIcon.id)
|
if (iconData.id == selectedIcon.id)
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||||
else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||||
@@ -237,6 +237,7 @@ fun ApplicationIconScreen(
|
|||||||
) {
|
) {
|
||||||
ApplicationIcon.entries.forEach {
|
ApplicationIcon.entries.forEach {
|
||||||
val iconData = IconUtils.getIconDetails(it)
|
val iconData = IconUtils.getIconDetails(it)
|
||||||
|
|
||||||
IconCard(iconData, selectedIcon == iconData.id)
|
IconCard(iconData, selectedIcon == iconData.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import androidx.activity.OnBackPressedDispatcher
|
|||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -84,7 +83,7 @@ class CrashLogsActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun shareCrash(context: Context, filename: String) {
|
fun shareCrash(context: Context, filename: String) {
|
||||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||||
val crashPath = File(LaunchUtils.getCrashDirectory(context), filename)
|
val crashPath = File(LaunchUtils.getCrashDirectory(context), filename)
|
||||||
|
|
||||||
if (!crashPath.exists()) {
|
if (!crashPath.exists()) {
|
||||||
@@ -111,20 +110,11 @@ fun shareCrash(context: Context, filename: String) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CrashCard(crashDump: CrashDump, crashViewModel: CrashViewModel, modifier: Modifier = Modifier) {
|
fun CrashCard(crashDump: CrashDump, crashViewModel: CrashViewModel, modifier: Modifier = Modifier) {
|
||||||
val isReadable = crashDump.filename.endsWith(".log")
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 1.dp, horizontal = 8.dp)
|
.padding(vertical = 1.dp, horizontal = 8.dp)
|
||||||
.height(64.dp)
|
.height(64.dp)
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
.clickable(isReadable) {
|
|
||||||
val launchIntent = Intent(context, TextViewActivity::class.java).run {
|
|
||||||
putExtra(TextViewActivity.EXTRA_LOG_VIEW_FILENAME, crashDump.fullPath)
|
|
||||||
}
|
|
||||||
context.startActivity(launchIntent)
|
|
||||||
}
|
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||||
.padding(vertical = 8.dp, horizontal = 8.dp)
|
.padding(vertical = 8.dp, horizontal = 8.dp)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import androidx.compose.ui.res.pluralStringResource
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.geode.launcher.BuildConfig
|
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
import com.geode.launcher.preferences.components.OptionsButton
|
import com.geode.launcher.preferences.components.OptionsButton
|
||||||
import com.geode.launcher.preferences.components.OptionsGroup
|
import com.geode.launcher.preferences.components.OptionsGroup
|
||||||
@@ -169,12 +168,6 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
SettingsCard(
|
|
||||||
title = stringResource(R.string.preference_native_pacing),
|
|
||||||
description = stringResource(R.string.preference_native_pacing_description),
|
|
||||||
preferenceKey = PreferenceUtils.Key.NATIVE_FRAME_PACING,
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
SettingsCard(
|
SettingsCard(
|
||||||
@@ -204,6 +197,20 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
|||||||
onClick = { onOpenLogs(context) }
|
onClick = { onOpenLogs(context) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OptionsButton(
|
||||||
|
title = stringResource(R.string.preferences_view_geode_logs),
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_description),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
val launchIntent = Intent(context, GeodeLogsListingActivity::class.java)
|
||||||
|
context.startActivity(launchIntent)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
OptionsButton(
|
OptionsButton(
|
||||||
title = stringResource(R.string.preferences_copy_external_button),
|
title = stringResource(R.string.preferences_copy_external_button),
|
||||||
description = LaunchUtils.getBaseDirectory(context).path,
|
description = LaunchUtils.getBaseDirectory(context).path,
|
||||||
@@ -211,7 +218,6 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
OptionsGroup(title = stringResource(R.string.preference_category_updater)) {
|
OptionsGroup(title = stringResource(R.string.preference_category_updater)) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@@ -235,7 +241,6 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
|||||||
preferenceKey = PreferenceUtils.Key.DISABLE_UPDATE_CACHE
|
preferenceKey = PreferenceUtils.Key.DISABLE_UPDATE_CACHE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
OptionsGroup(title = stringResource(R.string.preference_category_testing)) {
|
OptionsGroup(title = stringResource(R.string.preference_category_testing)) {
|
||||||
SettingsSelectCard(
|
SettingsSelectCard(
|
||||||
|
|||||||
+7
-52
@@ -1,11 +1,7 @@
|
|||||||
package com.geode.launcher.preferences
|
package com.geode.launcher.preferences
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.DocumentsContract
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.OnBackPressedDispatcher
|
import androidx.activity.OnBackPressedDispatcher
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
@@ -16,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
@@ -38,27 +35,23 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
import com.geode.launcher.UserDirectoryProvider
|
import com.geode.launcher.log.GeodeLogsViewModel
|
||||||
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
||||||
import com.geode.launcher.ui.theme.LocalTheme
|
import com.geode.launcher.ui.theme.LocalTheme
|
||||||
import com.geode.launcher.ui.theme.Theme
|
import com.geode.launcher.ui.theme.Theme
|
||||||
import com.geode.launcher.ui.theme.Typography
|
import com.geode.launcher.ui.theme.Typography
|
||||||
import com.geode.launcher.ui.theme.robotoMonoFamily
|
import com.geode.launcher.ui.theme.robotoMonoFamily
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
|
|
||||||
class TextViewActivity : ComponentActivity() {
|
class GeodeLogViewActivity : ComponentActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_LOG_VIEW_FILENAME = "EXTRA_LOG_FILENAME"
|
const val EXTRA_LOG_VIEW_FILENAME = "EXTRA_LOG_FILENAME"
|
||||||
}
|
}
|
||||||
@@ -97,48 +90,19 @@ class TextViewActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onShareFile(context: Context, filename: File) {
|
|
||||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
|
||||||
|
|
||||||
val providerLocation = filename.toRelativeString(baseDirectory)
|
|
||||||
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
|
|
||||||
|
|
||||||
val uri = DocumentsContract.buildDocumentUri("${context.packageName}.user", documentsPath)
|
|
||||||
|
|
||||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
|
||||||
putExtra(Intent.EXTRA_STREAM, uri)
|
|
||||||
type = "application/octet-stream"
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
context.startActivity(shareIntent)
|
|
||||||
} catch (_: ActivityNotFoundException) {
|
|
||||||
Toast.makeText(context, R.string.no_activity_found, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GeodeLogView(
|
fun GeodeLogView(
|
||||||
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||||
filename: String,
|
filename: String,
|
||||||
|
geodeLogsViewModel: GeodeLogsViewModel = viewModel(factory = GeodeLogsViewModel.Factory)
|
||||||
) {
|
) {
|
||||||
var loadingFile by remember { mutableStateOf(true) }
|
var loadingFile by remember { mutableStateOf(true) }
|
||||||
var description by remember { mutableStateOf(emptyList<String>()) }
|
var description by remember { mutableStateOf(emptyList<String>()) }
|
||||||
|
|
||||||
val toRead = File(filename)
|
|
||||||
|
|
||||||
LaunchedEffect(filename) {
|
LaunchedEffect(filename) {
|
||||||
loadingFile = true
|
loadingFile = true
|
||||||
|
description = geodeLogsViewModel.getFileText(filename)
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
if (!toRead.isFile) {
|
|
||||||
description = listOf("File does not exist!")
|
|
||||||
} else {
|
|
||||||
description = toRead.readLines()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingFile = false
|
loadingFile = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,21 +123,12 @@ fun GeodeLogView(
|
|||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
toRead.name,
|
filename,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
actions = {
|
|
||||||
val context = LocalContext.current
|
|
||||||
IconButton(onClick = { onShareFile(context, toRead) }) {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.icon_share),
|
|
||||||
contentDescription = stringResource(R.string.application_logs_share)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (loadingFile) {
|
if (loadingFile) {
|
||||||
@@ -80,7 +80,7 @@ class GeodeLogsListingActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun shareLog(context: Context, filename: String) {
|
fun shareLog(context: Context, filename: String) {
|
||||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||||
val crashPath = File(LaunchUtils.getGeodeLogsDirectory(context), filename)
|
val crashPath = File(LaunchUtils.getGeodeLogsDirectory(context), filename)
|
||||||
|
|
||||||
if (!crashPath.exists()) {
|
if (!crashPath.exists()) {
|
||||||
@@ -117,8 +117,8 @@ fun GeodeLogCard(geodeLog: GeodeLog, geodeLogsViewModel: GeodeLogsViewModel, mod
|
|||||||
.height(64.dp)
|
.height(64.dp)
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
.clickable {
|
.clickable {
|
||||||
val launchIntent = Intent(context, TextViewActivity::class.java).run {
|
val launchIntent = Intent(context, GeodeLogViewActivity::class.java).run {
|
||||||
putExtra(TextViewActivity.EXTRA_LOG_VIEW_FILENAME, geodeLog.fullPath)
|
putExtra(GeodeLogViewActivity.EXTRA_LOG_VIEW_FILENAME, geodeLog.filename)
|
||||||
}
|
}
|
||||||
context.startActivity(launchIntent)
|
context.startActivity(launchIntent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.geode.launcher.BuildConfig
|
import com.geode.launcher.BuildConfig
|
||||||
|
import com.geode.launcher.MainActivity
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
import com.geode.launcher.UserDirectoryProvider
|
import com.geode.launcher.UserDirectoryProvider
|
||||||
import com.geode.launcher.preferences.components.BackupButton
|
import com.geode.launcher.preferences.components.BackupButton
|
||||||
@@ -496,7 +497,7 @@ fun GameplaySettingsGroup() {
|
|||||||
SafeModeDialog(onDismiss = {
|
SafeModeDialog(onDismiss = {
|
||||||
showSafeModeDialog = false
|
showSafeModeDialog = false
|
||||||
}) {
|
}) {
|
||||||
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)!!.apply {
|
val launchIntent = Intent(context, MainActivity::class.java).apply {
|
||||||
// hardcode it, why not
|
// hardcode it, why not
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
data = "geode-launcher://main/launch?safe-mode=true".toUri()
|
data = "geode-launcher://main/launch?safe-mode=true".toUri()
|
||||||
@@ -509,10 +510,6 @@ fun GameplaySettingsGroup() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpdateSettingsGroup(releaseViewModel: ReleaseViewModel, snackbarHostState: SnackbarHostState, onUpdate: () -> Unit) {
|
fun UpdateSettingsGroup(releaseViewModel: ReleaseViewModel, snackbarHostState: SnackbarHostState, onUpdate: () -> Unit) {
|
||||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val updateStatus by releaseViewModel.uiState.collectAsState()
|
val updateStatus by releaseViewModel.uiState.collectAsState()
|
||||||
|
|
||||||
OptionsGroup(stringResource(R.string.preference_category_updater)) {
|
OptionsGroup(stringResource(R.string.preference_category_updater)) {
|
||||||
@@ -584,20 +581,6 @@ fun AdvancedSettingsGroup(developerModeEnabled: Boolean, snackbarHostState: Snac
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionsButton(
|
|
||||||
title = stringResource(R.string.preferences_view_geode_logs),
|
|
||||||
icon = {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.icon_description),
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
val launchIntent = Intent(context, GeodeLogsListingActivity::class.java)
|
|
||||||
context.startActivity(launchIntent)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
OptionsButton(
|
OptionsButton(
|
||||||
title = stringResource(R.string.preferences_view_crashes),
|
title = stringResource(R.string.preferences_view_crashes),
|
||||||
icon = {
|
icon = {
|
||||||
@@ -664,14 +647,11 @@ fun AboutSettingsGroup(developerModeEnabled: Boolean) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
|
||||||
OptionsLabel(
|
OptionsLabel(
|
||||||
stringResource(R.string.preference_loader_version_name),
|
stringResource(R.string.preference_loader_version_name),
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.preference_loader_version_description,
|
R.string.preference_loader_version_description,
|
||||||
if (BuildConfig.PREBUNDLED_GEODE != null)
|
currentRelease ?: "unknown"
|
||||||
"${BuildConfig.PREBUNDLED_GEODE}*"
|
|
||||||
else (currentRelease ?: "unknown")
|
|
||||||
),
|
),
|
||||||
displayInline = true
|
displayInline = true
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,20 +16,9 @@ val quicksandFamily = FontFamily(
|
|||||||
Font(R.font.quicksand_semibold, FontWeight.SemiBold)
|
Font(R.font.quicksand_semibold, FontWeight.SemiBold)
|
||||||
)
|
)
|
||||||
|
|
||||||
val googleSansFamily = FontFamily(
|
|
||||||
Font(R.font.googlesans_medium, FontWeight.Medium)
|
|
||||||
)
|
|
||||||
|
|
||||||
val launcherTitleStyle = TextStyle(
|
val launcherTitleStyle = TextStyle(
|
||||||
fontFamily = quicksandFamily,
|
fontFamily = quicksandFamily,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 32.sp,
|
fontSize = 32.sp,
|
||||||
letterSpacing = 0.sp
|
letterSpacing = 0.sp
|
||||||
)
|
)
|
||||||
|
|
||||||
val sapphireTitleStyle = TextStyle(
|
|
||||||
fontFamily = googleSansFamily,
|
|
||||||
fontWeight = FontWeight.Medium,
|
|
||||||
fontSize = 32.sp,
|
|
||||||
letterSpacing = 0.sp
|
|
||||||
)
|
|
||||||
@@ -3,9 +3,10 @@ package com.geode.launcher.updater
|
|||||||
import com.geode.launcher.utils.LaunchUtils
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Asset(
|
data class Asset @OptIn(ExperimentalTime::class) constructor(
|
||||||
val url: String,
|
val url: String,
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
@@ -13,11 +14,10 @@ data class Asset(
|
|||||||
val createdAt: Instant,
|
val createdAt: Instant,
|
||||||
val updatedAt: Instant,
|
val updatedAt: Instant,
|
||||||
val browserDownloadUrl: String,
|
val browserDownloadUrl: String,
|
||||||
val digest: String?,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Release(
|
data class Release @OptIn(ExperimentalTime::class) constructor(
|
||||||
val url: String,
|
val url: String,
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val targetCommitish: String,
|
val targetCommitish: String,
|
||||||
@@ -31,29 +31,12 @@ data class Release(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class LoaderPlatformDownload(
|
data class LoaderVersion @OptIn(ExperimentalTime::class) constructor(
|
||||||
val url: String,
|
|
||||||
val hash: String?,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class LoaderDownload(
|
|
||||||
val win: LoaderPlatformDownload,
|
|
||||||
val mac: LoaderPlatformDownload,
|
|
||||||
val android32: LoaderPlatformDownload,
|
|
||||||
val android64: LoaderPlatformDownload,
|
|
||||||
val ios: LoaderPlatformDownload,
|
|
||||||
val resources: LoaderPlatformDownload,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class LoaderVersion(
|
|
||||||
val tag: String,
|
val tag: String,
|
||||||
val version: String,
|
val version: String,
|
||||||
val createdAt: Instant,
|
val createdAt: Instant,
|
||||||
val commitHash: String,
|
val commitHash: String,
|
||||||
val prerelease: Boolean,
|
val prerelease: Boolean
|
||||||
val downloads: LoaderDownload,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -62,25 +45,7 @@ data class LoaderPayload<T>(
|
|||||||
val error: String
|
val error: String
|
||||||
)
|
)
|
||||||
|
|
||||||
data class DownloadableAsset(
|
data class DownloadableAsset(val url: String, val filename: String, val size: Long? = null)
|
||||||
val url: String,
|
|
||||||
val filename: String,
|
|
||||||
val size: Long? = null,
|
|
||||||
val hash: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun mapDownload(download: Asset): DownloadableAsset {
|
|
||||||
val hash = if (download.digest?.startsWith("sha256:") == true)
|
|
||||||
download.digest.removePrefix("sha256:")
|
|
||||||
else null
|
|
||||||
|
|
||||||
return DownloadableAsset(
|
|
||||||
url = download.browserDownloadUrl,
|
|
||||||
filename = download.name,
|
|
||||||
size = download.size.toLong(),
|
|
||||||
hash = hash,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class Downloadable {
|
abstract class Downloadable {
|
||||||
/**
|
/**
|
||||||
@@ -97,11 +62,6 @@ abstract class Downloadable {
|
|||||||
* Release download link for the current platform
|
* Release download link for the current platform
|
||||||
*/
|
*/
|
||||||
abstract fun getDownload(): DownloadableAsset?
|
abstract fun getDownload(): DownloadableAsset?
|
||||||
|
|
||||||
/**
|
|
||||||
* Resources download link for current platform.
|
|
||||||
*/
|
|
||||||
abstract fun getResourcesDownload(): DownloadableAsset?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadable() {
|
class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadable() {
|
||||||
@@ -118,6 +78,7 @@ class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadab
|
|||||||
return release.tagName
|
return release.tagName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
override fun getDescriptor(): Long {
|
override fun getDescriptor(): Long {
|
||||||
return release.createdAt.epochSeconds
|
return release.createdAt.epochSeconds
|
||||||
}
|
}
|
||||||
@@ -134,15 +95,11 @@ class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadab
|
|||||||
|
|
||||||
override fun getDownload(): DownloadableAsset? {
|
override fun getDownload(): DownloadableAsset? {
|
||||||
val download = getGitHubDownload() ?: return null
|
val download = getGitHubDownload() ?: return null
|
||||||
return mapDownload(download)
|
return DownloadableAsset(
|
||||||
}
|
url = download.browserDownloadUrl,
|
||||||
|
filename = download.name,
|
||||||
override fun getResourcesDownload(): DownloadableAsset? {
|
size = download.size.toLong()
|
||||||
val download = release.assets.find {
|
)
|
||||||
it.name == "resources.zip"
|
|
||||||
} ?: return null
|
|
||||||
|
|
||||||
return mapDownload(download)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +108,7 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
|||||||
return release.tagName
|
return release.tagName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
override fun getDescriptor(): Long {
|
override fun getDescriptor(): Long {
|
||||||
return release.createdAt.epochSeconds
|
return release.createdAt.epochSeconds
|
||||||
}
|
}
|
||||||
@@ -168,17 +126,18 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
|||||||
* 0 | 0 | 1
|
* 0 | 0 | 1
|
||||||
* surprise! it's an xnor
|
* surprise! it's an xnor
|
||||||
*/
|
*/
|
||||||
asset.name.endsWith("apk") && asset.name.contains("android32") == use32BitPlatform
|
|
||||||
|
(asset.name.contains("android32")) == use32BitPlatform
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDownload(): DownloadableAsset? {
|
override fun getDownload(): DownloadableAsset? {
|
||||||
val download = getGitHubDownload() ?: return null
|
val download = getGitHubDownload() ?: return null
|
||||||
return mapDownload(download)
|
return DownloadableAsset(
|
||||||
}
|
url = download.browserDownloadUrl,
|
||||||
|
filename = download.name,
|
||||||
override fun getResourcesDownload(): DownloadableAsset? {
|
size = download.size.toLong()
|
||||||
return null
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,27 +146,16 @@ class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadab
|
|||||||
return version.tag
|
return version.tag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
override fun getDescriptor(): Long {
|
override fun getDescriptor(): Long {
|
||||||
return version.createdAt.epochSeconds
|
return version.createdAt.epochSeconds
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDownload(): DownloadableAsset {
|
override fun getDownload(): DownloadableAsset? {
|
||||||
val data = if (LaunchUtils.is64bit) version.downloads.android64 else version.downloads.android32
|
|
||||||
val filename = "geode-${version.tag}-${LaunchUtils.platformName}.zip"
|
val filename = "geode-${version.tag}-${LaunchUtils.platformName}.zip"
|
||||||
return DownloadableAsset(
|
return DownloadableAsset(
|
||||||
url = data.url,
|
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/$filename",
|
||||||
filename = filename,
|
filename = filename
|
||||||
hash = data.hash,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getResourcesDownload(): DownloadableAsset {
|
|
||||||
val data = version.downloads.resources
|
|
||||||
val filename = "resources.zip"
|
|
||||||
return DownloadableAsset(
|
|
||||||
url = data.url,
|
|
||||||
filename = filename,
|
|
||||||
hash = data.hash,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,16 +18,12 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okio.HashingSink
|
|
||||||
import okio.blackholeSink
|
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import okio.source
|
import okio.source
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.InterruptedIOException
|
import java.io.InterruptedIOException
|
||||||
import java.io.IOException
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
@@ -75,8 +71,7 @@ class ReleaseManager private constructor(
|
|||||||
|
|
||||||
class UpdateException(reason: Reason? = null, cause: Throwable? = null) : Exception(reason?.name, cause) {
|
class UpdateException(reason: Reason? = null, cause: Throwable? = null) : Exception(reason?.name, cause) {
|
||||||
enum class Reason {
|
enum class Reason {
|
||||||
EXTERNAL_FILE_IN_USE,
|
EXTERNAL_FILE_IN_USE
|
||||||
HASH_VALIDATION_FAILED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var reason: Reason? = reason
|
var reason: Reason? = reason
|
||||||
@@ -99,10 +94,7 @@ class ReleaseManager private constructor(
|
|||||||
|
|
||||||
var dismissedLauncherUpdate = false
|
var dismissedLauncherUpdate = false
|
||||||
|
|
||||||
var skipStateUpdate = false
|
|
||||||
|
|
||||||
private fun sendError(e: Exception) {
|
private fun sendError(e: Exception) {
|
||||||
skipStateUpdate = true
|
|
||||||
_uiState.value = ReleaseManagerState.Failure(e)
|
_uiState.value = ReleaseManagerState.Failure(e)
|
||||||
|
|
||||||
// ignore cancellation, it's good actually
|
// ignore cancellation, it's good actually
|
||||||
@@ -156,17 +148,17 @@ class ReleaseManager private constructor(
|
|||||||
val outputFile = File(outputDirectory, download.filename)
|
val outputFile = File(outputDirectory, download.filename)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val hash = DownloadUtils.downloadFile(
|
DownloadUtils.downloadStream(
|
||||||
httpClient,
|
httpClient,
|
||||||
download.url,
|
download.url,
|
||||||
outputFile
|
onResponse = { body ->
|
||||||
)
|
body.source().use { source ->
|
||||||
|
outputFile.sink().buffer().use { sink ->
|
||||||
if (download.hash != null && download.hash != hash) {
|
sink.writeAll(source)
|
||||||
Log.w("ReleaseManager", "downloadLauncherUpdate failed: found $hash but expected ${download.hash}")
|
|
||||||
outputFile.delete()
|
|
||||||
throw UpdateException(UpdateException.Reason.HASH_VALIDATION_FAILED)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
return Result.failure(e)
|
return Result.failure(e)
|
||||||
}
|
}
|
||||||
@@ -174,70 +166,7 @@ class ReleaseManager private constructor(
|
|||||||
return Result.success(outputFile)
|
return Result.success(outputFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun performResourceDownload(resourceAsset: DownloadableAsset, initialSize: Long): Boolean {
|
|
||||||
val guessSize = if (resourceAsset.size == null) null else initialSize + resourceAsset.size
|
|
||||||
val guessInitial = if (guessSize == null) 0 else initialSize
|
|
||||||
_uiState.value = ReleaseManagerState.InDownload(guessInitial, guessSize)
|
|
||||||
|
|
||||||
val outputDir = getTempResourcesDirectory()
|
|
||||||
val outputPath = outputDir.path
|
|
||||||
|
|
||||||
val finalDir = LaunchUtils.getGeodeResourcesDirectory(applicationContext)
|
|
||||||
|
|
||||||
val downloadFile = getTempFile("resources.zip")
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!outputDir.exists()) {
|
|
||||||
outputDir.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
val hash = DownloadUtils.downloadFile(
|
|
||||||
httpClient,
|
|
||||||
resourceAsset.url,
|
|
||||||
downloadFile,
|
|
||||||
onProgress = { progress, outOf ->
|
|
||||||
if (!skipStateUpdate)
|
|
||||||
_uiState.value = ReleaseManagerState.InDownload(initialSize + progress, outOf + initialSize)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if (resourceAsset.hash != null && resourceAsset.hash != hash) {
|
|
||||||
Log.w("ReleaseManager", "performResourceDownload failed: found $hash but expected ${resourceAsset.hash}")
|
|
||||||
throw UpdateException(UpdateException.Reason.HASH_VALIDATION_FAILED)
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadFile.inputStream().use { zip ->
|
|
||||||
DownloadUtils.copyZipStreamToDirectory(
|
|
||||||
zip,
|
|
||||||
outputDir
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finalDir.exists()) {
|
|
||||||
finalDir.deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!outputDir.renameTo(finalDir)) {
|
|
||||||
println("Failed to rename temporary directory!")
|
|
||||||
DownloadUtils.copyDirectory(outputDir, finalDir)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
sendError(e)
|
|
||||||
return false
|
|
||||||
} finally {
|
|
||||||
val tempPathClone = File(outputPath)
|
|
||||||
try {
|
|
||||||
if (downloadFile.exists()) downloadFile.delete()
|
|
||||||
if (tempPathClone.exists()) tempPathClone.deleteRecursively()
|
|
||||||
} catch (_: IOException) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun performUpdate(release: Downloadable) {
|
private suspend fun performUpdate(release: Downloadable) {
|
||||||
skipStateUpdate = false
|
|
||||||
|
|
||||||
val releaseAsset = release.getDownload()
|
val releaseAsset = release.getDownload()
|
||||||
if (releaseAsset == null) {
|
if (releaseAsset == null) {
|
||||||
val noAssetException = Exception("missing Android download")
|
val noAssetException = Exception("missing Android download")
|
||||||
@@ -246,23 +175,15 @@ class ReleaseManager private constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val resourcesAsset = release.getResourcesDownload()
|
|
||||||
|
|
||||||
var releaseSize = releaseAsset.size ?: 0L
|
|
||||||
val resourcesSize = resourcesAsset?.size ?: 0L
|
|
||||||
|
|
||||||
// set an initial download size
|
// set an initial download size
|
||||||
val initialSize = if (releaseAsset.size == null && resourcesAsset?.size == null) null else releaseSize + resourcesSize
|
_uiState.value = ReleaseManagerState.InDownload(0, releaseAsset.size)
|
||||||
_uiState.value = ReleaseManagerState.InDownload(0, initialSize)
|
|
||||||
|
|
||||||
val outputFile = getTempFile(LaunchUtils.geodeFilename)
|
val outputFile = getTempFile()
|
||||||
// clone the file instance as renameTo may move the original file
|
// clone the file instance as renameTo may move the original file
|
||||||
val tempFilePath = outputFile.path
|
val tempFilePath = outputFile.path
|
||||||
|
|
||||||
val geodeFile = getGeodeOutputPath()
|
val geodeFile = getGeodeOutputPath()
|
||||||
|
|
||||||
val downloadFile = getTempFile("geode.zip")
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// tempFile should be in same path as geodeFile
|
// tempFile should be in same path as geodeFile
|
||||||
val geodeParent = geodeFile.parentFile
|
val geodeParent = geodeFile.parentFile
|
||||||
@@ -270,30 +191,18 @@ class ReleaseManager private constructor(
|
|||||||
geodeParent.mkdirs()
|
geodeParent.mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
val hash = DownloadUtils.downloadFile(
|
DownloadUtils.downloadStream(
|
||||||
httpClient,
|
httpClient,
|
||||||
releaseAsset.url,
|
releaseAsset.url,
|
||||||
downloadFile,
|
|
||||||
onProgress = { progress, outOf ->
|
onProgress = { progress, outOf ->
|
||||||
if (!skipStateUpdate)
|
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
|
||||||
_uiState.value = ReleaseManagerState.InDownload(progress, outOf + resourcesSize)
|
},
|
||||||
|
onResponse = { body ->
|
||||||
releaseSize = max(outOf, releaseSize)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (releaseAsset.hash != null && releaseAsset.hash != hash) {
|
|
||||||
Log.w("ReleaseManager", "performUpdate failed: found $hash but expected ${releaseAsset.hash}")
|
|
||||||
throw UpdateException(UpdateException.Reason.HASH_VALIDATION_FAILED)
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadFile.inputStream().use { zip ->
|
|
||||||
DownloadUtils.extractFileFromZipStream(
|
DownloadUtils.extractFileFromZipStream(
|
||||||
zip,
|
body.byteStream(),
|
||||||
outputFile,
|
outputFile,
|
||||||
geodeFile.name
|
geodeFile.name
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// work around a permission issue from adb push
|
// work around a permission issue from adb push
|
||||||
if (geodeFile.exists()) {
|
if (geodeFile.exists()) {
|
||||||
@@ -308,20 +217,15 @@ class ReleaseManager private constructor(
|
|||||||
// attempt a manual copy if rename fails for whatever reason
|
// attempt a manual copy if rename fails for whatever reason
|
||||||
DownloadUtils.copyFile(outputFile, geodeFile)
|
DownloadUtils.copyFile(outputFile, geodeFile)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
sendError(e)
|
sendError(e)
|
||||||
return
|
return
|
||||||
} finally {
|
} finally {
|
||||||
val tempFileClone = File(tempFilePath)
|
val tempFileClone = File(tempFilePath)
|
||||||
try {
|
runCatching {
|
||||||
if (downloadFile.exists()) downloadFile.delete()
|
|
||||||
if (tempFileClone.exists()) tempFileClone.delete()
|
if (tempFileClone.exists()) tempFileClone.delete()
|
||||||
} catch (_: IOException) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourcesAsset != null) {
|
|
||||||
if (!performResourceDownload(resourcesAsset, releaseSize)) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,18 +249,14 @@ class ReleaseManager private constructor(
|
|||||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||||
|
|
||||||
val originalFileHash = sharedPreferences.getString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED)
|
val originalFileHash = sharedPreferences.getString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED)
|
||||||
?: return true
|
?: return false
|
||||||
|
|
||||||
val currentFileHash = computeFileHash(geodeFile)
|
val currentFileHash = computeFileHash(geodeFile)
|
||||||
|
|
||||||
return originalFileHash != currentFileHash
|
return originalFileHash != currentFileHash
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getLatestLauncherUpdate(): DownloadableLauncherRelease? {
|
private suspend fun getLatestLauncherUpdate(): DownloadableLauncherRelease? {
|
||||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
// don't check for updates on Google Play builds
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
releaseRepository.getLatestLauncherRelease()
|
releaseRepository.getLatestLauncherRelease()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -366,8 +266,6 @@ class ReleaseManager private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun checkLauncherUpdate() {
|
suspend fun checkLauncherUpdate() {
|
||||||
if (BuildConfig.GOOGLE_PLAY_BUILD) return
|
|
||||||
|
|
||||||
val launcherRelease = getLatestLauncherUpdate() ?: return
|
val launcherRelease = getLatestLauncherUpdate() ?: return
|
||||||
|
|
||||||
// unconditionally track it, we're not using it for checks and it might be good to have
|
// unconditionally track it, we're not using it for checks and it might be good to have
|
||||||
@@ -384,8 +282,6 @@ class ReleaseManager private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun checkCachedLauncherUpdate() {
|
fun checkCachedLauncherUpdate() {
|
||||||
if (BuildConfig.GOOGLE_PLAY_BUILD) return
|
|
||||||
|
|
||||||
val lastUpdate = PreferenceUtils.get(applicationContext)
|
val lastUpdate = PreferenceUtils.get(applicationContext)
|
||||||
.getString(PreferenceUtils.Key.LAST_LAUNCHER_UPDATE) ?: return
|
.getString(PreferenceUtils.Key.LAST_LAUNCHER_UPDATE) ?: return
|
||||||
|
|
||||||
@@ -395,12 +291,6 @@ class ReleaseManager private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun downloadLatestLauncherUpdate(): Result<File> {
|
suspend fun downloadLatestLauncherUpdate(): Result<File> {
|
||||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
return Result.failure(UnsupportedOperationException(
|
|
||||||
"Launcher updates are not supported on Google Play builds"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
val update = _availableLauncherUpdateDetails.value
|
val update = _availableLauncherUpdateDetails.value
|
||||||
?: getLatestLauncherUpdate()
|
?: getLatestLauncherUpdate()
|
||||||
?: return Result.failure(FileNotFoundException())
|
?: return Result.failure(FileNotFoundException())
|
||||||
@@ -409,10 +299,6 @@ class ReleaseManager private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun shouldUseCache(): Boolean {
|
fun shouldUseCache(): Boolean {
|
||||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||||
if (sharedPreferences.getBoolean(PreferenceUtils.Key.DISABLE_UPDATE_CACHE)) {
|
if (sharedPreferences.getBoolean(PreferenceUtils.Key.DISABLE_UPDATE_CACHE)) {
|
||||||
return false
|
return false
|
||||||
@@ -431,11 +317,6 @@ class ReleaseManager private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun checkForNewRelease(isManual: Boolean = false) {
|
private suspend fun checkForNewRelease(isManual: Boolean = false) {
|
||||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
_uiState.value = ReleaseManagerState.Finished()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||||
|
|
||||||
if (!isManual && shouldUseCache()) {
|
if (!isManual && shouldUseCache()) {
|
||||||
@@ -503,14 +384,8 @@ class ReleaseManager private constructor(
|
|||||||
sharedPreferences.setString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED, fileHash)
|
sharedPreferences.setString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED, fileHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeFileHash(file: File): String {
|
private fun computeFileHash(file: File): String =
|
||||||
val sink = HashingSink.md5(blackholeSink())
|
file.source().buffer().use { it.readByteString().md5().hex() }
|
||||||
file.source().buffer().use {
|
|
||||||
it.readAll(sink)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sink.hash.hex()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getGeodeOutputPath(): File {
|
private fun getGeodeOutputPath(): File {
|
||||||
val geodeName = LaunchUtils.geodeFilename
|
val geodeName = LaunchUtils.geodeFilename
|
||||||
@@ -519,40 +394,25 @@ class ReleaseManager private constructor(
|
|||||||
return File(geodeDirectory, geodeName)
|
return File(geodeDirectory, geodeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRandomString(): String {
|
private fun getTempFile(): File {
|
||||||
val alphabet = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
val geodeName = LaunchUtils.geodeFilename
|
||||||
return buildString(8) {
|
|
||||||
repeat(8) { append(alphabet.random()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getTempFile(filename: String): File {
|
|
||||||
val geodeDirectory = LaunchUtils.getBaseDirectory(applicationContext)
|
val geodeDirectory = LaunchUtils.getBaseDirectory(applicationContext)
|
||||||
|
|
||||||
// warning!! while File::createTempFile may look tempting, a certain brand of phones has a messed up implementation of it
|
// warning!! while File::createTempFile may look tempting, a certain brand of phones has a messed up implementation of it
|
||||||
// so we're making a temp file manually (as long as it doesn't collide with the geode download, it's okay)
|
// so we're making a temp file manually (as long as it doesn't collide with the geode download, it's okay)
|
||||||
|
|
||||||
val suffix = createRandomString()
|
val alphabet = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
||||||
val tmpName = "tmp-$suffix.$filename"
|
val suffix = buildString(8) {
|
||||||
|
repeat(8) { append(alphabet.random()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val tmpName = "tmp-$suffix.$geodeName"
|
||||||
|
|
||||||
val tempFile = File(geodeDirectory, tmpName)
|
val tempFile = File(geodeDirectory, tmpName)
|
||||||
|
|
||||||
return tempFile
|
return tempFile
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTempResourcesDirectory(): File {
|
|
||||||
val finalDir = LaunchUtils.getGeodeResourcesDirectory(applicationContext)
|
|
||||||
val suffix = createRandomString()
|
|
||||||
val tempDirName = "tmp-$suffix-geode.loader"
|
|
||||||
|
|
||||||
val parentDir = finalDir.parentFile
|
|
||||||
return if (parentDir != null) {
|
|
||||||
File(finalDir.parentFile, tempDirName)
|
|
||||||
} else {
|
|
||||||
File(tempDirName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels the current update job.
|
* Cancels the current update job.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.AP
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import com.geode.launcher.BuildConfig
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -90,10 +89,6 @@ class ReleaseViewModel(private val application: Application): ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun runReleaseCheck(isManual: Boolean = false) {
|
fun runReleaseCheck(isManual: Boolean = false) {
|
||||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hasPerformedCheck = true
|
hasPerformedCheck = true
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ object Constants {
|
|||||||
const val SUPPORTED_VERSION_CODE_MIN = 37L
|
const val SUPPORTED_VERSION_CODE_MIN = 37L
|
||||||
|
|
||||||
// anything below this version code shows a warning on the main menu
|
// anything below this version code shows a warning on the main menu
|
||||||
const val SUPPORTED_VERSION_CODE_MIN_WARNING = 41L
|
const val SUPPORTED_VERSION_CODE_MIN_WARNING = 40L
|
||||||
|
|
||||||
// anything above this version code will show a warning when starting the game
|
// anything above this version code will show a warning when starting the game
|
||||||
const val SUPPORTED_VERSION_CODE = 41L
|
const val SUPPORTED_VERSION_CODE = 41L
|
||||||
|
|||||||
@@ -17,12 +17,10 @@ import okio.BufferedSource
|
|||||||
import okio.FileSystem
|
import okio.FileSystem
|
||||||
import okio.ForwardingSink
|
import okio.ForwardingSink
|
||||||
import okio.ForwardingSource
|
import okio.ForwardingSource
|
||||||
import okio.HashingSink
|
|
||||||
import okio.Path.Companion.toOkioPath
|
import okio.Path.Companion.toOkioPath
|
||||||
import okio.Sink
|
import okio.Sink
|
||||||
import okio.Source
|
import okio.Source
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@@ -34,31 +32,12 @@ import java.util.zip.ZipInputStream
|
|||||||
typealias ProgressCallback = (progress: Long, outOf: Long) -> Unit
|
typealias ProgressCallback = (progress: Long, outOf: Long) -> Unit
|
||||||
|
|
||||||
object DownloadUtils {
|
object DownloadUtils {
|
||||||
suspend fun downloadFile(
|
|
||||||
httpClient: OkHttpClient,
|
|
||||||
url: String,
|
|
||||||
outputFile: File,
|
|
||||||
onProgress: ProgressCallback? = null,
|
|
||||||
): String = downloadStream(
|
|
||||||
httpClient,
|
|
||||||
url,
|
|
||||||
onProgress = onProgress
|
|
||||||
).use { body ->
|
|
||||||
val hashingSink = HashingSink.sha256(outputFile.sink().buffer())
|
|
||||||
hashingSink.use { sink ->
|
|
||||||
body.source().use { source ->
|
|
||||||
source.readAll(sink)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hashingSink.hash.hex()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun downloadStream(
|
suspend fun downloadStream(
|
||||||
httpClient: OkHttpClient,
|
httpClient: OkHttpClient,
|
||||||
url: String,
|
url: String,
|
||||||
onProgress: ProgressCallback? = null,
|
onProgress: ProgressCallback? = null,
|
||||||
): ResponseBody {
|
onResponse: suspend (ResponseBody) -> Unit,
|
||||||
|
) {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.build()
|
.build()
|
||||||
@@ -85,42 +64,10 @@ object DownloadUtils {
|
|||||||
val progressClient = progressClientBuilder.build()
|
val progressClient = progressClientBuilder.build()
|
||||||
|
|
||||||
val call = progressClient.newCall(request)
|
val call = progressClient.newCall(request)
|
||||||
val response = call.executeAsync()
|
call.executeAsync().use { response ->
|
||||||
|
when (response.code) {
|
||||||
val responseCode = response.code
|
200 -> onResponse(response.body)
|
||||||
if (responseCode != 200) {
|
else -> throw IOException("unexpected response ${response.code}")
|
||||||
response.close()
|
|
||||||
throw IOException("unexpected response ${response.code}")
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.body
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun copyZipStreamToDirectory(inputStream: InputStream, output: File) = runInterruptible {
|
|
||||||
ZipInputStream(inputStream).use { zipStream ->
|
|
||||||
if (output.exists()) output.deleteRecursively()
|
|
||||||
output.mkdirs()
|
|
||||||
|
|
||||||
val canonicalOutput = output.canonicalPath
|
|
||||||
|
|
||||||
var entry = zipStream.nextEntry
|
|
||||||
while (entry != null) {
|
|
||||||
val destination = File(output, entry.name)
|
|
||||||
if (!destination.canonicalPath.startsWith(canonicalOutput)) {
|
|
||||||
throw IOException("attempted copy to outside of output directory: ${entry.name}")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destination.isDirectory) {
|
|
||||||
destination.mkdirs()
|
|
||||||
} else {
|
|
||||||
destination.parentFile?.mkdirs()
|
|
||||||
|
|
||||||
destination.outputStream().use { destinationStream ->
|
|
||||||
zipStream.copyTo(destinationStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = zipStream.nextEntry
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,13 +131,6 @@ object DownloadUtils {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun copyDirectory(sourceFile: File, outputFile: File) = withContext(Dispatchers.IO) {
|
|
||||||
sourceFile.copyRecursively(
|
|
||||||
outputFile,
|
|
||||||
overwrite = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// based on <https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java>
|
// based on <https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import android.os.Environment
|
|||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import androidx.core.net.toUri
|
|
||||||
|
|
||||||
class FileUtils {
|
class FileUtils {
|
||||||
// copied from https://stackoverflow.com/questions/17546101/get-real-path-for-uri-android
|
// copied from https://stackoverflow.com/questions/17546101/get-real-path-for-uri-android
|
||||||
@@ -87,7 +86,7 @@ class FileUtils {
|
|||||||
val file = File(id)
|
val file = File(id)
|
||||||
if (file.exists()) return id
|
if (file.exists()) return id
|
||||||
}
|
}
|
||||||
val contentUri = ContentUris.withAppendedId("content://downloads/public_downloads".toUri(), java.lang.Long.valueOf(id))
|
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
|
||||||
return getDataColumn(context, contentUri, null, null)
|
return getDataColumn(context, contentUri, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,10 +63,6 @@ object GamePackageUtils {
|
|||||||
return gameVersionMap[versionCode] ?: getGameVersionString(packageManager)
|
return gameVersionMap[versionCode] ?: getGameVersionString(packageManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSupportedUnifiedVersion(): String {
|
|
||||||
return gameVersionMap[Constants.SUPPORTED_VERSION_CODE] ?: Constants.SUPPORTED_VERSION_STRING
|
|
||||||
}
|
|
||||||
|
|
||||||
fun detectAbiMismatch(context: Context, packageInfo: PackageInfo, loadException: Error): Boolean {
|
fun detectAbiMismatch(context: Context, packageInfo: PackageInfo, loadException: Error): Boolean {
|
||||||
val abi = LaunchUtils.applicationArchitecture
|
val abi = LaunchUtils.applicationArchitecture
|
||||||
val isExtracted =
|
val isExtracted =
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ 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
|
||||||
|
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
|
||||||
@@ -214,15 +215,13 @@ object GeodeUtils {
|
|||||||
val relativePath = pathFile.relativeTo(baseDirectory)
|
val relativePath = pathFile.relativeTo(baseDirectory)
|
||||||
val uri = DocumentsContract.buildDocumentUri(
|
val uri = DocumentsContract.buildDocumentUri(
|
||||||
"com.android.externalstorage.documents",
|
"com.android.externalstorage.documents",
|
||||||
if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
"primary:Android/data/${context.packageName}/$relativePath"
|
|
||||||
} else {
|
|
||||||
"primary:Android/media/${context.packageName}/$relativePath"
|
"primary:Android/media/${context.packageName}/$relativePath"
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Intent(Intent.ACTION_VIEW).apply {
|
Intent(Intent.ACTION_VIEW).apply {
|
||||||
setDataAndType(uri, DocumentsContract.Document.MIME_TYPE_DIR)
|
setDataAndType(uri, DocumentsContract.Document.MIME_TYPE_DIR)
|
||||||
|
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
@@ -410,7 +409,7 @@ object GeodeUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isGeodeUri(uri: Uri): Boolean {
|
fun isGeodeUri(uri: Uri): Boolean {
|
||||||
return "${BuildConfig.APPLICATION_ID}.user" == uri.authority
|
return "com.geode.launcher.user" == uri.authority
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val INTERNAL_PERMISSION_PREFIX = "geode.permission_internal"
|
private const val INTERNAL_PERMISSION_PREFIX = "geode.permission_internal"
|
||||||
@@ -429,7 +428,7 @@ object GeodeUtils {
|
|||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
)
|
)
|
||||||
|
|
||||||
permissions.all {
|
return permissions.all {
|
||||||
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
|
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -558,7 +557,6 @@ object GeodeUtils {
|
|||||||
const val CAPABILITY_EXTENDED_INPUT = "extended_input"
|
const val CAPABILITY_EXTENDED_INPUT = "extended_input"
|
||||||
const val CAPABILITY_TIMESTAMP_INPUT = "timestamp_inputs"
|
const val CAPABILITY_TIMESTAMP_INPUT = "timestamp_inputs"
|
||||||
const val CAPABILITY_RICH_INPUT = "internal_callbacks_v2"
|
const val CAPABILITY_RICH_INPUT = "internal_callbacks_v2"
|
||||||
const val CAPABILITY_RESIZE_CALLBACK = "resize_callbacks"
|
|
||||||
|
|
||||||
private var capabilityListener: WeakReference<CapabilityListener?> = WeakReference(null)
|
private var capabilityListener: WeakReference<CapabilityListener?> = WeakReference(null)
|
||||||
|
|
||||||
|
|||||||
@@ -43,26 +43,23 @@ enum class ApplicationIcon {
|
|||||||
DEFAULT,
|
DEFAULT,
|
||||||
GEODE,
|
GEODE,
|
||||||
PRIDE,
|
PRIDE,
|
||||||
TRANS,
|
TRANS;
|
||||||
SAPPHIRE;
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromId(id: String) = when (id) {
|
fun fromId(id: String) = when (id) {
|
||||||
"default" -> DEFAULT
|
"default" -> ApplicationIcon.DEFAULT
|
||||||
"geode" -> GEODE
|
"geode" -> ApplicationIcon.GEODE
|
||||||
"pride" -> PRIDE
|
"pride" -> ApplicationIcon.PRIDE
|
||||||
"trans" -> TRANS
|
"trans" -> ApplicationIcon.TRANS
|
||||||
"sapphire" -> SAPPHIRE
|
else -> ApplicationIcon.DEFAULT
|
||||||
else -> DEFAULT
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toId() = when (this) {
|
fun toId() = when (this) {
|
||||||
PRIDE -> "pride"
|
ApplicationIcon.PRIDE -> "pride"
|
||||||
GEODE -> "geode"
|
ApplicationIcon.GEODE -> "geode"
|
||||||
DEFAULT -> "default"
|
ApplicationIcon.DEFAULT -> "default"
|
||||||
TRANS -> "trans"
|
ApplicationIcon.TRANS -> "trans"
|
||||||
SAPPHIRE -> "sapphire"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,11 +96,5 @@ object IconUtils {
|
|||||||
iconId = R.mipmap.ic_launcher_trans,
|
iconId = R.mipmap.ic_launcher_trans,
|
||||||
nameId = R.string.application_icon_trans
|
nameId = R.string.application_icon_trans
|
||||||
)
|
)
|
||||||
ApplicationIcon.SAPPHIRE -> ApplicationIconDetails(
|
|
||||||
id = id.toId(),
|
|
||||||
component = "MainActivitySapphire",
|
|
||||||
iconId = R.mipmap.ic_launcher_sapphire,
|
|
||||||
nameId = R.string.application_icon_sapphire,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ package com.geode.launcher.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import com.geode.launcher.BuildConfig
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
@@ -42,9 +41,6 @@ object LaunchUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isGeodeInstalled(context: Context): Boolean {
|
fun isGeodeInstalled(context: Context): Boolean {
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
|
||||||
if (BuildConfig.PREBUNDLED_GEODE != null) return true
|
|
||||||
|
|
||||||
return getInstalledGeodePath(context) != null
|
return getInstalledGeodePath(context) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,14 +48,10 @@ object LaunchUtils {
|
|||||||
* Returns the directory that Geode/the game should base itself off of.
|
* Returns the directory that Geode/the game should base itself off of.
|
||||||
*/
|
*/
|
||||||
fun getBaseDirectory(context: Context, ignoreProfile: Boolean = false): File {
|
fun getBaseDirectory(context: Context, ignoreProfile: Boolean = false): File {
|
||||||
val dir = if (BuildConfig.GOOGLE_PLAY_BUILD) {
|
|
||||||
context.getExternalFilesDir(null)!!.parentFile!!
|
|
||||||
} else {
|
|
||||||
// deprecated, but seems to be the best choice of directory (i forced mat to test it)
|
// deprecated, but seems to be the best choice of directory (i forced mat to test it)
|
||||||
// also, is getting the first item the correct choice here?? what do they mean
|
// also, is getting the first item the correct choice here?? what do they mean
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
context.externalMediaDirs.first()
|
val dir = context.externalMediaDirs.first()
|
||||||
}
|
|
||||||
|
|
||||||
// prevent having resources added to system gallery
|
// prevent having resources added to system gallery
|
||||||
// accessing this file every time the directory is read may be a little wasteful...
|
// accessing this file every time the directory is read may be a little wasteful...
|
||||||
@@ -81,7 +73,7 @@ object LaunchUtils {
|
|||||||
return File(getBaseDirectory(context), "save")
|
return File(getBaseDirectory(context), "save")
|
||||||
}
|
}
|
||||||
|
|
||||||
val CRASH_INDICATOR_NAMES = setOf("lastSessionDidCrash", "last-pid", "last-crashed")
|
const val CRASH_INDICATOR_NAME = "lastSessionDidCrash"
|
||||||
|
|
||||||
fun getCrashDirectory(context: Context): File {
|
fun getCrashDirectory(context: Context): File {
|
||||||
val base = getBaseDirectory(context)
|
val base = getBaseDirectory(context)
|
||||||
@@ -93,11 +85,6 @@ object LaunchUtils {
|
|||||||
return File(base, "game/geode/logs/")
|
return File(base, "game/geode/logs/")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGeodeResourcesDirectory(context: Context): File {
|
|
||||||
val base = getBaseDirectory(context)
|
|
||||||
return File(base, "game/geode/resources/geode.loader/")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLastCrash(context: Context): File? {
|
fun getLastCrash(context: Context): File? {
|
||||||
val crashDirectory = getCrashDirectory(context)
|
val crashDirectory = getCrashDirectory(context)
|
||||||
if (!crashDirectory.exists()) {
|
if (!crashDirectory.exists()) {
|
||||||
@@ -106,7 +93,7 @@ object LaunchUtils {
|
|||||||
|
|
||||||
val children = crashDirectory.listFiles {
|
val children = crashDirectory.listFiles {
|
||||||
// ignore indicator files (including old file)
|
// ignore indicator files (including old file)
|
||||||
_, name -> !CRASH_INDICATOR_NAMES.contains(name)
|
_, name -> name != CRASH_INDICATOR_NAME && name != "last-pid"
|
||||||
}
|
}
|
||||||
|
|
||||||
return children?.maxByOrNull { it.lastModified() }
|
return children?.maxByOrNull { it.lastModified() }
|
||||||
@@ -114,9 +101,9 @@ object LaunchUtils {
|
|||||||
|
|
||||||
fun lastSessionCrashed(context: Context): Boolean {
|
fun lastSessionCrashed(context: Context): Boolean {
|
||||||
val base = getCrashDirectory(context)
|
val base = getCrashDirectory(context)
|
||||||
return CRASH_INDICATOR_NAMES.any {
|
val crashIndicatorFile = File(base, CRASH_INDICATOR_NAME)
|
||||||
File(base, it).exists()
|
|
||||||
}
|
return crashIndicatorFile.exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class LauncherError : Serializable {
|
enum class LauncherError : Serializable {
|
||||||
@@ -132,6 +119,12 @@ object LaunchUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class FullLauncherError(
|
||||||
|
val error: LauncherError,
|
||||||
|
val returnMessage: String,
|
||||||
|
val extendedMessage: String? = null,
|
||||||
|
) : Serializable
|
||||||
|
|
||||||
const val LAUNCHER_KEY_RETURN_ERROR = "return_error"
|
const val LAUNCHER_KEY_RETURN_ERROR = "return_error"
|
||||||
const val LAUNCHER_KEY_RETURN_MESSAGE = "return_message"
|
const val LAUNCHER_KEY_RETURN_MESSAGE = "return_message"
|
||||||
const val LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE = "return_extended_message"
|
const val LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE = "return_extended_message"
|
||||||
|
|||||||
@@ -149,8 +149,6 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
|||||||
LAST_UPDATE_CHECK_TIME,
|
LAST_UPDATE_CHECK_TIME,
|
||||||
LAST_LAUNCHER_UPDATE,
|
LAST_LAUNCHER_UPDATE,
|
||||||
DISABLE_UPDATE_CACHE,
|
DISABLE_UPDATE_CACHE,
|
||||||
PREBUNDLED_GEODE_MIGRATION_PERFORMED,
|
|
||||||
NATIVE_FRAME_PACING,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun defaultValueForBooleanKey(key: Key): Boolean {
|
private fun defaultValueForBooleanKey(key: Key): Boolean {
|
||||||
@@ -201,8 +199,6 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
|||||||
Key.LAST_UPDATE_CHECK_TIME -> "PreferenceLastUpdateCheckTime"
|
Key.LAST_UPDATE_CHECK_TIME -> "PreferenceLastUpdateCheckTime"
|
||||||
Key.LAST_LAUNCHER_UPDATE -> "PreferenceLastLauncherUpdate"
|
Key.LAST_LAUNCHER_UPDATE -> "PreferenceLastLauncherUpdate"
|
||||||
Key.DISABLE_UPDATE_CACHE -> "PreferenceDisableUpdateCache"
|
Key.DISABLE_UPDATE_CACHE -> "PreferenceDisableUpdateCache"
|
||||||
Key.PREBUNDLED_GEODE_MIGRATION_PERFORMED -> "PreferencePrebundledGeodeMigrationPerformed"
|
|
||||||
Key.NATIVE_FRAME_PACING -> "PreferenceNativeFramePacing"
|
|
||||||
// Key.RELEASE_CHANNEL -> "PreferenceReleaseChannel"
|
// Key.RELEASE_CHANNEL -> "PreferenceReleaseChannel"
|
||||||
// Key.LAST_DISMISSED_UPDATE -> "PreferenceLastDismissedUpdate"
|
// Key.LAST_DISMISSED_UPDATE -> "PreferenceLastDismissedUpdate"
|
||||||
// Key.FORCE_HRR -> "PreferenceForceHighRefreshRate"
|
// Key.FORCE_HRR -> "PreferenceForceHighRefreshRate"
|
||||||
|
|||||||
@@ -6,19 +6,17 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
const val MS_TO_SEC = 1000L
|
const val MS_TO_SEC = 1000L
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun useCountdownTimer(
|
fun useCountdownTimer(
|
||||||
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
|
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
|
||||||
time: Duration,
|
time: Long,
|
||||||
onCountdownFinish: () -> Unit
|
onCountdownFinish: () -> Unit
|
||||||
): Long {
|
): Long {
|
||||||
var millisUntilFinished by remember {
|
var millisUntilFinished by remember {
|
||||||
mutableStateOf(time)
|
mutableLongStateOf(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldBeCounting by remember {
|
var shouldBeCounting by remember {
|
||||||
@@ -30,9 +28,9 @@ fun useCountdownTimer(
|
|||||||
return@LaunchedEffect
|
return@LaunchedEffect
|
||||||
}
|
}
|
||||||
|
|
||||||
if (millisUntilFinished > Duration.ZERO) {
|
if (millisUntilFinished > 0) {
|
||||||
delay(1.seconds)
|
delay(MS_TO_SEC)
|
||||||
millisUntilFinished -= 1.seconds
|
millisUntilFinished -= MS_TO_SEC
|
||||||
} else {
|
} else {
|
||||||
onCountdownFinish()
|
onCountdownFinish()
|
||||||
}
|
}
|
||||||
@@ -60,5 +58,5 @@ fun useCountdownTimer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return millisUntilFinished.inWholeSeconds
|
return millisUntilFinished / MS_TO_SEC
|
||||||
}
|
}
|
||||||
@@ -1,45 +1,31 @@
|
|||||||
package org.cocos2dx.lib
|
package org.cocos2dx.lib
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.*
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Paint.FontMetricsInt
|
import android.graphics.Paint.FontMetricsInt
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import androidx.core.graphics.createBitmap
|
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
import java.util.LinkedList
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
|
||||||
private const val HORIZONTALALIGN_LEFT = 1
|
|
||||||
private const val HORIZONTALALIGN_RIGHT = 2
|
|
||||||
private const val HORIZONTALALIGN_CENTER = 3
|
|
||||||
private const val VERTICALALIGN_TOP = 1
|
|
||||||
private const val VERTICALALIGN_BOTTOM = 2
|
|
||||||
private const val VERTICALALIGN_CENTER = 3
|
|
||||||
|
|
||||||
@Suppress("KotlinJniMissingFunction")
|
@Suppress("KotlinJniMissingFunction")
|
||||||
object Cocos2dxBitmap {
|
object Cocos2dxBitmap {
|
||||||
private lateinit var context: WeakReference<Context>
|
private lateinit var context: WeakReference<Context>
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun nativeInitBitmapDC(pWidth: Int, pHeight: Int, pPixels: ByteArray)
|
private external fun nativeInitBitmapDC(pWidth: Int, pHeight: Int, pPixels: ByteArray)
|
||||||
|
|
||||||
fun setContext(context: Context) {
|
fun setContext(context: Context) {
|
||||||
this.context = WeakReference(context)
|
this.context = WeakReference(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
/*
|
||||||
@Suppress("unused")
|
|
||||||
fun createTextBitmap(
|
fun createTextBitmap(
|
||||||
string: String,
|
string: String,
|
||||||
fontName: String,
|
fontName: String,
|
||||||
@@ -69,6 +55,7 @@ object Cocos2dxBitmap {
|
|||||||
1.0f
|
1.0f
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@@ -92,18 +79,19 @@ object Cocos2dxBitmap {
|
|||||||
strokeB: Float,
|
strokeB: Float,
|
||||||
strokeSize: Float
|
strokeSize: Float
|
||||||
) {
|
) {
|
||||||
val horizontalAlignment = alignment and 0x0F
|
val bitmapTotalHeight: Int
|
||||||
val verticalAlignment = (alignment shr 4) and 0x0F
|
val horizontalAlignment = alignment and 15
|
||||||
|
val verticalAlignment = alignment shr 4 and 15
|
||||||
val pString2 = refactorString(string)
|
val pString2 = refactorString(string)
|
||||||
val paint = newPaint(fontName, fontSize, horizontalAlignment)
|
val paint = newPaint(fontName, fontSize, horizontalAlignment)
|
||||||
paint.setARGB(
|
paint.setARGB(
|
||||||
255,
|
MotionEvent.ACTION_MASK,
|
||||||
(255.0 * fontTintR).toInt(),
|
(255.0 * fontTintR.toDouble()).toInt(),
|
||||||
(255.0 * fontTintG).toInt(),
|
(255.0 * fontTintG.toDouble()).toInt(),
|
||||||
(255.0 * fontTintB).toInt()
|
(255.0 * fontTintB.toDouble()).toInt()
|
||||||
)
|
)
|
||||||
val textProperty = computeTextProperty(pString2, width, height, paint)
|
val textProperty = computeTextProperty(pString2, width, height, paint)
|
||||||
val bitmapTotalHeight = if (height == 0) {
|
bitmapTotalHeight = if (height == 0) {
|
||||||
textProperty.totalHeight
|
textProperty.totalHeight
|
||||||
} else {
|
} else {
|
||||||
height
|
height
|
||||||
@@ -113,8 +101,7 @@ object Cocos2dxBitmap {
|
|||||||
var renderTextDeltaX = 0.0f
|
var renderTextDeltaX = 0.0f
|
||||||
var renderTextDeltaY = 0.0f
|
var renderTextDeltaY = 0.0f
|
||||||
if (shadow) {
|
if (shadow) {
|
||||||
val shadowColor = 0xff7d7d7du.toInt()
|
paint.setShadowLayer(shadowBlur, shadowDX, shadowDY, -8553091)
|
||||||
paint.setShadowLayer(shadowBlur, shadowDX, shadowDY, shadowColor)
|
|
||||||
bitmapPaddingX = abs(shadowDX)
|
bitmapPaddingX = abs(shadowDX)
|
||||||
bitmapPaddingY = abs(shadowDY)
|
bitmapPaddingY = abs(shadowDY)
|
||||||
if (shadowDX.toDouble() < 0.0) {
|
if (shadowDX.toDouble() < 0.0) {
|
||||||
@@ -124,47 +111,56 @@ object Cocos2dxBitmap {
|
|||||||
renderTextDeltaY = bitmapPaddingY
|
renderTextDeltaY = bitmapPaddingY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val bitmap = Bitmap.createBitmap(
|
||||||
val bitmap = try {
|
|
||||||
createBitmap(
|
|
||||||
textProperty.maxWidth + bitmapPaddingX.toInt(),
|
textProperty.maxWidth + bitmapPaddingX.toInt(),
|
||||||
bitmapTotalHeight + bitmapPaddingY.toInt()
|
bitmapPaddingY.toInt() + bitmapTotalHeight,
|
||||||
|
Bitmap.Config.ARGB_8888
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
|
||||||
// bandaid fix, hopefully someone checks logs to see this
|
|
||||||
Log.e("Cocos2dxBitmap", "Error creating bitmap: $e")
|
|
||||||
nativeInitBitmapDC(0, 0, ByteArray(0))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val canvas = Canvas(bitmap)
|
val canvas = Canvas(bitmap)
|
||||||
val fontMetricsInt = paint.fontMetricsInt
|
val fontMetricsInt = paint.fontMetricsInt
|
||||||
var y = computeY(fontMetricsInt, height, textProperty.totalHeight, verticalAlignment)
|
var y = computeY(fontMetricsInt, height, textProperty.totalHeight, verticalAlignment)
|
||||||
val lines = textProperty.lines
|
val lines = textProperty.lines
|
||||||
|
val length = lines.size
|
||||||
for (line in lines) {
|
for (i in 0 until length) {
|
||||||
val x = computeX(line, textProperty.maxWidth, horizontalAlignment)
|
val line = lines[i]
|
||||||
canvas.drawText(line, x + renderTextDeltaX, y + renderTextDeltaY, paint)
|
canvas.drawText(
|
||||||
|
line!!,
|
||||||
|
computeX(
|
||||||
|
line,
|
||||||
|
textProperty.maxWidth,
|
||||||
|
horizontalAlignment
|
||||||
|
).toFloat() + renderTextDeltaX,
|
||||||
|
y.toFloat() + renderTextDeltaY,
|
||||||
|
paint
|
||||||
|
)
|
||||||
y += textProperty.heightPerLine
|
y += textProperty.heightPerLine
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stroke) {
|
if (stroke) {
|
||||||
val paintStroke = newPaint(fontName, fontSize, horizontalAlignment)
|
val paintStroke = newPaint(fontName, fontSize, horizontalAlignment)
|
||||||
paintStroke.style = Paint.Style.STROKE
|
paintStroke.style = Paint.Style.STROKE
|
||||||
paintStroke.strokeWidth = strokeSize * 0.5f
|
paintStroke.strokeWidth = 0.5f * strokeSize
|
||||||
paintStroke.setARGB(
|
paintStroke.setARGB(
|
||||||
255,
|
MotionEvent.ACTION_MASK,
|
||||||
strokeR.toInt() * MotionEvent.ACTION_MASK,
|
strokeR.toInt() * MotionEvent.ACTION_MASK,
|
||||||
strokeG.toInt() * MotionEvent.ACTION_MASK,
|
strokeG.toInt() * MotionEvent.ACTION_MASK,
|
||||||
strokeB.toInt() * MotionEvent.ACTION_MASK
|
strokeB.toInt() * MotionEvent.ACTION_MASK
|
||||||
)
|
)
|
||||||
var y = computeY(fontMetricsInt, height, textProperty.totalHeight, verticalAlignment)
|
var y2 = computeY(fontMetricsInt, height, textProperty.totalHeight, verticalAlignment)
|
||||||
val lines = textProperty.lines
|
val lines2 = textProperty.lines
|
||||||
|
val length2 = lines2.size
|
||||||
for (line in lines) {
|
for (i2 in 0 until length2) {
|
||||||
val x = computeX(line, textProperty.maxWidth, horizontalAlignment)
|
val line2 = lines2[i2]
|
||||||
canvas.drawText(line, x + renderTextDeltaX, y + renderTextDeltaY, paintStroke)
|
canvas.drawText(
|
||||||
y += textProperty.heightPerLine
|
line2!!,
|
||||||
|
computeX(
|
||||||
|
line2,
|
||||||
|
textProperty.maxWidth,
|
||||||
|
horizontalAlignment
|
||||||
|
).toFloat() + renderTextDeltaX,
|
||||||
|
y2.toFloat() + renderTextDeltaY,
|
||||||
|
paintStroke
|
||||||
|
)
|
||||||
|
y2 += textProperty.heightPerLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initNativeObject(bitmap)
|
initNativeObject(bitmap)
|
||||||
@@ -172,24 +168,24 @@ object Cocos2dxBitmap {
|
|||||||
|
|
||||||
private fun newPaint(fontName: String, fontSize: Int, horizontalAlignment: Int): Paint {
|
private fun newPaint(fontName: String, fontSize: Int, horizontalAlignment: Int): Paint {
|
||||||
val paint = Paint()
|
val paint = Paint()
|
||||||
paint.color = Color.WHITE
|
paint.color = -1
|
||||||
paint.textSize = fontSize.toFloat()
|
paint.textSize = fontSize.toFloat()
|
||||||
paint.isAntiAlias = true
|
paint.isAntiAlias = true
|
||||||
if (fontName.endsWith(".ttf")) {
|
if (fontName.endsWith(".ttf")) {
|
||||||
try {
|
try {
|
||||||
context.get()!!.apply {
|
context.get()?.apply {
|
||||||
paint.typeface = Cocos2dxTypefaces[this, fontName]
|
paint.typeface = Cocos2dxTypefaces[this, fontName]
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Cocos2dxBitmap", "error to create ttf type face: $fontName - $e")
|
Log.e("Cocos2dxBitmap", "error to create ttf type face: $fontName")
|
||||||
paint.typeface = Typeface.create(fontName, Typeface.NORMAL)
|
paint.typeface = Typeface.create(fontName, Typeface.NORMAL)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
paint.typeface = Typeface.create(fontName, Typeface.NORMAL)
|
paint.typeface = Typeface.create(fontName, Typeface.NORMAL)
|
||||||
}
|
}
|
||||||
when (horizontalAlignment) {
|
when (horizontalAlignment) {
|
||||||
HORIZONTALALIGN_RIGHT -> paint.textAlign = Paint.Align.RIGHT
|
2 -> paint.textAlign = Paint.Align.RIGHT
|
||||||
HORIZONTALALIGN_CENTER -> paint.textAlign = Paint.Align.CENTER
|
3 -> paint.textAlign = Paint.Align.CENTER
|
||||||
else -> paint.textAlign = Paint.Align.LEFT
|
else -> paint.textAlign = Paint.Align.LEFT
|
||||||
}
|
}
|
||||||
return paint
|
return paint
|
||||||
@@ -202,14 +198,14 @@ object Cocos2dxBitmap {
|
|||||||
paint: Paint
|
paint: Paint
|
||||||
): TextProperty {
|
): TextProperty {
|
||||||
val fm = paint.fontMetricsInt
|
val fm = paint.fontMetricsInt
|
||||||
val h = fm.bottom - fm.top
|
val h = ceil((fm.bottom - fm.top).toDouble()).toInt()
|
||||||
var maxContentWidth = 0
|
var maxContentWidth = 0
|
||||||
val lines = splitString(string, width, height, paint)
|
val lines = splitString(string, width, height, paint)
|
||||||
if (width != 0) {
|
if (width != 0) {
|
||||||
maxContentWidth = width
|
maxContentWidth = width
|
||||||
} else {
|
} else {
|
||||||
for (line in lines) {
|
for (line in lines) {
|
||||||
val temp = ceil(paint.measureText(line, 0, line.length)).toInt()
|
val temp = ceil(paint.measureText(line, 0, line!!.length).toDouble()).toInt()
|
||||||
if (temp > maxContentWidth) {
|
if (temp > maxContentWidth) {
|
||||||
maxContentWidth = temp
|
maxContentWidth = temp
|
||||||
}
|
}
|
||||||
@@ -218,10 +214,10 @@ object Cocos2dxBitmap {
|
|||||||
return TextProperty(maxContentWidth, h, lines)
|
return TextProperty(maxContentWidth, h, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeX(@Suppress("UNUSED_PARAMETER") text: String, maxWidth: Int, horizontalAlignment: Int): Int {
|
private fun computeX(@Suppress("UNUSED_PARAMETER") text: String?, maxWidth: Int, horizontalAlignment: Int): Int {
|
||||||
return when (horizontalAlignment) {
|
return when (horizontalAlignment) {
|
||||||
HORIZONTALALIGN_RIGHT -> maxWidth
|
2 -> maxWidth
|
||||||
HORIZONTALALIGN_CENTER -> maxWidth / 2
|
3 -> maxWidth / 2
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,15 +229,13 @@ object Cocos2dxBitmap {
|
|||||||
verticalAlignment: Int
|
verticalAlignment: Int
|
||||||
): Int {
|
): Int {
|
||||||
val y = -fontMetricsInt.top
|
val y = -fontMetricsInt.top
|
||||||
return if (constrainHeight > totalHeight) {
|
return if (constrainHeight <= totalHeight) {
|
||||||
when (verticalAlignment) {
|
|
||||||
VERTICALALIGN_TOP -> -fontMetricsInt.top
|
|
||||||
VERTICALALIGN_CENTER -> -fontMetricsInt.top + (constrainHeight - totalHeight) / 2
|
|
||||||
VERTICALALIGN_BOTTOM -> -fontMetricsInt.top + (constrainHeight - totalHeight)
|
|
||||||
else -> y
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
y
|
y
|
||||||
|
} else when (verticalAlignment) {
|
||||||
|
1 -> -fontMetricsInt.top
|
||||||
|
2 -> -fontMetricsInt.top + (constrainHeight - totalHeight)
|
||||||
|
3 -> -fontMetricsInt.top + (constrainHeight - totalHeight) / 2
|
||||||
|
else -> y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,56 +244,56 @@ object Cocos2dxBitmap {
|
|||||||
maxWidth: Int,
|
maxWidth: Int,
|
||||||
maxHeight: Int,
|
maxHeight: Int,
|
||||||
paint: Paint
|
paint: Paint
|
||||||
): List<String> {
|
): Array<String?> {
|
||||||
val lines = string.split("\n")
|
val lines: Array<String?> = string.split("\\n").toTypedArray()
|
||||||
val fm = paint.fontMetricsInt
|
val fm = paint.fontMetricsInt
|
||||||
val heightPerLine = fm.bottom - fm.top
|
val maxLines = maxHeight / ceil((fm.bottom - fm.top).toDouble()).toInt()
|
||||||
val maxLines = maxHeight / heightPerLine
|
|
||||||
return if (maxWidth != 0) {
|
return if (maxWidth != 0) {
|
||||||
val strList = LinkedList<String>()
|
val strList = LinkedList<String?>()
|
||||||
for (line in lines) {
|
for (line in lines) {
|
||||||
val lineWidth = ceil(paint.measureText(line)).toInt()
|
if (ceil(paint.measureText(line).toDouble()).toInt() > maxWidth) {
|
||||||
|
|
||||||
if (lineWidth > maxWidth) {
|
|
||||||
strList.addAll(divideStringWithMaxWidth(line, maxWidth, paint))
|
strList.addAll(divideStringWithMaxWidth(line, maxWidth, paint))
|
||||||
} else {
|
} else {
|
||||||
strList.add(line)
|
strList.add(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxLines > 0 && strList.size >= maxLines) {
|
if (maxLines > 0 && strList.size >= maxLines) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxLines > 0 && strList.size > maxLines) {
|
if (maxLines > 0 && strList.size > maxLines) {
|
||||||
while (strList.size > maxLines) {
|
while (strList.size > maxLines) {
|
||||||
strList.removeLast()
|
strList.removeLast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val ret = arrayOfNulls<String>(strList.size)
|
||||||
strList
|
strList.toArray(ret)
|
||||||
} else if (maxHeight != 0 && lines.size > maxLines) {
|
ret
|
||||||
lines.take(maxLines)
|
} else if (maxHeight == 0 || lines.size <= maxLines) {
|
||||||
} else {
|
|
||||||
lines
|
lines
|
||||||
|
} else {
|
||||||
|
val strList2 = LinkedList<String?>()
|
||||||
|
for (i in 0 until maxLines) {
|
||||||
|
strList2.add(lines[i])
|
||||||
|
}
|
||||||
|
val ret2 = arrayOfNulls<String>(strList2.size)
|
||||||
|
strList2.toArray(ret2)
|
||||||
|
ret2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun divideStringWithMaxWidth(
|
private fun divideStringWithMaxWidth(
|
||||||
string: String,
|
string: String?,
|
||||||
maxWidth: Int,
|
maxWidth: Int,
|
||||||
paint: Paint
|
paint: Paint
|
||||||
): LinkedList<String> {
|
): LinkedList<String?> {
|
||||||
val charLength = string.length
|
val charLength = string!!.length
|
||||||
var start = 0
|
var start = 0
|
||||||
val strList = LinkedList<String>()
|
val strList = LinkedList<String?>()
|
||||||
|
|
||||||
var i = 1
|
var i = 1
|
||||||
while (i <= charLength) {
|
while (i <= charLength) {
|
||||||
val tempWidth = ceil(paint.measureText(string, start, i)).toInt()
|
val tempWidth = ceil(paint.measureText(string, start, i).toDouble()).toInt()
|
||||||
if (tempWidth >= maxWidth) {
|
if (tempWidth >= maxWidth) {
|
||||||
val lastIndexOfSpace = string.substring(0, i).lastIndexOf(" ")
|
val lastIndexOfSpace = string.substring(0, i).lastIndexOf(" ")
|
||||||
|
|
||||||
if (lastIndexOfSpace != -1 && lastIndexOfSpace > start) {
|
if (lastIndexOfSpace != -1 && lastIndexOfSpace > start) {
|
||||||
strList.add(string.substring(start, lastIndexOfSpace))
|
strList.add(string.substring(start, lastIndexOfSpace))
|
||||||
i = lastIndexOfSpace + 1
|
i = lastIndexOfSpace + 1
|
||||||
@@ -309,20 +303,16 @@ object Cocos2dxBitmap {
|
|||||||
} else {
|
} else {
|
||||||
strList.add(string.substring(start, i))
|
strList.add(string.substring(start, i))
|
||||||
}
|
}
|
||||||
|
|
||||||
while (i < charLength && string[i] == ' ') {
|
while (i < charLength && string[i] == ' ') {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
start = i
|
start = i
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start < charLength) {
|
if (start < charLength) {
|
||||||
strList.add(string.substring(start))
|
strList.add(string.substring(start))
|
||||||
}
|
}
|
||||||
|
|
||||||
return strList
|
return strList
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,22 +320,19 @@ object Cocos2dxBitmap {
|
|||||||
if (pString.compareTo("") == 0) {
|
if (pString.compareTo("") == 0) {
|
||||||
return " "
|
return " "
|
||||||
}
|
}
|
||||||
|
|
||||||
val strBuilder = StringBuilder(pString)
|
val strBuilder = StringBuilder(pString)
|
||||||
var start = 0
|
var start = 0
|
||||||
var index = strBuilder.indexOf("\n")
|
var index = strBuilder.indexOf("\n")
|
||||||
while (index != -1) {
|
while (index != -1) {
|
||||||
start = if (index == 0 || strBuilder[index - 1] == '\n') {
|
start = if (index == 0 || strBuilder[index - 1].code == 10) {
|
||||||
strBuilder.insert(start, " ")
|
strBuilder.insert(start, " ")
|
||||||
index + 2
|
index + 2
|
||||||
} else {
|
} else {
|
||||||
index + 1
|
index + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start > strBuilder.length || index == strBuilder.length) {
|
if (start > strBuilder.length || index == strBuilder.length) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
index = strBuilder.indexOf("\n", start)
|
index = strBuilder.indexOf("\n", start)
|
||||||
}
|
}
|
||||||
return strBuilder.toString()
|
return strBuilder.toString()
|
||||||
@@ -353,10 +340,15 @@ object Cocos2dxBitmap {
|
|||||||
|
|
||||||
private fun initNativeObject(pBitmap: Bitmap) {
|
private fun initNativeObject(pBitmap: Bitmap) {
|
||||||
val pixels = getPixels(pBitmap)
|
val pixels = getPixels(pBitmap)
|
||||||
|
if (pixels != null) {
|
||||||
nativeInitBitmapDC(pBitmap.width, pBitmap.height, pixels)
|
nativeInitBitmapDC(pBitmap.width, pBitmap.height, pixels)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getPixels(pBitmap: Bitmap): ByteArray {
|
private fun getPixels(pBitmap: Bitmap?): ByteArray? {
|
||||||
|
if (pBitmap == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
val pixels = ByteArray(pBitmap.width * pBitmap.height * 4)
|
val pixels = ByteArray(pBitmap.width * pBitmap.height * 4)
|
||||||
val buf = ByteBuffer.wrap(pixels)
|
val buf = ByteBuffer.wrap(pixels)
|
||||||
buf.order(ByteOrder.nativeOrder())
|
buf.order(ByteOrder.nativeOrder())
|
||||||
@@ -369,18 +361,13 @@ object Cocos2dxBitmap {
|
|||||||
private fun getFontSizeAccordingHeight(height: Int): Int {
|
private fun getFontSizeAccordingHeight(height: Int): Int {
|
||||||
val paint = Paint()
|
val paint = Paint()
|
||||||
val bounds = Rect()
|
val bounds = Rect()
|
||||||
|
|
||||||
paint.typeface = Typeface.DEFAULT
|
paint.typeface = Typeface.DEFAULT
|
||||||
var incrTextSize = 1
|
var incrTextSize = 1
|
||||||
var foundDesiredSize = false
|
var foundDesiredSize = false
|
||||||
|
|
||||||
while (!foundDesiredSize) {
|
while (!foundDesiredSize) {
|
||||||
paint.textSize = incrTextSize.toFloat()
|
paint.textSize = incrTextSize.toFloat()
|
||||||
val text = "SghMNy"
|
paint.getTextBounds("SghMNy", 0, "SghMNy".length, bounds)
|
||||||
paint.getTextBounds(text, 0, text.length, bounds)
|
|
||||||
|
|
||||||
incrTextSize++
|
incrTextSize++
|
||||||
|
|
||||||
if (height - bounds.height() <= 2) {
|
if (height - bounds.height() <= 2) {
|
||||||
foundDesiredSize = true
|
foundDesiredSize = true
|
||||||
}
|
}
|
||||||
@@ -395,19 +382,23 @@ object Cocos2dxBitmap {
|
|||||||
if (TextUtils.isEmpty(string)) {
|
if (TextUtils.isEmpty(string)) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val paint = TextPaint()
|
val paint = TextPaint()
|
||||||
paint.typeface = Typeface.DEFAULT
|
paint.typeface = Typeface.DEFAULT
|
||||||
paint.textSize = fontSize
|
paint.textSize = fontSize
|
||||||
|
|
||||||
return TextUtils.ellipsize(string, paint, width, TextUtils.TruncateAt.END).toString()
|
return TextUtils.ellipsize(string, paint, width, TextUtils.TruncateAt.END).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TextProperty(
|
private class TextProperty constructor(
|
||||||
val maxWidth: Int,
|
val maxWidth: Int,
|
||||||
val heightPerLine: Int,
|
val heightPerLine: Int, pLines: Array<String?>
|
||||||
val lines: Collection<String>
|
|
||||||
) {
|
) {
|
||||||
val totalHeight: Int = lines.size * heightPerLine
|
val lines: Array<String?>
|
||||||
|
|
||||||
|
val totalHeight: Int
|
||||||
|
|
||||||
|
init {
|
||||||
|
totalHeight = pLines.size * heightPerLine
|
||||||
|
lines = pLines
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,18 +17,15 @@ import android.view.inputmethod.InputMethodManager
|
|||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.widget.AppCompatEditText
|
import androidx.appcompat.widget.AppCompatEditText
|
||||||
import com.customRobTop.BaseRobTopActivity
|
import com.customRobTop.BaseRobTopActivity
|
||||||
import com.geode.launcher.ExplicitGLSurfaceView
|
|
||||||
import com.geode.launcher.LauncherFix
|
|
||||||
import com.geode.launcher.utils.GeodeUtils
|
import com.geode.launcher.utils.GeodeUtils
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.roundToLong
|
|
||||||
|
|
||||||
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 MS_TO_NS = 1_000_000
|
private const val MS_TO_NS = 1_000_000
|
||||||
private const val SEC_TO_NS = 1_000_000_000.0
|
private const val SEC_TO_NS = 1_000_000_000.0
|
||||||
|
|
||||||
class Cocos2dxGLSurfaceView(context: Context) : ExplicitGLSurfaceView(context) {
|
class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
|
||||||
companion object {
|
companion object {
|
||||||
lateinit var cocos2dxGLSurfaceView: Cocos2dxGLSurfaceView
|
lateinit var cocos2dxGLSurfaceView: Cocos2dxGLSurfaceView
|
||||||
private lateinit var handler: Handler
|
private lateinit var handler: Handler
|
||||||
@@ -66,8 +63,6 @@ class Cocos2dxGLSurfaceView(context: Context) : ExplicitGLSurfaceView(context) {
|
|||||||
var manualBackEvents = false
|
var manualBackEvents = false
|
||||||
var controllerEventsEnabled = false
|
var controllerEventsEnabled = false
|
||||||
private set
|
private set
|
||||||
var supportsResizeEvents = false
|
|
||||||
var saveNativeFramePacing = false
|
|
||||||
|
|
||||||
var cocos2dxEditText: AppCompatEditText? = null
|
var cocos2dxEditText: AppCompatEditText? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -143,10 +138,6 @@ class Cocos2dxGLSurfaceView(context: Context) : ExplicitGLSurfaceView(context) {
|
|||||||
|
|
||||||
println("updateRefreshRate: selecting refresh rate of ${chosenDisplay.refreshRate} (display ${chosenDisplay.modeId})")
|
println("updateRefreshRate: selecting refresh rate of ${chosenDisplay.refreshRate} (display ${chosenDisplay.modeId})")
|
||||||
|
|
||||||
if (saveNativeFramePacing) {
|
|
||||||
LauncherFix.setSwapInterval((1_000_000_000L / chosenRefreshRate).roundToLong())
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.surface.setFrameRate(chosenRefreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
|
holder.surface.setFrameRate(chosenRefreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
|
||||||
if (isAttachedToWindow) {
|
if (isAttachedToWindow) {
|
||||||
(context as Activity).window.attributes.preferredRefreshRate = chosenRefreshRate
|
(context as Activity).window.attributes.preferredRefreshRate = chosenRefreshRate
|
||||||
@@ -161,7 +152,6 @@ class Cocos2dxGLSurfaceView(context: Context) : ExplicitGLSurfaceView(context) {
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
renderMode = RENDERMODE_CONTINUOUSLY
|
renderMode = RENDERMODE_CONTINUOUSLY
|
||||||
queueEvent { cocos2dxRenderer.handleOnResume() }
|
queueEvent { cocos2dxRenderer.handleOnResume() }
|
||||||
}
|
}
|
||||||
@@ -298,12 +288,6 @@ class Cocos2dxGLSurfaceView(context: Context) : ExplicitGLSurfaceView(context) {
|
|||||||
if (!isInEditMode) {
|
if (!isInEditMode) {
|
||||||
cocos2dxRenderer.setScreenWidthAndHeight(newSurfaceWidth, newSurfaceHeight)
|
cocos2dxRenderer.setScreenWidthAndHeight(newSurfaceWidth, newSurfaceHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportsResizeEvents) {
|
|
||||||
queueEvent {
|
|
||||||
GeodeUtils.resizeSurface(newSurfaceWidth, newSurfaceHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun legacyKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
|
private fun legacyKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
|
||||||
@@ -542,11 +526,6 @@ class Cocos2dxGLSurfaceView(context: Context) : ExplicitGLSurfaceView(context) {
|
|||||||
setRenderer(this.cocos2dxRenderer)
|
setRenderer(this.cocos2dxRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setNativeFramePacing(nativeFramePacing: Boolean) {
|
|
||||||
super.setNativeFramePacing(nativeFramePacing)
|
|
||||||
saveNativeFramePacing = nativeFramePacing
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getContentText(): String {
|
private fun getContentText(): String {
|
||||||
return cocos2dxRenderer.getContentText()
|
return cocos2dxRenderer.getContentText()
|
||||||
}
|
}
|
||||||
@@ -581,10 +560,6 @@ class Cocos2dxGLSurfaceView(context: Context) : ExplicitGLSurfaceView(context) {
|
|||||||
inputManager.unregisterInputDeviceListener(InputListener)
|
inputManager.unregisterInputDeviceListener(InputListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setMaxFrameRate(rate: Int) {
|
|
||||||
cocos2dxRenderer.limitFrameRate(rate)
|
|
||||||
}
|
|
||||||
|
|
||||||
object InputListener : InputManager.InputDeviceListener {
|
object InputListener : InputManager.InputDeviceListener {
|
||||||
override fun onInputDeviceAdded(deviceId: Int) {
|
override fun onInputDeviceAdded(deviceId: Int) {
|
||||||
GeodeUtils.inputDeviceAdded(deviceId)
|
GeodeUtils.inputDeviceAdded(deviceId)
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package org.cocos2dx.lib
|
package org.cocos2dx.lib
|
||||||
|
|
||||||
import android.opengl.EGLConfig
|
import android.opengl.GLSurfaceView
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import com.geode.launcher.ExplicitGLSurfaceView
|
import javax.microedition.khronos.egl.EGLConfig
|
||||||
|
import javax.microedition.khronos.opengles.GL10
|
||||||
|
|
||||||
private const val NANOSECONDS_PER_SECOND = 1000000000L
|
private const val NANOSECONDS_PER_SECOND = 1000000000L
|
||||||
private const val NANOSECONDS_PER_MICROSECOND = 1000000L
|
private const val NANOSECONDS_PER_MICROSECOND = 1000000L
|
||||||
|
|
||||||
@Suppress("unused", "KotlinJniMissingFunction")
|
@Suppress("unused", "KotlinJniMissingFunction")
|
||||||
class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : ExplicitGLSurfaceView.Renderer {
|
class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceView.Renderer {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun setAnimationInterval(@Suppress("UNUSED_PARAMETER") animationInterval: Double) {
|
fun setAnimationInterval(@Suppress("UNUSED_PARAMETER") animationInterval: Double) {
|
||||||
@@ -79,12 +80,12 @@ class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : ExplicitGLS
|
|||||||
screenHeight = surfaceHeight
|
screenHeight = surfaceHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSurfaceCreated(eglConfig: EGLConfig?) {
|
override fun onSurfaceCreated(gl10: GL10?, eglConfig: EGLConfig?) {
|
||||||
nativeInit(screenWidth, screenHeight)
|
nativeInit(screenWidth, screenHeight)
|
||||||
lastTickInNanoSeconds = System.nanoTime()
|
lastTickInNanoSeconds = System.nanoTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSurfaceChanged(width: Int, height: Int) {
|
override fun onSurfaceChanged(gl10: GL10?, width: Int, height: Int) {
|
||||||
if (setFrameRate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (setFrameRate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
handler.updateRefreshRate()
|
handler.updateRefreshRate()
|
||||||
}
|
}
|
||||||
@@ -92,7 +93,7 @@ class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : ExplicitGLS
|
|||||||
println("renderer surfaceChanged: ${width}x${height}@${handler.display.refreshRate}fps")
|
println("renderer surfaceChanged: ${width}x${height}@${handler.display.refreshRate}fps")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDrawFrame() {
|
override fun onDrawFrame(gl: GL10?) {
|
||||||
val animationInterval = mAnimationInterval
|
val animationInterval = mAnimationInterval
|
||||||
if (animationInterval == null) {
|
if (animationInterval == null) {
|
||||||
nativeRender()
|
nativeRender()
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="1024"
|
|
||||||
android:viewportHeight="1024">
|
|
||||||
<path
|
|
||||||
android:pathData="M0,0h1024v1024h-1024z"
|
|
||||||
android:fillColor="#070066"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M512.5,521.1L915.2,638.7L1082,922.6L915.2,1206.4L512.5,1324L109.8,1206.4L-57,922.6L109.8,638.7L512.5,521.1Z"
|
|
||||||
android:fillColor="#6694C8"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M915.2,638.7L1082,922.6V638.7H915.2Z"
|
|
||||||
android:fillColor="#4268B8"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M109.8,638.7L512.5,521.1L109.8,397.2V638.7Z"
|
|
||||||
android:fillColor="#2D5EC7"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M-57,922.6L109.8,638.7H-57V922.6Z"
|
|
||||||
android:fillColor="#4268B8"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M512.5,521.1L915.2,638.7V397.2L512.5,521.1Z"
|
|
||||||
android:fillColor="#2D5EC7"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M-57,638.7H109.8V397.2L-57,638.7Z"
|
|
||||||
android:fillColor="#2248AA"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M109.8,397.2L512.5,521.1V194L109.8,397.2Z"
|
|
||||||
android:fillColor="#020F9D"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M512.5,521.1L915.2,397.2L512.5,194V521.1Z"
|
|
||||||
android:fillColor="#020F9D"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M915.2,638.7H1082L915.2,397.2V638.7Z"
|
|
||||||
android:fillColor="#2248AA"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="643"
|
|
||||||
android:viewportHeight="766">
|
|
||||||
<group android:scaleX="0.41038585"
|
|
||||||
android:scaleY="0.4888889"
|
|
||||||
android:translateX="191.0163"
|
|
||||||
android:translateY="195.75555">
|
|
||||||
<path
|
|
||||||
android:pathData="M299.94,33.78C313.01,25.85 329.41,25.85 342.48,33.78L370.9,51.03L268.17,116.49L268.23,116.51L92.68,223.61C84.64,228.51 79.74,237.25 79.74,246.66V509.84C79.74,519.26 84.64,527.99 92.68,532.89L306.22,663.17C314.85,668.44 325.71,668.44 334.34,663.17L547.89,532.89C555.92,527.99 560.82,519.26 560.82,509.85V246.66C560.82,246.2 560.81,245.74 560.78,245.28V166.26L599.75,189.9C612,197.34 619.48,210.63 619.48,224.96V540.81C619.48,555.14 612,568.42 599.75,575.86L342.48,731.98C329.41,739.91 313.01,739.91 299.94,731.98L42.66,575.86C30.41,568.42 22.93,555.14 22.93,540.81V224.96C22.93,210.63 30.41,197.34 42.66,189.9L299.94,33.78Z"
|
|
||||||
android:fillColor="#000000"
|
|
||||||
android:fillAlpha="0.15"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M311.75,173.17C318.78,168.88 327.62,168.88 334.65,173.17L481.88,262.87C488.43,266.86 492.43,273.98 492.43,281.66V463.16C492.43,470.84 488.43,477.96 481.88,481.95L334.65,571.65C327.62,575.94 318.78,575.94 311.75,571.65L164.52,481.95C157.97,477.96 153.97,470.84 153.97,463.16V414.95L208.24,448.54C208.52,448.75 208.8,448.95 209.1,449.13L315.66,515.36C319.22,517.57 323.72,517.57 327.28,515.36L433.84,449.13C437.07,447.13 439.03,443.59 439.03,439.79V305.9C439.03,302.09 437.07,298.56 433.84,296.55L327.28,230.33C323.72,228.12 319.22,228.12 315.66,230.33L209.1,296.55C208.62,296.85 208.17,297.18 207.75,297.55L153.97,331.28V281.66C153.97,273.98 157.97,266.86 164.52,262.87L311.75,173.17Z"
|
|
||||||
android:fillColor="#000000"
|
|
||||||
android:fillAlpha="0.15"
|
|
||||||
android:fillType="evenOdd"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M299.94,5.95C313.01,-1.98 329.41,-1.98 342.48,5.95L374.73,25.52L264.09,96.01L264.15,96.03L74.11,211.97C66.07,216.88 61.17,225.61 61.17,235.02V520.77C61.17,530.18 66.07,538.92 74.11,543.82L306.15,685.38C314.78,690.65 325.64,690.65 334.27,685.38L566.31,543.82C574.34,538.92 579.25,530.18 579.25,520.77V235.02C579.25,234.53 579.23,234.05 579.2,233.56V149.6L622.68,175.99C634.93,183.42 642.41,196.71 642.41,211.04V554.72C642.41,569.05 634.93,582.34 622.68,589.78L342.48,759.82C329.41,767.75 313.01,767.75 299.94,759.82L19.73,589.78C7.48,582.34 0,569.05 0,554.72V211.04C0,196.71 7.48,183.42 19.73,175.99L299.94,5.95Z"
|
|
||||||
android:fillColor="#ffffff"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M311.75,142.59C318.78,138.31 327.62,138.31 334.65,142.59L507.79,248.08C514.34,252.08 518.34,259.2 518.34,266.87V479.95C518.34,487.62 514.34,494.74 507.79,498.74L334.65,604.23C327.62,608.51 318.78,608.51 311.75,604.23L138.61,498.74C132.06,494.74 128.06,487.62 128.06,479.95V422.46L189.93,460.76C190.22,460.98 190.52,461.19 190.84,461.39L315.4,538.8C318.95,541.01 323.46,541.01 327.01,538.8L451.58,461.39C454.8,459.38 456.77,455.85 456.77,452.04V295.77C456.77,291.97 454.8,288.44 451.58,286.43L327.01,209.02C323.46,206.81 318.95,206.81 315.4,209.02L190.84,286.43C190.32,286.76 189.83,287.12 189.37,287.52L128.06,325.98V266.87C128.06,259.2 132.06,252.08 138.61,248.08L311.75,142.59Z"
|
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:fillType="evenOdd"/>
|
|
||||||
</group>
|
|
||||||
</vector>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="643"
|
|
||||||
android:viewportHeight="766">
|
|
||||||
<group android:scaleX="0.41038585"
|
|
||||||
android:scaleY="0.4888889"
|
|
||||||
android:translateX="191.0163"
|
|
||||||
android:translateY="195.75555">
|
|
||||||
<path
|
|
||||||
android:pathData="M299.94,5.95C313.01,-1.98 329.41,-1.98 342.48,5.95L374.73,25.52L264.09,96.01L264.15,96.03L74.11,211.97C66.07,216.88 61.17,225.61 61.17,235.02V520.77C61.17,530.18 66.07,538.92 74.11,543.82L306.15,685.38C314.78,690.65 325.64,690.65 334.27,685.38L566.31,543.82C574.34,538.92 579.25,530.18 579.25,520.77V235.02C579.25,234.53 579.23,234.05 579.2,233.56V149.6L622.68,175.99C634.93,183.42 642.41,196.71 642.41,211.04V554.72C642.41,569.05 634.93,582.34 622.68,589.78L342.48,759.82C329.41,767.75 313.01,767.75 299.94,759.82L19.73,589.78C7.48,582.34 0,569.05 0,554.72V211.04C0,196.71 7.48,183.42 19.73,175.99L299.94,5.95Z"
|
|
||||||
android:fillColor="#ffffff"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M311.75,142.59C318.78,138.31 327.62,138.31 334.65,142.59L507.79,248.08C514.34,252.08 518.34,259.2 518.34,266.87V479.95C518.34,487.62 514.34,494.74 507.79,498.74L334.65,604.23C327.62,608.51 318.78,608.51 311.75,604.23L138.61,498.74C132.06,494.74 128.06,487.62 128.06,479.95V422.46L189.93,460.76C190.22,460.98 190.52,461.19 190.84,461.39L315.4,538.8C318.95,541.01 323.46,541.01 327.01,538.8L451.58,461.39C454.8,459.38 456.77,455.85 456.77,452.04V295.77C456.77,291.97 454.8,288.44 451.58,286.43L327.01,209.02C323.46,206.81 318.95,206.81 315.4,209.02L190.84,286.43C190.32,286.76 189.83,287.12 189.37,287.52L128.06,325.98V266.87C128.06,259.2 132.06,252.08 138.61,248.08L311.75,142.59Z"
|
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:fillType="evenOdd"/>
|
|
||||||
</group>
|
|
||||||
</vector>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
Binary file not shown.
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_sapphire_background"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_sapphire_foreground"/>
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_sapphire_monochrome" />
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</string>
|
</string>
|
||||||
|
|
||||||
<string name="launcher_game_update_required_short">A Geometry Dash update is required.</string>
|
<string name="launcher_game_update_required_short">A Geometry Dash update is required.</string>
|
||||||
<string name="launcher_game_update_required">The currently installed version of Geometry Dash is no longer supported. Please update Geometry Dash to version %1$s to continue receiving Geode updates.</string>
|
<string name="launcher_game_update_required">The currently installed version of Geometry Dash is no longer supported. Please update Geometry Dash to continue receiving Geode updates.</string>
|
||||||
|
|
||||||
<string name="launcher_update_available">A new launcher update is available!</string>
|
<string name="launcher_update_available">A new launcher update is available!</string>
|
||||||
<string name="launcher_update_dismiss">Dismiss</string>
|
<string name="launcher_update_dismiss">Dismiss</string>
|
||||||
@@ -53,7 +53,6 @@
|
|||||||
<string name="launcher_error_view_logs">View logs</string>
|
<string name="launcher_error_view_logs">View logs</string>
|
||||||
<string name="launcher_error_copy_error_alt">Copy</string>
|
<string name="launcher_error_copy_error_alt">Copy</string>
|
||||||
<string name="launcher_error_copy">Copy details</string>
|
<string name="launcher_error_copy">Copy details</string>
|
||||||
<string name="launcher_error_view_details">View details</string>
|
|
||||||
<string name="launcher_error_export_crash">Share crash</string>
|
<string name="launcher_error_export_crash">Share crash</string>
|
||||||
<string name="launcher_error_export_missing">No crash dump was found!</string>
|
<string name="launcher_error_export_missing">No crash dump was found!</string>
|
||||||
<string name="launcher_error_device_info">Android %2$s, %1$s (%5$s)\nGD %4$s, Geode %3$s + v%6$s</string>
|
<string name="launcher_error_device_info">Android %2$s, %1$s (%5$s)\nGD %4$s, Geode %3$s + v%6$s</string>
|
||||||
@@ -82,7 +81,7 @@
|
|||||||
<string name="launcher_cancelled_crash">Launch paused due to a crash.</string>
|
<string name="launcher_cancelled_crash">Launch paused due to a crash.</string>
|
||||||
<string name="launcher_cancelled_crash_instant">Geode crashed.</string>
|
<string name="launcher_cancelled_crash_instant">Geode crashed.</string>
|
||||||
<string name="launcher_cancelled_error">Geode cannot be used.</string>
|
<string name="launcher_cancelled_error">Geode cannot be used.</string>
|
||||||
<string name="launcher_cancelled_outdated">Geometry Dash version %1$s is required.</string>
|
<string name="launcher_cancelled_outdated">A Geometry Dash update is required.</string>
|
||||||
<string name="launcher_cancelled_restart">Restart</string>
|
<string name="launcher_cancelled_restart">Restart</string>
|
||||||
<string name="launcher_cancelled_resume">Resume</string>
|
<string name="launcher_cancelled_resume">Resume</string>
|
||||||
<string name="launcher_cancelled_retry">Retry</string>
|
<string name="launcher_cancelled_retry">Retry</string>
|
||||||
@@ -105,7 +104,6 @@
|
|||||||
<string name="release_fetch_manual_check_required">Externally managed library in use, please manually check for updates if you want to overwrite it.</string>
|
<string name="release_fetch_manual_check_required">Externally managed library in use, please manually check for updates if you want to overwrite it.</string>
|
||||||
|
|
||||||
<string name="release_fetch_external_in_use_short">Update failed. External library in use.</string>
|
<string name="release_fetch_external_in_use_short">Update failed. External library in use.</string>
|
||||||
<string name="release_fetch_hash_validation_failed_short">Update failed. Integrity check failed.</string>
|
|
||||||
<string name="release_fetch_no_internet_short">Update failed. Internet not connected.</string>
|
<string name="release_fetch_no_internet_short">Update failed. Internet not connected.</string>
|
||||||
<string name="release_fetch_generic_short">Update failed. Please try again later.</string>
|
<string name="release_fetch_generic_short">Update failed. Please try again later.</string>
|
||||||
|
|
||||||
@@ -120,10 +118,10 @@
|
|||||||
<string name="load_failed_crashed_description">A crash was reported during the last session. Try to:</string>
|
<string name="load_failed_crashed_description">A crash was reported during the last session. Try to:</string>
|
||||||
<string name="load_failed_recommendation_reinstall">Reinstall Geometry Dash.</string>
|
<string name="load_failed_recommendation_reinstall">Reinstall Geometry Dash.</string>
|
||||||
<string name="load_failed_recommendation_switch_32bit_abi">Install the 32-bit version of the launcher.</string>
|
<string name="load_failed_recommendation_switch_32bit_abi">Install the 32-bit version of the launcher.</string>
|
||||||
<string name="load_failed_recommendation_switch_64bit_abi">Install the non-32-bit version of the launcher.</string>
|
<string name="load_failed_recommendation_switch_64bit_abi">Install the non 32-bit version of the launcher.</string>
|
||||||
<string name="load_failed_recommendation_update">Verify that the latest versions of the launcher and Geometry Dash are installed.</string>
|
<string name="load_failed_recommendation_update">Verify that the latest versions of the launcher and Geometry Dash are installed.</string>
|
||||||
<string name="load_failed_recommendation_report">Report this issue to the developers.</string>
|
<string name="load_failed_recommendation_report">Report this issue to the developers.</string>
|
||||||
<string name="load_failed_recommendation_safe_mode">Use the safe mode button in Settings.</string>
|
<string name="load_failed_recommendation_safe_mode">Enable the safe mode button in Settings.</string>
|
||||||
<string name="load_failed_recommendation_dev_stl">Devs: If this is a locally built Geode version, please use the c++_shared ANDROID_STL!</string>
|
<string name="load_failed_recommendation_dev_stl">Devs: If this is a locally built Geode version, please use the c++_shared ANDROID_STL!</string>
|
||||||
|
|
||||||
<string name="message_box_cancel">Cancel</string>
|
<string name="message_box_cancel">Cancel</string>
|
||||||
@@ -298,9 +296,6 @@
|
|||||||
<string name="preference_profiles_delete_alt">Delete</string>
|
<string name="preference_profiles_delete_alt">Delete</string>
|
||||||
<string name="preference_profiles_delete_undo_alt">Undo</string>
|
<string name="preference_profiles_delete_undo_alt">Undo</string>
|
||||||
|
|
||||||
<string name="preference_native_pacing">Native frame pacing</string>
|
|
||||||
<string name="preference_native_pacing_description">Reduces latency, but may worsen frame drops.</string>
|
|
||||||
|
|
||||||
<!-- ApplicationLogsActivity strings -->
|
<!-- ApplicationLogsActivity strings -->
|
||||||
<string name="title_activity_application_logs">Application logs</string>
|
<string name="title_activity_application_logs">Application logs</string>
|
||||||
<string name="application_logs_no_logs">No logs available.</string>
|
<string name="application_logs_no_logs">No logs available.</string>
|
||||||
@@ -340,7 +335,6 @@
|
|||||||
<string name="application_icon_geode">Geode</string>
|
<string name="application_icon_geode">Geode</string>
|
||||||
<string name="application_icon_pride">Pride</string>
|
<string name="application_icon_pride">Pride</string>
|
||||||
<string name="application_icon_trans">Transgender</string>
|
<string name="application_icon_trans">Transgender</string>
|
||||||
<string name="application_icon_sapphire">Sapphire</string>
|
|
||||||
<string name="application_icon_confirm">Change icon to %1$s?</string>
|
<string name="application_icon_confirm">Change icon to %1$s?</string>
|
||||||
<string name="application_icon_confirm_description">Geode Launcher will close itself to change the application icon.</string>
|
<string name="application_icon_confirm_description">Geode Launcher will close itself to change the application icon.</string>
|
||||||
|
|
||||||
|
|||||||
+8
-4
@@ -1,9 +1,13 @@
|
|||||||
|
buildscript {
|
||||||
|
val composeBOM by extra("2026.02.00")
|
||||||
|
}
|
||||||
|
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "9.4.0" apply false
|
id("com.android.application") version "9.0.1" apply false
|
||||||
id("com.android.library") version "9.4.0" apply false
|
id("com.android.library") version "9.0.1" apply false
|
||||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.4.10" apply false
|
id("org.jetbrains.kotlin.plugin.serialization") version "2.3.10" apply false
|
||||||
id("org.jetbrains.kotlin.plugin.compose") version "2.4.10" apply false
|
id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Delete>("clean") {
|
tasks.register<Delete>("clean") {
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+2
-3
@@ -1,9 +1,8 @@
|
|||||||
|
#Wed Jan 28 04:12:33 MST 2026
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
retries=0
|
|
||||||
retryBackOffMs=500
|
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015 the original authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# gradlew start up script for POSIX generated by Gradle.
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
#
|
#
|
||||||
# Important for running:
|
# Important for running:
|
||||||
#
|
#
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
# bash, then to run this script, type that shell name before the whole
|
# bash, then to run this script, type that shell name before the whole
|
||||||
# command line, like:
|
# command line, like:
|
||||||
#
|
#
|
||||||
# ksh gradlew
|
# ksh Gradle
|
||||||
#
|
#
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
# requires all of these POSIX shell features:
|
# requires all of these POSIX shell features:
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -114,6 +114,7 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -171,6 +172,7 @@ fi
|
|||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
@@ -210,7 +212,8 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
|
|||||||
Vendored
+24
-12
@@ -19,12 +19,12 @@
|
|||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem gradlew startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
@rem Set local scope for the variables with windows NT shell
|
||||||
setlocal EnableExtensions
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
@@ -51,7 +51,7 @@ echo. 1>&2
|
|||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
"%COMSPEC%" /c exit 1
|
goto fail
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
@@ -65,18 +65,30 @@ echo. 1>&2
|
|||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
"%COMSPEC%" /c exit 1
|
goto fail
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute gradlew
|
@rem Execute Gradle
|
||||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
@rem which allows us to clear the local environment before executing the java command
|
|
||||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
|
||||||
|
|
||||||
:exitWithErrorLevel
|
:end
|
||||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
@rem End local scope for the variables with windows NT shell
|
||||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
Reference in new issue
Block a user