diff --git a/.gitignore b/.gitignore
index ca775f0..aa852fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,7 @@ app.*.map.json
/android/app/profile
/android/app/release
android/.gradle/
+/.gradle/
android/local.properties
android/**/gradle-wrapper.jar
android/key.properties
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 2d74980..f2a405c 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -28,7 +28,7 @@ check(sharedSigningStoreFile.isFile) {
}
android {
- namespace = "com.gametime.app"
+ namespace = "com.idea.gametime"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@@ -38,8 +38,8 @@ android {
}
defaultConfig {
- applicationId = "com.gametime.app"
- minSdk = flutter.minSdkVersion
+ applicationId = "com.idea.gametime"
+ minSdk = 26
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
@@ -72,5 +72,7 @@ flutter {
}
dependencies {
+ implementation("androidx.health.connect:connect-client:1.1.0")
implementation("com.google.android.gms:play-services-wearable:19.0.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
}
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index 399f698..b04c115 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -4,4 +4,6 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index c2344a4..3e1adb5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -6,12 +6,15 @@
+
+
+
+
+ android:icon="@mipmap/ic_launcher">
@@ -37,6 +40,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/kotlin/com/gametime/app/MainActivity.kt b/android/app/src/main/kotlin/com/gametime/app/MainActivity.kt
deleted file mode 100644
index 89dbc3a..0000000
--- a/android/app/src/main/kotlin/com/gametime/app/MainActivity.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.gametime.app
-
-import com.gametime.app.session.SessionNotificationPlugin
-import com.gametime.app.watch.WatchBridgePlugin
-import io.flutter.embedding.android.FlutterActivity
-import io.flutter.embedding.engine.FlutterEngine
-
-class MainActivity : FlutterActivity() {
- override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
- super.configureFlutterEngine(flutterEngine)
- WatchBridgePlugin.register(flutterEngine, applicationContext)
- SessionNotificationPlugin.register(flutterEngine, this)
- }
-}
diff --git a/android/app/src/main/kotlin/com/idea/gametime/MainActivity.kt b/android/app/src/main/kotlin/com/idea/gametime/MainActivity.kt
new file mode 100644
index 0000000..0c1d5ca
--- /dev/null
+++ b/android/app/src/main/kotlin/com/idea/gametime/MainActivity.kt
@@ -0,0 +1,22 @@
+package com.idea.gametime
+
+import android.os.Bundle
+import com.idea.gametime.health.HealthConnectPlugin
+import com.idea.gametime.session.SessionNotificationPlugin
+import com.idea.gametime.watch.WatchBridgePlugin
+import io.flutter.embedding.android.FlutterFragmentActivity
+import io.flutter.embedding.engine.FlutterEngine
+
+class MainActivity : FlutterFragmentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ HealthConnectPlugin.prepare(this)
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
+ super.configureFlutterEngine(flutterEngine)
+ WatchBridgePlugin.register(flutterEngine, applicationContext)
+ SessionNotificationPlugin.register(flutterEngine, this)
+ HealthConnectPlugin.register(flutterEngine, this)
+ }
+}
diff --git a/android/app/src/main/kotlin/com/idea/gametime/health/HealthConnectPlugin.kt b/android/app/src/main/kotlin/com/idea/gametime/health/HealthConnectPlugin.kt
new file mode 100644
index 0000000..085d86f
--- /dev/null
+++ b/android/app/src/main/kotlin/com/idea/gametime/health/HealthConnectPlugin.kt
@@ -0,0 +1,470 @@
+package com.idea.gametime.health
+
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import androidx.activity.ComponentActivity
+import androidx.activity.result.ActivityResultLauncher
+import androidx.health.connect.client.HealthConnectClient
+import androidx.health.connect.client.PermissionController
+import androidx.lifecycle.Lifecycle
+import androidx.health.connect.client.permission.HealthPermission
+import androidx.health.connect.client.records.DistanceRecord
+import androidx.health.connect.client.records.ExerciseSessionRecord
+import androidx.health.connect.client.records.HeartRateRecord
+import androidx.health.connect.client.records.Record
+import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
+import androidx.health.connect.client.records.metadata.Metadata
+import androidx.health.connect.client.units.Energy
+import androidx.health.connect.client.units.Length
+import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import java.lang.ref.WeakReference
+import java.time.Instant
+import java.time.ZoneOffset
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+object HealthConnectPlugin {
+ private const val METHOD_CHANNEL = "gametime.health_connect/methods"
+ private const val LOG_TAG = "GTHealthConnect"
+ private const val HEALTH_CONNECT_PROVIDER_PACKAGE = "com.google.android.apps.healthdata"
+ private const val PLAY_STORE_PACKAGE = "com.android.vending"
+ private const val FALLBACK_OPEN_DELAY_MS = 1200L
+
+ private val requiredPermissions =
+ setOf(
+ HealthPermission.getWritePermission(ExerciseSessionRecord::class),
+ HealthPermission.getWritePermission(DistanceRecord::class),
+ HealthPermission.getWritePermission(TotalCaloriesBurnedRecord::class),
+ HealthPermission.getWritePermission(HeartRateRecord::class),
+ )
+
+ private var appContext: Context? = null
+ private var activityRef: WeakReference? = null
+ private var permissionRequestLauncher: ActivityResultLauncher>? = null
+ private var pendingPermissionResult: MethodChannel.Result? = null
+ private var fallbackOpenRunnable: Runnable? = null
+ private val mainHandler = Handler(Looper.getMainLooper())
+ private val mainScope = CoroutineScope(Dispatchers.Main)
+
+ fun prepare(activity: Activity) {
+ appContext = activity.applicationContext
+ activityRef = WeakReference(activity)
+ registerPermissionRequestLauncher(activity)
+ }
+
+ fun register(flutterEngine: FlutterEngine, activity: Activity) {
+ appContext = activity.applicationContext
+ activityRef = WeakReference(activity)
+ if (permissionRequestLauncher == null) {
+ registerPermissionRequestLauncher(activity)
+ }
+ MethodChannel(flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL)
+ .setMethodCallHandler(::handleMethodCall)
+ }
+
+ private fun registerPermissionRequestLauncher(activity: Activity) {
+ Log.d(LOG_TAG, "registerPermissionRequestLauncher(activity=${activity::class.java.name})")
+ val componentActivity = activity as? ComponentActivity
+ if (componentActivity == null) {
+ Log.e(LOG_TAG, "Activity is not a ComponentActivity; permission launcher unavailable.")
+ return
+ }
+ try {
+ val context = activity.applicationContext
+ permissionRequestLauncher?.unregister()
+ permissionRequestLauncher =
+ componentActivity.activityResultRegistry.register(
+ "gametime_health_connect_permissions",
+ PermissionController.createRequestPermissionResultContract(),
+ ) { grantedPermissions ->
+ Log.d(
+ LOG_TAG,
+ "permission request callback(granted=${grantedPermissions.size})",
+ )
+ cancelFallbackOpen()
+ val result = pendingPermissionResult ?: return@register
+ pendingPermissionResult = null
+ mainScope.launch {
+ val state = try {
+ connectionStateMap(
+ context,
+ grantedPermissions,
+ permissionRequestOutcome =
+ if (grantedPermissions.containsAll(requiredPermissions)) {
+ "granted"
+ } else {
+ "denied"
+ },
+ )
+ } catch (error: Throwable) {
+ Log.e(LOG_TAG, "connection state failed after callback.", error)
+ result.error("health_connect_state_failed", error.message, null)
+ return@launch
+ }
+ result.success(state)
+ }
+ }
+ Log.d(LOG_TAG, "permission launcher registered.")
+ } catch (error: Throwable) {
+ Log.e(LOG_TAG, "permission launcher registration failed.", error)
+ permissionRequestLauncher = null
+ }
+ }
+
+ private fun handleMethodCall(call: MethodCall, result: MethodChannel.Result) {
+ val context = appContext
+ if (context == null) {
+ result.error("health_connect_unavailable", "Application context unavailable.", null)
+ return
+ }
+ when (call.method) {
+ "connectionState" -> connectionState(context, result)
+ "requestPermissions" -> requestPermissions(context, result)
+ "openSettings" -> openSettings(context, result)
+ "exportWorkout" -> exportWorkout(context, call.arguments, result)
+ else -> result.notImplemented()
+ }
+ }
+
+ private fun connectionState(context: Context, result: MethodChannel.Result) {
+ mainScope.launch {
+ try {
+ result.success(connectionStateMap(context))
+ } catch (error: Throwable) {
+ result.error("health_connect_state_failed", error.message, null)
+ }
+ }
+ }
+
+ private suspend fun connectionStateMap(
+ context: Context,
+ grantedOverride: Set? = null,
+ permissionRequestOutcome: String? = null,
+ ): Map {
+ var availability = availability(context)
+ val grantedPermissions = if (availability == "available") {
+ try {
+ grantedOverride ?: HealthConnectClient.getOrCreate(context)
+ .permissionController
+ .getGrantedPermissions()
+ } catch (_: SecurityException) {
+ availability = "accessBlocked"
+ emptySet()
+ } catch (_: IllegalStateException) {
+ availability = "accessBlocked"
+ emptySet()
+ }
+ } else {
+ emptySet()
+ }
+ val state = mutableMapOf(
+ "availability" to availability,
+ "requiredPermissions" to requiredPermissions.toList(),
+ "grantedPermissions" to grantedPermissions.toList(),
+ )
+ if (permissionRequestOutcome != null) {
+ state["permissionRequestOutcome"] = permissionRequestOutcome
+ }
+ return state
+ }
+
+ private fun availability(context: Context): String {
+ return when (HealthConnectClient.getSdkStatus(context)) {
+ HealthConnectClient.SDK_AVAILABLE -> "available"
+ HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED ->
+ "providerUpdateRequired"
+ else -> "unavailable"
+ }
+ }
+
+ private fun requestPermissions(context: Context, result: MethodChannel.Result) {
+ val availability = availability(context)
+ Log.d(LOG_TAG, "requestPermissions availability=$availability")
+ if (availability != "available") {
+ mainScope.launch {
+ result.success(connectionStateMap(context))
+ }
+ return
+ }
+ val activity = activityRef?.get()
+ if (activity == null) {
+ Log.e(LOG_TAG, "requestPermissions failed: activity unavailable.")
+ result.error("health_connect_unavailable", "Activity unavailable.", null)
+ return
+ }
+ if (pendingPermissionResult != null) {
+ Log.e(LOG_TAG, "requestPermissions failed: request already active.")
+ result.error(
+ "health_connect_request_in_progress",
+ "Permission request already active.",
+ null,
+ )
+ return
+ }
+ pendingPermissionResult = result
+ val launcher = permissionRequestLauncher
+ if (launcher == null) {
+ Log.e(LOG_TAG, "requestPermissions fallback: launcher unavailable.")
+ openSettingsFallback(context, activity, "settingsFallback")
+ return
+ }
+ try {
+ Log.d(LOG_TAG, "launching Health Connect permission contract.")
+ launcher.launch(requiredPermissions)
+ Log.d(LOG_TAG, "Health Connect permission contract launch returned.")
+ scheduleFallbackOpen(context, activity)
+ } catch (error: Throwable) {
+ Log.e(LOG_TAG, "Health Connect permission contract launch failed.", error)
+ openSettingsFallback(context, activity, "launchFailed")
+ }
+ }
+
+ private fun scheduleFallbackOpen(context: Context, activity: Activity) {
+ cancelFallbackOpen()
+ val runnable = Runnable {
+ if (pendingPermissionResult == null) {
+ return@Runnable
+ }
+ val componentActivity = activity as? ComponentActivity
+ if (componentActivity != null &&
+ !componentActivity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
+ ) {
+ Log.d(LOG_TAG, "permission UI appears to have focus; fallback not opened.")
+ return@Runnable
+ }
+ Log.d(LOG_TAG, "permission UI did not take focus; opening Health Connect settings fallback.")
+ openSettingsFallback(context, activity, "settingsFallback")
+ }
+ fallbackOpenRunnable = runnable
+ mainHandler.postDelayed(runnable, FALLBACK_OPEN_DELAY_MS)
+ }
+
+ private fun cancelFallbackOpen() {
+ fallbackOpenRunnable?.let(mainHandler::removeCallbacks)
+ fallbackOpenRunnable = null
+ }
+
+ private fun openSettingsFallback(context: Context, activity: Activity, outcome: String) {
+ cancelFallbackOpen()
+ val pendingResult = pendingPermissionResult
+ pendingPermissionResult = null
+ try {
+ activity.startActivity(settingsIntent())
+ Log.d(LOG_TAG, "Health Connect settings fallback opened(outcome=$outcome).")
+ mainScope.launch {
+ try {
+ pendingResult?.success(
+ connectionStateMap(
+ context,
+ permissionRequestOutcome = outcome,
+ ),
+ )
+ } catch (stateError: Throwable) {
+ Log.e(LOG_TAG, "connection state failed after settings fallback.", stateError)
+ pendingResult?.error("health_connect_state_failed", stateError.message, null)
+ }
+ }
+ } catch (error: Throwable) {
+ Log.e(LOG_TAG, "Health Connect settings fallback failed.", error)
+ pendingResult?.error("health_connect_permission_request_failed", error.message, null)
+ }
+ }
+
+ private fun openSettings(context: Context, result: MethodChannel.Result) {
+ val activity = activityRef?.get()
+ if (activity == null) {
+ result.success(false)
+ return
+ }
+ val intents =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ listOf(settingsIntent())
+ } else if (availability(context) == "providerUpdateRequired") {
+ listOf(providerUpdateIntent(context), settingsIntent())
+ } else {
+ listOf(settingsIntent())
+ }
+ val opened = intents.any { intent ->
+ try {
+ Log.d(LOG_TAG, "openSettings launching ${intent.action}.")
+ activity.startActivity(intent)
+ Log.d(LOG_TAG, "openSettings launch ok.")
+ true
+ } catch (error: ActivityNotFoundException) {
+ Log.e(LOG_TAG, "openSettings activity not found.", error)
+ false
+ } catch (error: SecurityException) {
+ Log.e(LOG_TAG, "openSettings security failure.", error)
+ false
+ }
+ }
+ result.success(opened)
+ }
+
+ private fun providerUpdateIntent(context: Context): Intent {
+ val uri =
+ "market://details?id=$HEALTH_CONNECT_PROVIDER_PACKAGE&url=healthconnect%3A%2F%2Fonboarding"
+ return Intent(Intent.ACTION_VIEW).apply {
+ setPackage(PLAY_STORE_PACKAGE)
+ data = Uri.parse(uri)
+ putExtra("overlay", true)
+ putExtra("callerId", context.packageName)
+ }
+ }
+
+ private fun settingsIntent(): Intent =
+ Intent(HealthConnectClient.ACTION_HEALTH_CONNECT_SETTINGS)
+
+ private fun exportWorkout(
+ context: Context,
+ arguments: Any?,
+ result: MethodChannel.Result,
+ ) {
+ val map = arguments as? Map<*, *>
+ if (map == null) {
+ result.error("invalid_health_connect_workout", "Workout payload must be a map.", null)
+ return
+ }
+ mainScope.launch {
+ try {
+ if (availability(context) != "available") {
+ result.error("health_connect_unavailable", "Health Connect is unavailable.", null)
+ return@launch
+ }
+ val grantedPermissions =
+ HealthConnectClient.getOrCreate(context)
+ .permissionController
+ .getGrantedPermissions()
+ if (!grantedPermissions.containsAll(requiredPermissions)) {
+ result.error(
+ "health_connect_permissions_required",
+ "Health Connect write permissions are not granted.",
+ null,
+ )
+ return@launch
+ }
+ val records = recordsFromWorkoutPayload(map)
+ HealthConnectClient.getOrCreate(context).insertRecords(records)
+ result.success(null)
+ } catch (error: Throwable) {
+ result.error("health_connect_export_failed", error.message, null)
+ }
+ }
+ }
+
+ private fun recordsFromWorkoutPayload(map: Map<*, *>): List {
+ val historyId = nonBlankString(map["historyId"], "historyId")
+ val title = map["title"] as? String ?: "GameTime Basketball"
+ val start = epochMs(map["startedAtEpochMs"], "startedAtEpochMs")
+ val end = epochMs(map["endedAtEpochMs"], "endedAtEpochMs")
+ if (!end.isAfter(start)) {
+ throw IllegalArgumentException("Workout end time must be after start time.")
+ }
+ val records = mutableListOf(
+ ExerciseSessionRecord(
+ startTime = start,
+ startZoneOffset = ZoneOffset.UTC,
+ endTime = end,
+ endZoneOffset = ZoneOffset.UTC,
+ exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BASKETBALL,
+ title = title,
+ notes = "Exported from GameTime.",
+ metadata = Metadata.manualEntry(
+ clientRecordId = clientRecordId(historyId, "exercise-session"),
+ ),
+ ),
+ )
+ val distanceMeters = positiveDouble(map["totalDistanceMeters"])
+ if (distanceMeters != null) {
+ records += DistanceRecord(
+ startTime = start,
+ startZoneOffset = ZoneOffset.UTC,
+ endTime = end,
+ endZoneOffset = ZoneOffset.UTC,
+ distance = Length.meters(distanceMeters),
+ metadata = Metadata.manualEntry(
+ clientRecordId = clientRecordId(historyId, "distance"),
+ ),
+ )
+ }
+ val caloriesKcal = positiveDouble(map["totalCaloriesKcal"])
+ if (caloriesKcal != null) {
+ records += TotalCaloriesBurnedRecord(
+ startTime = start,
+ startZoneOffset = ZoneOffset.UTC,
+ endTime = end,
+ endZoneOffset = ZoneOffset.UTC,
+ energy = Energy.kilocalories(caloriesKcal),
+ metadata = Metadata.manualEntry(
+ clientRecordId = clientRecordId(historyId, "calories"),
+ ),
+ )
+ }
+ val heartRateSamples = heartRateSamples(map["heartRateSamples"], start, end)
+ if (heartRateSamples.isNotEmpty()) {
+ records += HeartRateRecord(
+ startTime = start,
+ startZoneOffset = ZoneOffset.UTC,
+ endTime = end,
+ endZoneOffset = ZoneOffset.UTC,
+ samples = heartRateSamples,
+ metadata = Metadata.manualEntry(
+ clientRecordId = clientRecordId(historyId, "heart-rate"),
+ ),
+ )
+ }
+ return records
+ }
+
+ private fun nonBlankString(value: Any?, fieldName: String): String {
+ val text = (value as? String)?.trim()
+ if (text.isNullOrEmpty()) {
+ throw IllegalArgumentException("$fieldName must be a non-blank string.")
+ }
+ return text
+ }
+
+ private fun clientRecordId(historyId: String, kind: String): String =
+ "gametime:$historyId:$kind"
+
+ private fun heartRateSamples(
+ payload: Any?,
+ start: Instant,
+ end: Instant,
+ ): List {
+ val items = payload as? List<*> ?: return emptyList()
+ return items.mapNotNull { item ->
+ val map = item as? Map<*, *> ?: return@mapNotNull null
+ val time =
+ (map["timeEpochMs"] as? Number)?.toLong()?.let(Instant::ofEpochMilli)
+ ?: return@mapNotNull null
+ val beatsPerMinute = (map["beatsPerMinute"] as? Number)?.toLong()
+ ?: return@mapNotNull null
+ if (beatsPerMinute <= 0 || time.isBefore(start) || time.isAfter(end)) {
+ return@mapNotNull null
+ }
+ HeartRateRecord.Sample(time = time, beatsPerMinute = beatsPerMinute)
+ }
+ }
+
+ private fun epochMs(value: Any?, fieldName: String): Instant {
+ val epochMs = (value as? Number)?.toLong()
+ ?: throw IllegalArgumentException("$fieldName must be an epoch millisecond number.")
+ return Instant.ofEpochMilli(epochMs)
+ }
+
+ private fun positiveDouble(value: Any?): Double? {
+ val number = (value as? Number)?.toDouble() ?: return null
+ return if (number > 0.0) number else null
+ }
+}
diff --git a/android/app/src/main/kotlin/com/idea/gametime/health/HealthConnectRationaleActivity.kt b/android/app/src/main/kotlin/com/idea/gametime/health/HealthConnectRationaleActivity.kt
new file mode 100644
index 0000000..f960aba
--- /dev/null
+++ b/android/app/src/main/kotlin/com/idea/gametime/health/HealthConnectRationaleActivity.kt
@@ -0,0 +1,40 @@
+package com.idea.gametime.health
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.ViewGroup
+import android.widget.ScrollView
+import android.widget.TextView
+
+class HealthConnectRationaleActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ title = "Health Connect"
+
+ val textView =
+ TextView(this).apply {
+ text =
+ """
+ GameTime utilise Health Connect pour enregistrer les donnees compatibles de vos seances de basketball.
+
+ Les donnees envoyees peuvent inclure la seance d'exercice, la distance, les calories et les mesures de frequence cardiaque disponibles.
+
+ GameTime n'utilise ces donnees que pour le suivi sportif affiche dans l'application et dans Health Connect. Vous pouvez modifier ou retirer ces permissions a tout moment depuis Health Connect.
+ """.trimIndent()
+ textSize = 16f
+ setPadding(48, 48, 48, 48)
+ }
+
+ val scrollView =
+ ScrollView(this).apply {
+ addView(
+ textView,
+ ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ),
+ )
+ }
+ setContentView(scrollView)
+ }
+}
diff --git a/android/app/src/main/kotlin/com/gametime/app/session/SessionNotificationPlugin.kt b/android/app/src/main/kotlin/com/idea/gametime/session/SessionNotificationPlugin.kt
similarity index 99%
rename from android/app/src/main/kotlin/com/gametime/app/session/SessionNotificationPlugin.kt
rename to android/app/src/main/kotlin/com/idea/gametime/session/SessionNotificationPlugin.kt
index d6bc950..9aef577 100644
--- a/android/app/src/main/kotlin/com/gametime/app/session/SessionNotificationPlugin.kt
+++ b/android/app/src/main/kotlin/com/idea/gametime/session/SessionNotificationPlugin.kt
@@ -1,4 +1,4 @@
-package com.gametime.app.session
+package com.idea.gametime.session
import android.Manifest
import android.app.Activity
diff --git a/android/app/src/main/kotlin/com/gametime/app/session/SessionStatusForegroundService.kt b/android/app/src/main/kotlin/com/idea/gametime/session/SessionStatusForegroundService.kt
similarity index 93%
rename from android/app/src/main/kotlin/com/gametime/app/session/SessionStatusForegroundService.kt
rename to android/app/src/main/kotlin/com/idea/gametime/session/SessionStatusForegroundService.kt
index f399f6b..ab87789 100644
--- a/android/app/src/main/kotlin/com/gametime/app/session/SessionStatusForegroundService.kt
+++ b/android/app/src/main/kotlin/com/idea/gametime/session/SessionStatusForegroundService.kt
@@ -1,4 +1,4 @@
-package com.gametime.app.session
+package com.idea.gametime.session
import android.app.Notification
import android.app.NotificationChannel
@@ -8,8 +8,8 @@ import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
-import com.gametime.app.MainActivity
-import com.gametime.app.R
+import com.idea.gametime.MainActivity
+import com.idea.gametime.R
class SessionStatusForegroundService : Service() {
override fun onCreate() {
@@ -62,7 +62,7 @@ class SessionStatusForegroundService : Service() {
Notification.BigTextStyle().bigText("$primaryLine\n$it")
}
return builder
- .setSmallIcon(R.mipmap.ic_launcher)
+ .setSmallIcon(R.drawable.ic_ongoing_gt)
.setContentTitle(title)
.setContentText(primaryLine)
.setSubText(secondaryLine)
@@ -89,7 +89,7 @@ class SessionStatusForegroundService : Service() {
}
companion object {
- const val ACTION_SHOW = "com.gametime.app.session.SHOW"
+ const val ACTION_SHOW = "com.idea.gametime.session.SHOW"
const val EXTRA_TITLE = "title"
const val EXTRA_PRIMARY_LINE = "primaryLine"
const val EXTRA_SECONDARY_LINE = "secondaryLine"
diff --git a/android/app/src/main/kotlin/com/gametime/app/watch/PhoneWatchBridgeListenerService.kt b/android/app/src/main/kotlin/com/idea/gametime/watch/PhoneWatchBridgeListenerService.kt
similarity index 98%
rename from android/app/src/main/kotlin/com/gametime/app/watch/PhoneWatchBridgeListenerService.kt
rename to android/app/src/main/kotlin/com/idea/gametime/watch/PhoneWatchBridgeListenerService.kt
index 6dceee7..a2925f6 100644
--- a/android/app/src/main/kotlin/com/gametime/app/watch/PhoneWatchBridgeListenerService.kt
+++ b/android/app/src/main/kotlin/com/idea/gametime/watch/PhoneWatchBridgeListenerService.kt
@@ -1,4 +1,4 @@
-package com.gametime.app.watch
+package com.idea.gametime.watch
import com.google.android.gms.wearable.CapabilityInfo
import com.google.android.gms.wearable.MessageEvent
diff --git a/android/app/src/main/kotlin/com/gametime/app/watch/WatchBridgePlugin.kt b/android/app/src/main/kotlin/com/idea/gametime/watch/WatchBridgePlugin.kt
similarity index 90%
rename from android/app/src/main/kotlin/com/gametime/app/watch/WatchBridgePlugin.kt
rename to android/app/src/main/kotlin/com/idea/gametime/watch/WatchBridgePlugin.kt
index c5d7c7d..1c93c6b 100644
--- a/android/app/src/main/kotlin/com/gametime/app/watch/WatchBridgePlugin.kt
+++ b/android/app/src/main/kotlin/com/idea/gametime/watch/WatchBridgePlugin.kt
@@ -1,4 +1,4 @@
-package com.gametime.app.watch
+package com.idea.gametime.watch
import android.content.Context
import android.content.Intent
@@ -214,32 +214,7 @@ object WatchBridgePlugin {
sendMessage(context, targetNode, ACK_PATH, payload, result)
return
}
- Wearable.getCapabilityClient(context)
- .getCapability(WATCH_CAPABILITY, CapabilityClient.FILTER_REACHABLE)
- .addOnSuccessListener { capability ->
- val nodes = capability.nodes.toList()
- if (nodes.isEmpty()) {
- result.success(null)
- return@addOnSuccessListener
- }
- var remaining = nodes.size
- var failed = false
- for (node in nodes) {
- Wearable.getMessageClient(context)
- .sendMessage(node.id, ACK_PATH, payload)
- .addOnSuccessListener {
- remaining -= 1
- if (remaining == 0 && !failed) result.success(null)
- }
- .addOnFailureListener { error ->
- failed = true
- result.error("send_ack_failed", error.message, null)
- }
- }
- }
- .addOnFailureListener { error ->
- result.error("capability_lookup_failed", error.message, null)
- }
+ result.success(null)
}
private fun sendMessage(
diff --git a/android/app/src/main/kotlin/com/gametime/app/watch/WatchCompanionForegroundService.kt b/android/app/src/main/kotlin/com/idea/gametime/watch/WatchCompanionForegroundService.kt
similarity index 93%
rename from android/app/src/main/kotlin/com/gametime/app/watch/WatchCompanionForegroundService.kt
rename to android/app/src/main/kotlin/com/idea/gametime/watch/WatchCompanionForegroundService.kt
index ef5db54..772f963 100644
--- a/android/app/src/main/kotlin/com/gametime/app/watch/WatchCompanionForegroundService.kt
+++ b/android/app/src/main/kotlin/com/idea/gametime/watch/WatchCompanionForegroundService.kt
@@ -1,4 +1,4 @@
-package com.gametime.app.watch
+package com.idea.gametime.watch
import android.app.Notification
import android.app.NotificationChannel
@@ -7,7 +7,7 @@ import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
-import com.gametime.app.R
+import com.idea.gametime.R
class WatchCompanionForegroundService : Service() {
override fun onCreate() {
@@ -30,7 +30,7 @@ class WatchCompanionForegroundService : Service() {
Notification.Builder(this)
}
return builder
- .setSmallIcon(R.mipmap.ic_launcher)
+ .setSmallIcon(R.drawable.ic_ongoing_gt)
.setContentTitle("GameTime")
.setContentText("Séance en cours")
.setOngoing(true)
diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml
index 399f698..b04c115 100644
--- a/android/app/src/profile/AndroidManifest.xml
+++ b/android/app/src/profile/AndroidManifest.xml
@@ -4,4 +4,6 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
+
+