forked from eatvio/eatvio_app
feat(auth): Issue-691
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user