Current section

Files

Jump to
mob_new priv templates mob.new android app src main java MainActivity.kt.eex
Raw

priv/templates/mob.new/android/app/src/main/java/MainActivity.kt.eex

package <%= java_package %>
import android.app.Activity
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.core.content.FileProvider
import java.io.File
class MainActivity : ComponentActivity() {
companion object {
private const val TAG = "<%= display_name %>"
init { System.loadLibrary("<%= lib_name %>") }
}
external fun nativeSetActivity(activity: Activity)
external fun nativeStartBeam()
// ── Camera launchers ──────────────────────────────────────────────────
private var cameraPhotoUri: Uri? = null
private val cameraPhotoLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
MobBridge.handleCameraPhotoResult(if (success) cameraPhotoUri else null)
}
private val cameraVideoLauncher =
registerForActivityResult(ActivityResultContracts.CaptureVideo()) { success ->
MobBridge.handleCameraVideoResult(if (success) cameraPhotoUri else null)
}
fun launchCameraPhoto() {
val file = File(cacheDir, "mob_cam_${System.currentTimeMillis()}.jpg")
cameraPhotoUri = FileProvider.getUriForFile(this, "$packageName.fileprovider", file)
cameraPhotoLauncher.launch(cameraPhotoUri!!)
}
fun launchCameraVideo() {
val file = File(cacheDir, "mob_cam_${System.currentTimeMillis()}.mp4")
cameraPhotoUri = FileProvider.getUriForFile(this, "$packageName.fileprovider", file)
cameraVideoLauncher.launch(cameraPhotoUri!!)
}
// ── Photo picker launcher ─────────────────────────────────────────────
private val photosPickerLauncher =
registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia()) { uris ->
MobBridge.handlePhotosResult(uris)
}
fun launchPhotosPicker(max: Int) {
photosPickerLauncher.launch(
androidx.activity.result.PickVisualMediaRequest(
androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.ImageAndVideo
)
)
}
// ── File picker launcher ──────────────────────────────────────────────
private val filePickerLauncher =
registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { uris ->
MobBridge.handleFilesResult(uris)
}
fun launchFilePicker() {
filePickerLauncher.launch(arrayOf("*/*"))
}
// ── QR scanner launcher ───────────────────────────────────────────────
// For QR scanning we use an intent to a helper activity (MobScannerActivity)
// that uses CameraX + ML Kit. It returns the scanned value as a result string.
private val scannerLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val value = result.data?.getStringExtra("scan_value")
val type = result.data?.getStringExtra("scan_type") ?: "qr"
MobBridge.handleScanResult(value, type)
}
fun launchQrScanner() {
val intent = android.content.Intent(this, MobScannerActivity::class.java)
scannerLauncher.launch(intent)
}
// ── Permission result ─────────────────────────────────────────────────
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 9001) {
val granted = grantResults.isNotEmpty() &&
grantResults.all { it == PackageManager.PERMISSION_GRANTED }
MobBridge.onPermissionResult(granted)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MobBridge.init(this)
// Forward launcher-supplied env vars into the BEAM process. Set BEFORE
// nativeStartBeam below so the BEAM (and Mob.Dist in particular) sees
// them when it reads getenv()/System.get_env/1.
//
// mob_node_suffix — appended to the configured node name
// (`<app>_android` → `<app>_android_<suffix>`).
// Lets multiple Android phones running the same
// app coexist in Mac's shared EPMD.
// mob_dist_port — Erlang dist listen port (default 9100).
intent?.extras?.getString("mob_node_suffix")?.takeIf { it.isNotEmpty() }?.let { suffix ->
android.system.Os.setenv("MOB_NODE_SUFFIX", suffix, true)
Log.i(TAG, "onCreate: MOB_NODE_SUFFIX=$suffix")
}
intent?.extras?.getInt("mob_dist_port", -1)?.takeIf { it > 0 }?.let { port ->
android.system.Os.setenv("MOB_DIST_PORT", port.toString(), true)
Log.i(TAG, "onCreate: MOB_DIST_PORT=$port")
}
// Check if launched from a notification tap
intent?.extras?.getString("mob_notification_json")?.let { json ->
MobBridge.setLaunchNotification(json)
}
setContent {
val state by MobBridge.rootState
BackHandler(enabled = state.node != null) { MobBridge.nativeHandleBack() }
AnimatedContent(
targetState = state,
contentKey = { it.navKey },
transitionSpec = {
when (targetState.transition) {
"push" ->
slideInHorizontally(animationSpec = tween(300)) { it } togetherWith
slideOutHorizontally(animationSpec = tween(300)) { -it / 3 }
"pop" ->
slideInHorizontally(animationSpec = tween(300)) { -it / 3 } togetherWith
slideOutHorizontally(animationSpec = tween(300)) { it }
"reset" ->
fadeIn(animationSpec = tween(250)) togetherWith
fadeOut(animationSpec = tween(250))
else ->
EnterTransition.None togetherWith ExitTransition.None
}
},
label = "nav"
) { s ->
s.node?.let { RenderNode(it, modifier = Modifier.fillMaxSize()) }
}
}
Log.i(TAG, "onCreate — handing off to BEAM")
nativeSetActivity(this)
Thread({ nativeStartBeam() }, "beam-main").start()
}
// Manifest declares `android:configChanges` including `uiMode`, so a
// dark/light toggle delivers here instead of recreating the activity.
// Forward to MobBridge so Mob.Device :appearance subscribers can react
// (e.g. re-resolve Mob.Theme.Adaptive without an app restart).
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val nightMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
val scheme = if (nightMode == Configuration.UI_MODE_NIGHT_YES) "dark" else "light"
MobBridge.notifyColorSchemeChanged(scheme)
}
}