forked from eatvio/eatvio_app
Compare commits
4 Commits
main
...
issue-1089
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
508c321913 | ||
|
|
4a5c67632e | ||
|
|
5e6e114d5d | ||
|
|
e9cfcb9637 |
@@ -437,73 +437,21 @@ import flutter_foreground_task
|
|||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
|
|
||||||
private func bringAppToForeground() {
|
/// The answer callback runs before the scene is visible. A custom-scheme
|
||||||
DispatchQueue.main.async {
|
/// open from here is an untrusted user action and SpringBoard denies it.
|
||||||
for scene in UIApplication.shared.connectedScenes {
|
/// CallKit foregrounds a video call itself after `fulfill`. Flutter reads
|
||||||
guard let windowScene = scene as? UIWindowScene else { continue }
|
/// the stored payload once that scene is actually active.
|
||||||
for window in windowScene.windows {
|
func deliverStoredAcceptedCall() {
|
||||||
window.makeKeyAndVisible()
|
guard voipDiagnosticsChannel != nil else { return }
|
||||||
}
|
guard let payload = UserDefaults.standard.dictionary(forKey: pendingAcceptedVoipCallKey) else {
|
||||||
}
|
|
||||||
self.window?.makeKeyAndVisible()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func openAppForAcceptedCall(_ call: Call) {
|
|
||||||
guard let conversationUuid = conversationUuid(fromCall: call), !conversationUuid.isEmpty else {
|
|
||||||
recordVoipDiagnostic(
|
|
||||||
"accepted_call_missing_conversation_uuid",
|
|
||||||
data: ["callKitId": call.data.uuid]
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let acceptedAt = (payload[pendingAcceptedAtKey] as? NSNumber)?.doubleValue
|
||||||
var components = URLComponents()
|
if let acceptedAt, Date().timeIntervalSince1970 - acceptedAt > pendingAcceptedCallTtl {
|
||||||
components.scheme = appUrlScheme
|
UserDefaults.standard.removeObject(forKey: pendingAcceptedVoipCallKey)
|
||||||
components.host = "video-call"
|
|
||||||
var queryItems = [
|
|
||||||
URLQueryItem(name: "callKitId", value: call.data.uuid),
|
|
||||||
URLQueryItem(name: "conversationUuid", value: conversationUuid)
|
|
||||||
]
|
|
||||||
let serverCallId = extraServerCallId(call.data.extra)
|
|
||||||
if !serverCallId.isEmpty {
|
|
||||||
queryItems.append(URLQueryItem(name: "call_id", value: serverCallId))
|
|
||||||
}
|
|
||||||
components.queryItems = queryItems
|
|
||||||
|
|
||||||
guard let url = components.url else {
|
|
||||||
recordVoipDiagnostic(
|
|
||||||
"accepted_call_url_build_failed",
|
|
||||||
data: [
|
|
||||||
"callKitId": call.data.uuid,
|
|
||||||
"conversationUuid": conversationUuid,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
voipDiagnosticsChannel?.invokeMethod("acceptedVoipCall", arguments: payload)
|
||||||
recordVoipDiagnostic(
|
|
||||||
"accepted_call_open_app_requested",
|
|
||||||
data: [
|
|
||||||
"callKitId": call.data.uuid,
|
|
||||||
"conversationUuid": conversationUuid,
|
|
||||||
"url": url.absoluteString,
|
|
||||||
"appState": UIApplication.shared.applicationState.rawValue,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
bringAppToForeground()
|
|
||||||
|
|
||||||
UIApplication.shared.open(url, options: [:]) { [weak self] success in
|
|
||||||
self?.recordVoipDiagnostic(
|
|
||||||
success ? "accepted_call_open_app_succeeded" : "accepted_call_open_app_failed",
|
|
||||||
data: [
|
|
||||||
"callKitId": call.data.uuid,
|
|
||||||
"conversationUuid": conversationUuid,
|
|
||||||
"url": url.absoluteString,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sanitizedDiagnosticValue(_ value: Any?) -> Any {
|
private func sanitizedDiagnosticValue(_ value: Any?) -> Any {
|
||||||
@@ -1177,6 +1125,8 @@ import flutter_foreground_task
|
|||||||
// activation and can start I/O before didActivate.
|
// activation and can start I/O before didActivate.
|
||||||
prepareAvAudioSessionCategory()
|
prepareAvAudioSessionCategory()
|
||||||
storePendingAcceptedCall(call)
|
storePendingAcceptedCall(call)
|
||||||
|
let extra = call.data.extra as? [String: Any] ?? [:]
|
||||||
|
VideoCallDeclineClient.sendAccept(extra: extra, handle: call.data.handle)
|
||||||
recordVoipDiagnostic(
|
recordVoipDiagnostic(
|
||||||
"callkit_accept_native",
|
"callkit_accept_native",
|
||||||
data: [
|
data: [
|
||||||
@@ -1185,11 +1135,7 @@ import flutter_foreground_task
|
|||||||
"data": call.data.toJSON(),
|
"data": call.data.toJSON(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
voipDiagnosticsChannel?.invokeMethod("acceptedVoipCall", arguments: call.data.toJSON())
|
deliverStoredAcceptedCall()
|
||||||
DispatchQueue.main.async { [weak self] in
|
|
||||||
self?.bringAppToForeground()
|
|
||||||
self?.openAppForAcceptedCall(call)
|
|
||||||
}
|
|
||||||
action.fulfill()
|
action.fulfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ class SceneDelegate: FlutterSceneDelegate {
|
|||||||
override func sceneDidBecomeActive(_ scene: UIScene) {
|
override func sceneDidBecomeActive(_ scene: UIScene) {
|
||||||
super.sceneDidBecomeActive(scene)
|
super.sceneDidBecomeActive(scene)
|
||||||
registerCallChannels()
|
registerCallChannels()
|
||||||
|
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
appDelegate.deliverStoredAcceptedCall()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
override func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||||
|
|||||||
@@ -8,25 +8,37 @@ enum VideoCallDeclineClient {
|
|||||||
/// `POST /video-call/decline` from the CallKit callback. The process is
|
/// `POST /video-call/decline` from the CallKit callback. The process is
|
||||||
/// awake here even when the user never opens the app afterwards.
|
/// awake here even when the user never opens the app afterwards.
|
||||||
static func send(extra: [String: Any], handle: String) {
|
static func send(extra: [String: Any], handle: String) {
|
||||||
|
post(action: "decline", extra: extra, handle: handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `POST /video-call/accept` from the CallKit answer callback. Flutter may
|
||||||
|
/// still be frozen on the splash snapshot, so the other device must be
|
||||||
|
/// told from here. A later Dart retry with the same hw_id is idempotent.
|
||||||
|
static func sendAccept(extra: [String: Any], handle: String) {
|
||||||
|
post(action: "accept", extra: extra, handle: handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func post(action: String, extra: [String: Any], handle: String) {
|
||||||
let fields = declineFields(extra: extra, handle: handle)
|
let fields = declineFields(extra: extra, handle: handle)
|
||||||
guard let request = makeRequest(
|
guard let request = makeRequest(
|
||||||
baseUrl: prefsString("apiBaseUrl") ?? prefsString("customBaseUrl"),
|
baseUrl: prefsString("apiBaseUrl") ?? prefsString("customBaseUrl"),
|
||||||
token: prefsString("token"),
|
token: prefsString("token"),
|
||||||
hwId: UIDevice.current.identifierForVendor?.uuidString,
|
hwId: UIDevice.current.identifierForVendor?.uuidString,
|
||||||
fields: fields
|
fields: fields,
|
||||||
|
action: action
|
||||||
) else {
|
) else {
|
||||||
NSLog(
|
NSLog(
|
||||||
"[EatvioVoIP] decline POST skipped conversation=\(fields.conversationUuid) ended=\(fields.endedPush)"
|
"[EatvioVoIP] \(action) POST skipped conversation=\(fields.conversationUuid) ended=\(fields.endedPush)"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let task = EatvioBackgroundTask()
|
let task = EatvioBackgroundTask()
|
||||||
task.begin(name: "video-call-decline")
|
task.begin(name: "video-call-\(action)")
|
||||||
let urlTask = URLSession.shared.dataTask(with: request) { _, response, error in
|
let urlTask = URLSession.shared.dataTask(with: request) { _, response, error in
|
||||||
let code = (response as? HTTPURLResponse)?.statusCode ?? -1
|
let code = (response as? HTTPURLResponse)?.statusCode ?? -1
|
||||||
NSLog(
|
NSLog(
|
||||||
"[EatvioVoIP] decline POST status=\(code) conversation=\(fields.conversationUuid) error=\(error?.localizedDescription ?? "-")"
|
"[EatvioVoIP] \(action) POST status=\(code) conversation=\(fields.conversationUuid) error=\(error?.localizedDescription ?? "-")"
|
||||||
)
|
)
|
||||||
task.end()
|
task.end()
|
||||||
}
|
}
|
||||||
@@ -37,7 +49,8 @@ enum VideoCallDeclineClient {
|
|||||||
baseUrl: String?,
|
baseUrl: String?,
|
||||||
token: String?,
|
token: String?,
|
||||||
hwId: String?,
|
hwId: String?,
|
||||||
fields: VideoCallDeclineFields
|
fields: VideoCallDeclineFields,
|
||||||
|
action: String = "decline"
|
||||||
) -> URLRequest? {
|
) -> URLRequest? {
|
||||||
if fields.endedPush { return nil }
|
if fields.endedPush { return nil }
|
||||||
let conversation = fields.conversationUuid.trimmingCharacters(in: .whitespacesAndNewlines)
|
let conversation = fields.conversationUuid.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
@@ -50,8 +63,9 @@ enum VideoCallDeclineClient {
|
|||||||
guard !conversation.isEmpty, !authToken.isEmpty else { return nil }
|
guard !conversation.isEmpty, !authToken.isEmpty else { return nil }
|
||||||
guard deviceId.count >= 8, deviceId.count <= 64 else { return nil }
|
guard deviceId.count >= 8, deviceId.count <= 64 else { return nil }
|
||||||
guard root.hasPrefix("http://") || root.hasPrefix("https://") else { return nil }
|
guard root.hasPrefix("http://") || root.hasPrefix("https://") else { return nil }
|
||||||
|
guard action == "accept" || action == "decline" else { return nil }
|
||||||
guard let encoded = conversation.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed),
|
guard let encoded = conversation.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed),
|
||||||
let url = URL(string: "\(root)/api/v2/chat/\(encoded)/video-call/decline")
|
let url = URL(string: "\(root)/api/v2/chat/\(encoded)/video-call/\(action)")
|
||||||
else { return nil }
|
else { return nil }
|
||||||
|
|
||||||
var body: [String: String] = ["hw_id": deviceId]
|
var body: [String: String] = ["hw_id": deviceId]
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
|
|||||||
import 'package:eatvio_app/features/payment/presentation/cubit/consultation_payment_cubit/consultation_payment_cubit.dart';
|
import 'package:eatvio_app/features/payment/presentation/cubit/consultation_payment_cubit/consultation_payment_cubit.dart';
|
||||||
import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart';
|
import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart';
|
||||||
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans;
|
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart'
|
||||||
|
as profile_plans;
|
||||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
||||||
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
|
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
|
||||||
@@ -70,16 +71,26 @@ class CubitRegistrations {
|
|||||||
/// Регистрация Cubit'ов как синглтонов
|
/// Регистрация Cubit'ов как синглтонов
|
||||||
static void _registerSingletonCubits() {
|
static void _registerSingletonCubits() {
|
||||||
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
|
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
|
||||||
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit(wsManager: sl(), notificationsRepository: sl()));
|
sl.registerLazySingleton<NotificationsCubit>(
|
||||||
sl.registerLazySingleton<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
|
() => NotificationsCubit(wsManager: sl(), notificationsRepository: sl()),
|
||||||
sl.registerLazySingleton<ChatsCubit>(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl()));
|
);
|
||||||
sl.registerLazySingleton<InternetConnectionCubit>(() => InternetConnectionCubit());
|
sl.registerLazySingleton<ProfileAvatarCubit>(
|
||||||
sl.registerLazySingleton<VideoCallManager>(
|
() => ProfileAvatarCubit(profileRepository: sl()),
|
||||||
() => VideoCallManager(
|
);
|
||||||
videoCallRepository: sl(),
|
sl.registerLazySingleton<ChatsCubit>(
|
||||||
|
() => ChatsCubit(
|
||||||
|
chatRepository: sl(),
|
||||||
|
wsManager: sl(),
|
||||||
profileRepository: sl(),
|
profileRepository: sl(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
sl.registerLazySingleton<InternetConnectionCubit>(
|
||||||
|
() => InternetConnectionCubit(),
|
||||||
|
);
|
||||||
|
sl.registerLazySingleton<VideoCallManager>(
|
||||||
|
() =>
|
||||||
|
VideoCallManager(videoCallRepository: sl(), profileRepository: sl()),
|
||||||
|
);
|
||||||
sl.registerLazySingleton<VideoCallSessionCubit>(
|
sl.registerLazySingleton<VideoCallSessionCubit>(
|
||||||
() => VideoCallSessionCubit(
|
() => VideoCallSessionCubit(
|
||||||
videoCallManager: sl(),
|
videoCallManager: sl(),
|
||||||
@@ -91,48 +102,130 @@ class CubitRegistrations {
|
|||||||
/// Регистрация Cubit'ов как фабрик
|
/// Регистрация Cubit'ов как фабрик
|
||||||
static void _registerFactoryCubits() {
|
static void _registerFactoryCubits() {
|
||||||
// Auth related cubits
|
// Auth related cubits
|
||||||
sl.registerFactory<AuthCubit>(() => AuthCubit(authRepository: sl(), firebaseApi: sl(), prefs: sl(), profileRepository: sl()));
|
sl.registerFactory<AuthCubit>(
|
||||||
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl(), prefs: sl()));
|
() => AuthCubit(
|
||||||
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
|
authRepository: sl(),
|
||||||
sl.registerFactory<VerifyEmailCubit>(() => VerifyEmailCubit(profileRepository: sl(), prefs: sl()));
|
firebaseApi: sl(),
|
||||||
sl.registerFactory<OAuthCubit>(() => OAuthCubit(authRepository: sl(), prefs: sl()));
|
prefs: sl(),
|
||||||
sl.registerFactory<RegistrationCubitV2>(() => RegistrationCubitV2(registrationRepository: sl()));
|
profileRepository: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
sl.registerFactory<RegistrationCubit>(
|
||||||
|
() => RegistrationCubit(registrationRepository: sl(), prefs: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<ResetPasswordCubit>(
|
||||||
|
() => ResetPasswordCubit(resetPasswordRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<VerifyEmailCubit>(
|
||||||
|
() => VerifyEmailCubit(profileRepository: sl(), prefs: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<OAuthCubit>(
|
||||||
|
() => OAuthCubit(authRepository: sl(), firebaseApi: sl(), prefs: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<RegistrationCubitV2>(
|
||||||
|
() =>
|
||||||
|
RegistrationCubitV2(registrationRepository: sl(), firebaseApi: sl()),
|
||||||
|
);
|
||||||
// Feed related cubits
|
// Feed related cubits
|
||||||
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
|
sl.registerFactory<RecipesCubit>(
|
||||||
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
() => RecipesCubit(
|
||||||
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
|
feedRepository: sl(),
|
||||||
sl.registerFactory<ConsultantsCubit>(() => ConsultantsCubit(feedRepository: sl()));
|
recipeRepository: sl(),
|
||||||
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl(), commentsRepository: sl()));
|
aboutRecipeRepository: sl(),
|
||||||
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
|
),
|
||||||
|
);
|
||||||
|
sl.registerFactory<DietsCubit>(
|
||||||
|
() => DietsCubit(feedRepository: sl(), dietsRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<PlansCubit>(
|
||||||
|
() => PlansCubit(feedRepository: sl(), planRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<ConsultantsCubit>(
|
||||||
|
() => ConsultantsCubit(feedRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<CommentsCubit>(
|
||||||
|
() => CommentsCubit(feedRepository: sl(), commentsRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<FeedProfileCubit>(
|
||||||
|
() => FeedProfileCubit(profileRepository: sl()),
|
||||||
|
);
|
||||||
|
|
||||||
// Chat related cubits
|
// Chat related cubits
|
||||||
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl()));
|
sl.registerFactory<ChatRoomCubit>(
|
||||||
|
() => ChatRoomCubit(
|
||||||
|
cardsRepository: sl(),
|
||||||
|
chatRepository: sl(),
|
||||||
|
wsManager: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Profile related cubits
|
// Profile related cubits
|
||||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl(), consultantRepository: sl()));
|
sl.registerFactory<ProfileCubit>(
|
||||||
sl.registerFactory<profile_plans.PlansCubit>(() => profile_plans.PlansCubit(plansRepository: sl()));
|
() => ProfileCubit(
|
||||||
|
profileRepository: sl(),
|
||||||
|
chatRepository: sl(),
|
||||||
|
consultantRepository: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
sl.registerFactory<profile_plans.PlansCubit>(
|
||||||
|
() => profile_plans.PlansCubit(plansRepository: sl()),
|
||||||
|
);
|
||||||
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
||||||
|
|
||||||
// Content creation cubits
|
// Content creation cubits
|
||||||
sl.registerFactory<CreateDietCubit>(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl()));
|
sl.registerFactory<CreateDietCubit>(
|
||||||
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit(createPlanRepository: sl()));
|
() => CreateDietCubit(
|
||||||
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl(), glossaryRepository: sl()));
|
profileRepository: sl(),
|
||||||
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
|
createDietRepository: sl(),
|
||||||
|
glossaryRepository: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
sl.registerFactory<CreatePlanCubit>(
|
||||||
|
() => CreatePlanCubit(createPlanRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<CreateRecipeCubit>(
|
||||||
|
() => CreateRecipeCubit(recipeRepository: sl(), glossaryRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<BlogPostCreateCubit>(
|
||||||
|
() => BlogPostCreateCubit(blogPostRepository: sl()),
|
||||||
|
);
|
||||||
|
|
||||||
// Content viewing cubits
|
// Content viewing cubits
|
||||||
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
sl.registerFactory<AboutDietCubit>(
|
||||||
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl(), profileRepository: sl(), dietRepository: sl()));
|
() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()),
|
||||||
sl.registerFactory<AboutRecipeCubit>(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()));
|
);
|
||||||
|
sl.registerFactory<AboutPlanCubit>(
|
||||||
|
() => AboutPlanCubit(
|
||||||
|
planRepository: sl(),
|
||||||
|
profileRepository: sl(),
|
||||||
|
dietRepository: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
sl.registerFactory<AboutRecipeCubit>(
|
||||||
|
() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()),
|
||||||
|
);
|
||||||
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
||||||
|
|
||||||
// User content cubits
|
// User content cubits
|
||||||
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
|
sl.registerFactory<MyDietsCubit>(
|
||||||
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()),
|
||||||
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl(), recipeRepository: sl()));
|
);
|
||||||
|
sl.registerFactory<MyPlansCubit>(
|
||||||
|
() => MyPlansCubit(myPlansRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<MyRecipesCubit>(
|
||||||
|
() => MyRecipesCubit(
|
||||||
|
myRecipesRepository: sl(),
|
||||||
|
aboutRecipeRepository: sl(),
|
||||||
|
recipeRepository: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Other feature cubits
|
// Other feature cubits
|
||||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
sl.registerFactory<CalendarCubit>(
|
||||||
|
() => CalendarCubit(calendarRepository: sl()),
|
||||||
|
);
|
||||||
sl.registerFactory<DiaryCubit>(
|
sl.registerFactory<DiaryCubit>(
|
||||||
() => DiaryCubit(
|
() => DiaryCubit(
|
||||||
chatRepository: sl(),
|
chatRepository: sl(),
|
||||||
@@ -144,28 +237,73 @@ class CubitRegistrations {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
sl.registerFactory<SettingsCubit>(
|
sl.registerFactory<SettingsCubit>(
|
||||||
() => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl(), consultationsRepository: sl()),
|
() => SettingsCubit(
|
||||||
|
profileRepository: sl(),
|
||||||
|
settingsRepository: sl(),
|
||||||
|
consultantRepository: sl(),
|
||||||
|
consultationsRepository: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
sl.registerFactory<RegisterConsultantCubit>(
|
||||||
|
() => RegisterConsultantCubit(consultantRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<ComplainingCubit>(
|
||||||
|
() => ComplainingCubit(repository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<UserCardsCubit>(
|
||||||
|
() => UserCardsCubit(cardsRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<OrderInfoCubit>(
|
||||||
|
() => OrderInfoCubit(chatRepository: sl(), profileRepository: sl()),
|
||||||
);
|
);
|
||||||
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
|
|
||||||
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
|
|
||||||
sl.registerFactory<UserCardsCubit>(() => UserCardsCubit(cardsRepository: sl()));
|
|
||||||
sl.registerFactory<OrderInfoCubit>(() => OrderInfoCubit(chatRepository: sl(), profileRepository: sl()));
|
|
||||||
sl.registerFactory<ConsultationPaymentCubit>(
|
sl.registerFactory<ConsultationPaymentCubit>(
|
||||||
() => ConsultationPaymentCubit(paymentRepository: sl(), sbpRepository: sl()),
|
() => ConsultationPaymentCubit(
|
||||||
|
paymentRepository: sl(),
|
||||||
|
sbpRepository: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
sl.registerFactory<ConsultationsCubit>(
|
||||||
|
() => ConsultationsCubit(consultationsRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<ConsultantCommentsCubit>(
|
||||||
|
() => ConsultantCommentsCubit(diaryRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<CancelPlanCubit>(
|
||||||
|
() => CancelPlanCubit(cancelPlanRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<ConsultationsArchiveCubit>(
|
||||||
|
() => ConsultationsArchiveCubit(consultationsRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<ConsultantFinancesCubit>(
|
||||||
|
() => ConsultantFinancesCubit(consultationsRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<UserPurchasesCubit>(
|
||||||
|
() => UserPurchasesCubit(consultationsRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<BlockedUsersCubit>(
|
||||||
|
() => BlockedUsersCubit(blockedUsersRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<NotificationSettingsCubit>(
|
||||||
|
() => NotificationSettingsCubit(notificationSettingsRepository: sl()),
|
||||||
|
);
|
||||||
|
sl.registerFactory<ConsultantRatingCubit>(
|
||||||
|
() => ConsultantRatingCubit(
|
||||||
|
feedRepository: sl(),
|
||||||
|
repository: sl(),
|
||||||
|
profileRepository: sl(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl()));
|
|
||||||
sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl()));
|
|
||||||
sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl()));
|
|
||||||
sl.registerFactory<ConsultationsArchiveCubit>(() => ConsultationsArchiveCubit(consultationsRepository: sl()));
|
|
||||||
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
|
|
||||||
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
|
|
||||||
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
|
|
||||||
sl.registerFactory<NotificationSettingsCubit>(() => NotificationSettingsCubit(notificationSettingsRepository: sl()));
|
|
||||||
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(feedRepository: sl(), repository: sl(), profileRepository: sl()));
|
|
||||||
sl.registerFactory<ViewsCubit>(() => ViewsCubit(viewsRepository: sl()));
|
sl.registerFactory<ViewsCubit>(() => ViewsCubit(viewsRepository: sl()));
|
||||||
sl.registerFactory<UserDetailsCubit>(() => UserDetailsCubit(profileRepository: sl()));
|
sl.registerFactory<UserDetailsCubit>(
|
||||||
|
() => UserDetailsCubit(profileRepository: sl()),
|
||||||
|
);
|
||||||
sl.registerFactory<MenuCubit>(() => MenuCubit(sl()));
|
sl.registerFactory<MenuCubit>(() => MenuCubit(sl()));
|
||||||
sl.registerFactory<MarketingCubit>(() => MarketingCubit(sl()));
|
sl.registerFactory<MarketingCubit>(() => MarketingCubit(sl()));
|
||||||
sl.registerFactory<PersonalDietSuggestionCubit>(() => PersonalDietSuggestionCubit(dietRepository: sl(), personalDietSuggestionService: sl()));
|
sl.registerFactory<PersonalDietSuggestionCubit>(
|
||||||
|
() => PersonalDietSuggestionCubit(
|
||||||
|
dietRepository: sl(),
|
||||||
|
personalDietSuggestionService: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/core/firebase_api.dart';
|
||||||
import 'package:eatvio_app/core/mixins/registration_date_cache_mixin.dart';
|
import 'package:eatvio_app/core/mixins/registration_date_cache_mixin.dart';
|
||||||
import 'package:eatvio_app/core/prefs.dart';
|
import 'package:eatvio_app/core/prefs.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
@@ -11,12 +12,22 @@ import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
|||||||
|
|
||||||
class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
||||||
final AuthRepository authRepository;
|
final AuthRepository authRepository;
|
||||||
|
final FirebaseApi firebaseApi;
|
||||||
|
|
||||||
OAuthCubit({required this.authRepository, required Prefs prefs}) : super(const OAuthState()) {
|
OAuthCubit({
|
||||||
|
required this.authRepository,
|
||||||
|
required this.firebaseApi,
|
||||||
|
required Prefs prefs,
|
||||||
|
}) : super(const OAuthState()) {
|
||||||
this.prefs = prefs;
|
this.prefs = prefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> signInViaSocialMedia({required String provider, required String accessToken, required String refreshToken, required String clientId}) async {
|
Future<void> signInViaSocialMedia({
|
||||||
|
required String provider,
|
||||||
|
required String accessToken,
|
||||||
|
required String refreshToken,
|
||||||
|
required String clientId,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
emit(const OAuthState.loading());
|
emit(const OAuthState.loading());
|
||||||
|
|
||||||
@@ -28,7 +39,7 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
);
|
);
|
||||||
|
|
||||||
log('OAuthCubit: Got token model: ${tokenModel.token}');
|
log('OAuthCubit: Got token model: ${tokenModel.token}');
|
||||||
TokenStorage.saveTokenInStorage(tokenModel.token);
|
await TokenStorage.saveTokenInStorage(tokenModel.token);
|
||||||
emit(OAuthState.loaded(token: tokenModel.token));
|
emit(OAuthState.loaded(token: tokenModel.token));
|
||||||
} on DioException catch (e, st) {
|
} on DioException catch (e, st) {
|
||||||
await reportFrom(e, st, this);
|
await reportFrom(e, st, this);
|
||||||
@@ -45,7 +56,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
if (errorStr.contains('Некорректный формат email')) {
|
if (errorStr.contains('Некорректный формат email')) {
|
||||||
errorsList.add(RegistrationValidationErrors.incorrectEmailFormat);
|
errorsList.add(RegistrationValidationErrors.incorrectEmailFormat);
|
||||||
}
|
}
|
||||||
if (errorStr.contains('почта уже существует') || errorStr.contains('Эл. почта уже существует')) {
|
if (errorStr.contains('почта уже существует') ||
|
||||||
|
errorStr.contains('Эл. почта уже существует')) {
|
||||||
errorsList.add(RegistrationValidationErrors.emailExist);
|
errorsList.add(RegistrationValidationErrors.emailExist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,14 +65,20 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
if (phoneErrors != null && phoneErrors.isNotEmpty) {
|
if (phoneErrors != null && phoneErrors.isNotEmpty) {
|
||||||
for (final error in phoneErrors) {
|
for (final error in phoneErrors) {
|
||||||
final errorStr = error.toString();
|
final errorStr = error.toString();
|
||||||
if (errorStr.contains('phone уже существует') || errorStr.contains('телефон уже существует')) {
|
if (errorStr.contains('phone уже существует') ||
|
||||||
|
errorStr.contains('телефон уже существует')) {
|
||||||
errorsList.add(RegistrationValidationErrors.phoneExist);
|
errorsList.add(RegistrationValidationErrors.phoneExist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorsList.isNotEmpty) {
|
if (errorsList.isNotEmpty) {
|
||||||
emit(OAuthState.error(errorMessage: 'validation_error', registrationValidationErrors: errorsList));
|
emit(
|
||||||
|
OAuthState.error(
|
||||||
|
errorMessage: 'validation_error',
|
||||||
|
registrationValidationErrors: errorsList,
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
emit(OAuthState.error(errorMessage: e.toString()));
|
emit(OAuthState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
@@ -103,7 +121,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
height: height,
|
height: height,
|
||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
);
|
);
|
||||||
TokenStorage.saveTokenInStorage(token);
|
await TokenStorage.saveTokenInStorage(token);
|
||||||
|
await firebaseApi.registerPushToken();
|
||||||
cacheRegistrationDate(DateTime.now());
|
cacheRegistrationDate(DateTime.now());
|
||||||
emit(OAuthState.loaded(token: token));
|
emit(OAuthState.loaded(token: token));
|
||||||
} on DioException catch (e, st) {
|
} on DioException catch (e, st) {
|
||||||
@@ -120,7 +139,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
if (errorStr.contains('Некорректный формат email')) {
|
if (errorStr.contains('Некорректный формат email')) {
|
||||||
errorsList.add(RegistrationValidationErrors.incorrectEmailFormat);
|
errorsList.add(RegistrationValidationErrors.incorrectEmailFormat);
|
||||||
}
|
}
|
||||||
if (errorStr.contains('почта уже существует') || errorStr.contains('Эл. почта уже существует')) {
|
if (errorStr.contains('почта уже существует') ||
|
||||||
|
errorStr.contains('Эл. почта уже существует')) {
|
||||||
errorsList.add(RegistrationValidationErrors.emailExist);
|
errorsList.add(RegistrationValidationErrors.emailExist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,14 +148,17 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
if (phoneErrors != null && phoneErrors.isNotEmpty) {
|
if (phoneErrors != null && phoneErrors.isNotEmpty) {
|
||||||
for (final error in phoneErrors) {
|
for (final error in phoneErrors) {
|
||||||
final errorStr = error.toString();
|
final errorStr = error.toString();
|
||||||
if (errorStr.contains('phone уже существует') || errorStr.contains('телефон уже существует')) {
|
if (errorStr.contains('phone уже существует') ||
|
||||||
|
errorStr.contains('телефон уже существует')) {
|
||||||
errorsList.add(RegistrationValidationErrors.phoneExist);
|
errorsList.add(RegistrationValidationErrors.phoneExist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dateOfBirth != null && dateOfBirth.isNotEmpty) {
|
if (dateOfBirth != null && dateOfBirth.isNotEmpty) {
|
||||||
if (dateOfBirth.contains('Некорректный формат даты рождения')) {
|
if (dateOfBirth.contains('Некорректный формат даты рождения')) {
|
||||||
errorsList.add(RegistrationValidationErrors.incorrectDateOfBirthFormat);
|
errorsList.add(
|
||||||
|
RegistrationValidationErrors.incorrectDateOfBirthFormat,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (errorsList.isNotEmpty) {
|
if (errorsList.isNotEmpty) {
|
||||||
@@ -146,13 +169,23 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<OAuthVerificationRequestResponse?> requestOAuthVerification({String? email, String? phone}) async {
|
Future<OAuthVerificationRequestResponse?> requestOAuthVerification({
|
||||||
|
String? email,
|
||||||
|
String? phone,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
emit(const OAuthState.loading());
|
emit(const OAuthState.loading());
|
||||||
log('OAuthCubit: Requesting verification code for email: $email, phone: $phone');
|
log(
|
||||||
final response = await authRepository.requestOAuthVerification(email: email, phone: phone);
|
'OAuthCubit: Requesting verification code for email: $email, phone: $phone',
|
||||||
|
);
|
||||||
|
final response = await authRepository.requestOAuthVerification(
|
||||||
|
email: email,
|
||||||
|
phone: phone,
|
||||||
|
);
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
log('OAuthCubit: Verification code requested successfully, masked email: ${response.email}');
|
log(
|
||||||
|
'OAuthCubit: Verification code requested successfully, masked email: ${response.email}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Сбрасываем состояние после успешного запроса кода
|
// Сбрасываем состояние после успешного запроса кода
|
||||||
emit(const OAuthState());
|
emit(const OAuthState());
|
||||||
@@ -184,7 +217,9 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
emit(const OAuthState.loading());
|
emit(const OAuthState.loading());
|
||||||
log('OAuthCubit: Confirming OAuth verification - provider: $provider, verificationType: $verificationType, code: $code');
|
log(
|
||||||
|
'OAuthCubit: Confirming OAuth verification - provider: $provider, verificationType: $verificationType, code: $code',
|
||||||
|
);
|
||||||
final response = await authRepository.confirmOAuthVerification(
|
final response = await authRepository.confirmOAuthVerification(
|
||||||
provider: provider,
|
provider: provider,
|
||||||
accessToken: accessToken,
|
accessToken: accessToken,
|
||||||
@@ -202,8 +237,11 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
weight: weight,
|
weight: weight,
|
||||||
height: height,
|
height: height,
|
||||||
);
|
);
|
||||||
log('OAuthCubit: OAuth verification confirmed successfully, token received');
|
log(
|
||||||
TokenStorage.saveTokenInStorage(response.token);
|
'OAuthCubit: OAuth verification confirmed successfully, token received',
|
||||||
|
);
|
||||||
|
await TokenStorage.saveTokenInStorage(response.token);
|
||||||
|
await firebaseApi.registerPushToken();
|
||||||
emit(OAuthState.loaded(token: response.token));
|
emit(OAuthState.loaded(token: response.token));
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
await reportFrom(e, st, this);
|
await reportFrom(e, st, this);
|
||||||
@@ -212,4 +250,3 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/core/firebase_api.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
import 'package:eatvio_app/core/utils/validation_utils.dart';
|
import 'package:eatvio_app/core/utils/validation_utils.dart';
|
||||||
@@ -13,7 +14,11 @@ import 'package:gpassword/gpassword.dart';
|
|||||||
|
|
||||||
class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
|
class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
|
||||||
final RegistrationRepositoryV2 registrationRepository;
|
final RegistrationRepositoryV2 registrationRepository;
|
||||||
RegistrationCubitV2({required this.registrationRepository}) : super(const RegistrationStateV2());
|
final FirebaseApi firebaseApi;
|
||||||
|
RegistrationCubitV2({
|
||||||
|
required this.registrationRepository,
|
||||||
|
required this.firebaseApi,
|
||||||
|
}) : super(const RegistrationStateV2());
|
||||||
|
|
||||||
void setFirstName(String firstName) {
|
void setFirstName(String firstName) {
|
||||||
emit(state.copyWith(firstName: firstName));
|
emit(state.copyWith(firstName: firstName));
|
||||||
@@ -194,9 +199,12 @@ class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
|
|||||||
bool didApiComplainOnValidation = false;
|
bool didApiComplainOnValidation = false;
|
||||||
String errorMessage = '';
|
String errorMessage = '';
|
||||||
try {
|
try {
|
||||||
final token = await registrationRepository.register(registrationModel: registrationModel);
|
final token = await registrationRepository.register(
|
||||||
|
registrationModel: registrationModel,
|
||||||
|
);
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
TokenStorage.saveTokenInStorage(token);
|
await TokenStorage.saveTokenInStorage(token);
|
||||||
|
await firebaseApi.registerPushToken();
|
||||||
emit(state.copyWith(registrationError: null));
|
emit(state.copyWith(registrationError: null));
|
||||||
return (true, false);
|
return (true, false);
|
||||||
}
|
}
|
||||||
@@ -214,11 +222,21 @@ class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
|
|||||||
for (var error in emailErrors) {
|
for (var error in emailErrors) {
|
||||||
allErrors.add(error.toString());
|
allErrors.add(error.toString());
|
||||||
if (error.toString() == 'Некорректный формат email') {
|
if (error.toString() == 'Некорректный формат email') {
|
||||||
emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Некорректный формат email'));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isEmailValid: false,
|
||||||
|
emailDisplayError: 'Некорректный формат email',
|
||||||
|
),
|
||||||
|
);
|
||||||
didApiComplainOnValidation = true;
|
didApiComplainOnValidation = true;
|
||||||
}
|
}
|
||||||
if (error.toString() == 'Такой Эл. почта уже существует.') {
|
if (error.toString() == 'Такой Эл. почта уже существует.') {
|
||||||
emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Такая Эл. почта уже существует.'));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isEmailValid: false,
|
||||||
|
emailDisplayError: 'Такая Эл. почта уже существует.',
|
||||||
|
),
|
||||||
|
);
|
||||||
didApiComplainOnValidation = true;
|
didApiComplainOnValidation = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,7 +245,12 @@ class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
|
|||||||
for (var error in phoneErrors) {
|
for (var error in phoneErrors) {
|
||||||
allErrors.add(error.toString());
|
allErrors.add(error.toString());
|
||||||
if (error.toString() == 'Такой phone уже существует.') {
|
if (error.toString() == 'Такой phone уже существует.') {
|
||||||
emit(state.copyWith(isPhoneValid: false, phoneDisplayError: 'Такой номер телефона уже существует.'));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isPhoneValid: false,
|
||||||
|
phoneDisplayError: 'Такой номер телефона уже существует.',
|
||||||
|
),
|
||||||
|
);
|
||||||
didApiComplainOnValidation = true;
|
didApiComplainOnValidation = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,7 +259,12 @@ class RegistrationCubitV2 extends Cubit<RegistrationStateV2> {
|
|||||||
for (var error in dateOfBirth) {
|
for (var error in dateOfBirth) {
|
||||||
allErrors.add(error.toString());
|
allErrors.add(error.toString());
|
||||||
if (error.toString() == 'Некорректный формат даты рождения') {
|
if (error.toString() == 'Некорректный формат даты рождения') {
|
||||||
emit(state.copyWith(isBirthdayValid: false, dateOfBirthDisplayError: 'Некорректный формат даты рождения'));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
isBirthdayValid: false,
|
||||||
|
dateOfBirthDisplayError: 'Некорректный формат даты рождения',
|
||||||
|
),
|
||||||
|
);
|
||||||
didApiComplainOnValidation = true;
|
didApiComplainOnValidation = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class VideoCallCallKitCoordinator {
|
|||||||
|
|
||||||
_PendingIncomingAccept? _pendingIncomingAccept;
|
_PendingIncomingAccept? _pendingIncomingAccept;
|
||||||
_AndroidAcceptResumeObserver? _androidResumeObserver;
|
_AndroidAcceptResumeObserver? _androidResumeObserver;
|
||||||
|
_AppResumeObserver? _iosResumeObserver;
|
||||||
bool _pendingAcceptDrainScheduled = false;
|
bool _pendingAcceptDrainScheduled = false;
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
@@ -86,6 +87,12 @@ class VideoCallCallKitCoordinator {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
await _firebaseApi.registerPushToken();
|
await _firebaseApi.registerPushToken();
|
||||||
|
_iosResumeObserver = _AppResumeObserver(
|
||||||
|
onResumed: () {
|
||||||
|
unawaited(_drainPendingNativeAcceptedVoipCall());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
WidgetsBinding.instance.addObserver(_iosResumeObserver!);
|
||||||
await _drainPendingNativeAcceptedVoipCall();
|
await _drainPendingNativeAcceptedVoipCall();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_scheduleNativeVoipDiagnosticsDrain();
|
_scheduleNativeVoipDiagnosticsDrain();
|
||||||
@@ -257,6 +264,11 @@ class VideoCallCallKitCoordinator {
|
|||||||
WidgetsBinding.instance.removeObserver(observer);
|
WidgetsBinding.instance.removeObserver(observer);
|
||||||
_androidResumeObserver = null;
|
_androidResumeObserver = null;
|
||||||
}
|
}
|
||||||
|
final iosObserver = _iosResumeObserver;
|
||||||
|
if (iosObserver != null) {
|
||||||
|
WidgetsBinding.instance.removeObserver(iosObserver);
|
||||||
|
_iosResumeObserver = null;
|
||||||
|
}
|
||||||
unawaited(_eventSubscription?.cancel());
|
unawaited(_eventSubscription?.cancel());
|
||||||
_eventSubscription = null;
|
_eventSubscription = null;
|
||||||
_callKitParamsById.clear();
|
_callKitParamsById.clear();
|
||||||
@@ -1228,6 +1240,19 @@ class _PendingIncomingAccept {
|
|||||||
final bool? counterpartIsConsultant;
|
final bool? counterpartIsConsultant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _AppResumeObserver with WidgetsBindingObserver {
|
||||||
|
_AppResumeObserver({required this.onResumed});
|
||||||
|
|
||||||
|
final VoidCallback onResumed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
onResumed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _AndroidAcceptResumeObserver with WidgetsBindingObserver {
|
class _AndroidAcceptResumeObserver with WidgetsBindingObserver {
|
||||||
_AndroidAcceptResumeObserver({required this.onResumed});
|
_AndroidAcceptResumeObserver({required this.onResumed});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: eatvio_app
|
name: eatvio_app
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
version: 1.3.67+156
|
version: 1.3.69+158
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/core/firebase_api.dart';
|
||||||
import 'package:eatvio_app/core/prefs.dart';
|
import 'package:eatvio_app/core/prefs.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_response.dart';
|
import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_response.dart';
|
||||||
@@ -16,6 +17,17 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import '../../../../../fixtures/fixture_reader.dart';
|
import '../../../../../fixtures/fixture_reader.dart';
|
||||||
import 'oauth_cubit_test.mocks.dart';
|
import 'oauth_cubit_test.mocks.dart';
|
||||||
|
|
||||||
|
class _RecordingFirebaseApi extends Fake implements FirebaseApi {
|
||||||
|
int registerPushTokenCalls = 0;
|
||||||
|
bool hadAuthToken = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> registerPushToken() async {
|
||||||
|
registerPushTokenCalls++;
|
||||||
|
hadAuthToken = await TokenStorage.haveTokenInStorage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GenerateNiceMocks([MockSpec<AuthRepository>(), MockSpec<Prefs>()])
|
@GenerateNiceMocks([MockSpec<AuthRepository>(), MockSpec<Prefs>()])
|
||||||
void main() {
|
void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -24,26 +36,36 @@ void main() {
|
|||||||
OAuthCubit? oauthCubit;
|
OAuthCubit? oauthCubit;
|
||||||
late MockAuthRepository mockAuthRepository;
|
late MockAuthRepository mockAuthRepository;
|
||||||
late MockPrefs mockPrefs;
|
late MockPrefs mockPrefs;
|
||||||
|
late _RecordingFirebaseApi firebaseApi;
|
||||||
late TokenModel tokenFixture;
|
late TokenModel tokenFixture;
|
||||||
late Map<String, dynamic> signInRequestFixture;
|
late Map<String, dynamic> signInRequestFixture;
|
||||||
late Map<String, dynamic> signUpRequestFixture;
|
late Map<String, dynamic> signUpRequestFixture;
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
tokenFixture = TokenModel.fromJson(FixtureReader.readAsMap('auth/token_response.json'));
|
tokenFixture = TokenModel.fromJson(
|
||||||
signInRequestFixture = FixtureReader.readAsMap('auth/oauth_signin_request.json');
|
FixtureReader.readAsMap('auth/token_response.json'),
|
||||||
signUpRequestFixture = FixtureReader.readAsMap('auth/oauth_signup_request.json');
|
);
|
||||||
|
signInRequestFixture = FixtureReader.readAsMap(
|
||||||
|
'auth/oauth_signin_request.json',
|
||||||
|
);
|
||||||
|
signUpRequestFixture = FixtureReader.readAsMap(
|
||||||
|
'auth/oauth_signup_request.json',
|
||||||
|
);
|
||||||
|
|
||||||
mockAuthRepository = MockAuthRepository();
|
mockAuthRepository = MockAuthRepository();
|
||||||
mockPrefs = MockPrefs();
|
mockPrefs = MockPrefs();
|
||||||
|
firebaseApi = _RecordingFirebaseApi();
|
||||||
|
|
||||||
SharedPreferences.setMockInitialValues({});
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
|
||||||
// Mock Fluttertoast plugin
|
// Mock Fluttertoast plugin
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('PonnamKarthik/fluttertoast'), (
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||||
MethodCall methodCall,
|
.setMockMethodCallHandler(
|
||||||
) async {
|
const MethodChannel('PonnamKarthik/fluttertoast'),
|
||||||
return null;
|
(MethodCall methodCall) async {
|
||||||
});
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
@@ -52,18 +74,31 @@ void main() {
|
|||||||
oauthCubit!.close();
|
oauthCubit!.close();
|
||||||
oauthCubit = null;
|
oauthCubit = null;
|
||||||
}
|
}
|
||||||
|
TokenStorage.clearCache();
|
||||||
|
|
||||||
// Clear mock handlers
|
// Clear mock handlers
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(const MethodChannel('PonnamKarthik/fluttertoast'), null);
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||||
|
.setMockMethodCallHandler(
|
||||||
|
const MethodChannel('PonnamKarthik/fluttertoast'),
|
||||||
|
null,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('начальное состояние - OAuthState', () {
|
test('начальное состояние - OAuthState', () {
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
expect(oauthCubit!.state, const OAuthState());
|
expect(oauthCubit!.state, const OAuthState());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('signInViaSocialMedia - успешный вход через соцсеть', () async {
|
test('signInViaSocialMedia - успешный вход через соцсеть', () async {
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
|
|
||||||
when(
|
when(
|
||||||
mockAuthRepository.signInViaSocialMedia(
|
mockAuthRepository.signInViaSocialMedia(
|
||||||
@@ -92,7 +127,11 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('signInViaSocialMedia - переход в состояние loading', () async {
|
test('signInViaSocialMedia - переход в состояние loading', () async {
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
|
|
||||||
when(
|
when(
|
||||||
mockAuthRepository.signInViaSocialMedia(
|
mockAuthRepository.signInViaSocialMedia(
|
||||||
@@ -119,7 +158,11 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('signInViaSocialMedia - обработка ошибки', () async {
|
test('signInViaSocialMedia - обработка ошибки', () async {
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
|
|
||||||
when(
|
when(
|
||||||
mockAuthRepository.signInViaSocialMedia(
|
mockAuthRepository.signInViaSocialMedia(
|
||||||
@@ -144,7 +187,11 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('signUpViaSocialMedia - успешная регистрация через соцсеть', () async {
|
test('signUpViaSocialMedia - успешная регистрация через соцсеть', () async {
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
|
|
||||||
when(
|
when(
|
||||||
mockAuthRepository.signUpViaSocialMedia(
|
mockAuthRepository.signUpViaSocialMedia(
|
||||||
@@ -164,7 +211,9 @@ void main() {
|
|||||||
),
|
),
|
||||||
).thenAnswer((_) async => tokenFixture.token);
|
).thenAnswer((_) async => tokenFixture.token);
|
||||||
|
|
||||||
when(mockPrefs.setString(any, any)).thenAnswer((_) async => Future.value());
|
when(
|
||||||
|
mockPrefs.setString(any, any),
|
||||||
|
).thenAnswer((_) async => Future.value());
|
||||||
|
|
||||||
final result = await oauthCubit!.signUpViaSocialMedia(
|
final result = await oauthCubit!.signUpViaSocialMedia(
|
||||||
provider: signUpRequestFixture['provider'] as String,
|
provider: signUpRequestFixture['provider'] as String,
|
||||||
@@ -187,48 +236,62 @@ void main() {
|
|||||||
expect(oauthCubit!.state, isA<OAuthStateLoaded>());
|
expect(oauthCubit!.state, isA<OAuthStateLoaded>());
|
||||||
final loadedState = oauthCubit!.state as OAuthStateLoaded;
|
final loadedState = oauthCubit!.state as OAuthStateLoaded;
|
||||||
expect(loadedState.token, tokenFixture.token);
|
expect(loadedState.token, tokenFixture.token);
|
||||||
|
expect(await TokenStorage.getTokenFromStorage(), tokenFixture.token);
|
||||||
|
expect(firebaseApi.registerPushTokenCalls, 1);
|
||||||
|
expect(firebaseApi.hadAuthToken, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('signInViaSocialMedia - обработка ошибки валидации email/phone', () async {
|
test(
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
'signInViaSocialMedia - обработка ошибки валидации email/phone',
|
||||||
|
() async {
|
||||||
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
|
|
||||||
final dioException = DioException(
|
final dioException = DioException(
|
||||||
requestOptions: RequestOptions(path: '/api/auth/oauth/signin'),
|
|
||||||
response: Response(
|
|
||||||
statusCode: 422,
|
|
||||||
data: {
|
|
||||||
'errors': {
|
|
||||||
'email': ['Некорректный формат email'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestOptions: RequestOptions(path: '/api/auth/oauth/signin'),
|
requestOptions: RequestOptions(path: '/api/auth/oauth/signin'),
|
||||||
),
|
response: Response(
|
||||||
);
|
statusCode: 422,
|
||||||
|
data: {
|
||||||
|
'errors': {
|
||||||
|
'email': ['Некорректный формат email'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
requestOptions: RequestOptions(path: '/api/auth/oauth/signin'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
when(
|
when(
|
||||||
mockAuthRepository.signInViaSocialMedia(
|
mockAuthRepository.signInViaSocialMedia(
|
||||||
provider: anyNamed('provider'),
|
provider: anyNamed('provider'),
|
||||||
accessToken: anyNamed('accessToken'),
|
accessToken: anyNamed('accessToken'),
|
||||||
refreshToken: anyNamed('refreshToken'),
|
refreshToken: anyNamed('refreshToken'),
|
||||||
clientId: anyNamed('clientId'),
|
clientId: anyNamed('clientId'),
|
||||||
),
|
),
|
||||||
).thenThrow(dioException);
|
).thenThrow(dioException);
|
||||||
|
|
||||||
await oauthCubit!.signInViaSocialMedia(
|
await oauthCubit!.signInViaSocialMedia(
|
||||||
provider: signInRequestFixture['provider'] as String,
|
provider: signInRequestFixture['provider'] as String,
|
||||||
accessToken: signInRequestFixture['access_token'] as String,
|
accessToken: signInRequestFixture['access_token'] as String,
|
||||||
refreshToken: signInRequestFixture['refresh_token'] as String,
|
refreshToken: signInRequestFixture['refresh_token'] as String,
|
||||||
clientId: signInRequestFixture['client_id'] as String,
|
clientId: signInRequestFixture['client_id'] as String,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
expect(oauthCubit!.state, isA<OAuthStateError>());
|
expect(oauthCubit!.state, isA<OAuthStateError>());
|
||||||
final errorState = oauthCubit!.state as OAuthStateError;
|
final errorState = oauthCubit!.state as OAuthStateError;
|
||||||
expect(errorState.errorMessage, 'validation_error');
|
expect(errorState.errorMessage, 'validation_error');
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('signUpViaSocialMedia - обработка ошибки валидации email', () async {
|
test('signUpViaSocialMedia - обработка ошибки валидации email', () async {
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
|
|
||||||
final dioException = DioException(
|
final dioException = DioException(
|
||||||
requestOptions: RequestOptions(path: '/api/auth/oauth/signup'),
|
requestOptions: RequestOptions(path: '/api/auth/oauth/signup'),
|
||||||
@@ -282,82 +345,130 @@ void main() {
|
|||||||
expect(result[0], RegistrationValidationErrors.incorrectEmailFormat);
|
expect(result[0], RegistrationValidationErrors.incorrectEmailFormat);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('requestOAuthVerification - успешный запрос кода верификации', () async {
|
test(
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
'requestOAuthVerification - успешный запрос кода верификации',
|
||||||
|
() async {
|
||||||
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
|
|
||||||
const verificationResponse = OAuthVerificationRequestResponse(email: 't***@example.com', status: 'sent');
|
const verificationResponse = OAuthVerificationRequestResponse(
|
||||||
|
email: 't***@example.com',
|
||||||
|
status: 'sent',
|
||||||
|
);
|
||||||
|
|
||||||
when(mockAuthRepository.requestOAuthVerification(email: anyNamed('email'), phone: anyNamed('phone'))).thenAnswer((_) async => verificationResponse);
|
when(
|
||||||
|
mockAuthRepository.requestOAuthVerification(
|
||||||
|
email: anyNamed('email'),
|
||||||
|
phone: anyNamed('phone'),
|
||||||
|
),
|
||||||
|
).thenAnswer((_) async => verificationResponse);
|
||||||
|
|
||||||
final result = await oauthCubit!.requestOAuthVerification(email: 'test@example.com');
|
final result = await oauthCubit!.requestOAuthVerification(
|
||||||
|
email: 'test@example.com',
|
||||||
|
);
|
||||||
|
|
||||||
expect(result, isNotNull);
|
expect(result, isNotNull);
|
||||||
expect(result!.email, 't***@example.com');
|
expect(result!.email, 't***@example.com');
|
||||||
expect(oauthCubit!.state, isA<OAuthState>());
|
expect(oauthCubit!.state, isA<OAuthState>());
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('requestOAuthVerification - обработка ошибки', () async {
|
test('requestOAuthVerification - обработка ошибки', () async {
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
|
|
||||||
when(mockAuthRepository.requestOAuthVerification(email: anyNamed('email'), phone: anyNamed('phone'))).thenThrow(Exception('Ошибка запроса'));
|
when(
|
||||||
|
mockAuthRepository.requestOAuthVerification(
|
||||||
|
email: anyNamed('email'),
|
||||||
|
phone: anyNamed('phone'),
|
||||||
|
),
|
||||||
|
).thenThrow(Exception('Ошибка запроса'));
|
||||||
|
|
||||||
final result = await oauthCubit!.requestOAuthVerification(email: 'test@example.com');
|
final result = await oauthCubit!.requestOAuthVerification(
|
||||||
|
email: 'test@example.com',
|
||||||
|
);
|
||||||
|
|
||||||
expect(result, isNull);
|
expect(result, isNull);
|
||||||
expect(oauthCubit!.state, isA<OAuthStateError>());
|
expect(oauthCubit!.state, isA<OAuthStateError>());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('confirmOAuthVerification - успешное подтверждение верификации', () async {
|
test(
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
'confirmOAuthVerification - успешное подтверждение верификации',
|
||||||
|
() async {
|
||||||
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
|
|
||||||
final verificationResponse = OAuthVerificationConfirmResponse(success: true, message: 'Success', token: tokenFixture.token, user: {}, isNewUser: false);
|
final verificationResponse = OAuthVerificationConfirmResponse(
|
||||||
|
success: true,
|
||||||
|
message: 'Success',
|
||||||
|
token: tokenFixture.token,
|
||||||
|
user: {},
|
||||||
|
isNewUser: false,
|
||||||
|
);
|
||||||
|
|
||||||
when(
|
when(
|
||||||
mockAuthRepository.confirmOAuthVerification(
|
mockAuthRepository.confirmOAuthVerification(
|
||||||
provider: anyNamed('provider'),
|
provider: anyNamed('provider'),
|
||||||
accessToken: anyNamed('accessToken'),
|
accessToken: anyNamed('accessToken'),
|
||||||
refreshToken: anyNamed('refreshToken'),
|
refreshToken: anyNamed('refreshToken'),
|
||||||
clientId: anyNamed('clientId'),
|
clientId: anyNamed('clientId'),
|
||||||
id: anyNamed('id'),
|
id: anyNamed('id'),
|
||||||
email: anyNamed('email'),
|
email: anyNamed('email'),
|
||||||
phone: anyNamed('phone'),
|
phone: anyNamed('phone'),
|
||||||
code: anyNamed('code'),
|
code: anyNamed('code'),
|
||||||
verificationType: anyNamed('verificationType'),
|
verificationType: anyNamed('verificationType'),
|
||||||
firstName: anyNamed('firstName'),
|
firstName: anyNamed('firstName'),
|
||||||
lastName: anyNamed('lastName'),
|
lastName: anyNamed('lastName'),
|
||||||
dateOfBirth: anyNamed('dateOfBirth'),
|
dateOfBirth: anyNamed('dateOfBirth'),
|
||||||
gender: anyNamed('gender'),
|
gender: anyNamed('gender'),
|
||||||
weight: anyNamed('weight'),
|
weight: anyNamed('weight'),
|
||||||
height: anyNamed('height'),
|
height: anyNamed('height'),
|
||||||
),
|
),
|
||||||
).thenAnswer((_) async => verificationResponse);
|
).thenAnswer((_) async => verificationResponse);
|
||||||
|
|
||||||
await oauthCubit!.confirmOAuthVerification(
|
await oauthCubit!.confirmOAuthVerification(
|
||||||
provider: 'google',
|
provider: 'google',
|
||||||
accessToken: 'access_token',
|
accessToken: 'access_token',
|
||||||
refreshToken: 'refresh_token',
|
refreshToken: 'refresh_token',
|
||||||
clientId: 'client_id',
|
clientId: 'client_id',
|
||||||
id: 'id',
|
id: 'id',
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
phone: null,
|
phone: null,
|
||||||
code: '123456',
|
code: '123456',
|
||||||
verificationType: 'email',
|
verificationType: 'email',
|
||||||
firstName: 'Test',
|
firstName: 'Test',
|
||||||
lastName: 'User',
|
lastName: 'User',
|
||||||
dateOfBirth: '2000-01-01',
|
dateOfBirth: '2000-01-01',
|
||||||
gender: 'male',
|
gender: 'male',
|
||||||
weight: 70,
|
weight: 70,
|
||||||
height: 175,
|
height: 175,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
expect(oauthCubit!.state, isA<OAuthStateLoaded>());
|
expect(oauthCubit!.state, isA<OAuthStateLoaded>());
|
||||||
final loadedState = oauthCubit!.state as OAuthStateLoaded;
|
final loadedState = oauthCubit!.state as OAuthStateLoaded;
|
||||||
expect(loadedState.token, tokenFixture.token);
|
expect(loadedState.token, tokenFixture.token);
|
||||||
});
|
expect(await TokenStorage.getTokenFromStorage(), tokenFixture.token);
|
||||||
|
expect(firebaseApi.registerPushTokenCalls, 1);
|
||||||
|
expect(firebaseApi.hadAuthToken, isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('confirmOAuthVerification - обработка ошибки', () async {
|
test('confirmOAuthVerification - обработка ошибки', () async {
|
||||||
oauthCubit = OAuthCubit(authRepository: mockAuthRepository, prefs: mockPrefs);
|
oauthCubit = OAuthCubit(
|
||||||
|
authRepository: mockAuthRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
prefs: mockPrefs,
|
||||||
|
);
|
||||||
|
|
||||||
when(
|
when(
|
||||||
mockAuthRepository.confirmOAuthVerification(
|
mockAuthRepository.confirmOAuthVerification(
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/core/firebase_api.dart';
|
||||||
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
|
import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart';
|
||||||
import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart';
|
import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -9,43 +11,60 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
|
|
||||||
import 'registration_cubit_v2_test.mocks.dart';
|
import 'registration_cubit_v2_test.mocks.dart';
|
||||||
|
|
||||||
|
class _RecordingFirebaseApi extends Fake implements FirebaseApi {
|
||||||
|
int registerPushTokenCalls = 0;
|
||||||
|
bool hadAuthToken = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> registerPushToken() async {
|
||||||
|
registerPushTokenCalls++;
|
||||||
|
hadAuthToken = await TokenStorage.haveTokenInStorage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GenerateMocks([RegistrationRepositoryV2])
|
@GenerateMocks([RegistrationRepositoryV2])
|
||||||
void main() {
|
void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
group('RegistrationCubitV2', () {
|
group('RegistrationCubitV2', () {
|
||||||
late RegistrationCubitV2 cubit;
|
late RegistrationCubitV2 cubit;
|
||||||
late MockRegistrationRepositoryV2 mockRepository;
|
late MockRegistrationRepositoryV2 mockRepository;
|
||||||
const MethodChannel passwordGeneratorChannel = MethodChannel('flutter_password_generator');
|
late _RecordingFirebaseApi firebaseApi;
|
||||||
|
const MethodChannel passwordGeneratorChannel = MethodChannel(
|
||||||
|
'flutter_password_generator',
|
||||||
|
);
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
mockRepository = MockRegistrationRepositoryV2();
|
mockRepository = MockRegistrationRepositoryV2();
|
||||||
cubit = RegistrationCubitV2(registrationRepository: mockRepository);
|
firebaseApi = _RecordingFirebaseApi();
|
||||||
|
cubit = RegistrationCubitV2(
|
||||||
|
registrationRepository: mockRepository,
|
||||||
|
firebaseApi: firebaseApi,
|
||||||
|
);
|
||||||
|
|
||||||
// Инициализируем SharedPreferences для тестов
|
// Инициализируем SharedPreferences для тестов
|
||||||
SharedPreferences.setMockInitialValues({});
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
|
||||||
// Мокируем MethodChannel для flutter_password_generator
|
// Мокируем MethodChannel для flutter_password_generator
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||||
passwordGeneratorChannel,
|
.setMockMethodCallHandler(passwordGeneratorChannel, (
|
||||||
(MethodCall methodCall) async {
|
MethodCall methodCall,
|
||||||
if (methodCall.method == 'generateSecureRandomData') {
|
) async {
|
||||||
// Возвращаем Uint8List для генерации пароля
|
if (methodCall.method == 'generateSecureRandomData') {
|
||||||
// MethodChannel автоматически преобразует Uint8List в List<int>
|
// Возвращаем Uint8List для генерации пароля
|
||||||
return Uint8List.fromList(List<int>.generate(32, (i) => i % 256));
|
// MethodChannel автоматически преобразует Uint8List в List<int>
|
||||||
}
|
return Uint8List.fromList(List<int>.generate(32, (i) => i % 256));
|
||||||
return null;
|
}
|
||||||
},
|
return null;
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() {
|
tearDown(() {
|
||||||
|
TokenStorage.clearCache();
|
||||||
cubit.close();
|
cubit.close();
|
||||||
// Очищаем моки
|
// Очищаем моки
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||||
passwordGeneratorChannel,
|
.setMockMethodCallHandler(passwordGeneratorChannel, null);
|
||||||
null,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('начальное состояние', () {
|
test('начальное состояние', () {
|
||||||
@@ -182,14 +201,24 @@ void main() {
|
|||||||
cubit.setBirthday(DateTime(1990, 1, 1));
|
cubit.setBirthday(DateTime(1990, 1, 1));
|
||||||
|
|
||||||
const token = 'test-token';
|
const token = 'test-token';
|
||||||
when(mockRepository.register(registrationModel: anyNamed('registrationModel')))
|
when(
|
||||||
.thenAnswer((_) async => token);
|
mockRepository.register(
|
||||||
|
registrationModel: anyNamed('registrationModel'),
|
||||||
|
),
|
||||||
|
).thenAnswer((_) async => token);
|
||||||
|
|
||||||
final result = await cubit.register();
|
final result = await cubit.register();
|
||||||
|
|
||||||
expect(result.$1, isTrue);
|
expect(result.$1, isTrue);
|
||||||
expect(result.$2, isFalse);
|
expect(result.$2, isFalse);
|
||||||
verify(mockRepository.register(registrationModel: anyNamed('registrationModel'))).called(1);
|
expect(await TokenStorage.getTokenFromStorage(), token);
|
||||||
|
expect(firebaseApi.registerPushTokenCalls, 1);
|
||||||
|
expect(firebaseApi.hadAuthToken, isTrue);
|
||||||
|
verify(
|
||||||
|
mockRepository.register(
|
||||||
|
registrationModel: anyNamed('registrationModel'),
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('register - возвращает false при невалидных полях', () async {
|
test('register - возвращает false при невалидных полях', () async {
|
||||||
@@ -199,7 +228,11 @@ void main() {
|
|||||||
|
|
||||||
expect(result.$1, isFalse);
|
expect(result.$1, isFalse);
|
||||||
expect(result.$2, isTrue);
|
expect(result.$2, isTrue);
|
||||||
verifyNever(mockRepository.register(registrationModel: anyNamed('registrationModel')));
|
verifyNever(
|
||||||
|
mockRepository.register(
|
||||||
|
registrationModel: anyNamed('registrationModel'),
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('register - обработка ошибки валидации email', () async {
|
test('register - обработка ошибки валидации email', () async {
|
||||||
@@ -226,16 +259,21 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
when(mockRepository.register(registrationModel: anyNamed('registrationModel')))
|
when(
|
||||||
.thenThrow(dioException);
|
mockRepository.register(
|
||||||
|
registrationModel: anyNamed('registrationModel'),
|
||||||
|
),
|
||||||
|
).thenThrow(dioException);
|
||||||
|
|
||||||
final result = await cubit.register();
|
final result = await cubit.register();
|
||||||
|
|
||||||
expect(result.$1, isFalse);
|
expect(result.$1, isFalse);
|
||||||
expect(result.$2, isTrue);
|
expect(result.$2, isTrue);
|
||||||
expect(cubit.state.isEmailValid, isFalse);
|
expect(cubit.state.isEmailValid, isFalse);
|
||||||
expect(cubit.state.emailDisplayError, equals('Некорректный формат email'));
|
expect(
|
||||||
|
cubit.state.emailDisplayError,
|
||||||
|
equals('Некорректный формат email'),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user