fix(apk 0.2.1): in-app installer "nic się nie dzieje" + oo launcher icon
INSTALL BUG ("klikam Install i nic się nie dzieje"): ApkInstallerModule
commitował PackageInstaller.Session z PendingIntent → MainActivity, ale Android
po commit odsyła STATUS_PENDING_USER_ACTION + EXTRA_INTENT który MUSI być
startActivity()-owany żeby pokazać systemowy dialog "Install update?". Nic tego
nie obsługiwało → download OK, sesja commit OK, ale dialog NIGDY się nie
pokazywał. Fix: getBroadcast + runtime BroadcastReceiver → na PENDING_USER_ACTION
launchuje EXTRA_INTENT (FLAG_ACTIVITY_NEW_TASK), unregister na terminal status.
(Native — działa dla 0.2.1→przyszłe; do 0.2.1 user sideload z goon-foss.org.)
LAUNCHER ICON: regenerowane mipmapy (oo logo) z assets/icon.png przez PIL —
ic_launcher / ic_launcher_round / ic_launcher_foreground we wszystkich 5
densities (webp). iconBackground #0E1018 (stary navy) → #15110D (warm charcoal).
version 0.2.1 / versionCode 11. Build verified: podpis SHA-256 == ALLOWED,
Running "main" bez crasha. Deployed: /version=0.2.1, /static + webroot + landing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
@ -115,7 +115,7 @@ def version() -> dict[str, str | None]:
|
|||
# mobile sklei z baseUrl.
|
||||
public_url = os.environ.get("BACKEND_PUBLIC_URL", "").rstrip("/")
|
||||
apk_url = f"{public_url}/static/app-release.apk" if public_url else "/static/app-release.apk"
|
||||
return {"version": "0.2.0", "apk_url": apk_url}
|
||||
return {"version": "0.2.1", "apk_url": apk_url}
|
||||
|
||||
|
||||
@app.get("/readyz")
|
||||
|
|
|
|||
|
|
@ -93,8 +93,8 @@ android {
|
|||
applicationId 'com.goon.mobile'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 10
|
||||
versionName "0.2.0"
|
||||
versionCode 11
|
||||
versionName "0.2.1"
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.goon.mobile
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
|
|
@ -122,22 +125,53 @@ class ApkInstallerModule(reactContext: ReactApplicationContext) :
|
|||
}
|
||||
}
|
||||
|
||||
// PendingIntent musi być MUTABLE bo PackageInstaller dorzuca extras
|
||||
// (PackageInstaller.EXTRA_STATUS) przed delivery do nas.
|
||||
val intent = Intent(ctx, MainActivity::class.java).apply {
|
||||
action = "com.goon.mobile.APK_INSTALL_RESULT"
|
||||
// BUG-FIX 2026-05-31 ("klikam Install i nic się nie dzieje"):
|
||||
// commit() NIE pokazuje od razu dialogu. Android najpierw odsyła
|
||||
// STATUS_PENDING_USER_ACTION + EXTRA_INTENT, który MUSIMY ręcznie
|
||||
// startActivity() żeby pokazać systemowy dialog "Install update?".
|
||||
// Poprzednio target był MainActivity bez obsługi tego statusu →
|
||||
// download się udawał, sesja commitowała, ale dialog nigdy nie
|
||||
// wyskakiwał. Teraz: getBroadcast → receiver → launch EXTRA_INTENT.
|
||||
val action = "com.goon.mobile.APK_INSTALL_STATUS"
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(c: Context, i: Intent) {
|
||||
val status = i.getIntExtra(
|
||||
PackageInstaller.EXTRA_STATUS,
|
||||
PackageInstaller.STATUS_FAILURE,
|
||||
)
|
||||
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
|
||||
@Suppress("DEPRECATION")
|
||||
val confirm = i.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||
confirm?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if (confirm != null) c.startActivity(confirm)
|
||||
} else {
|
||||
// terminal (SUCCESS/FAILURE/ABORTED) — sprzątamy receiver.
|
||||
try { c.applicationContext.unregisterReceiver(this) } catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
val filter = IntentFilter(action)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
ctx.applicationContext.registerReceiver(
|
||||
receiver, filter, Context.RECEIVER_NOT_EXPORTED,
|
||||
)
|
||||
} else {
|
||||
@Suppress("UnspecifiedRegisterReceiverFlag")
|
||||
ctx.applicationContext.registerReceiver(receiver, filter)
|
||||
}
|
||||
|
||||
val intent = Intent(action).setPackage(ctx.packageName)
|
||||
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(ctx, sessionId, intent, flags)
|
||||
val pendingIntent = PendingIntent.getBroadcast(ctx, sessionId, intent, flags)
|
||||
s.commit(pendingIntent.intentSender)
|
||||
}
|
||||
|
||||
// commit() jest async — Android pokaże dialog "Install update?" w tej samej
|
||||
// chwili. Resolwujemy od razu — JS nie czeka na user choice (Android UI).
|
||||
// commit() async — receiver odpali systemowy dialog gdy Android poprosi
|
||||
// o user-action. JS nie czeka na wynik (to Android UI).
|
||||
promise.resolve(null)
|
||||
} catch (e: Exception) {
|
||||
promise.reject("ERR_INSTALL", e.message ?: "install fail", e)
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 626 B |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 764 B |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 9 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 3.5 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
<resources>
|
||||
<color name="splashscreen_background">#0E1018</color>
|
||||
<color name="iconBackground">#0E1018</color>
|
||||
<color name="iconBackground">#15110D</color>
|
||||
<color name="colorPrimary">#023c69</color>
|
||||
<color name="colorPrimaryDark">#0E1018</color>
|
||||
</resources>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"expo": {
|
||||
"name": "goon",
|
||||
"slug": "goon",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"orientation": "portrait",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"newArchEnabled": false,
|
||||
|
|
|
|||