From fc09fa36b8ae48c78e1d88c7e39240040b797036 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 24 Sep 2025 23:47:11 +0300 Subject: [PATCH 1/3] feat(auth): Issue-691 --- android/app/build.gradle | 3 + .../auth/presentation/pages/auth_screen.dart | 94 +++++------ lib/features/auth/sdk/social_auth_facade.dart | 152 ++++++++++++++++-- pubspec.yaml | 2 + 4 files changed, 194 insertions(+), 57 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 9ffa87c4..457960f7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -13,6 +13,8 @@ if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } +def yandexClientId = project.hasProperty('YANDEX_CLIENT_ID') ? project.property('YANDEX_CLIENT_ID') : (System.getenv('YANDEX_CLIENT_ID') ?: "") + android { namespace = "com.eatvioapp.app" compileSdk = flutter.compileSdkVersion @@ -33,6 +35,7 @@ android { targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName + manifestPlaceholders = [YANDEX_CLIENT_ID: yandexClientId] } signingConfigs { diff --git a/lib/features/auth/presentation/pages/auth_screen.dart b/lib/features/auth/presentation/pages/auth_screen.dart index ea457d6c..f9eeddf5 100644 --- a/lib/features/auth/presentation/pages/auth_screen.dart +++ b/lib/features/auth/presentation/pages/auth_screen.dart @@ -169,51 +169,51 @@ class _AuthScreenState extends State { ], ), // const SizedBox(height: 16), - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // _SocialButton( - // onTap: _onVkPressed, - // backgroundColor: const Color(0xFF2787F5), - // tooltip: 'VK ID', - // child: SvgPicture.asset( - // 'assets/icons/vk.svg', - // height: 24, - // width: 24, - // colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), - // ), - // ), - // _SocialButton( - // onTap: _onOkPressed, - // backgroundColor: const Color(0xFFF58220), - // tooltip: 'Одноклассники', - // child: SvgPicture.asset( - // 'assets/icons/ok.svg', - // height: 24, - // width: 24, - // colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), - // ), - // ), - // const _SocialButton( - // onTap: _onMailRuPressed, - // backgroundColor: Color(0xFF005FF9), - // tooltip: 'Mail.ru', - // child: Text( - // 'Mail', - // style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700), - // ), - // ), - // const _SocialButton( - // onTap: _onYandexPressed, - // backgroundColor: Color(0xFFFFCC00), - // tooltip: 'Яндекс ID', - // child: Text( - // 'Yandex', - // style: TextStyle(color: Colors.black, fontWeight: FontWeight.w700), - // ), - // ), - // ], - // ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _SocialButton( + onTap: () => _onVkPressed(context), + backgroundColor: const Color(0xFF2787F5), + tooltip: 'VK ID', + child: SvgPicture.asset( + 'assets/icons/vk.svg', + height: 24, + width: 24, + colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), + ), + ), + _SocialButton( + onTap: _onOkPressed, + backgroundColor: const Color(0xFFF58220), + tooltip: 'Одноклассники', + child: SvgPicture.asset( + 'assets/icons/ok.svg', + height: 24, + width: 24, + colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), + ), + ), + const _SocialButton( + onTap: _onMailRuPressed, + backgroundColor: Color(0xFF005FF9), + tooltip: 'Mail.ru', + child: Text( + 'Mail', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700), + ), + ), + const _SocialButton( + onTap: _onYandexPressed, + backgroundColor: Color(0xFFFFCC00), + tooltip: 'Яндекс ID', + child: Text( + 'Yandex', + style: TextStyle(color: Colors.black, fontWeight: FontWeight.w700), + ), + ), + ], + ), const Spacer(), Center( child: RichText( @@ -395,8 +395,8 @@ class _SocialButton extends StatelessWidget { } // ignore: unused_element -void _onVkPressed() async { - final accessToken = await SocialAuthFacade.signInWithVk(); +void _onVkPressed(BuildContext context) async { + final accessToken = await SocialAuthFacade.signInWithVk(context: context); if (accessToken == null) return; final isAuthenticated = await sl().getTokenBySocial(provider: 'vk', accessToken: accessToken); if (isAuthenticated) { diff --git a/lib/features/auth/sdk/social_auth_facade.dart b/lib/features/auth/sdk/social_auth_facade.dart index 62985d5d..e5761498 100644 --- a/lib/features/auth/sdk/social_auth_facade.dart +++ b/lib/features/auth/sdk/social_auth_facade.dart @@ -1,25 +1,157 @@ import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_login_yandex/flutter_login_yandex.dart'; +import 'package:eatvio_app/core/app_router.dart'; +import 'package:vk_id/vk_id.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class VkAuthResult { + VkAuthResult({required this.accessToken, this.refreshToken, this.userId, this.firstName, this.lastName, this.email, this.phone, this.gender, this.birthDate}); + + final String accessToken; + final String? refreshToken; + final String? userId; + final String? firstName; + final String? lastName; + final String? email; + final String? phone; + final String? gender; + final String? birthDate; +} class SocialAuthFacade { - static Future signInWithVk() async { - // TODO: Integrate official VK ID SDK and return access token - return null; + static Future signInWithVk({required BuildContext context}) async { + final result = await signInWithVkAndFetchProfile(context: context); + return result?.accessToken; + } + + static Future signInWithVkAndFetchProfile({required BuildContext context}) async { + final clientIdStr = dotenv.maybeGet('VK_CLIENT_ID')?.trim() ?? Platform.environment['VK_CLIENT_ID']; + if (clientIdStr == null || clientIdStr.isEmpty) { + return null; + } + + try { + dynamic controller = VkIDController(clID: int.parse(clientIdStr)); + final authUri = controller.generateAuthorizeLinkWithCodeVerifier(); + final redirectScheme = 'vk$clientIdStr://'; + final redirectParams = await _presentOAuthWebView(context: context, initialUrl: authUri.toString(), redirectSchemePrefix: redirectScheme); + if (redirectParams == null) return null; + final code = redirectParams['code']; + final deviceId = redirectParams['device_id']; + if (code == null || deviceId == null) return null; + + final tokenResponse = await controller.exchangeAuthorizationCodeForToken(code: code, deviceId: deviceId); + final accessToken = _extractAccessToken(tokenResponse); + if (accessToken == null) return null; + return VkAuthResult(accessToken: accessToken); + } catch (_) { + return null; + } } static Future signInWithOk() async { - // TODO: Integrate official OK SDK and return access token - return null; + final context = AppRouter.navigatorKey.currentContext; + if (context == null) return null; + return _signInWithVkIdProvider(context: context, provider: 'ok'); } static Future signInWithMailRu() async { - // TODO: Integrate official Mail.ru SDK and return access token - return null; + final context = AppRouter.navigatorKey.currentContext; + if (context == null) return null; + return _signInWithVkIdProvider(context: context, provider: 'mail'); } static Future signInWithYandex() async { - // TODO: Integrate official Yandex ID SDK and return access token + final clientId = dotenv.maybeGet('YANDEX_CLIENT_ID')?.trim() ?? Platform.environment['YANDEX_CLIENT_ID']; + final redirectUri = dotenv.maybeGet('YANDEX_REDIRECT_URI')?.trim() ?? Platform.environment['YANDEX_REDIRECT_URI']; + + if (clientId == null || clientId.isEmpty || redirectUri == null || redirectUri.isEmpty) { + return null; + } + try { + final plugin = FlutterLoginYandex(); + final response = await plugin.signIn(); + final token = (response is Map) ? response['token'] as String? : null; + return (token != null && token.isNotEmpty) ? token : null; + } catch (_) { + return null; + } + } + + static Future _signInWithVkIdProvider({required BuildContext context, required String provider}) async { + final clientIdStr = dotenv.maybeGet('VK_CLIENT_ID')?.trim() ?? Platform.environment['VK_CLIENT_ID']; + if (clientIdStr == null || clientIdStr.isEmpty) { + return null; + } + try { + dynamic controller = VkIDController(clID: int.parse(clientIdStr)); + final authUri = controller.generateAuthorizeLinkWithCodeVerifier(provider: provider); + final redirectScheme = 'vk$clientIdStr://'; + final redirectParams = await _presentOAuthWebView(context: context, initialUrl: authUri.toString(), redirectSchemePrefix: redirectScheme); + if (redirectParams == null) return null; + final code = redirectParams['code']; + final deviceId = redirectParams['device_id']; + if (code == null || deviceId == null) return null; + final tokenResponse = await controller.exchangeAuthorizationCodeForToken(code: code, deviceId: deviceId, provider: provider); + final accessToken = _extractAccessToken(tokenResponse); + return accessToken; + } catch (_) { + return null; + } + } + + static String? _extractAccessToken(dynamic tokenResponse) { + if (tokenResponse == null) return null; + if (tokenResponse is Map) { + return (tokenResponse['access_token'] as String?) ?? (tokenResponse['token'] as String?); + } + try { + final token = (tokenResponse as dynamic).accessToken as String?; + if (token != null && token.isNotEmpty) return token; + } catch (_) {} + try { + final token = (tokenResponse as dynamic).token as String?; + if (token != null && token.isNotEmpty) return token; + } catch (_) {} return null; } + + static Future?> _presentOAuthWebView({ + required BuildContext context, + required String initialUrl, + required String redirectSchemePrefix, + }) async { + final controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: (request) { + final url = request.url; + if (url.startsWith(redirectSchemePrefix)) { + final uri = Uri.parse(url); + final params = {}..addAll(uri.queryParameters); + Navigator.of(context, rootNavigator: true).pop(params); + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse(initialUrl)); + + return showDialog>( + context: context, + barrierDismissible: false, + builder: (_) { + return Dialog( + insetPadding: const EdgeInsets.all(16), + child: SizedBox(height: 560, child: WebViewWidget(controller: controller)), + ); + }, + ); + } } - - diff --git a/pubspec.yaml b/pubspec.yaml index 3f1e036e..a62a13af 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -75,6 +75,8 @@ dependencies: uuid: ^4.5.1 appmetrica_plugin: ^3.3.0 huawei_push: ^6.11.0+300 + vk_id: ^1.0.3 + flutter_login_yandex: ^0.0.5 dev_dependencies: build_runner: ^2.7.0 From a0b29f42df50de20d6ab863bcb2c5bbcdf532b4e Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 26 Sep 2025 10:31:34 +0300 Subject: [PATCH 2/3] feat(hp): Issue-660 --- android/app/agconnect-services.json | 1 + android/app/build.gradle | 17 ++++- android/app/proguard-rules.pro | 21 ++++++ android/app/src/main/AndroidManifest.xml | 10 +++ .../com/eatvioapp/app/HmsPushService.kt | 19 ++++++ .../kotlin/com/eatvioapp/app/MainActivity.kt | 65 +++++++++++++++++++ android/build.gradle | 39 +++++++++++ .../reports/problems/problems-report.html | 4 +- android/settings.gradle | 1 + lib/core/firebase_api.dart | 56 +++++++--------- pubspec.yaml | 1 - 11 files changed, 199 insertions(+), 35 deletions(-) create mode 100644 android/app/agconnect-services.json create mode 100644 android/app/proguard-rules.pro create mode 100644 android/app/src/main/kotlin/com/eatvioapp/app/HmsPushService.kt diff --git a/android/app/agconnect-services.json b/android/app/agconnect-services.json new file mode 100644 index 00000000..8ca899db --- /dev/null +++ b/android/app/agconnect-services.json @@ -0,0 +1 @@ +{"agcgw":{"url":"connect-drru.hispace.dbankcloud.ru","backurl":"connect-drru.hispace.dbankcloud.cn","websocketurl":"connect-ws-drru.hispace.dbankcloud.ru","websocketbackurl":"connect-ws-drru.hispace.dbankcloud.cn"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"20007000036693451","product_id":"461323198429446894","client_id":"1595824543740483264","client_secret":"2A1727B0D31AA9BE1F5D9E8A5CDA1A67B9EF084AC9FB68D6C633A3CCB08ECDEE","project_id":"461323198429446894","app_id":"113238569","api_key":"DQEDAJNRITp9M3uc1rPHyT8PzLc7bnVcKJAeavZZk0TQNMx9ifvBebr+JOmZe9tEvjnLwv7c1hAHXmPGR/gWG86iABDrCVotq+NGnw==","package_name":"com.eatvioapp.app"},"oauth_client":{"client_id":"113238569","client_type":1},"app_info":{"app_id":"113238569","package_name":"com.eatvioapp.app"},"service":{"analytics":{"collector_url":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drru.ai.dbankcloud.ru,ml-api-drru.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drru.cloud.huawei.ru"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"RU","configuration_version":"3.0","appInfos":[{"package_name":"com.eatvioapp.app","client":{"app_id":"113238569"},"app_info":{"package_name":"com.eatvioapp.app","app_id":"113238569"},"oauth_client":{"client_type":1,"client_id":"113238569"}}]} \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 457960f7..15095cb5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -5,6 +5,7 @@ plugins { // END: FlutterFire Configuration id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" + id 'com.huawei.agconnect' } def keystoreProperties = new Properties() @@ -35,7 +36,10 @@ android { targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName - manifestPlaceholders = [YANDEX_CLIENT_ID: yandexClientId] + manifestPlaceholders = [ + YANDEX_CLIENT_ID: yandexClientId, + applicationName: "io.flutter.app.FlutterApplication" + ] } signingConfigs { @@ -50,6 +54,9 @@ android { buildTypes { release { signingConfig signingConfigs.release + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { signingConfig signingConfigs.debug @@ -60,6 +67,14 @@ android { } } +dependencies { + implementation 'com.huawei.hms:push:6.13.0.300' + // Huawei Analytics dependency to satisfy com.huawei.hianalytics.* references during R8 + implementation 'com.huawei.hms:hianalytics:6.13.0.300' + // BouncyCastle provider required by Huawei secure utils used transitively by HMS + implementation 'org.bouncycastle:bcprov-jdk15to18:1.78.1' +} + flutter { source = "../.." } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 00000000..b657fa16 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +-keep class com.huawei.** { *; } +-dontwarn com.huawei.** + +-keep class org.bouncycastle.** { *; } +-dontwarn org.bouncycastle.** + +-keep class io.flutter.** { *; } +-dontwarn io.flutter.** + +-keep class kotlinx.coroutines.** { *; } +-dontwarn kotlinx.coroutines.** + +-keep class **.R$* { *; } +-keepclassmembers class ** { @androidx.annotation.Keep *; } +-keep @androidx.annotation.Keep class * { *; } + +-keep public class * extends android.app.Application +-keep public class * extends android.content.ContentProvider + +-keep class * implements android.os.Parcelable { *; } +-keepclassmembers enum * { *; } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0d239126..a99f3deb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -11,6 +11,16 @@ android:usesCleartextTraffic="true" android:name="${applicationName}" android:icon="@mipmap/ic_launcher"> + + + + + + + when (call.method) { + "getHuaweiToken" -> { + CoroutineScope(Dispatchers.IO).launch { + try { + // Try to read appId from agconnect-services.json + var appId: String? = try { + AGConnectServicesConfig.fromContext(applicationContext) + .getString("client/app_id") + } catch (_: Exception) { + null + } + + // Fallback to manifest meta-data if needed + if (appId.isNullOrBlank()) { + try { + val pm: PackageManager = applicationContext.packageManager + val ai: ApplicationInfo = pm.getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA) + val raw = ai.metaData?.getString("com.huawei.hms.client.appid") + if (!raw.isNullOrBlank()) { + appId = raw.removePrefix("appid=") + } + } catch (_: Exception) { + // ignore and handle below + } + } + + if (appId.isNullOrBlank()) { + runOnUiThread { + result.error("HMS_TOKEN_ERROR", "Missing Huawei appId. Ensure agconnect-services.json or manifest meta-data is configured.", null) + } + return@launch + } + val token = HmsInstanceId.getInstance(applicationContext) + .getToken(appId, "HCM") + runOnUiThread { + result.success(token) + } + } catch (e: Exception) { + runOnUiThread { + result.error("HMS_TOKEN_ERROR", e.message, null) + } + } + } + } + else -> result.notImplemented() + } + } + } } diff --git a/android/build.gradle b/android/build.gradle index 9db63328..03774aff 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,16 @@ +buildscript { + repositories { + google() + mavenCentral() + maven { url 'https://developer.huawei.com/repo/' } + gradlePluginPortal() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.12.0' + classpath 'com.huawei.agconnect:agcp:1.9.1.301' + } +} + allprojects { repositories { google() @@ -14,6 +27,32 @@ subprojects { project.evaluationDependsOn(":app") } +// Temporary safeguard: set missing namespaces for Android library modules based on their manifest package +subprojects { Project subproject -> + subproject.pluginManager.withPlugin('com.android.library') { + def androidExt = subproject.extensions.findByName('android') + if (androidExt != null) { + def hasNamespaceProp = androidExt.hasProperty('namespace') + def currentNamespace = hasNamespaceProp ? androidExt.namespace : null + if (hasNamespaceProp && !currentNamespace) { + def manifestFile = file("${subproject.projectDir}/src/main/AndroidManifest.xml") + if (manifestFile.exists()) { + try { + def manifest = new groovy.xml.XmlSlurper().parse(manifestFile) + def manifestPackage = manifest.@package?.text() + if (manifestPackage) { + androidExt.namespace = manifestPackage + subproject.logger.lifecycle("[namespace-fix] Set namespace for ${subproject.path} to '${manifestPackage}' from AndroidManifest.xml") + } + } catch (Throwable t) { + subproject.logger.warn("[namespace-fix] Failed to read manifest for ${subproject.path}: ${t.message}") + } + } + } + } + } +} + tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html index 4921e6e4..ce07f9e3 100644 --- a/android/build/reports/problems/problems-report.html +++ b/android/build/reports/problems/problems-report.html @@ -650,12 +650,12 @@ code + .copy-button { diff --git a/android/settings.gradle b/android/settings.gradle index e173b2c9..76846d0d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -12,6 +12,7 @@ pluginManagement { repositories { google() mavenCentral() + maven { url 'https://developer.huawei.com/repo/' } gradlePluginPortal() } } diff --git a/lib/core/firebase_api.dart b/lib/core/firebase_api.dart index 2ba89290..2d89bd43 100644 --- a/lib/core/firebase_api.dart +++ b/lib/core/firebase_api.dart @@ -1,28 +1,27 @@ import 'dart:developer'; import 'dart:io'; +import 'package:eatvio_app/core/utils/toasts.dart'; +import 'package:flutter/services.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/token_storage.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:huawei_push/huawei_push.dart'; +import 'package:flutter/services.dart' show MethodChannel, PlatformException; class FirebaseApi { final _firebaseMessaging = FirebaseMessaging.instance; Future getToken() async { - await _firebaseMessaging.requestPermission(); + String? token; - String? token = await _firebaseMessaging.getToken(); - - if (token == null && Platform.isAndroid) { - final isHuawei = await _isHuaweiDevice(); - if (isHuawei) { - token = await _getHuaweiPushToken(); - } + if (await _isHuaweiDevice()) { + token = await _getHuaweiPushTokenNative(); + } else { + await _firebaseMessaging.requestPermission(); + token = await _firebaseMessaging.getToken(); } - return token; } @@ -30,10 +29,11 @@ class FirebaseApi { if (await TokenStorage.haveTokenInStorage()) { try { final deviceInfo = DeviceInfoPlugin(); + final isHuawei = Platform.isAndroid ? await _isHuaweiDevice() : false; await sl().post( '/api/profile/notification/token/register', data: { - 'platform': Platform.isIOS ? 'ios' : 'android', + 'platform': Platform.isIOS ? 'ios' : (isHuawei ? 'huawei' : 'android'), 'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id, 'token': await getToken(), }, @@ -47,33 +47,27 @@ class FirebaseApi { Future _isHuaweiDevice() async { if (!Platform.isAndroid) return false; try { - final android = await DeviceInfoPlugin().androidInfo; - final brand = android.brand.toLowerCase(); - final manufacturer = android.manufacturer.toLowerCase(); - return brand.contains('huawei') || manufacturer.contains('huawei') || brand.contains('honor') || manufacturer.contains('honor'); + final info = await DeviceInfoPlugin().androidInfo; + final brand = info.brand.toLowerCase(); + final manufacturer = info.manufacturer.toLowerCase(); + return brand.contains('huawei') || brand.contains('honor') || manufacturer.contains('huawei') || manufacturer.contains('honor'); } catch (_) { return false; } } - Future _getHuaweiPushToken() async { + Future _getHuaweiPushTokenNative() async { try { - await Push.turnOnPush(); - - String? resolvedToken; - final sub = Push.getTokenStream.listen((t) => resolvedToken ??= t, onError: (_) {}); - - Push.getToken(''); - - final started = DateTime.now(); - while (resolvedToken == null && DateTime.now().difference(started).inSeconds < 5) { - await Future.delayed(const Duration(milliseconds: 100)); - } - - await sub.cancel(); - return resolvedToken; + const channel = MethodChannel('com.eatvioapp/hms_push'); + final String? token = await channel.invokeMethod('getHuaweiToken'); + return token; + } on PlatformException catch (e, st) { + showErrorToast('$e $st'); + log(e.toString(), stackTrace: st); + return null; } catch (e, st) { - log('Huawei Push token error: $e', stackTrace: st); + showErrorToast('$e $st'); + log(e.toString(), stackTrace: st); return null; } } diff --git a/pubspec.yaml b/pubspec.yaml index a62a13af..98a4429f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,7 +74,6 @@ dependencies: flutter_dotenv: ^5.2.1 uuid: ^4.5.1 appmetrica_plugin: ^3.3.0 - huawei_push: ^6.11.0+300 vk_id: ^1.0.3 flutter_login_yandex: ^0.0.5 From 46d0e31f5f974b09f957eaef545b39ead9ae6bc5 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 29 Sep 2025 13:29:39 +0300 Subject: [PATCH 3/3] fix(huawei-push): Issue-660 --- .gitignore | 4 +++- .../sessions/kotlin-compiler-10039299475187077818.salive | 0 android/app/build.gradle | 5 +++-- android/build.gradle | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 android/.kotlin/sessions/kotlin-compiler-10039299475187077818.salive diff --git a/.gitignore b/.gitignore index 06e89a5e..afeb23f8 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,6 @@ android/build/ ios/XCBuildData/ env/.env -.env \ No newline at end of file +.env +android/key.properties +app/upload-keystore.jks \ No newline at end of file diff --git a/android/.kotlin/sessions/kotlin-compiler-10039299475187077818.salive b/android/.kotlin/sessions/kotlin-compiler-10039299475187077818.salive new file mode 100644 index 00000000..e69de29b diff --git a/android/app/build.gradle b/android/app/build.gradle index 15095cb5..8fc22d6d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -68,9 +68,10 @@ android { } dependencies { - implementation 'com.huawei.hms:push:6.13.0.300' + implementation 'com.huawei.agconnect:agconnect-core:1.9.1.301' + implementation 'com.huawei.hms:push:6.12.0.300' // Huawei Analytics dependency to satisfy com.huawei.hianalytics.* references during R8 - implementation 'com.huawei.hms:hianalytics:6.13.0.300' + implementation 'com.huawei.hms:hianalytics:6.12.0.300' // BouncyCastle provider required by Huawei secure utils used transitively by HMS implementation 'org.bouncycastle:bcprov-jdk15to18:1.78.1' } diff --git a/android/build.gradle b/android/build.gradle index 03774aff..af06c34c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,7 @@ buildscript { repositories { google() mavenCentral() - maven { url 'https://developer.huawei.com/repo/' } + maven {url 'https://developer.huawei.com/repo/'} gradlePluginPortal() } dependencies { @@ -15,7 +15,7 @@ allprojects { repositories { google() mavenCentral() - maven { url 'https://developer.huawei.com/repo/' } + maven {url 'https://developer.huawei.com/repo/'} } }