feat(companion): share-the-app from Mesh Party + party entry on the intro screen — 0.5.8
- Party screen 'Share this app': shares this install's own APK through the system sheet (FileProvider over cache/share/), so a nearby friend gets the companion via Quick Share/Bluetooth with zero internet — the party-mode premise end to end. - Intro screen: Mesh Party button under Get Started — party works with no node at all, so it belongs on the first screen a fresh install shows. - Served APK: 0.5.8 (vc28), ready for the QR/OTA/ISO cut. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId = "com.archipelago.app"
|
applicationId = "com.archipelago.app"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 27
|
versionCode = 28
|
||||||
versionName = "0.5.7"
|
versionName = "0.5.8"
|
||||||
|
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
|
|||||||
@@ -23,6 +23,18 @@
|
|||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="35">
|
tools:targetApi="35">
|
||||||
|
|
||||||
|
<!-- Party-screen "Share this app": exposes the copied APK from
|
||||||
|
cache/share/ to the system share sheet, nothing else. -->
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -126,6 +126,9 @@ fun AppNavHost(
|
|||||||
) {
|
) {
|
||||||
composable(Routes.INTRO) {
|
composable(Routes.INTRO) {
|
||||||
IntroScreen(
|
IntroScreen(
|
||||||
|
onMeshParty = {
|
||||||
|
navController.navigate(Routes.MESH_PARTY)
|
||||||
|
},
|
||||||
onContinue = {
|
onContinue = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
prefs.markIntroSeen()
|
prefs.markIntroSeen()
|
||||||
|
|||||||
@@ -55,7 +55,12 @@ import com.archipelago.app.ui.theme.TextPrimary
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun IntroScreen(onContinue: () -> Unit) {
|
fun IntroScreen(
|
||||||
|
onContinue: () -> Unit,
|
||||||
|
// Mesh Party works with no node at all (phone↔phone) — offered right on
|
||||||
|
// the first screen so a friend who just got the app can join a party.
|
||||||
|
onMeshParty: () -> Unit = {},
|
||||||
|
) {
|
||||||
val logoAlpha = remember { Animatable(0f) }
|
val logoAlpha = remember { Animatable(0f) }
|
||||||
var showContent by remember { mutableStateOf(false) }
|
var showContent by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -143,6 +148,14 @@ fun IntroScreen(onContinue: () -> Unit) {
|
|||||||
onClick = onContinue,
|
onClick = onContinue,
|
||||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
GlassButton(
|
||||||
|
text = stringResource(R.string.mesh_party),
|
||||||
|
onClick = onMeshParty,
|
||||||
|
modifier = Modifier.fillMaxWidth().height(48.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,6 +281,16 @@ fun PartyScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(12.dp))
|
Spacer(Modifier.height(12.dp))
|
||||||
|
// Hand the app itself to a friend: shares this install's own APK
|
||||||
|
// through the system sheet (Quick Share/Bluetooth), so a nearby
|
||||||
|
// phone gets the companion with zero internet — the whole party
|
||||||
|
// premise.
|
||||||
|
GlassButton(
|
||||||
|
text = "Share this app",
|
||||||
|
onClick = { shareCompanionApk(context) },
|
||||||
|
modifier = Modifier.fillMaxWidth().height(48.dp),
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Party QR scanner overlay
|
// Party QR scanner overlay
|
||||||
@@ -364,3 +374,28 @@ private fun renderQr(payload: String, size: Int = 640): Bitmap? = try {
|
|||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Share this install's own APK via the system share sheet — a nearby friend
|
||||||
|
* gets the companion with no internet at all (Quick Share / Bluetooth). */
|
||||||
|
private fun shareCompanionApk(context: android.content.Context) {
|
||||||
|
try {
|
||||||
|
val src = java.io.File(context.applicationInfo.sourceDir)
|
||||||
|
val dir = java.io.File(context.cacheDir, "share").apply { mkdirs() }
|
||||||
|
val out = java.io.File(dir, "archipelago-companion.apk")
|
||||||
|
src.copyTo(out, overwrite = true)
|
||||||
|
val uri = androidx.core.content.FileProvider.getUriForFile(
|
||||||
|
context, "${context.packageName}.fileprovider", out,
|
||||||
|
)
|
||||||
|
val send = android.content.Intent(android.content.Intent.ACTION_SEND).apply {
|
||||||
|
type = "application/vnd.android.package-archive"
|
||||||
|
putExtra(android.content.Intent.EXTRA_STREAM, uri)
|
||||||
|
addFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
}
|
||||||
|
context.startActivity(
|
||||||
|
android.content.Intent.createChooser(send, "Share Archipelago Companion")
|
||||||
|
.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||||
|
)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
// No share targets / copy failed — nothing sensible to do here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<string name="welcome_title">Your Sovereign\nPersonal Server</string>
|
<string name="welcome_title">Your Sovereign\nPersonal Server</string>
|
||||||
<string name="welcome_subtitle">Bitcoin node, app platform, and private cloud — all in one box you control.</string>
|
<string name="welcome_subtitle">Bitcoin node, app platform, and private cloud — all in one box you control.</string>
|
||||||
<string name="get_started">Get Started</string>
|
<string name="get_started">Get Started</string>
|
||||||
|
<string name="mesh_party">Mesh Party</string>
|
||||||
<string name="use_https">Use HTTPS</string>
|
<string name="use_https">Use HTTPS</string>
|
||||||
<string name="port_label">Port (optional)</string>
|
<string name="port_label">Port (optional)</string>
|
||||||
<string name="saved_servers">Saved Servers</string>
|
<string name="saved_servers">Saved Servers</string>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- FileProvider scope for the party-screen "Share this app" APK handoff. -->
|
||||||
|
<paths>
|
||||||
|
<cache-path name="share" path="share/" />
|
||||||
|
</paths>
|
||||||
Binary file not shown.
Reference in New Issue
Block a user