forked from eatvio/eatvio_app
Merge pull request 'Issue-691, 660' (#709) from mso7nueh/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#709
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -17,4 +17,6 @@
|
||||
android/build/
|
||||
ios/XCBuildData/
|
||||
env/.env
|
||||
.env
|
||||
.env
|
||||
android/key.properties
|
||||
app/upload-keystore.jks
|
||||
1
android/app/agconnect-services.json
Normal file
1
android/app/agconnect-services.json
Normal file
@@ -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"}}]}
|
||||
@@ -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()
|
||||
@@ -13,6 +14,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 +36,10 @@ android {
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
manifestPlaceholders = [
|
||||
YANDEX_CLIENT_ID: yandexClientId,
|
||||
applicationName: "io.flutter.app.FlutterApplication"
|
||||
]
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -47,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
|
||||
@@ -57,6 +67,15 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
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.12.0.300'
|
||||
// BouncyCastle provider required by Huawei secure utils used transitively by HMS
|
||||
implementation 'org.bouncycastle:bcprov-jdk15to18:1.78.1'
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
21
android/app/proguard-rules.pro
vendored
Normal file
21
android/app/proguard-rules.pro
vendored
Normal file
@@ -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 * { *; }
|
||||
@@ -11,6 +11,16 @@
|
||||
android:usesCleartextTraffic="true"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<service
|
||||
android:name=".HmsPushService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<meta-data
|
||||
android:name="push_kit_auto_init_enabled"
|
||||
android:value="true" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.eatvioapp.app
|
||||
|
||||
import android.util.Log
|
||||
import com.huawei.hms.push.HmsMessageService
|
||||
import com.huawei.hms.push.RemoteMessage
|
||||
|
||||
class HmsPushService : HmsMessageService() {
|
||||
override fun onNewToken(token: String?) {
|
||||
super.onNewToken(token)
|
||||
Log.i("HMS", "onNewToken: ${token ?: "null"}")
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage?) {
|
||||
super.onMessageReceived(message)
|
||||
Log.i("HMS", "onMessageReceived: ${message?.data}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,71 @@
|
||||
package com.eatvioapp.app
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import com.huawei.agconnect.config.AGConnectServicesConfig
|
||||
import com.huawei.hms.aaid.HmsInstanceId
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
private val channelName = "com.eatvioapp/hms_push"
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
|
||||
.setMethodCallHandler { call, result ->
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
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()
|
||||
mavenCentral()
|
||||
maven { url 'https://developer.huawei.com/repo/' }
|
||||
maven {url 'https://developer.huawei.com/repo/'}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -12,6 +12,7 @@ pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://developer.huawei.com/repo/' }
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String?> 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<Dio>().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<bool> _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<String?> _getHuaweiPushToken() async {
|
||||
Future<String?> _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<void>.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
await sub.cancel();
|
||||
return resolvedToken;
|
||||
const channel = MethodChannel('com.eatvioapp/hms_push');
|
||||
final String? token = await channel.invokeMethod<String>('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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,51 +169,51 @@ class _AuthScreenState extends State<AuthScreen> {
|
||||
],
|
||||
),
|
||||
// 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<AuthCubit>().getTokenBySocial(provider: 'vk', accessToken: accessToken);
|
||||
if (isAuthenticated) {
|
||||
|
||||
@@ -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<String?> signInWithVk() async {
|
||||
// TODO: Integrate official VK ID SDK and return access token
|
||||
return null;
|
||||
static Future<String?> signInWithVk({required BuildContext context}) async {
|
||||
final result = await signInWithVkAndFetchProfile(context: context);
|
||||
return result?.accessToken;
|
||||
}
|
||||
|
||||
static Future<VkAuthResult?> 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<String?> 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<String?> 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<String?> 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<String?> _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<Map<String, String>?> _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 = <String, String>{}..addAll(uri.queryParameters);
|
||||
Navigator.of(context, rootNavigator: true).pop(params);
|
||||
return NavigationDecision.prevent;
|
||||
}
|
||||
return NavigationDecision.navigate;
|
||||
},
|
||||
),
|
||||
)
|
||||
..loadRequest(Uri.parse(initialUrl));
|
||||
|
||||
return showDialog<Map<String, String>>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) {
|
||||
return Dialog(
|
||||
insetPadding: const EdgeInsets.all(16),
|
||||
child: SizedBox(height: 560, child: WebViewWidget(controller: controller)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -74,7 +74,8 @@ 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
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.7.0
|
||||
|
||||
Reference in New Issue
Block a user