feat: add device authentication, place gates support, and PiP navigation features

This commit is contained in:
Hamza-Ayed
2026-09-19 12:34:26 +03:00
parent 55830beee8
commit ce896df30a
86 changed files with 3414 additions and 341 deletions
+8 -9
View File
@@ -3,6 +3,7 @@ import java.io.FileInputStream
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
@@ -14,7 +15,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
namespace = "com.siro_map.siro_maps"
namespace = "com.urukmap.app"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@@ -23,9 +24,13 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
applicationId = "com.siro_map.siro_maps"
minSdk = 23
applicationId = "com.urukmap.app"
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
@@ -71,12 +76,6 @@ dependencies {
implementation("androidx.car.app:app-projected:1.4.0")
}
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
}
}
flutter {
source = "../.."
}
+2 -2
View File
@@ -29,8 +29,8 @@
-keep class androidx.car.app.validation.** { *; }
# Keep our custom CarAppService, Session, Screen and State models
-keep class com.siro_map.siro_maps.car.** { *; }
-keepclassmembers class com.siro_map.siro_maps.car.** { *; }
-keep class com.urukmap.app.car.** { *; }
-keepclassmembers class com.urukmap.app.car.** { *; }
-dontwarn androidx.car.app.**
@@ -10,7 +10,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:label="خرائط سيرو"
android:label="Uruk Map"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
@@ -51,6 +51,23 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geo" />
</intent-filter>
<!-- Google Navigation Scheme: google.navigation:q=lat,lng -->
<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="google.navigation" />
</intent-filter>
<!-- Universal Web Deep Links: maps.siro.app & map-saas.intaleqapp.com -->
<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="https" android:host="maps.siro.app" />
<data android:scheme="https" android:host="map-saas.intaleqapp.com" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
@@ -1,63 +0,0 @@
package com.siro_map.siro_maps
import com.siro_map.siro_maps.car.CarNavigationState
import com.siro_map.siro_maps.car.SiroCarAppService
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.siro.siro_maps/car_navigation"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"isCarAppConnected" -> {
result.success(SiroCarAppService.isConnected)
}
"updateNavState" -> {
try {
val lat = call.argument<Double>("lat") ?: 0.0
val lng = call.argument<Double>("lng") ?: 0.0
val bearing = call.argument<Double>("bearing") ?: 0.0
val speed = call.argument<Double>("speed") ?: 0.0
val instruction = call.argument<String>("instruction") ?: ""
val distanceToStep = call.argument<Double>("distanceToStep") ?: 0.0
val totalDistance = call.argument<Double>("totalDistance") ?: 0.0
val eta = call.argument<Double>("eta") ?: 0.0
val maneuver = call.argument<Int>("maneuver") ?: 0
val isNavigating = call.argument<Boolean>("isNavigating") ?: false
val isMapDarkMode = call.argument<Boolean>("isMapDarkMode") ?: false
val newState = CarNavigationState(
lat = lat,
lng = lng,
bearing = bearing,
speed = speed,
instruction = instruction,
distanceToStep = distanceToStep,
totalDistance = totalDistance,
eta = eta,
maneuver = maneuver,
isNavigating = isNavigating,
isMapDarkMode = isMapDarkMode
)
SiroCarAppService.updateNavState(newState)
result.success(true)
} catch (e: Exception) {
result.error("UPDATE_FAILED", e.localizedMessage, null)
}
}
"stopNavigation" -> {
SiroCarAppService.stopNavigation()
result.success(true)
}
else -> {
result.notImplemented()
}
}
}
}
}
@@ -0,0 +1,208 @@
package com.urukmap.app
import android.app.PictureInPictureParams
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.util.Rational
import com.urukmap.app.car.CarNavigationState
import com.urukmap.app.car.SiroCarAppService
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CAR_CHANNEL = "com.siro.siro_maps/car_navigation"
private val PIP_CHANNEL = "com.siro.siro_maps/pip"
private val DEEP_LINK_CHANNEL = "com.siro.siro_maps/deep_link"
private var pipMethodChannel: MethodChannel? = null
private var deepLinkMethodChannel: MethodChannel? = null
private var pendingDeepLink: String? = null
private var isNavigating: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
handleIntent(intent)
}
private fun handleIntent(intent: Intent?) {
if (intent?.action == Intent.ACTION_VIEW) {
val dataString = intent.dataString
if (!dataString.isNullOrEmpty()) {
pendingDeepLink = dataString
deepLinkMethodChannel?.invokeMethod("onDeepLink", mapOf("url" to dataString))
}
}
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 1. Car Navigation Channel
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CAR_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"isCarAppConnected" -> {
result.success(SiroCarAppService.isConnected)
}
"updateNavState" -> {
try {
val lat = call.argument<Double>("lat") ?: 0.0
val lng = call.argument<Double>("lng") ?: 0.0
val bearing = call.argument<Double>("bearing") ?: 0.0
val speed = call.argument<Double>("speed") ?: 0.0
val instruction = call.argument<String>("instruction") ?: ""
val distanceToStep = call.argument<Double>("distanceToStep") ?: 0.0
val totalDistance = call.argument<Double>("totalDistance") ?: 0.0
val eta = call.argument<Double>("eta") ?: 0.0
val maneuver = call.argument<Int>("maneuver") ?: 0
val isNav = call.argument<Boolean>("isNavigating") ?: false
val isMapDarkMode = call.argument<Boolean>("isMapDarkMode") ?: false
updateNavigationState(isNav)
val newState = CarNavigationState(
lat = lat,
lng = lng,
bearing = bearing,
speed = speed,
instruction = instruction,
distanceToStep = distanceToStep,
totalDistance = totalDistance,
eta = eta,
maneuver = maneuver,
isNavigating = isNav,
isMapDarkMode = isMapDarkMode
)
SiroCarAppService.updateNavState(newState)
result.success(true)
} catch (e: Exception) {
result.error("UPDATE_FAILED", e.localizedMessage, null)
}
}
"stopNavigation" -> {
updateNavigationState(false)
SiroCarAppService.stopNavigation()
result.success(true)
}
else -> {
result.notImplemented()
}
}
}
// 2. Deep Link Channel
deepLinkMethodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, DEEP_LINK_CHANNEL).apply {
setMethodCallHandler { call, result ->
when (call.method) {
"getInitialLink" -> {
val link = pendingDeepLink
pendingDeepLink = null
result.success(link)
}
else -> result.notImplemented()
}
}
}
// Deliver pending deep link if arrived before engine configuration
pendingDeepLink?.let { link ->
deepLinkMethodChannel?.invokeMethod("onDeepLink", mapOf("url" to link))
}
// 3. Picture-in-Picture (PiP) Channel
pipMethodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, PIP_CHANNEL).apply {
setMethodCallHandler { call, result ->
when (call.method) {
"enterPictureInPicture" -> {
val success = enterPipMode()
result.success(success)
}
"isPipSupported" -> {
val supported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
} else {
false
}
result.success(supported)
}
"setNavigating" -> {
val navigating = call.argument<Boolean>("isNavigating") ?: false
updateNavigationState(navigating)
result.success(true)
}
else -> result.notImplemented()
}
}
}
// 4. Device Hardware Channel
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.siro.siro_maps/device_hardware").setMethodCallHandler { call, result ->
if (call.method == "getHardwareInfo") {
val androidId = android.provider.Settings.Secure.getString(contentResolver, android.provider.Settings.Secure.ANDROID_ID) ?: ""
val info = mapOf(
"hardwareId" to androidId,
"brand" to Build.BRAND,
"manufacturer" to Build.MANUFACTURER,
"model" to Build.MODEL,
"device" to Build.DEVICE,
"board" to Build.BOARD,
"hardware" to Build.HARDWARE
)
result.success(info)
} else {
result.notImplemented()
}
}
}
private fun updateNavigationState(navigating: Boolean) {
isNavigating = navigating
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
val pipParams = PictureInPictureParams.Builder()
.setAutoEnterEnabled(navigating)
.setAspectRatio(Rational(3, 4))
.build()
setPictureInPictureParams(pipParams)
} catch (_: Exception) {}
}
}
private fun enterPipMode(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
return try {
val pipParams = PictureInPictureParams.Builder()
.setAspectRatio(Rational(3, 4))
.build()
enterPictureInPictureMode(pipParams)
} catch (e: Exception) {
false
}
}
}
return false
}
override fun onUserLeaveHint() {
super.onUserLeaveHint()
// Auto-enter PiP on Android 8.0 - 11 when user presses Home while navigating
if (isNavigating && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
enterPipMode()
}
}
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
pipMethodChannel?.invokeMethod("onPipChanged", mapOf("isInPip" to isInPictureInPictureMode))
}
}
@@ -1,4 +1,4 @@
package com.siro_map.siro_maps.car
package com.urukmap.app.car
data class CarNavigationState(
val lat: Double = 0.0,
@@ -1,4 +1,4 @@
package com.siro_map.siro_maps.car
package com.urukmap.app.car
import androidx.car.app.CarAppService
import androidx.car.app.Session
@@ -1,4 +1,4 @@
package com.siro_map.siro_maps.car
package com.urukmap.app.car
import android.content.Intent
import androidx.car.app.Screen
@@ -1,4 +1,4 @@
package com.siro_map.siro_maps.car
package com.urukmap.app.car
import androidx.car.app.CarContext
import androidx.car.app.Screen
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 56 KiB

@@ -2,4 +2,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
+2 -2
View File
@@ -19,8 +19,8 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "9.0.1" apply false
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}
include(":app")