From 3c8bd2a1bc814f840ae2246ef09f6c465abcbba9 Mon Sep 17 00:00:00 2001 From: crc-32 Date: Fri, 30 Aug 2024 13:13:37 -0400 Subject: [PATCH] add KMP version of appstore api --- .../cobble/bridges/common/KMPApiBridge.kt | 22 ++++ .../kotlin/io/rebble/cobble/di/AppModule.kt | 5 + .../cobble/di/bridges/CommonBridgesModule.kt | 7 ++ .../io/rebble/cobble/pigeons/Pigeons.java | 64 +++++++++++ android/gradle/libs.versions.toml | 9 +- android/shared/build.gradle.kts | 4 + .../cobble/shared/api/AppstoreClient.kt | 49 +++++++++ .../kotlin/io/rebble/cobble/shared/api/RWS.kt | 22 ++++ .../io/rebble/cobble/shared/di/StateModule.kt | 5 + .../shared/domain/api/appstore/LockerEntry.kt | 101 ++++++++++++++++++ .../shared/domain/state/CurrentToken.kt | 12 +++ ios/Runner/Pigeon/Pigeons.h | 9 ++ ios/Runner/Pigeon/Pigeons.m | 68 ++++++++++++ lib/domain/api/auth/oauth.dart | 2 + lib/infrastructure/pigeons/pigeons.g.dart | 56 ++++++++++ lib/main.dart | 6 ++ pigeons/pigeons.dart | 6 ++ 17 files changed, 446 insertions(+), 1 deletion(-) create mode 100644 android/app/src/main/kotlin/io/rebble/cobble/bridges/common/KMPApiBridge.kt create mode 100644 android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/api/AppstoreClient.kt create mode 100644 android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/api/RWS.kt create mode 100644 android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/domain/api/appstore/LockerEntry.kt create mode 100644 android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/domain/state/CurrentToken.kt diff --git a/android/app/src/main/kotlin/io/rebble/cobble/bridges/common/KMPApiBridge.kt b/android/app/src/main/kotlin/io/rebble/cobble/bridges/common/KMPApiBridge.kt new file mode 100644 index 00000000..441b0cfe --- /dev/null +++ b/android/app/src/main/kotlin/io/rebble/cobble/bridges/common/KMPApiBridge.kt @@ -0,0 +1,22 @@ +package io.rebble.cobble.bridges.common + +import io.rebble.cobble.bridges.FlutterBridge +import io.rebble.cobble.bridges.ui.BridgeLifecycleController +import io.rebble.cobble.pigeons.Pigeons +import io.rebble.cobble.shared.domain.state.CurrentToken +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +class KMPApiBridge @Inject constructor( + private val tokenState: MutableStateFlow, + bridgeLifecycleController: BridgeLifecycleController +): FlutterBridge, Pigeons.KMPApi { + + init { + bridgeLifecycleController.setupControl(Pigeons.KMPApi::setup, this) + } + + override fun updateToken(token: Pigeons.StringWrapper) { + tokenState.value = token.value?.let { CurrentToken.LoggedIn(it) } ?: CurrentToken.LoggedOut + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/io/rebble/cobble/di/AppModule.kt b/android/app/src/main/kotlin/io/rebble/cobble/di/AppModule.kt index 4e1621ce..0125d570 100644 --- a/android/app/src/main/kotlin/io/rebble/cobble/di/AppModule.kt +++ b/android/app/src/main/kotlin/io/rebble/cobble/di/AppModule.kt @@ -14,6 +14,7 @@ import io.rebble.cobble.shared.database.dao.NotificationChannelDao import io.rebble.cobble.shared.database.dao.PersistedNotificationDao import io.rebble.cobble.shared.datastore.KMPPrefs import io.rebble.cobble.shared.domain.calendar.CalendarSync +import io.rebble.cobble.shared.domain.state.CurrentToken import io.rebble.cobble.shared.handlers.CalendarActionHandler import io.rebble.libpebblecommon.services.blobdb.BlobDBService import kotlinx.coroutines.CoroutineExceptionHandler @@ -62,6 +63,10 @@ abstract class AppModule { return KMPPrefs() } @Provides + fun provideTokenState(): MutableStateFlow { + return KoinPlatformTools.defaultContext().get().get(named("currentToken")) + } + @Provides fun providePersistedNotificationDao(context: Context): PersistedNotificationDao { return AppDatabase.instance().persistedNotificationDao() } diff --git a/android/app/src/main/kotlin/io/rebble/cobble/di/bridges/CommonBridgesModule.kt b/android/app/src/main/kotlin/io/rebble/cobble/di/bridges/CommonBridgesModule.kt index e11df13d..9b10dd61 100644 --- a/android/app/src/main/kotlin/io/rebble/cobble/di/bridges/CommonBridgesModule.kt +++ b/android/app/src/main/kotlin/io/rebble/cobble/di/bridges/CommonBridgesModule.kt @@ -88,6 +88,13 @@ abstract class CommonBridgesModule { abstract fun bindIncomingPacketsBridge( packetsBridge: RawIncomingPacketsBridge ): FlutterBridge + + @Binds + @IntoSet + @CommonBridge + abstract fun bindKmpApiBridge( + kmpApiBridge: KMPApiBridge + ): FlutterBridge } @Qualifier diff --git a/android/app/src/main/kotlin/io/rebble/cobble/pigeons/Pigeons.java b/android/app/src/main/kotlin/io/rebble/cobble/pigeons/Pigeons.java index b9ab88b9..606969f1 100644 --- a/android/app/src/main/kotlin/io/rebble/cobble/pigeons/Pigeons.java +++ b/android/app/src/main/kotlin/io/rebble/cobble/pigeons/Pigeons.java @@ -6240,4 +6240,68 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable KeepUnused } } } + + private static class KMPApiCodec extends StandardMessageCodec { + public static final KMPApiCodec INSTANCE = new KMPApiCodec(); + + private KMPApiCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return StringWrapper.fromList((ArrayList) readValue(buffer)); + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof StringWrapper) { + stream.write(128); + writeValue(stream, ((StringWrapper) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface KMPApi { + + void updateToken(@NonNull StringWrapper token); + + /** The codec used by KMPApi. */ + static @NonNull MessageCodec getCodec() { + return KMPApiCodec.INSTANCE; + } + /**Sets up an instance of `KMPApi` to handle messages through the `binaryMessenger`. */ + static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable KMPApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.KMPApi.updateToken", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + StringWrapper tokenArg = (StringWrapper) args.get(0); + try { + api.updateToken(tokenArg); + wrapped.add(0, null); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } } diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 5935f90a..c4dd7c1b 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -18,6 +18,8 @@ room-sqlite = "2.5.0-alpha05" datastore = "1.1.1" uuidVersion = "0.8.4" +ktorVersion = "2.3.12" + [plugins] multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } @@ -52,4 +54,9 @@ kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version. kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationJson" } libpebblecommon = { module = "io.rebble.libpebblecommon:libpebblecommon", version.ref = "libpebblecommonVersion" } -uuid = { module = "com.benasher44:uuid", version.ref = "uuidVersion" } \ No newline at end of file +uuid = { module = "com.benasher44:uuid", version.ref = "uuidVersion" } + +ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorVersion" } +ktor-client-contentnegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktorVersion" } +ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktorVersion" } +ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktorVersion" } \ No newline at end of file diff --git a/android/shared/build.gradle.kts b/android/shared/build.gradle.kts index 116bb84c..0b22601a 100644 --- a/android/shared/build.gradle.kts +++ b/android/shared/build.gradle.kts @@ -51,8 +51,12 @@ kotlin { implementation(libs.androidx.sqlite.bundled) implementation(libs.androidx.datastore) implementation(libs.androidx.datastore.preferences) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.contentnegotiation) + implementation(libs.ktor.serialization.json) } androidMain.dependencies { + implementation(libs.ktor.client.okhttp) implementation("io.insert-koin:koin-android:$koinVersion") implementation("androidx.core:core-ktx:$androidxVersion") implementation("com.jakewharton.timber:timber:$timberVersion") diff --git a/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/api/AppstoreClient.kt b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/api/AppstoreClient.kt new file mode 100644 index 00000000..c7e342d2 --- /dev/null +++ b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/api/AppstoreClient.kt @@ -0,0 +1,49 @@ +package io.rebble.cobble.shared.api + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.request.* +import io.ktor.http.HttpHeaders +import io.ktor.serialization.kotlinx.json.json +import io.rebble.cobble.shared.domain.api.appstore.LockerEntry + +class AppstoreClient( + val baseUrl: String, + private val token: String +) { + private val version = "v1" + private val client = HttpClient { + install(ContentNegotiation) { + json() + } + } + + suspend fun getLocker(): List { + val body: Map> = client.get("$baseUrl/$version/locker") { + headers { + append(HttpHeaders.Accept, "application/json") + append(HttpHeaders.Authorization, "Bearer $token") + } + }.body() + return body["applications"] ?: emptyList() + } + + suspend fun addToLocker(uuid: String) { + client.put("$baseUrl/$version/locker/$uuid") { + headers { + append(HttpHeaders.Accept, "application/json") + append(HttpHeaders.Authorization, "Bearer $token") + } + } + } + + suspend fun removeFromLocker(uuid: String) { + client.delete("$baseUrl/$version/locker/$uuid") { + headers { + append(HttpHeaders.Accept, "application/json") + append(HttpHeaders.Authorization, "Bearer $token") + } + } + } +} \ No newline at end of file diff --git a/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/api/RWS.kt b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/api/RWS.kt new file mode 100644 index 00000000..657ad6fe --- /dev/null +++ b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/api/RWS.kt @@ -0,0 +1,22 @@ +package io.rebble.cobble.shared.api + +import io.rebble.cobble.shared.domain.state.CurrentToken +import io.rebble.cobble.shared.domain.state.CurrentToken.LoggedOut.tokenOrNull +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.qualifier.named + +object RWS: KoinComponent { + private val domainSuffix = "rebble.io" + private val token: StateFlow by inject(named("currentToken")) + private val scope = CoroutineScope(Dispatchers.Default) + + private val _appstoreClient = token.map { + it.tokenOrNull?.let { t -> AppstoreClient("https://appstore-api.$domainSuffix", t) } + }.stateIn(scope, SharingStarted.Eagerly, null) + val appstoreClient: AppstoreClient? + get() = _appstoreClient.value +} \ No newline at end of file diff --git a/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/di/StateModule.kt b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/di/StateModule.kt index 6d690444..b607c7a0 100644 --- a/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/di/StateModule.kt +++ b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/di/StateModule.kt @@ -1,6 +1,7 @@ package io.rebble.cobble.shared.di import io.rebble.cobble.shared.domain.state.ConnectionState +import io.rebble.cobble.shared.domain.state.CurrentToken import io.rebble.cobble.shared.domain.state.watchOrNull import kotlinx.coroutines.flow.* import org.koin.core.qualifier.named @@ -16,4 +17,8 @@ val stateModule = module { .flatMapLatest { it.watchOrNull?.metadata?.take(1) ?: flowOf(null) } .filterNotNull() } + + single(named("currentToken")) { + MutableStateFlow(CurrentToken.LoggedOut) + } bind StateFlow::class } \ No newline at end of file diff --git a/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/domain/api/appstore/LockerEntry.kt b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/domain/api/appstore/LockerEntry.kt new file mode 100644 index 00000000..e51c5092 --- /dev/null +++ b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/domain/api/appstore/LockerEntry.kt @@ -0,0 +1,101 @@ +package io.rebble.cobble.shared.domain.api.appstore + +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName + +@Serializable +data class LockerEntry( + val id: String, + val uuid: String, + @SerialName("user_token") val userToken: String, + val title: String, + val type: String, + val category: String, + val version: String? = null, + val hearts: Int, + @SerialName("is_configurable") val isConfigurable: Boolean, + @SerialName("is_timeline_enabled") val isTimelineEnabled: Boolean, + val links: LockerEntryLinks, + val developer: LockerEntryDeveloper, + @SerialName("hardware_platforms") val hardwarePlatforms: List, + val compatibility: LockerEntryCompatibility, + val companions: Map, + val pbw: LockerEntryPBW? = null +) + +@Serializable +data class LockerEntryLinks( + val remove: String, + val href: String, + val share: String +) + +@Serializable +data class LockerEntryDeveloper( + val id: String, + val name: String, + @SerialName("contact_email") val contactEmail: String +) + +@Serializable +data class LockerEntryPlatform( + @SerialName("sdk_version") val sdkVersion: String, + @SerialName("pebble_process_info_flags") val pebbleProcessInfoFlags: Int, + val name: String, + val description: String, + val images: LockerEntryPlatformImages +) + +@Serializable +data class LockerEntryPlatformImages( + val icon: String, + val list: String, + val screenshot: String +) + +@Serializable +data class LockerEntryCompatibility( + val ios: LockerEntryCompatibilityPhonePlatformDetails, + val android: LockerEntryCompatibilityPhonePlatformDetails, + val aplite: LockerEntryCompatibilityWatchPlatformDetails, + val basalt: LockerEntryCompatibilityWatchPlatformDetails, + val chalk: LockerEntryCompatibilityWatchPlatformDetails, + val diorite: LockerEntryCompatibilityWatchPlatformDetails, + val emery: LockerEntryCompatibilityWatchPlatformDetails +) + +@Serializable +data class LockerEntryCompatibilityPhonePlatformDetails( + val supported: Boolean, + @SerialName("min_js_version") val minJsVersion: Int? = null +) + +@Serializable +data class LockerEntryCompatibilityWatchPlatformDetails( + val supported: Boolean, + val firmware: LockerEntryFirmwareVersion +) + +@Serializable +data class LockerEntryFirmwareVersion( + val major: Int, + val minor: Int? = null, + val patch: Int? = null +) + +@Serializable +data class LockerEntryCompanionApp( + val id: Int, + val icon: String, + val name: String, + val url: String, + val required: Boolean, + @SerialName("pebblekit_version") val pebblekitVersion: String +) + +@Serializable +data class LockerEntryPBW( + val file: String, + @SerialName("icon_resource_id") val iconResourceId: Int, + @SerialName("release_id") val releaseId: String +) \ No newline at end of file diff --git a/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/domain/state/CurrentToken.kt b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/domain/state/CurrentToken.kt new file mode 100644 index 00000000..9c1671b6 --- /dev/null +++ b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/domain/state/CurrentToken.kt @@ -0,0 +1,12 @@ +package io.rebble.cobble.shared.domain.state + +open class CurrentToken { + object LoggedOut : CurrentToken() + data class LoggedIn(val token: String) : CurrentToken() + + val CurrentToken.tokenOrNull: String? + get() = when (this) { + is LoggedIn -> token + else -> null + } +} \ No newline at end of file diff --git a/ios/Runner/Pigeon/Pigeons.h b/ios/Runner/Pigeon/Pigeons.h index 278c71e0..73ddfc25 100644 --- a/ios/Runner/Pigeon/Pigeons.h +++ b/ios/Runner/Pigeon/Pigeons.h @@ -703,4 +703,13 @@ NSObject *KeepUnusedHackGetCodec(void); extern void KeepUnusedHackSetup(id binaryMessenger, NSObject *_Nullable api); +/// The codec used by KMPApi. +NSObject *KMPApiGetCodec(void); + +@protocol KMPApi +- (void)updateTokenToken:(StringWrapper *)token error:(FlutterError *_Nullable *_Nonnull)error; +@end + +extern void KMPApiSetup(id binaryMessenger, NSObject *_Nullable api); + NS_ASSUME_NONNULL_END diff --git a/ios/Runner/Pigeon/Pigeons.m b/ios/Runner/Pigeon/Pigeons.m index 47feb99f..8a91d43a 100644 --- a/ios/Runner/Pigeon/Pigeons.m +++ b/ios/Runner/Pigeon/Pigeons.m @@ -4066,3 +4066,71 @@ void KeepUnusedHackSetup(id binaryMessenger, NSObject *KMPApiGetCodec(void) { + static FlutterStandardMessageCodec *sSharedObject = nil; + static dispatch_once_t sPred = 0; + dispatch_once(&sPred, ^{ + KMPApiCodecReaderWriter *readerWriter = [[KMPApiCodecReaderWriter alloc] init]; + sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; + }); + return sSharedObject; +} + +void KMPApiSetup(id binaryMessenger, NSObject *api) { + { + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.KMPApi.updateToken" + binaryMessenger:binaryMessenger + codec:KMPApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(updateTokenToken:error:)], @"KMPApi api (%@) doesn't respond to @selector(updateTokenToken:error:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + StringWrapper *arg_token = GetNullableObjectAtIndex(args, 0); + FlutterError *error; + [api updateTokenToken:arg_token error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } +} diff --git a/lib/domain/api/auth/oauth.dart b/lib/domain/api/auth/oauth.dart index de8cff9a..e257b7c4 100644 --- a/lib/domain/api/auth/oauth.dart +++ b/lib/domain/api/auth/oauth.dart @@ -8,6 +8,7 @@ import 'package:cobble/domain/api/boot/boot.dart'; import 'package:cobble/domain/api/status_exception.dart'; import 'package:cobble/infrastructure/datasources/preferences.dart'; import 'package:cobble/infrastructure/datasources/secure_storage.dart'; +import 'package:cobble/infrastructure/pigeons/pigeons.g.dart'; import 'package:crypto/crypto.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -158,6 +159,7 @@ class OAuthClient { await _prefs.setOAuthTokenCreationDate( DateTime.now().subtract(const Duration(hours: 1))); await _secureStorage.setToken(token); + await KMPApi().updateToken(StringWrapper(value: token.accessToken)); return token; } } diff --git a/lib/infrastructure/pigeons/pigeons.g.dart b/lib/infrastructure/pigeons/pigeons.g.dart index c7a76f9c..1e2d08e3 100644 --- a/lib/infrastructure/pigeons/pigeons.g.dart +++ b/lib/infrastructure/pigeons/pigeons.g.dart @@ -4033,3 +4033,59 @@ class KeepUnusedHack { } } } + +class _KMPApiCodec extends StandardMessageCodec { + const _KMPApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is StringWrapper) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return StringWrapper.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class KMPApi { + /// Constructor for [KMPApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + KMPApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _KMPApiCodec(); + + Future updateToken(StringWrapper arg_token) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.KMPApi.updateToken', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_token]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} diff --git a/lib/main.dart b/lib/main.dart index 0e5404fd..703ecf22 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:cobble/domain/logging.dart'; import 'package:cobble/infrastructure/backgroundcomm/BackgroundReceiver.dart'; import 'package:cobble/infrastructure/backgroundcomm/BackgroundRpc.dart'; import 'package:cobble/infrastructure/datasources/preferences.dart'; +import 'package:cobble/infrastructure/datasources/secure_storage.dart'; import 'package:cobble/localization/localization.dart'; import 'package:cobble/localization/localization_delegate.dart'; import 'package:cobble/localization/model/model_generator.model.dart'; @@ -114,6 +115,11 @@ class MyApp extends HookConsumerWidget { permissionControl.requestCallsPermissions(); } } + + final token = await ref.read(secureStorageProvider).getToken(); + if (token != null && token.accessToken.isNotEmpty) { + await KMPApi().updateToken(StringWrapper(value: token.accessToken)); + } }); return null; }, ["one-time"]); diff --git a/pigeons/pigeons.dart b/pigeons/pigeons.dart index 3ea6bdc1..94292bc7 100644 --- a/pigeons/pigeons.dart +++ b/pigeons/pigeons.dart @@ -591,3 +591,9 @@ abstract class KeepUnusedHack { void keepWatchResource(WatchResource cls); } + +//TODO: Move all api use to KMP so we don't need this +@HostApi() +abstract class KMPApi { + void updateToken(StringWrapper token); +} \ No newline at end of file