From a0b29f42df50de20d6ab863bcb2c5bbcdf532b4e Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 26 Sep 2025 10:31:34 +0300 Subject: [PATCH] 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