diff --git a/assets/icons/logo_purple.svg b/assets/icons/logo_purple.svg new file mode 100644 index 0000000..abcb85d --- /dev/null +++ b/assets/icons/logo_purple.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/logo.svg b/assets/icons/logo_white.svg similarity index 100% rename from assets/icons/logo.svg rename to assets/icons/logo_white.svg diff --git a/assets/images/landing_1.jpg b/assets/images/landing_1.jpg deleted file mode 100644 index 5eff078..0000000 Binary files a/assets/images/landing_1.jpg and /dev/null differ diff --git a/assets/images/landing_1.png b/assets/images/landing_1.png new file mode 100644 index 0000000..8be1ebd Binary files /dev/null and b/assets/images/landing_1.png differ diff --git a/assets/images/landing_2.jpg b/assets/images/landing_2.jpg deleted file mode 100644 index 8345d37..0000000 Binary files a/assets/images/landing_2.jpg and /dev/null differ diff --git a/assets/images/landing_2.png b/assets/images/landing_2.png new file mode 100644 index 0000000..97e6bec Binary files /dev/null and b/assets/images/landing_2.png differ diff --git a/assets/images/landing_3.jpg b/assets/images/landing_3.jpg deleted file mode 100644 index 180fb68..0000000 Binary files a/assets/images/landing_3.jpg and /dev/null differ diff --git a/assets/images/landing_3.png b/assets/images/landing_3.png new file mode 100644 index 0000000..e1ddd29 Binary files /dev/null and b/assets/images/landing_3.png differ diff --git a/assets/images/loading.png b/assets/images/loading.png deleted file mode 100644 index 8650c79..0000000 Binary files a/assets/images/loading.png and /dev/null differ diff --git a/assets/translations/am.json b/assets/translations/am.json index 2a704b6..0147f17 100644 --- a/assets/translations/am.json +++ b/assets/translations/am.json @@ -193,7 +193,8 @@ "keep_momentum":"በጣም ጥሩ ስራ! በዚሁ ብርታት ይቀጥሉ።", "completed_practices": "የተጠናቀቁ ልምምዶች", "total_practices": "ጠቅላላ ልምምዶች", - "progress_percentage": "የእድገት መቶኛ" + "progress_percentage": "የእድገት መቶኛ", + "notifications": "ማሳወቂያዎች" } diff --git a/assets/translations/en.json b/assets/translations/en.json index 52b7365..69ececd 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -193,5 +193,6 @@ "keep_momentum":"Great job! Keep the momentum.", "completed_practices": "Completed Practices", "total_practices": "Total Practices", - "progress_percentage": "Progress Percentage" + "progress_percentage": "Progress Percentage", + "notifications": "Notifications" } diff --git a/lib/app/app.dart b/lib/app/app.dart index b50252d..b8493ef 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -35,7 +35,6 @@ import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart'; import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart'; import 'package:yimaru_app/ui/views/failure/failure_view.dart'; import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart'; -import 'package:yimaru_app/services/notification_service.dart'; import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart'; import 'package:yimaru_app/services/smart_auth_service.dart'; import 'package:yimaru_app/services/course_service.dart'; @@ -58,6 +57,9 @@ import 'package:yimaru_app/ui/views/course_module/course_module_view.dart'; import 'package:yimaru_app/services/onboarding_service.dart'; import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart'; import 'package:yimaru_app/ui/views/payment/payment_view.dart'; +import 'package:yimaru_app/ui/views/notification/notification_view.dart'; +import 'package:yimaru_app/services/in_app_notification_service.dart'; +import 'package:yimaru_app/services/push_notification_service.dart'; // @stacked-import @StackedApp( @@ -97,6 +99,7 @@ import 'package:yimaru_app/ui/views/payment/payment_view.dart'; MaterialRoute(page: CourseModuleView), MaterialRoute(page: LearnCourseView), MaterialRoute(page: PaymentView), + MaterialRoute(page: NotificationView), // @stacked-route ], dependencies: [ @@ -112,7 +115,6 @@ import 'package:yimaru_app/ui/views/payment/payment_view.dart'; LazySingleton(classType: ImagePickerService), LazySingleton(classType: GoogleAuthService), LazySingleton(classType: ImageDownloaderService), - LazySingleton(classType: NotificationService), LazySingleton(classType: SmartAuthService), LazySingleton(classType: CourseService), LazySingleton(classType: AudioPlayerService), @@ -124,6 +126,8 @@ import 'package:yimaru_app/ui/views/payment/payment_view.dart'; LazySingleton(classType: LearnService), LazySingleton(classType: LocalizationService), LazySingleton(classType: OnboardingService), + LazySingleton(classType: InAppNotificationService), + LazySingleton(classType: PushNotificationService), // @stacked-service ], bottomsheets: [ diff --git a/lib/app/app.locator.dart b/lib/app/app.locator.dart index 85eb85a..77de7df 100644 --- a/lib/app/app.locator.dart +++ b/lib/app/app.locator.dart @@ -20,13 +20,14 @@ import '../services/dio_service.dart'; import '../services/google_auth_service.dart'; import '../services/image_downloader_service.dart'; import '../services/image_picker_service.dart'; +import '../services/in_app_notification_service.dart'; import '../services/in_app_update_service.dart'; import '../services/learn_service.dart'; import '../services/localization_service.dart'; -import '../services/notification_service.dart'; import '../services/onboarding_service.dart'; import '../services/permission_handler_service.dart'; import '../services/phone_caller_service.dart'; +import '../services/push_notification_service.dart'; import '../services/secure_storage_service.dart'; import '../services/smart_auth_service.dart'; import '../services/status_checker_service.dart'; @@ -55,7 +56,6 @@ Future setupLocator( locator.registerLazySingleton(() => ImagePickerService()); locator.registerLazySingleton(() => GoogleAuthService()); locator.registerLazySingleton(() => ImageDownloaderService()); - locator.registerLazySingleton(() => NotificationService()); locator.registerLazySingleton(() => SmartAuthService()); locator.registerLazySingleton(() => CourseService()); locator.registerLazySingleton(() => AudioPlayerService()); @@ -67,4 +67,6 @@ Future setupLocator( locator.registerLazySingleton(() => LearnService()); locator.registerLazySingleton(() => LocalizationService()); locator.registerLazySingleton(() => OnboardingService()); + locator.registerLazySingleton(() => InAppNotificationService()); + locator.registerLazySingleton(() => PushNotificationService()); } diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart index a8d7e22..2495d89 100644 --- a/lib/app/app.router.dart +++ b/lib/app/app.router.dart @@ -7,18 +7,18 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:flutter/material.dart'; -import 'package:flutter/material.dart' as _i37; +import 'package:flutter/material.dart' as _i38; import 'package:stacked/stacked.dart' as _i1; -import 'package:stacked_services/stacked_services.dart' as _i47; -import 'package:yimaru_app/models/course.dart' as _i42; -import 'package:yimaru_app/models/course_catalog.dart' as _i44; -import 'package:yimaru_app/models/course_lesson.dart' as _i43; -import 'package:yimaru_app/models/course_module.dart' as _i45; -import 'package:yimaru_app/models/learn_course.dart' as _i38; -import 'package:yimaru_app/models/learn_lesson.dart' as _i40; -import 'package:yimaru_app/models/learn_module.dart' as _i39; -import 'package:yimaru_app/models/learn_subscription.dart' as _i46; -import 'package:yimaru_app/ui/common/enmus.dart' as _i41; +import 'package:stacked_services/stacked_services.dart' as _i48; +import 'package:yimaru_app/models/course.dart' as _i43; +import 'package:yimaru_app/models/course_catalog.dart' as _i45; +import 'package:yimaru_app/models/course_lesson.dart' as _i44; +import 'package:yimaru_app/models/course_module.dart' as _i46; +import 'package:yimaru_app/models/learn_course.dart' as _i39; +import 'package:yimaru_app/models/learn_lesson.dart' as _i41; +import 'package:yimaru_app/models/learn_module.dart' as _i40; +import 'package:yimaru_app/models/learn_subscription.dart' as _i47; +import 'package:yimaru_app/ui/common/enmus.dart' as _i42; import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart' as _i9; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i29; @@ -57,6 +57,8 @@ import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart' import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.dart' as _i30; import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17; +import 'package:yimaru_app/ui/views/notification/notification_view.dart' + as _i37; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3; import 'package:yimaru_app/ui/views/payment/payment_view.dart' as _i36; import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart' @@ -144,6 +146,8 @@ class Routes { static const paymentView = '/payment-view'; + static const notificationView = '/notification-view'; + static const all = { homeView, onboardingView, @@ -180,6 +184,7 @@ class Routes { courseModuleView, learnCourseView, paymentView, + notificationView, }; } @@ -325,6 +330,10 @@ class StackedRouter extends _i1.RouterBase { Routes.paymentView, page: _i36.PaymentView, ), + _i1.RouteDef( + Routes.notificationView, + page: _i37.NotificationView, + ), ]; final _pagesMap = { @@ -332,7 +341,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const HomeViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i2.HomeView(key: args.key), settings: data, ); @@ -341,7 +350,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const OnboardingViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i3.OnboardingView(key: args.key), settings: data, ); @@ -350,7 +359,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const StartupViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i4.StartupView(key: args.key, label: args.label), settings: data, ); @@ -359,7 +368,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ProfileViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i5.ProfileView(key: args.key), settings: data, ); @@ -368,7 +377,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ProfileDetailViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i6.ProfileDetailView(key: args.key), settings: data, ); @@ -377,7 +386,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const DownloadsViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i7.DownloadsView(key: args.key), settings: data, ); @@ -386,7 +395,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ProgressViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i8.ProgressView(key: args.key), settings: data, ); @@ -395,7 +404,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const AccountPrivacyViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i9.AccountPrivacyView(key: args.key), settings: data, ); @@ -404,7 +413,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const SupportViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i10.SupportView(key: args.key), settings: data, ); @@ -413,7 +422,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const TelegramSupportViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i11.TelegramSupportView(key: args.key), settings: data, ); @@ -422,7 +431,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const CallSupportViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i12.CallSupportView(key: args.key), settings: data, ); @@ -431,7 +440,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const LanguageViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i13.LanguageView(key: args.key), settings: data, ); @@ -440,7 +449,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const PrivacyPolicyViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i14.PrivacyPolicyView(key: args.key), settings: data, ); @@ -449,7 +458,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const TermsAndConditionsViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i15.TermsAndConditionsView(key: args.key), settings: data, ); @@ -458,7 +467,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const RegisterViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i16.RegisterView(key: args.key), settings: data, ); @@ -467,14 +476,14 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const LoginViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i17.LoginView(key: args.key), settings: data, ); }, _i18.LearnModuleView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i18.LearnModuleView( key: args.key, first: args.first, course: args.course), settings: data, @@ -482,7 +491,7 @@ class StackedRouter extends _i1.RouterBase { }, _i19.LearnLessonView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i19.LearnLessonView( key: args.key, first: args.first, module: args.module), settings: data, @@ -492,14 +501,14 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const ForgetPasswordViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i20.ForgetPasswordView(key: args.key), settings: data, ); }, _i21.LearnLessonDetailView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i21.LearnLessonDetailView( key: args.key, index: args.index, @@ -511,7 +520,7 @@ class StackedRouter extends _i1.RouterBase { }, _i22.LearnPracticeView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i22.LearnPracticeView( key: args.key, level: args.level, @@ -525,7 +534,7 @@ class StackedRouter extends _i1.RouterBase { }, _i23.CoursePaymentView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i23.CoursePaymentView(key: args.key, course: args.course), settings: data, @@ -533,7 +542,7 @@ class StackedRouter extends _i1.RouterBase { }, _i24.FailureView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i24.FailureView( key: args.key, onTap: args.onTap, label: args.label), settings: data, @@ -541,7 +550,7 @@ class StackedRouter extends _i1.RouterBase { }, _i25.CourseLessonDetailView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i25.CourseLessonDetailView(key: args.key, lesson: args.lesson), settings: data, @@ -551,7 +560,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const DuolingoViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i26.DuolingoView(key: args.key), settings: data, ); @@ -560,7 +569,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const CourseViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i27.CourseView(key: args.key), settings: data, ); @@ -569,14 +578,14 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const LearnProgramViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i28.LearnProgramView(key: args.key), settings: data, ); }, _i29.AssessmentView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i29.AssessmentView(key: args.key, data: args.data), settings: data, @@ -586,7 +595,7 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const LearnSubscriptionViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i30.LearnSubscriptionView(key: args.key), settings: data, ); @@ -595,14 +604,14 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const CourseCatalogViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i31.CourseCatalogView(key: args.key), settings: data, ); }, _i32.CourseUnitView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i32.CourseUnitView(key: args.key, catalog: args.catalog), settings: data, @@ -612,14 +621,14 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const LandingViewArguments(), ); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i33.LandingView(key: args.key), settings: data, ); }, _i34.CourseModuleView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i34.CourseModuleView( key: args.key, module: args.module, catalog: args.catalog), settings: data, @@ -627,7 +636,7 @@ class StackedRouter extends _i1.RouterBase { }, _i35.LearnCourseView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i35.LearnCourseView(key: args.key, id: args.id, first: args.first), settings: data, @@ -635,12 +644,21 @@ class StackedRouter extends _i1.RouterBase { }, _i36.PaymentView: (data) { final args = data.getArgs(nullOk: false); - return _i37.MaterialPageRoute( + return _i38.MaterialPageRoute( builder: (context) => _i36.PaymentView( key: args.key, phone: args.phone, subscription: args.subscription), settings: data, ); }, + _i37.NotificationView: (data) { + final args = data.getArgs( + orElse: () => const NotificationViewArguments(), + ); + return _i38.MaterialPageRoute( + builder: (context) => _i37.NotificationView(key: args.key), + settings: data, + ); + }, }; @override @@ -653,7 +671,7 @@ class StackedRouter extends _i1.RouterBase { class HomeViewArguments { const HomeViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -675,7 +693,7 @@ class HomeViewArguments { class OnboardingViewArguments { const OnboardingViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -700,7 +718,7 @@ class StartupViewArguments { this.label, }); - final _i37.Key? key; + final _i38.Key? key; final String? label; @@ -724,7 +742,7 @@ class StartupViewArguments { class ProfileViewArguments { const ProfileViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -746,7 +764,7 @@ class ProfileViewArguments { class ProfileDetailViewArguments { const ProfileDetailViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -768,7 +786,7 @@ class ProfileDetailViewArguments { class DownloadsViewArguments { const DownloadsViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -790,7 +808,7 @@ class DownloadsViewArguments { class ProgressViewArguments { const ProgressViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -812,7 +830,7 @@ class ProgressViewArguments { class AccountPrivacyViewArguments { const AccountPrivacyViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -834,7 +852,7 @@ class AccountPrivacyViewArguments { class SupportViewArguments { const SupportViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -856,7 +874,7 @@ class SupportViewArguments { class TelegramSupportViewArguments { const TelegramSupportViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -878,7 +896,7 @@ class TelegramSupportViewArguments { class CallSupportViewArguments { const CallSupportViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -900,7 +918,7 @@ class CallSupportViewArguments { class LanguageViewArguments { const LanguageViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -922,7 +940,7 @@ class LanguageViewArguments { class PrivacyPolicyViewArguments { const PrivacyPolicyViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -944,7 +962,7 @@ class PrivacyPolicyViewArguments { class TermsAndConditionsViewArguments { const TermsAndConditionsViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -966,7 +984,7 @@ class TermsAndConditionsViewArguments { class RegisterViewArguments { const RegisterViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -988,7 +1006,7 @@ class RegisterViewArguments { class LoginViewArguments { const LoginViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -1014,11 +1032,11 @@ class LearnModuleViewArguments { required this.course, }); - final _i37.Key? key; + final _i38.Key? key; final bool first; - final _i38.LearnCourse course; + final _i39.LearnCourse course; @override String toString() { @@ -1044,11 +1062,11 @@ class LearnLessonViewArguments { required this.module, }); - final _i37.Key? key; + final _i38.Key? key; final bool first; - final _i39.LearnModule module; + final _i40.LearnModule module; @override String toString() { @@ -1070,7 +1088,7 @@ class LearnLessonViewArguments { class ForgetPasswordViewArguments { const ForgetPasswordViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -1098,13 +1116,13 @@ class LearnLessonDetailViewArguments { required this.hasPractice, }); - final _i37.Key? key; + final _i38.Key? key; final int index; - final _i40.LearnLesson lesson; + final _i41.LearnLesson lesson; - final _i39.LearnModule module; + final _i40.LearnModule module; final bool hasPractice; @@ -1144,7 +1162,7 @@ class LearnPracticeViewArguments { required this.subtitle, }); - final _i37.Key? key; + final _i38.Key? key; final String? level; @@ -1154,7 +1172,7 @@ class LearnPracticeViewArguments { final String title; - final _i41.LearnPractices practice; + final _i42.LearnPractices practice; final String subtitle; @@ -1193,9 +1211,9 @@ class CoursePaymentViewArguments { required this.course, }); - final _i37.Key? key; + final _i38.Key? key; - final _i42.Course course; + final _i43.Course course; @override String toString() { @@ -1221,7 +1239,7 @@ class FailureViewArguments { required this.label, }); - final _i37.Key? key; + final _i38.Key? key; final void Function() onTap; @@ -1250,9 +1268,9 @@ class CourseLessonDetailViewArguments { required this.lesson, }); - final _i37.Key? key; + final _i38.Key? key; - final _i43.CourseLesson lesson; + final _i44.CourseLesson lesson; @override String toString() { @@ -1274,7 +1292,7 @@ class CourseLessonDetailViewArguments { class DuolingoViewArguments { const DuolingoViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -1296,7 +1314,7 @@ class DuolingoViewArguments { class CourseViewArguments { const CourseViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -1318,7 +1336,7 @@ class CourseViewArguments { class LearnProgramViewArguments { const LearnProgramViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -1343,7 +1361,7 @@ class AssessmentViewArguments { required this.data, }); - final _i37.Key? key; + final _i38.Key? key; final Map data; @@ -1367,7 +1385,7 @@ class AssessmentViewArguments { class LearnSubscriptionViewArguments { const LearnSubscriptionViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -1389,7 +1407,7 @@ class LearnSubscriptionViewArguments { class CourseCatalogViewArguments { const CourseCatalogViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -1414,9 +1432,9 @@ class CourseUnitViewArguments { required this.catalog, }); - final _i37.Key? key; + final _i38.Key? key; - final _i44.CourseCatalog catalog; + final _i45.CourseCatalog catalog; @override String toString() { @@ -1438,7 +1456,7 @@ class CourseUnitViewArguments { class LandingViewArguments { const LandingViewArguments({this.key}); - final _i37.Key? key; + final _i38.Key? key; @override String toString() { @@ -1464,11 +1482,11 @@ class CourseModuleViewArguments { required this.catalog, }); - final _i37.Key? key; + final _i38.Key? key; - final _i45.CourseModule? module; + final _i46.CourseModule? module; - final _i44.CourseCatalog catalog; + final _i45.CourseCatalog catalog; @override String toString() { @@ -1496,7 +1514,7 @@ class LearnCourseViewArguments { required this.first, }); - final _i37.Key? key; + final _i38.Key? key; final int id; @@ -1526,11 +1544,11 @@ class PaymentViewArguments { required this.subscription, }); - final _i37.Key? key; + final _i38.Key? key; final String phone; - final _i46.LearnSubscription subscription; + final _i47.LearnSubscription subscription; @override String toString() { @@ -1551,9 +1569,31 @@ class PaymentViewArguments { } } -extension NavigatorStateExtension on _i47.NavigationService { +class NotificationViewArguments { + const NotificationViewArguments({this.key}); + + final _i38.Key? key; + + @override + String toString() { + return '{"key": "$key"}'; + } + + @override + bool operator ==(covariant NotificationViewArguments other) { + if (identical(this, other)) return true; + return other.key == key; + } + + @override + int get hashCode { + return key.hashCode; + } +} + +extension NavigatorStateExtension on _i48.NavigationService { Future navigateToHomeView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1569,7 +1609,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToOnboardingView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1585,7 +1625,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToStartupView({ - _i37.Key? key, + _i38.Key? key, String? label, int? routerId, bool preventDuplicates = true, @@ -1602,7 +1642,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToProfileView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1618,7 +1658,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToProfileDetailView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1634,7 +1674,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToDownloadsView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1650,7 +1690,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToProgressView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1666,7 +1706,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToAccountPrivacyView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1682,7 +1722,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToSupportView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1698,7 +1738,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToTelegramSupportView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1714,7 +1754,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToCallSupportView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1730,7 +1770,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToLanguageView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1746,7 +1786,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToPrivacyPolicyView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1762,7 +1802,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToTermsAndConditionsView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1778,7 +1818,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToRegisterView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1794,7 +1834,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToLoginView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1810,9 +1850,9 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToLearnModuleView({ - _i37.Key? key, + _i38.Key? key, required bool first, - required _i38.LearnCourse course, + required _i39.LearnCourse course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1829,9 +1869,9 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToLearnLessonView({ - _i37.Key? key, + _i38.Key? key, required bool first, - required _i39.LearnModule module, + required _i40.LearnModule module, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1848,7 +1888,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToForgetPasswordView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1864,10 +1904,10 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToLearnLessonDetailView({ - _i37.Key? key, + _i38.Key? key, required int index, - required _i40.LearnLesson lesson, - required _i39.LearnModule module, + required _i41.LearnLesson lesson, + required _i40.LearnModule module, required bool hasPractice, int? routerId, bool preventDuplicates = true, @@ -1889,12 +1929,12 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToLearnPracticeView({ - _i37.Key? key, + _i38.Key? key, String? level, required int id, required String label, required String title, - required _i41.LearnPractices practice, + required _i42.LearnPractices practice, required String subtitle, int? routerId, bool preventDuplicates = true, @@ -1918,8 +1958,8 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToCoursePaymentView({ - _i37.Key? key, - required _i42.Course course, + _i38.Key? key, + required _i43.Course course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1935,7 +1975,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToFailureView({ - _i37.Key? key, + _i38.Key? key, required void Function() onTap, required String label, int? routerId, @@ -1953,8 +1993,8 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToCourseLessonDetailView({ - _i37.Key? key, - required _i43.CourseLesson lesson, + _i38.Key? key, + required _i44.CourseLesson lesson, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1970,7 +2010,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToDuolingoView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -1986,7 +2026,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToCourseView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2002,7 +2042,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToLearnProgramView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2018,7 +2058,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToAssessmentView({ - _i37.Key? key, + _i38.Key? key, required Map data, int? routerId, bool preventDuplicates = true, @@ -2035,7 +2075,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToLearnSubscriptionView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2051,7 +2091,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToCourseCatalogView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2067,8 +2107,8 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToCourseUnitView({ - _i37.Key? key, - required _i44.CourseCatalog catalog, + _i38.Key? key, + required _i45.CourseCatalog catalog, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2084,7 +2124,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToLandingView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2100,9 +2140,9 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToCourseModuleView({ - _i37.Key? key, - required _i45.CourseModule? module, - required _i44.CourseCatalog catalog, + _i38.Key? key, + required _i46.CourseModule? module, + required _i45.CourseCatalog catalog, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2119,7 +2159,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToLearnCourseView({ - _i37.Key? key, + _i38.Key? key, required int id, required bool first, int? routerId, @@ -2137,9 +2177,9 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future navigateToPaymentView({ - _i37.Key? key, + _i38.Key? key, required String phone, - required _i46.LearnSubscription subscription, + required _i47.LearnSubscription subscription, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2155,8 +2195,24 @@ extension NavigatorStateExtension on _i47.NavigationService { transition: transition); } + Future navigateToNotificationView({ + _i38.Key? key, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.notificationView, + arguments: NotificationViewArguments(key: key), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + Future replaceWithHomeView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2172,7 +2228,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithOnboardingView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2188,7 +2244,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithStartupView({ - _i37.Key? key, + _i38.Key? key, String? label, int? routerId, bool preventDuplicates = true, @@ -2205,7 +2261,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithProfileView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2221,7 +2277,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithProfileDetailView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2237,7 +2293,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithDownloadsView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2253,7 +2309,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithProgressView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2269,7 +2325,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithAccountPrivacyView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2285,7 +2341,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithSupportView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2301,7 +2357,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithTelegramSupportView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2317,7 +2373,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithCallSupportView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2333,7 +2389,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithLanguageView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2349,7 +2405,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithPrivacyPolicyView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2365,7 +2421,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithTermsAndConditionsView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2381,7 +2437,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithRegisterView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2397,7 +2453,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithLoginView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2413,9 +2469,9 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithLearnModuleView({ - _i37.Key? key, + _i38.Key? key, required bool first, - required _i38.LearnCourse course, + required _i39.LearnCourse course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2432,9 +2488,9 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithLearnLessonView({ - _i37.Key? key, + _i38.Key? key, required bool first, - required _i39.LearnModule module, + required _i40.LearnModule module, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2451,7 +2507,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithForgetPasswordView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2467,10 +2523,10 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithLearnLessonDetailView({ - _i37.Key? key, + _i38.Key? key, required int index, - required _i40.LearnLesson lesson, - required _i39.LearnModule module, + required _i41.LearnLesson lesson, + required _i40.LearnModule module, required bool hasPractice, int? routerId, bool preventDuplicates = true, @@ -2492,12 +2548,12 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithLearnPracticeView({ - _i37.Key? key, + _i38.Key? key, String? level, required int id, required String label, required String title, - required _i41.LearnPractices practice, + required _i42.LearnPractices practice, required String subtitle, int? routerId, bool preventDuplicates = true, @@ -2521,8 +2577,8 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithCoursePaymentView({ - _i37.Key? key, - required _i42.Course course, + _i38.Key? key, + required _i43.Course course, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2538,7 +2594,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithFailureView({ - _i37.Key? key, + _i38.Key? key, required void Function() onTap, required String label, int? routerId, @@ -2556,8 +2612,8 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithCourseLessonDetailView({ - _i37.Key? key, - required _i43.CourseLesson lesson, + _i38.Key? key, + required _i44.CourseLesson lesson, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2573,7 +2629,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithDuolingoView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2589,7 +2645,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithCourseView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2605,7 +2661,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithLearnProgramView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2621,7 +2677,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithAssessmentView({ - _i37.Key? key, + _i38.Key? key, required Map data, int? routerId, bool preventDuplicates = true, @@ -2638,7 +2694,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithLearnSubscriptionView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2654,7 +2710,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithCourseCatalogView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2670,8 +2726,8 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithCourseUnitView({ - _i37.Key? key, - required _i44.CourseCatalog catalog, + _i38.Key? key, + required _i45.CourseCatalog catalog, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2687,7 +2743,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithLandingView({ - _i37.Key? key, + _i38.Key? key, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2703,9 +2759,9 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithCourseModuleView({ - _i37.Key? key, - required _i45.CourseModule? module, - required _i44.CourseCatalog catalog, + _i38.Key? key, + required _i46.CourseModule? module, + required _i45.CourseCatalog catalog, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2722,7 +2778,7 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithLearnCourseView({ - _i37.Key? key, + _i38.Key? key, required int id, required bool first, int? routerId, @@ -2740,9 +2796,9 @@ extension NavigatorStateExtension on _i47.NavigationService { } Future replaceWithPaymentView({ - _i37.Key? key, + _i38.Key? key, required String phone, - required _i46.LearnSubscription subscription, + required _i47.LearnSubscription subscription, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -2757,4 +2813,20 @@ extension NavigatorStateExtension on _i47.NavigationService { parameters: parameters, transition: transition); } + + Future replaceWithNotificationView({ + _i38.Key? key, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.notificationView, + arguments: NotificationViewArguments(key: key), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } } diff --git a/lib/main.dart b/lib/main.dart index eee16d1..360d30a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,8 +7,8 @@ import 'package:yimaru_app/app/app.dialogs.dart'; import 'package:yimaru_app/app/app.locator.dart'; import 'package:yimaru_app/app/app.router.dart'; import 'package:stacked_services/stacked_services.dart'; -import 'package:yimaru_app/services/notification_service.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:yimaru_app/services/push_notification_service.dart'; import 'package:yimaru_app/ui/common/translations/codegen_loader.g.dart'; import 'firebase_options.dart'; @@ -17,7 +17,7 @@ Future main() async { await setupLocator(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - await locator().initialize(); + await locator().initialize(); await EasyLocalization.ensureInitialized(); setupDialogUi(); setupBottomSheetUi(); diff --git a/lib/models/in_app_notification.dart b/lib/models/in_app_notification.dart new file mode 100644 index 0000000..173197b --- /dev/null +++ b/lib/models/in_app_notification.dart @@ -0,0 +1,57 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:yimaru_app/models/notification_payload.dart'; + +part 'in_app_notification.g.dart'; + +@JsonSerializable() +class InAppNotification { + final String? id; + + final String? type; + + final String? level; + + final String? image; + + final NotificationPayload? payload; + + @JsonKey(name: 'is_read') + final bool? isRead; + + @JsonKey(name: 'reciever') + final String? receiver; + + @JsonKey(name: 'recipient_id') + final int? recipientId; + + @JsonKey(name: 'receiver_type') + final String? receiverType; + + @JsonKey(name: 'error_severity') + final String? errorSeverity; + + @JsonKey(name: 'delivery_status') + final String? deliveryStatus; + + @JsonKey(name: 'delivery_channel') + final String? deliveryChannel; + + const InAppNotification( + {this.id, + this.type, + this.image, + this.level, + this.isRead, + this.payload, + this.receiver, + this.recipientId, + this.receiverType, + this.errorSeverity, + this.deliveryStatus, + this.deliveryChannel}); + + factory InAppNotification.fromJson(Map json) => + _$InAppNotificationFromJson(json); + + Map toJson() => _$InAppNotificationToJson(this); +} diff --git a/lib/models/in_app_notification.g.dart b/lib/models/in_app_notification.g.dart new file mode 100644 index 0000000..8d4b51b --- /dev/null +++ b/lib/models/in_app_notification.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'in_app_notification.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +InAppNotification _$InAppNotificationFromJson(Map json) => + InAppNotification( + id: json['id'] as String?, + type: json['type'] as String?, + image: json['image'] as String?, + level: json['level'] as String?, + isRead: json['is_read'] as bool?, + payload: json['payload'] == null + ? null + : NotificationPayload.fromJson( + json['payload'] as Map), + receiver: json['reciever'] as String?, + recipientId: (json['recipient_id'] as num?)?.toInt(), + receiverType: json['receiver_type'] as String?, + errorSeverity: json['error_severity'] as String?, + deliveryStatus: json['delivery_status'] as String?, + deliveryChannel: json['delivery_channel'] as String?, + ); + +Map _$InAppNotificationToJson(InAppNotification instance) => + { + 'id': instance.id, + 'type': instance.type, + 'level': instance.level, + 'image': instance.image, + 'payload': instance.payload, + 'is_read': instance.isRead, + 'reciever': instance.receiver, + 'recipient_id': instance.recipientId, + 'receiver_type': instance.receiverType, + 'error_severity': instance.errorSeverity, + 'delivery_status': instance.deliveryStatus, + 'delivery_channel': instance.deliveryChannel, + }; diff --git a/lib/models/notification_payload.dart b/lib/models/notification_payload.dart new file mode 100644 index 0000000..ad41dbc --- /dev/null +++ b/lib/models/notification_payload.dart @@ -0,0 +1,17 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'notification_payload.g.dart'; + +@JsonSerializable() +class NotificationPayload { + final String? message; + + final String? headline; + + const NotificationPayload({this.message, this.headline}); + + factory NotificationPayload.fromJson(Map json) => + _$NotificationPayloadFromJson(json); + + Map toJson() => _$NotificationPayloadToJson(this); +} diff --git a/lib/models/notification_payload.g.dart b/lib/models/notification_payload.g.dart new file mode 100644 index 0000000..a116421 --- /dev/null +++ b/lib/models/notification_payload.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'notification_payload.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NotificationPayload _$NotificationPayloadFromJson(Map json) => + NotificationPayload( + message: json['message'] as String?, + headline: json['headline'] as String?, + ); + +Map _$NotificationPayloadToJson( + NotificationPayload instance) => + { + 'message': instance.message, + 'headline': instance.headline, + }; diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 8107694..252bea4 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; import 'package:yimaru_app/models/app_update.dart'; +import 'package:yimaru_app/models/in_app_notification.dart'; import 'package:yimaru_app/models/learn_lesson.dart'; import 'package:yimaru_app/models/learn_practice.dart'; import 'package:yimaru_app/models/learn_program.dart'; @@ -355,6 +356,55 @@ class ApiService { } } + // Mark notifications + Future markNotificationsRead() async { + try { + await _service.dio.post( + '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kNotificationsUrl/$kMarkNotificationRead'); + } catch (e) { + return; + } + } + + // Get unread notifications + Future getUnreadNotifications() async { + try { + final Response response = await _service.dio.get( + '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kNotificationsUrl/$kUnreadUrl'); + + if (response.statusCode == 200) { + return response.data['unread']; + } + return 0; + } catch (e) { + return 0; + } + } + + // Get notifications + Future> getAllNotifications() async { + try { + List notifications = []; + + final Response response = await _service.dio + .get('$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kNotificationsUrl'); + + if (response.statusCode == 200) { + var data = response.data; + var decodedData = data['notifications'] as List; + notifications = decodedData.map( + (e) { + return InAppNotification.fromJson(e); + }, + ).toList(); + return notifications; + } + return []; + } catch (e) { + return []; + } + } + // Get assessment question sets Future> getAssessments() async { try { diff --git a/lib/services/in_app_notification_service.dart b/lib/services/in_app_notification_service.dart new file mode 100644 index 0000000..bf00f31 --- /dev/null +++ b/lib/services/in_app_notification_service.dart @@ -0,0 +1,44 @@ +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/models/in_app_notification.dart'; + +import '../app/app.locator.dart'; +import 'api_service.dart'; + +class InAppNotificationService with ListenableServiceMixin { + // Dependency injection + final _apiService = locator(); + + // Initialization + learnService() { + listenToReactiveValues([_unreadCount, _notifications]); + } + + // Unread count + int _unreadCount = 0; + + int get unreadCount => _unreadCount; + + // Notifications + List _notifications = []; + + List get notifications => _notifications; + + // Unread notifications + Future markNotificationRead() async { + await _apiService.markNotificationsRead(); + _unreadCount = await _apiService.getUnreadNotifications(); + notifyListeners(); + } + + // All notifications + Future getAllNotifications() async { + _notifications = await _apiService.getAllNotifications(); + notifyListeners(); + } + + // Unread notifications + Future getUnreadNotifications() async { + _unreadCount = await _apiService.getUnreadNotifications(); + notifyListeners(); + } +} diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart deleted file mode 100644 index 0ca0960..0000000 --- a/lib/services/notification_service.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:yimaru_app/app/app.locator.dart'; - -@pragma('vm:entry-point') -Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - await locator().setupFlutterNotifications(); - await locator().showNotification(message); -} - -class NotificationService { - final _messaging = FirebaseMessaging.instance; - - bool _isFlutterLocalNotificationInitialized = false; - - final _localNotifications = FlutterLocalNotificationsPlugin(); - - Future initialize() async { - // Initialize FCM token - await updateFCMToken(); - - FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); - - // Request permission - await _requestPermission(); - - // setup message handle - await _setupMessageHandler(); - - // Subscribe to all devices - subscribeToTopic('yimaru'); - } - - Future _requestPermission() async { - await _messaging.requestPermission( - alert: true, - badge: true, - sound: true, - carPlay: false, - provisional: false, - announcement: false, - criticalAlert: false); - } - - Future setupFlutterNotifications() async { - if (_isFlutterLocalNotificationInitialized) { - return; - } - - // Android setup - const channel = AndroidNotificationChannel( - 'yimaru', // id - 'Yimaru', // title - importance: Importance.high, - ); - - await _localNotifications - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(channel); - - const initializationSettingsAndroid = - AndroidInitializationSettings('@mipmap/ic_launcher'); - - // IOS setup - const initializationSettingsDarwin = DarwinInitializationSettings(); - - const initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, - iOS: initializationSettingsDarwin); - - // Flutter notification setup - await _localNotifications.initialize( - settings: initializationSettings, - onDidReceiveNotificationResponse: (NotificationResponse response) { - if (response.payload == 'Page') { - // navigatorKey.currentState?.pushNamed('RouteName'); - } - }, - ); - - _isFlutterLocalNotificationInitialized = true; - } - - Future showNotification(RemoteMessage message) async { - RemoteNotification? notification = message.notification; - AndroidNotification? android = message.notification?.android; - - if (notification != null && android != null) { - await _localNotifications.show( - id: notification.hashCode, - title: notification.title, - body: notification.body, - notificationDetails: const NotificationDetails( - android: AndroidNotificationDetails('yimaru', 'Yimaru', - enableVibration: true, - priority: Priority.high, - icon: '@mipmap/ic_launcher', - importance: Importance.high), - iOS: DarwinNotificationDetails( - presentAlert: true, presentBadge: true, presentSound: true)), - ); - } - } - - Future _setupMessageHandler() async { - // Foreground message - FirebaseMessaging.onMessage - .listen((RemoteMessage message) => showNotification(message)); - - // Background message - FirebaseMessaging.onMessageOpenedApp.listen(_handleBackgroundMessage); - - // Opened app - final initialMessage = await _messaging.getInitialMessage(); - - if (initialMessage != null) { - _handleBackgroundMessage(initialMessage); - } - } - - void _handleBackgroundMessage(RemoteMessage message) { - if (message.data['type'] == 'Page') { - // navigatorKey.currentState?.pushNamed('RouteName'); - } - } - - Future subscribeToTopic(String topic) async { - await FirebaseMessaging.instance.subscribeToTopic(topic); - } - - Future updateFCMToken() async { - // print('DEVICE TOKEN: ${await _messaging.getToken()}'); - _messaging.onTokenRefresh.listen((newToken) { - // updateTokenOnServer(newToken); - }); - } -} diff --git a/lib/services/push_notification_service.dart b/lib/services/push_notification_service.dart new file mode 100644 index 0000000..f435ce3 --- /dev/null +++ b/lib/services/push_notification_service.dart @@ -0,0 +1,136 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:yimaru_app/app/app.locator.dart'; + +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await locator().setupFlutterNotifications(); + await locator().showNotification(message); +} +class PushNotificationService { final _messaging = FirebaseMessaging.instance; + +bool _isFlutterLocalNotificationInitialized = false; + +final _localNotifications = FlutterLocalNotificationsPlugin(); + +Future initialize() async { + // Initialize FCM token + await updateFCMToken(); + + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + + // Request permission + await _requestPermission(); + + // setup message handle + await _setupMessageHandler(); + + // Subscribe to all devices + subscribeToTopic('yimaru'); +} + +Future _requestPermission() async { + await _messaging.requestPermission( + alert: true, + badge: true, + sound: true, + carPlay: false, + provisional: false, + announcement: false, + criticalAlert: false); +} + +Future setupFlutterNotifications() async { + if (_isFlutterLocalNotificationInitialized) { + return; + } + + // Android setup + const channel = AndroidNotificationChannel( + 'yimaru', // id + 'Yimaru', // title + importance: Importance.high, + ); + + await _localNotifications + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(channel); + + const initializationSettingsAndroid = + AndroidInitializationSettings('@mipmap/ic_launcher'); + + // IOS setup + const initializationSettingsDarwin = DarwinInitializationSettings(); + + const initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsDarwin); + + // Flutter notification setup + await _localNotifications.initialize( + settings: initializationSettings, + onDidReceiveNotificationResponse: (NotificationResponse response) { + if (response.payload == 'Page') { + // navigatorKey.currentState?.pushNamed('RouteName'); + } + }, + ); + + _isFlutterLocalNotificationInitialized = true; +} + +Future showNotification(RemoteMessage message) async { + RemoteNotification? notification = message.notification; + AndroidNotification? android = message.notification?.android; + + if (notification != null && android != null) { + await _localNotifications.show( + id: notification.hashCode, + title: notification.title, + body: notification.body, + notificationDetails: const NotificationDetails( + android: AndroidNotificationDetails('yimaru', 'Yimaru', + enableVibration: true, + priority: Priority.high, + icon: '@mipmap/ic_launcher', + importance: Importance.high), + iOS: DarwinNotificationDetails( + presentAlert: true, presentBadge: true, presentSound: true)), + ); + } +} + +Future _setupMessageHandler() async { + // Foreground message + FirebaseMessaging.onMessage + .listen((RemoteMessage message) => showNotification(message)); + + // Background message + FirebaseMessaging.onMessageOpenedApp.listen(_handleBackgroundMessage); + + // Opened app + final initialMessage = await _messaging.getInitialMessage(); + + if (initialMessage != null) { + _handleBackgroundMessage(initialMessage); + } +} + +void _handleBackgroundMessage(RemoteMessage message) { + if (message.data['type'] == 'Page') { + // navigatorKey.currentState?.pushNamed('RouteName'); + } +} + +Future subscribeToTopic(String topic) async { + await FirebaseMessaging.instance.subscribeToTopic(topic); +} + +Future updateFCMToken() async { + // print('DEVICE TOKEN: ${await _messaging.getToken()}'); + _messaging.onTokenRefresh.listen((newToken) { + // updateTokenOnServer(newToken); + }); +} +} diff --git a/lib/ui/common/app_constants.dart b/lib/ui/common/app_constants.dart index e5af44d..fbece22 100644 --- a/lib/ui/common/app_constants.dart +++ b/lib/ui/common/app_constants.dart @@ -13,12 +13,15 @@ String kCheckUrl = 'check'; String kFilesUrl = 'files'; +String kUnreadUrl = 'unread'; + String kApiVersionUrl = 'v1'; String kLevelsUrl = 'levels'; String kCoursesUrl = 'courses'; + String kModulesUrl = 'modules'; String kLessonsUrl = 'lessons'; @@ -69,6 +72,8 @@ String kQuestionSetsUrl = 'question-sets'; String kRequestResetCode = 'sendResetCode'; +String kNotificationsUrl = 'notifications'; + String kSubcategoriesUrl = 'sub-categories'; String kProgressSummary = 'progress-summary'; @@ -79,6 +84,8 @@ String kCoursePracticeQuestions = 'questions'; String kCatalogCoursesUrl = 'catalog-courses'; +String kMarkNotificationRead = 'mark-all-read'; + String kUpdateProfileImage = 'profile-picture'; String kSubscriptionsUrl = 'subscription-plans'; diff --git a/lib/ui/common/enmus.dart b/lib/ui/common/enmus.dart index 04bda1b..f2858b6 100644 --- a/lib/ui/common/enmus.dart +++ b/lib/ui/common/enmus.dart @@ -50,6 +50,7 @@ enum StateObjects { profileUpdate, resetPassword, learnPractice, + notifications, courseCatalogs, loginWithEmail, coursePractice, diff --git a/lib/ui/common/translations/codegen_loader.g.dart b/lib/ui/common/translations/codegen_loader.g.dart index 4290513..d6d8da6 100644 --- a/lib/ui/common/translations/codegen_loader.g.dart +++ b/lib/ui/common/translations/codegen_loader.g.dart @@ -213,7 +213,8 @@ class CodegenLoader extends AssetLoader { "keep_momentum": "በጣም ጥሩ ስራ! በዚሁ ብርታት ይቀጥሉ።", "completed_practices": "የተጠናቀቁ ልምምዶች", "total_practices": "ጠቅላላ ልምምዶች", - "progress_percentage": "የእድገት መቶኛ" + "progress_percentage": "የእድገት መቶኛ", + "notifications": "ማሳወቂያዎች" }; static const Map _en = { "loading": "Loading", @@ -433,7 +434,8 @@ class CodegenLoader extends AssetLoader { "keep_momentum": "Great job! Keep the momentum.", "completed_practices": "Completed Practices", "total_practices": "Total Practices", - "progress_percentage": "Progress Percentage" + "progress_percentage": "Progress Percentage", + "notifications": "Notifications" }; static const Map> mapLocales = { "am": _am, diff --git a/lib/ui/common/translations/locale_keys.g.dart b/lib/ui/common/translations/locale_keys.g.dart index e3cee97..cdb2b48 100644 --- a/lib/ui/common/translations/locale_keys.g.dart +++ b/lib/ui/common/translations/locale_keys.g.dart @@ -199,4 +199,5 @@ abstract class LocaleKeys { static const completed_practices = 'completed_practices'; static const total_practices = 'total_practices'; static const progress_percentage = 'progress_percentage'; + static const notifications = 'notifications'; } diff --git a/lib/ui/common/ui_helpers.dart b/lib/ui/common/ui_helpers.dart index 7f2e1b0..adc305c 100644 --- a/lib/ui/common/ui_helpers.dart +++ b/lib/ui/common/ui_helpers.dart @@ -333,6 +333,12 @@ TextStyle style14LG400 = const TextStyle( color: kcLightGrey, ); +TextStyle style12W600 = const TextStyle( + fontSize: 12, + color: kcWhite, + fontWeight: FontWeight.w600 +); + TextStyle style14MG400 = const TextStyle( color: kcMediumGrey, ); diff --git a/lib/ui/views/course/course_view.dart b/lib/ui/views/course/course_view.dart index 82b09fd..8438875 100644 --- a/lib/ui/views/course/course_view.dart +++ b/lib/ui/views/course/course_view.dart @@ -51,6 +51,8 @@ class CourseView extends StackedView { Widget _buildAppBar(CourseViewModel viewModel) => ProfileAppBar( name: viewModel.user?.firstName, profileImage: viewModel.user?.profilePicture, + unreadCount: viewModel.unreadCount.toString(), + onTap: () async => await viewModel.navigateToNotification(), ); Widget _buildCategoryColumnWrapper(CourseViewModel viewModel) => diff --git a/lib/ui/views/course/course_viewmodel.dart b/lib/ui/views/course/course_viewmodel.dart index 5b15267..7f08e1a 100644 --- a/lib/ui/views/course/course_viewmodel.dart +++ b/lib/ui/views/course/course_viewmodel.dart @@ -5,6 +5,7 @@ import '../../../app/app.locator.dart'; import '../../../app/app.router.dart'; import '../../../models/user.dart'; import '../../../services/authentication_service.dart'; +import '../../../services/in_app_notification_service.dart'; class CourseViewModel extends ReactiveViewModel { // Dependency injection @@ -13,15 +14,23 @@ class CourseViewModel extends ReactiveViewModel { final _authenticationService = locator(); + final _inAppNotificationService = locator(); + + @override List get listenableServices => - [_authenticationService]; + [_authenticationService,_inAppNotificationService]; // Current user User? get _user => _authenticationService.user; User? get user => _user; + // Notification count + int get _unreadCount => _inAppNotificationService.unreadCount; + + int get unreadCount => _unreadCount; + // Course final List> _courses = [ { @@ -41,6 +50,11 @@ class CourseViewModel extends ReactiveViewModel { // Navigation void pop() => _navigationService.back(); + Future navigateToNotification() async => + await _navigationService.navigateToNotificationView(); + Future navigateToCourseCatalog() async => await _navigationService.navigateToCourseCatalogView(); + + } diff --git a/lib/ui/views/failure/failure_view.dart b/lib/ui/views/failure/failure_view.dart index d775e2d..bf09e78 100644 --- a/lib/ui/views/failure/failure_view.dart +++ b/lib/ui/views/failure/failure_view.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/views/failure/screens/first_failure_screen.dart'; +import 'package:yimaru_app/ui/views/failure/screens/second_failure_screen.dart'; +import 'package:yimaru_app/ui/views/failure/screens/third_failure_screen.dart'; import '../../common/app_colors.dart'; import '../../common/ui_helpers.dart'; @@ -26,94 +30,38 @@ class FailureView extends StackedView { _buildScaffoldWrapper(); Widget _buildScaffoldWrapper() => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(), + backgroundColor: kcPrimaryColor, + body: _buildStartupScreens(), ); - Widget _buildScaffold() => Stack( - children: _buildScaffoldChildren(), + Widget _buildStartupScreens() => FlutterCarousel( + options: FlutterCarouselOptions( + autoPlay: true, + viewportFraction: 1, + showIndicator: false, + height: double.maxFinite, + ), + items: _buildScreens(), ); - List _buildScaffoldChildren() => [ - _buildBackground(), - _buildColumn(), + List _buildScreens() => [ + _buildFirstFailure(), + _buildSecondFailure(), + _buildThirdFailure(), ]; - Widget _buildBackground() => Image.asset( - 'assets/images/loading.png', - fit: BoxFit.fill, - width: double.maxFinite, - height: double.maxFinite, - ); - - Widget _buildColumn() => Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildColumnChildren(), - ); - - List _buildColumnChildren() => - [_buildIconWrapper(), _buildSafeWrapper()]; - - Widget _buildIconWrapper() => Padding( - padding: const EdgeInsets.only(top: 100), - child: _buildIcon(), - ); - - Widget _buildIcon() => SvgPicture.asset('assets/icons/logo.svg', height: 50); - - Widget _buildSafeWrapper() => SafeArea(child: _buildBottomSectionWrapper()); - - Widget _buildBottomSectionWrapper() => Padding( - padding: const EdgeInsets.only(bottom: 50), - child: _buildBottomSectionColumn(), - ); - - Widget _buildBottomSectionColumn() => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: _buildBottomSectionChildren(), - ); - - List _buildBottomSectionChildren() => [ - _buildLoadingTextWrapper(), - verticalSpaceSmall, - _buildRetryButtonWrapper() - ]; - - Widget _buildLoadingTextWrapper() => Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: _buildLoadingTextChildren(), - ); - - List _buildLoadingTextChildren() => [ - _buildLoadingText(), - horizontalSpaceSmall, - _buildIndicatorWrapper(), - ]; - - Widget _buildLoadingText() => - Text('$label ...', style: const TextStyle(color: kcWhite, fontSize: 16)); - - Widget _buildIndicatorWrapper() => SizedBox( - width: 16, - height: 16, - child: _buildIndicator(), - ); - - Widget _buildIndicator() => - const CustomCircularProgressIndicator(color: kcWhite); - - Widget _buildRetryButtonWrapper() => GestureDetector( + Widget _buildFirstFailure() => FirstFailureScreen( + label: label, onTap: onTap, - child: _buildRetryButton(), ); - Widget _buildRetryButton() => Text( - 'Retry', - style: style16W600.copyWith( - fontStyle: FontStyle.italic, decoration: TextDecoration.underline), + Widget _buildSecondFailure() => SecondFailureScreen( + label: label, + onTap: onTap, + ); + + Widget _buildThirdFailure() => ThirdFailureScreen( + label: label, + onTap: onTap, ); } diff --git a/lib/ui/views/failure/screens/first_failure_screen.dart b/lib/ui/views/failure/screens/first_failure_screen.dart new file mode 100644 index 0000000..9b88da0 --- /dev/null +++ b/lib/ui/views/failure/screens/first_failure_screen.dart @@ -0,0 +1,165 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart'; +import 'package:yimaru_app/ui/views/startup/startup_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; + +import '../../../common/translations/locale_keys.g.dart'; +import '../../../widgets/custom_circular_progress_indicator.dart'; + +class FirstFailureScreen extends ViewModelWidget { + final String label; + final GestureTapCallback onTap; + + const FirstFailureScreen({super.key,required this.onTap,required this.label}); + + @override + Widget build(BuildContext context, FailureViewModel viewModel) => + _buildScaffoldWrapper(); + + Widget _buildScaffoldWrapper( ) => Scaffold( + backgroundColor: kcPrimaryColor, + body: _buildScaffoldPadding(), + ); + Widget _buildScaffoldPadding( ) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildScaffold(), + ); + + Widget _buildScaffold( ) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildScaffoldChildren(), + ); + + List _buildScaffoldChildren( ) => + [_buildUpperColumn(), _buildLowerColumnWrapper()]; + + Widget _buildUpperColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => + [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; + + Widget _buildIconWrapper() => Align( + alignment: Alignment.topLeft, + child: _buildIcon(), + ); + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/logo_white.svg', + height: 25, + ); + + Widget _buildLowerColumnWrapper( ) => Expanded( + child: _buildLowerColumn(), + ); + + Widget _buildLowerColumn( ) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildLowerColumnChildren(), + ); + + List _buildLowerColumnChildren( ) => [ + _buildTitle(), + verticalSpaceMedium, + _buildImageWrapper(), + verticalSpaceMedium, + _buildSafeWrapper() + ]; + + Widget _buildTitle() => Text.rich( + TextSpan( + text: 'እንግሊዝኛ\n', + style: style25W600, + children: [ + TextSpan( + text: 'በማንኛውም', + style: style25W400, + ), + TextSpan( + text: ' ሰዓት ', + style: style25W600, + ), + TextSpan( + text: 'ይማሩ!', + style: style25W400, + ), + ], + ), + ); + + Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); + + Widget _buildImageClipper() => ClipRRect( + borderRadius: BorderRadius.circular(25), + child: _buildImage(), + ); + + Widget _buildImage() => Image.asset( + 'assets/images/landing_1.png', + fit: BoxFit.cover, + ); + + Widget _buildSafeWrapper( ) => + SafeArea(child: _buildContinueButtonWrapper()); + + Widget _buildContinueButtonWrapper( ) => Align( + alignment: Alignment.bottomCenter, + child: _buildLoadingTextContainer(), + ); + + + + Widget _buildLoadingTextContainer() => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildLoadingTextWrapper(), + ); + + Widget _buildLoadingTextWrapper() => Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildLoadingTextChildren(), + ); + + List _buildLoadingTextChildren() => [ + _buildLoadingText(), + horizontalSpaceSmall, + _buildIndicatorWrapper(), + horizontalSpaceSmall, + _buildRetryButtonWrapper() + ]; + + Widget _buildLoadingText() => + Text('$label...', style: style16W600); + + Widget _buildIndicatorWrapper() => SizedBox( + width: 16, + height: 16, + child: _buildIndicator(), + ); + + Widget _buildIndicator() => + const CustomCircularProgressIndicator(color: kcWhite); + + + Widget _buildRetryButtonWrapper() => GestureDetector( + onTap: onTap, + child: _buildRetryButton(), + ); + + Widget _buildRetryButton() => Text( + 'Retry', + style: style16W600.copyWith( + fontStyle: FontStyle.italic, decoration: TextDecoration.underline), + ); +} diff --git a/lib/ui/views/failure/screens/second_failure_screen.dart b/lib/ui/views/failure/screens/second_failure_screen.dart new file mode 100644 index 0000000..43e835f --- /dev/null +++ b/lib/ui/views/failure/screens/second_failure_screen.dart @@ -0,0 +1,161 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; + +import '../../../common/translations/locale_keys.g.dart'; +import '../../../widgets/custom_circular_progress_indicator.dart'; + +class SecondFailureScreen extends ViewModelWidget { + final String label; + final GestureTapCallback onTap; + + const SecondFailureScreen( + {super.key, required this.onTap, required this.label}); + + @override + Widget build(BuildContext context, FailureViewModel viewModel) => + _buildScaffoldWrapper(); + + Widget _buildScaffoldWrapper() => Scaffold( + backgroundColor: Colors.amber, + body: _buildScaffoldPadding(), + ); + + Widget _buildScaffoldPadding() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildScaffold(), + ); + + Widget _buildScaffold() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildScaffoldChildren(), + ); + + List _buildScaffoldChildren() => + [_buildUpperColumn(), _buildLowerColumnWrapper()]; + + Widget _buildUpperColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => + [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; + + Widget _buildIconWrapper() => Align( + alignment: Alignment.topLeft, + child: _buildIcon(), + ); + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/logo_purple.svg', + height: 25, + ); + + Widget _buildLowerColumnWrapper() => Expanded( + child: _buildLowerColumn(), + ); + + Widget _buildLowerColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildLowerColumnChildren(), + ); + + List _buildLowerColumnChildren() => [ + _buildTitle(), + verticalSpaceMedium, + _buildImageWrapper(), + verticalSpaceMedium, + _buildSafeWrapper() + ]; + + Widget _buildTitle() => Text.rich( + TextSpan( + text: 'እንግሊዝኛ\n', + style: style25P600, + children: [ + TextSpan( + text: 'በማንኛውም', + style: style25P400, + ), + TextSpan( + text: ' እድሜ ', + style: style25P600, + ), + TextSpan( + text: 'ይማሩ!', + style: style25P400, + ), + ], + ), + ); + + Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); + + Widget _buildImageClipper() => ClipRRect( + borderRadius: BorderRadius.circular(25), + child: _buildImage(), + ); + + Widget _buildImage() => Image.asset( + 'assets/images/landing_2.png', + fit: BoxFit.cover, + ); + + Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper()); + + Widget _buildContinueButtonWrapper() => Align( + alignment: Alignment.bottomCenter, + child: _buildLoadingTextContainer(), + ); + + Widget _buildLoadingTextContainer() => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildLoadingTextWrapper(), + ); + + Widget _buildLoadingTextWrapper() => Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildLoadingTextChildren(), + ); + + List _buildLoadingTextChildren() => [ + _buildLoadingText(), + horizontalSpaceSmall, + _buildIndicatorWrapper(), + verticalSpaceSmall, + _buildRetryButtonWrapper() + ]; + + Widget _buildLoadingText() => Text('$label...', style: style16P600); + + Widget _buildIndicatorWrapper() => SizedBox( + width: 16, + height: 16, + child: _buildIndicator(), + ); + + Widget _buildIndicator() => + const CustomCircularProgressIndicator(color: kcPrimaryColor); + + Widget _buildRetryButtonWrapper() => GestureDetector( + onTap: onTap, + child: _buildRetryButton(), + ); + + Widget _buildRetryButton() => Text( + 'Retry', + style: style16P600.copyWith( + fontStyle: FontStyle.italic, decoration: TextDecoration.underline), + ); +} diff --git a/lib/ui/views/failure/screens/third_failure_screen.dart b/lib/ui/views/failure/screens/third_failure_screen.dart new file mode 100644 index 0000000..d7a64c4 --- /dev/null +++ b/lib/ui/views/failure/screens/third_failure_screen.dart @@ -0,0 +1,165 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; + +import '../../../common/translations/locale_keys.g.dart'; +import '../../../widgets/custom_circular_progress_indicator.dart'; + +class ThirdFailureScreen extends ViewModelWidget { + final String label; + final GestureTapCallback onTap; + + const ThirdFailureScreen( + {super.key, required this.onTap, required this.label}); + + @override + Widget build(BuildContext context, FailureViewModel viewModel) => + _buildScaffoldWrapper(); + + Widget _buildScaffoldWrapper( ) => Scaffold( + backgroundColor:kcWhite, + body: _buildScaffoldPadding(), + ); + Widget _buildScaffoldPadding( ) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildScaffold(), + ); + + Widget _buildScaffold( ) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildScaffoldChildren(), + ); + + List _buildScaffoldChildren( ) => + [_buildUpperColumn(), _buildLowerColumnWrapper()]; + + Widget _buildUpperColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => + [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; + + Widget _buildIconWrapper() => Align( + alignment: Alignment.topLeft, + child: _buildIcon(), + ); + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/logo_purple.svg', + height: 25, + ); + + Widget _buildLowerColumnWrapper( ) => Expanded( + child: _buildLowerColumn(), + ); + + Widget _buildLowerColumn( ) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildLowerColumnChildren(), + ); + + List _buildLowerColumnChildren( ) => [ + _buildTitle(), + verticalSpaceMedium, + _buildImageWrapper(), + verticalSpaceMedium, + _buildSafeWrapper() + ]; + + + Widget _buildTitle() => Text.rich( + TextSpan( + text: 'እንግሊዝኛ\n', + style: style25P600, + children: [ + TextSpan( + text: 'በማንኛውም', + style: style25P400, + ), + TextSpan( + text: ' ቦታ ', + style: style25P600, + ), + TextSpan( + text: 'ይማሩ!', + style: style25P400, + ), + ], + ), + ); + + Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); + + Widget _buildImageClipper() => ClipRRect( + borderRadius: BorderRadius.circular(25), + child: _buildImage(), + ); + + Widget _buildImage() => Image.asset( + 'assets/images/landing_3.png', + fit: BoxFit.cover, + ); + + Widget _buildSafeWrapper( ) => + SafeArea(child: _buildContinueButtonWrapper()); + + Widget _buildContinueButtonWrapper( ) => Align( + alignment: Alignment.bottomCenter, + child: _buildLoadingTextContainer(), + ); + + + + Widget _buildLoadingTextContainer() => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildLoadingTextWrapper(), + ); + + Widget _buildLoadingTextWrapper() => Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildLoadingTextChildren(), + ); + + List _buildLoadingTextChildren() => [ + _buildLoadingText(), + horizontalSpaceSmall, + _buildIndicatorWrapper(), + horizontalSpaceSmall,_buildRetryButtonWrapper() + ]; + + Widget _buildLoadingText() => + Text('$label...', style: style16P600); + + Widget _buildIndicatorWrapper() => SizedBox( + width: 16, + height: 16, + child: _buildIndicator(), + ); + + Widget _buildIndicator() => + const CustomCircularProgressIndicator(color: kcPrimaryColor); + + Widget _buildRetryButtonWrapper() => GestureDetector( + onTap: onTap, + child: _buildRetryButton(), + ); + + Widget _buildRetryButton() => Text( + 'Retry', + style: style16P600.copyWith( + fontStyle: FontStyle.italic, decoration: TextDecoration.underline), + ); +} + diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index 9118869..430f898 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -17,6 +17,8 @@ class HomeView extends StackedView { @override void onViewModelReady(HomeViewModel viewModel) async { await viewModel.inAppUpdate(); + await viewModel.getUnreadNotifications(); + super.onViewModelReady(viewModel); } diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index 4514e77..b87aba0 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -8,6 +8,7 @@ import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; import '../../../services/authentication_service.dart'; +import '../../../services/in_app_notification_service.dart'; import '../../../services/in_app_update_service.dart'; class HomeViewModel extends ReactiveViewModel { @@ -22,9 +23,11 @@ class HomeViewModel extends ReactiveViewModel { final _authenticationService = locator(); + final _inAppNotificationService = locator(); + @override List get listenableServices => - [_authenticationService]; + [_authenticationService,_inAppNotificationService]; // Current user User? get _user => _authenticationService.user; @@ -63,4 +66,12 @@ class HomeViewModel extends ReactiveViewModel { await _inAppUpdateService.checkForUpdate(); } } + + + // Unread notifications + Future getUnreadNotifications() async { + if (await _statusChecker.checkConnection()) { + await _inAppNotificationService.getUnreadNotifications(); + } + } } diff --git a/lib/ui/views/landing/landing_view.dart b/lib/ui/views/landing/landing_view.dart index 471389a..8b18299 100644 --- a/lib/ui/views/landing/landing_view.dart +++ b/lib/ui/views/landing/landing_view.dart @@ -3,7 +3,6 @@ import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/views/landing/screens/first_landing_screen.dart'; -import 'package:yimaru_app/ui/views/landing/screens/fourth_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/second_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/third_landing_screen.dart'; @@ -44,17 +43,15 @@ class LandingView extends StackedView { ); List _buildScreens() => [ - _buildFirstWelcome(), - _buildSecondWelcome(), - _buildThirdWelcome(), - _buildFourthWelcome() + _buildFirstLanding(), + _buildSecondLanding(), + _buildThirdLanding(), ]; - Widget _buildFirstWelcome() => const FirstLandingScreen(); + Widget _buildFirstLanding() => const FirstLandingScreen(); - Widget _buildSecondWelcome() => const SecondLandingScreen(); + Widget _buildSecondLanding() => const SecondLandingScreen(); - Widget _buildThirdWelcome() => const ThirdLandingScreen(); + Widget _buildThirdLanding() => const ThirdLandingScreen(); - Widget _buildFourthWelcome() => const FourthLandingScreen(); } diff --git a/lib/ui/views/landing/screens/first_landing_screen.dart b/lib/ui/views/landing/screens/first_landing_screen.dart index 3d047c0..932ea23 100644 --- a/lib/ui/views/landing/screens/first_landing_screen.dart +++ b/lib/ui/views/landing/screens/first_landing_screen.dart @@ -49,7 +49,7 @@ class FirstLandingScreen extends ViewModelWidget { ); Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/logo.svg', + 'assets/icons/logo_white.svg', height: 25, ); @@ -100,7 +100,7 @@ class FirstLandingScreen extends ViewModelWidget { ); Widget _buildImage() => Image.asset( - 'assets/images/landing_1.jpg', + 'assets/images/landing_1.png', fit: BoxFit.cover, ); @@ -126,10 +126,10 @@ class FirstLandingScreen extends ViewModelWidget { Widget _buildContinueButton(LandingViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Next', borderRadius: 25, - onTap: viewModel.next, + text: 'Get Started', backgroundColor: kcWhite, foregroundColor: kcPrimaryColor, + onTap: () async => await viewModel.setFirstTimeInstall(), ); } diff --git a/lib/ui/views/landing/screens/fourth_landing_screen.dart b/lib/ui/views/landing/screens/fourth_landing_screen.dart deleted file mode 100644 index 659c76a..0000000 --- a/lib/ui/views/landing/screens/fourth_landing_screen.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/ui/common/app_colors.dart'; -import 'package:yimaru_app/ui/common/ui_helpers.dart'; -import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; - -import '../../../widgets/custom_circular_progress_indicator.dart'; -import '../landing_viewmodel.dart'; - -class FourthLandingScreen extends ViewModelWidget { - const FourthLandingScreen({super.key}); - - @override - Widget build(BuildContext context, LandingViewModel viewModel) => - _buildScaffoldWrapper(viewModel); - - Widget _buildScaffoldWrapper(LandingViewModel viewModel) => Scaffold( - backgroundColor: Colors.amber, - body: _buildScaffoldPadding(viewModel), - ); - Widget _buildScaffoldPadding(LandingViewModel viewModel) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildScaffold(viewModel), - ); - - Widget _buildScaffold(LandingViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildScaffoldChildren(viewModel), - ); - - List _buildScaffoldChildren(LandingViewModel viewModel) => - [_buildUpperColumn(), _buildLowerColumnWrapper(viewModel)]; - - Widget _buildUpperColumn() => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildUpperColumnChildren(), - ); - - List _buildUpperColumnChildren() => - [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; - - Widget _buildIconWrapper() => Align( - alignment: Alignment.topLeft, - child: _buildIcon(), - ); - - Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/logo.svg', - color: kcPrimaryColor, - height: 25, - ); - - Widget _buildLowerColumnWrapper(LandingViewModel viewModel) => Expanded( - child: _buildLowerColumn(viewModel), - ); - - Widget _buildLowerColumn(LandingViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildLowerColumnChildren(viewModel), - ); - - List _buildLowerColumnChildren(LandingViewModel viewModel) => [ - _buildTitle(), - verticalSpaceMedium, - _buildImageWrapper(), - verticalSpaceMedium, - _buildSafeWrapper(viewModel) - ]; - - Widget _buildTitle() => Text.rich( - TextSpan( - text: 'እንግሊዝኛ\n', - style: style25P600, - children: [ - TextSpan( - text: 'በማንኛውም', - style: style25P400, - ), - TextSpan( - text: ' እድሜ ', - style: style25P600, - ), - TextSpan( - text: 'ይማሩ!', - style: style25P400, - ), - ], - ), - ); - - Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); - - Widget _buildImageClipper() => ClipRRect( - borderRadius: BorderRadius.circular(25), - child: _buildImage(), - ); - - Widget _buildImage() => Image.asset( - 'assets/images/landing_2.jpg', - fit: BoxFit.cover, - ); - - Widget _buildSafeWrapper(LandingViewModel viewModel) => - SafeArea(child: _buildContinueButtonWrapper(viewModel)); - - Widget _buildContinueButtonWrapper(LandingViewModel viewModel) => Align( - alignment: Alignment.bottomCenter, - child: _buildButtonContainer(viewModel), - ); - - Widget _buildButtonContainer(LandingViewModel viewModel) => Padding( - padding: const EdgeInsets.only(bottom: 50), - child: _buildContinueButtonState(viewModel), - ); - - Widget _buildContinueButtonState(LandingViewModel viewModel) => - viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel); - - Widget _buildIndicator() => - const CustomCircularProgressIndicator(color: kcWhite); - - Widget _buildContinueButton(LandingViewModel viewModel) => - CustomElevatedButton( - height: 55, - borderRadius: 25, - text: 'Get Started', - foregroundColor: kcWhite, - backgroundColor: kcPrimaryColor, - onTap: () async => await viewModel.setFirstTimeInstall(), - ); -} diff --git a/lib/ui/views/landing/screens/second_landing_screen.dart b/lib/ui/views/landing/screens/second_landing_screen.dart index c4657d3..a9d49da 100644 --- a/lib/ui/views/landing/screens/second_landing_screen.dart +++ b/lib/ui/views/landing/screens/second_landing_screen.dart @@ -49,7 +49,7 @@ class SecondLandingScreen extends ViewModelWidget { ); Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/logo.svg', + 'assets/icons/logo_purple.svg', color: kcPrimaryColor, height: 25, ); @@ -101,7 +101,7 @@ class SecondLandingScreen extends ViewModelWidget { ); Widget _buildImage() => Image.asset( - 'assets/images/landing_2.jpg', + 'assets/images/landing_2.png', fit: BoxFit.cover, ); @@ -127,10 +127,10 @@ class SecondLandingScreen extends ViewModelWidget { Widget _buildContinueButton(LandingViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Next', borderRadius: 25, - onTap: viewModel.next, + text: 'Get Started', foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, + onTap: () async => await viewModel.setFirstTimeInstall(), ); } diff --git a/lib/ui/views/landing/screens/third_landing_screen.dart b/lib/ui/views/landing/screens/third_landing_screen.dart index 046314e..69719da 100644 --- a/lib/ui/views/landing/screens/third_landing_screen.dart +++ b/lib/ui/views/landing/screens/third_landing_screen.dart @@ -49,7 +49,7 @@ class ThirdLandingScreen extends ViewModelWidget { ); Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/logo.svg', + 'assets/icons/logo_purple.svg', color: kcPrimaryColor, height: 25, ); @@ -101,7 +101,7 @@ class ThirdLandingScreen extends ViewModelWidget { ); Widget _buildImage() => Image.asset( - 'assets/images/landing_3.jpg', + 'assets/images/landing_3.png', fit: BoxFit.cover, ); @@ -127,10 +127,10 @@ class ThirdLandingScreen extends ViewModelWidget { Widget _buildContinueButton(LandingViewModel viewModel) => CustomElevatedButton( height: 55, - text: 'Next', borderRadius: 25, - onTap: viewModel.next, + text: 'Get Started', foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, + onTap: () async => await viewModel.setFirstTimeInstall(), ); } diff --git a/lib/ui/views/learn_course/learn_course_view.dart b/lib/ui/views/learn_course/learn_course_view.dart index 2f0ab8d..4787123 100644 --- a/lib/ui/views/learn_course/learn_course_view.dart +++ b/lib/ui/views/learn_course/learn_course_view.dart @@ -152,10 +152,8 @@ class LearnCourseView extends StackedView { context: context, viewModel: viewModel, course: viewModel.courses[index]), - onViewTap: () async => - await viewModel.navigateToLearnModule( - first: first && index ==0, - course: viewModel.courses[index]), + onViewTap: () async => await viewModel.navigateToLearnModule( + first: first && index == 0, course: viewModel.courses[index]), ), separatorBuilder: (context, index) => verticalSpaceSmall, ); diff --git a/lib/ui/views/learn_course/learn_course_viewmodel.dart b/lib/ui/views/learn_course/learn_course_viewmodel.dart index 2110943..e65e846 100644 --- a/lib/ui/views/learn_course/learn_course_viewmodel.dart +++ b/lib/ui/views/learn_course/learn_course_viewmodel.dart @@ -32,8 +32,10 @@ class LearnCourseViewModel extends ReactiveViewModel { Future navigateToLearnSubscription() async => await _navigationService.navigateToLearnSubscriptionView(); - Future navigateToLearnModule({required bool first,required LearnCourse course}) async => - _navigationService.navigateToLearnModuleView(first: first,course: course); + Future navigateToLearnModule( + {required bool first, required LearnCourse course}) async => + _navigationService.navigateToLearnModuleView( + first: first, course: course); Future navigateToLearnPractice( {required int id, required String level}) async => diff --git a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart index 0537477..f73cd2f 100644 --- a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart +++ b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart @@ -33,7 +33,6 @@ class LearnLessonDetailView extends StackedView { required LearnLessonDetailViewModel viewModel}) async { await viewModel.pause(); await viewModel.navigateToLearnPractice(lesson.id ?? 0); - } @override diff --git a/lib/ui/views/learn_module/learn_module_view.dart b/lib/ui/views/learn_module/learn_module_view.dart index f3bcd0d..e7d8a5e 100644 --- a/lib/ui/views/learn_module/learn_module_view.dart +++ b/lib/ui/views/learn_module/learn_module_view.dart @@ -203,10 +203,8 @@ class LearnModuleView extends StackedView { viewModel: viewModel, module: viewModel.modules[index], ), - onModuleTap: () async => - await viewModel.navigateToLearnLesson( - first: first && index == 0, - module: viewModel.modules[index]), + onModuleTap: () async => await viewModel.navigateToLearnLesson( + first: first && index == 0, module: viewModel.modules[index]), ), ); diff --git a/lib/ui/views/learn_module/learn_module_viewmodel.dart b/lib/ui/views/learn_module/learn_module_viewmodel.dart index c43f4b7..43b101b 100644 --- a/lib/ui/views/learn_module/learn_module_viewmodel.dart +++ b/lib/ui/views/learn_module/learn_module_viewmodel.dart @@ -35,8 +35,10 @@ class LearnModuleViewModel extends ReactiveViewModel { // Navigation void pop() => _navigationService.back(); - Future navigateToLearnLesson({required bool first, required LearnModule module}) async => - await _navigationService.navigateToLearnLessonView(first:first,module: module); + Future navigateToLearnLesson( + {required bool first, required LearnModule module}) async => + await _navigationService.navigateToLearnLessonView( + first: first, module: module); Future navigateToLearnPractice( {required int id, required String module}) async => diff --git a/lib/ui/views/learn_program/learn_program_view.dart b/lib/ui/views/learn_program/learn_program_view.dart index 4d661b7..3f23eb1 100644 --- a/lib/ui/views/learn_program/learn_program_view.dart +++ b/lib/ui/views/learn_program/learn_program_view.dart @@ -61,6 +61,8 @@ class LearnProgramView extends StackedView { Widget _buildAppBar(LearnProgramViewModel viewModel) => ProfileAppBar( name: viewModel.user?.firstName, profileImage: viewModel.user?.profilePicture, + unreadCount: viewModel.unreadCount.toString(), + onTap: () async => await viewModel.navigateToNotification(), ); Widget _buildProgramsColumnWrapper(LearnProgramViewModel viewModel) => diff --git a/lib/ui/views/learn_program/learn_program_viewmodel.dart b/lib/ui/views/learn_program/learn_program_viewmodel.dart index 738c359..b244f7f 100644 --- a/lib/ui/views/learn_program/learn_program_viewmodel.dart +++ b/lib/ui/views/learn_program/learn_program_viewmodel.dart @@ -6,6 +6,7 @@ import 'package:yimaru_app/models/learn_program.dart'; import '../../../app/app.locator.dart'; import '../../../models/user.dart'; import '../../../services/authentication_service.dart'; +import '../../../services/in_app_notification_service.dart'; import '../../../services/learn_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/enmus.dart'; @@ -21,23 +22,34 @@ class LearnProgramViewModel extends ReactiveViewModel { final _authenticationService = locator(); + final _inAppNotificationService = locator(); + @override List get listenableServices => - [_learnService, _authenticationService]; + [_learnService, _authenticationService,_inAppNotificationService]; // Current user User? get _user => _authenticationService.user; User? get user => _user; + // Notification count + int get _unreadCount => _inAppNotificationService.unreadCount; + + int get unreadCount => _unreadCount; + // Learn programs List get _learnPrograms => _learnService.programs; List get learnPrograms => _learnPrograms; // Navigation - Future navigateToLearnCourse({required int id,required bool first}) async => - _navigationService.navigateToLearnCourseView(id: id,first: first); + Future navigateToNotification() async => + await _navigationService.navigateToNotificationView(); + + Future navigateToLearnCourse( + {required int id, required bool first}) async => + _navigationService.navigateToLearnCourseView(id: id, first: first); // Remote api call diff --git a/lib/ui/views/notification/notification_view.dart b/lib/ui/views/notification/notification_view.dart new file mode 100644 index 0000000..e3bd6f5 --- /dev/null +++ b/lib/ui/views/notification/notification_view.dart @@ -0,0 +1,112 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/widgets/notification_card.dart'; + +import '../../../models/in_app_notification.dart'; +import '../../common/app_colors.dart'; +import '../../common/enmus.dart'; +import '../../common/translations/locale_keys.g.dart'; +import '../../common/ui_helpers.dart'; +import '../../widgets/custom_circular_progress_indicator.dart'; +import '../../widgets/small_app_bar.dart'; +import 'notification_viewmodel.dart'; + +class NotificationView extends StackedView { + const NotificationView({Key? key}) : super(key: key); + + @override + void onViewModelReady(NotificationViewModel viewModel) async { + await viewModel.getAllNotifications(); + await viewModel.markNotificationsRead(); + super.onViewModelReady(viewModel); + } + + @override + NotificationViewModel viewModelBuilder(BuildContext context) => + NotificationViewModel(); + + @override + Widget builder( + BuildContext context, + NotificationViewModel viewModel, + Widget? child, + ) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(NotificationViewModel viewModel) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffoldContainer(viewModel), + ); + + Widget _buildScaffoldContainer(NotificationViewModel viewModel) => Container( + decoration: bgDecoration, + child: _buildScaffold(viewModel), + ); + + Widget _buildScaffold(NotificationViewModel viewModel) => + SafeArea(child: _buildBodyWrapper(viewModel)); + + Widget _buildBodyWrapper(NotificationViewModel viewModel) => + _buildBody(viewModel); + + Widget _buildBody(NotificationViewModel viewModel) => _buildColumn(viewModel); + + Widget _buildColumn(NotificationViewModel viewModel) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildColumnChildren(viewModel), + ); + + List _buildColumnChildren(NotificationViewModel viewModel) => [ + verticalSpaceMedium, + _buildAppBarWrapper(viewModel), + verticalSpaceMedium, + _buildNotificationsColumnWrapper(viewModel) + ]; + + Widget _buildAppBarWrapper(NotificationViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildAppbar(viewModel), + ); + + Widget _buildAppbar(NotificationViewModel viewModel) => SmallAppBar( + showBackButton: true, + onPop: viewModel.pop, + title: LocaleKeys.notifications.tr(), + ); + + + Widget _buildNotificationsColumnWrapper(NotificationViewModel viewModel) => + Expanded(child: _buildNotificationsColumnScrollView(viewModel)); + + Widget _buildNotificationsColumnScrollView(NotificationViewModel viewModel) => + SingleChildScrollView( + child: _buildListViewBuilder(viewModel), + ); + + Widget _buildListViewBuilder(NotificationViewModel viewModel) => + viewModel.busy(StateObjects.notifications) + ? _buildProgressIndicator() + : _buildListView(viewModel); + + Widget _buildProgressIndicator() => const Center( + child: CustomCircularProgressIndicator(color: kcPrimaryColor), + ); + + Widget _buildListView(NotificationViewModel viewModel) => ListView.separated( + shrinkWrap: true, + itemCount: viewModel.notifications.length, + physics: const NeverScrollableScrollPhysics(), + separatorBuilder: (context, index) => verticalSpaceSmall, + itemBuilder: (context, index) => _buildCard( + notification: viewModel.notifications[index], + ), + ); + + Widget _buildCard({ + required InAppNotification notification, + }) => + NotificationCard(notification: notification); + + +} diff --git a/lib/ui/views/notification/notification_viewmodel.dart b/lib/ui/views/notification/notification_viewmodel.dart new file mode 100644 index 0000000..92ac2d9 --- /dev/null +++ b/lib/ui/views/notification/notification_viewmodel.dart @@ -0,0 +1,72 @@ +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +import '../../../app/app.locator.dart'; +import '../../../models/in_app_notification.dart'; +import '../../../services/in_app_notification_service.dart'; +import '../../../services/localization_service.dart'; +import '../../../services/status_checker_service.dart'; +import '../../common/enmus.dart'; + +class NotificationViewModel extends ReactiveViewModel { + // Dependency injection + final _statusChecker = locator(); + + final _navigationService = locator(); + + final _localizationService = locator(); + + final _inAppNotificationService = locator(); + + @override + List get listenableServices => + [_localizationService, _inAppNotificationService]; + + // Languages + Map get _selectedLanguage => + _localizationService.selectedLanguage; + + Map get selectedLanguage => _selectedLanguage; + + // Notifications + List get _notifications => + _inAppNotificationService.notifications; + + List get notifications => _notifications; + + // Notification count + int get _unreadCount => _inAppNotificationService.unreadCount; + + int get unreadCount => _unreadCount; + + // Navigation + void pop() => _navigationService.back(); + + // Remote api call + + // Notifications + Future getAllNotifications() async => + await runBusyFuture(_getAllNotifications(), + busyObject: StateObjects.notifications); + + Future _getAllNotifications() async { + if (await _statusChecker.checkConnection()) { + await _inAppNotificationService.getAllNotifications(); + } + } + + Future getUnreadNotifications() async => + await runBusyFuture(_getUnreadNotifications()); + + Future _getUnreadNotifications() async { + if (await _statusChecker.checkConnection()) { + await _inAppNotificationService.getUnreadNotifications(); + } + } + + Future markNotificationsRead() async { + if (await _statusChecker.checkConnection()) { + await _inAppNotificationService.markNotificationRead(); + } + } +} diff --git a/lib/ui/views/profile/profile_view.dart b/lib/ui/views/profile/profile_view.dart index 0fd9d51..e9807bb 100644 --- a/lib/ui/views/profile/profile_view.dart +++ b/lib/ui/views/profile/profile_view.dart @@ -5,6 +5,7 @@ import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/widgets/notification_icon.dart'; import 'package:yimaru_app/ui/widgets/profile_card.dart'; import 'package:yimaru_app/ui/widgets/profile_image.dart'; import 'package:yimaru_app/ui/widgets/view_profile_button.dart'; @@ -89,8 +90,9 @@ class ProfileView extends StackedView { required ProfileViewModel viewModel}) => Column( children: [ + verticalSpaceSmall, verticalSpaceMedium, - _buildNotificationIconWrapper(), + _buildNotificationIconWrapper(viewModel), _buildProfileSection(context: context, viewModel: viewModel), verticalSpaceSmall, _buildViewProfileButton(viewModel), @@ -102,12 +104,10 @@ class ProfileView extends StackedView { ], ); - Widget _buildNotificationIconWrapper() => - Align(alignment: Alignment.bottomRight, child: _buildNotificationIcon()); - - Widget _buildNotificationIcon() => const Icon( - Icons.notifications_none, - color: kcDarkGrey, + Widget _buildNotificationIconWrapper(ProfileViewModel viewModel) => + NotificationIcon( + count: viewModel.unreadCount.toString(), + onTap: () async => await viewModel.navigateToNotification(), ); Widget _buildProfileSection( diff --git a/lib/ui/views/profile/profile_viewmodel.dart b/lib/ui/views/profile/profile_viewmodel.dart index 6715fec..454e925 100644 --- a/lib/ui/views/profile/profile_viewmodel.dart +++ b/lib/ui/views/profile/profile_viewmodel.dart @@ -11,6 +11,7 @@ import '../../../models/user.dart'; import '../../../services/api_service.dart'; import '../../../services/authentication_service.dart'; import '../../../services/google_auth_service.dart'; +import '../../../services/in_app_notification_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/app_colors.dart'; @@ -23,21 +24,26 @@ class ProfileViewModel extends ReactiveViewModel { final _navigationService = locator(); - final _googleAuthService = locator(); - final _imagePickerService = locator(); final _authenticationService = locator(); + final _inAppNotificationService = locator(); + @override List get listenableServices => - [_authenticationService]; + [_authenticationService,_inAppNotificationService]; // Current user User? get _user => _authenticationService.user; User? get user => _user; + // Notification count + int get _unreadCount => _inAppNotificationService.unreadCount; + + int get unreadCount => _unreadCount; + // Image picker Future openCamera() async => runBusyFuture(_openCamera(), busyObject: StateObjects.profileImage); @@ -80,21 +86,24 @@ class ProfileViewModel extends ReactiveViewModel { // Navigation void pop() => _navigationService.back(); - Future navigateToProfileDetail() async => - await _navigationService.navigateToProfileDetailView(); - - Future navigateToDownloads() async => - await _navigationService.navigateToDownloadsView(); + Future navigateToSupport() async => + await _navigationService.navigateToSupportView(); Future navigateToProgress() async => await _navigationService.navigateToProgressView(); + Future navigateToDownloads() async => + await _navigationService.navigateToDownloadsView(); + + Future navigateToNotification() async => + await _navigationService.navigateToNotificationView(); + + Future navigateToProfileDetail() async => + await _navigationService.navigateToProfileDetailView(); + Future navigateToAccountPrivacy() async => await _navigationService.navigateToAccountPrivacyView(); - Future navigateToSupport() async => - await _navigationService.navigateToSupportView(); - Future navigateToLogin() async => await _navigationService.clearStackAndShow(Routes.loginView); diff --git a/lib/ui/views/startup/screens/first_startup_screen.dart b/lib/ui/views/startup/screens/first_startup_screen.dart new file mode 100644 index 0000000..9c4ca74 --- /dev/null +++ b/lib/ui/views/startup/screens/first_startup_screen.dart @@ -0,0 +1,151 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/views/startup/startup_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; + +import '../../../common/translations/locale_keys.g.dart'; +import '../../../widgets/custom_circular_progress_indicator.dart'; + +class FirstStartupScreen extends ViewModelWidget { + final String? label; + + const FirstStartupScreen({super.key,this.label}); + + @override + Widget build(BuildContext context, StartupViewModel viewModel) => + _buildScaffoldWrapper(); + + Widget _buildScaffoldWrapper( ) => Scaffold( + backgroundColor: kcPrimaryColor, + body: _buildScaffoldPadding(), + ); + Widget _buildScaffoldPadding( ) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildScaffold(), + ); + + Widget _buildScaffold( ) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildScaffoldChildren(), + ); + + List _buildScaffoldChildren( ) => + [_buildUpperColumn(), _buildLowerColumnWrapper()]; + + Widget _buildUpperColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => + [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; + + Widget _buildIconWrapper() => Align( + alignment: Alignment.topLeft, + child: _buildIcon(), + ); + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/logo_white.svg', + height: 25, + ); + + Widget _buildLowerColumnWrapper( ) => Expanded( + child: _buildLowerColumn(), + ); + + Widget _buildLowerColumn( ) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildLowerColumnChildren(), + ); + + List _buildLowerColumnChildren( ) => [ + _buildTitle(), + verticalSpaceMedium, + _buildImageWrapper(), + verticalSpaceMedium, + _buildSafeWrapper() + ]; + + Widget _buildTitle() => Text.rich( + TextSpan( + text: 'እንግሊዝኛ\n', + style: style25W600, + children: [ + TextSpan( + text: 'በማንኛውም', + style: style25W400, + ), + TextSpan( + text: ' ሰዓት ', + style: style25W600, + ), + TextSpan( + text: 'ይማሩ!', + style: style25W400, + ), + ], + ), + ); + + Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); + + Widget _buildImageClipper() => ClipRRect( + borderRadius: BorderRadius.circular(25), + child: _buildImage(), + ); + + Widget _buildImage() => Image.asset( + 'assets/images/landing_1.png', + fit: BoxFit.cover, + ); + + Widget _buildSafeWrapper( ) => + SafeArea(child: _buildContinueButtonWrapper()); + + Widget _buildContinueButtonWrapper( ) => Align( + alignment: Alignment.bottomCenter, + child: _buildLoadingTextContainer(), + ); + + + + Widget _buildLoadingTextContainer() => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildLoadingTextWrapper(), + ); + + Widget _buildLoadingTextWrapper() => Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildLoadingTextChildren(), + ); + + List _buildLoadingTextChildren() => [ + _buildLoadingText(), + horizontalSpaceSmall, + _buildIndicatorWrapper(), + ]; + + Widget _buildLoadingText() => + Text('${label ?? LocaleKeys.loading.tr()} ...', style: style16W600); + + Widget _buildIndicatorWrapper() => SizedBox( + width: 16, + height: 16, + child: _buildIndicator(), + ); + + Widget _buildIndicator() => + const CustomCircularProgressIndicator(color: kcWhite); + + +} diff --git a/lib/ui/views/startup/screens/second_startup_screen.dart b/lib/ui/views/startup/screens/second_startup_screen.dart new file mode 100644 index 0000000..387e79c --- /dev/null +++ b/lib/ui/views/startup/screens/second_startup_screen.dart @@ -0,0 +1,153 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; + +import '../../../common/translations/locale_keys.g.dart'; +import '../../../widgets/custom_circular_progress_indicator.dart'; +import '../startup_viewmodel.dart'; + +class SecondStartupScreen extends ViewModelWidget { + final String? label; + + const SecondStartupScreen({super.key,this.label}); + + @override + Widget build(BuildContext context, StartupViewModel viewModel) => + _buildScaffoldWrapper(); + + Widget _buildScaffoldWrapper( ) => Scaffold( + backgroundColor: Colors.amber, + body: _buildScaffoldPadding(), + ); + Widget _buildScaffoldPadding( ) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildScaffold(), + ); + + Widget _buildScaffold( ) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildScaffoldChildren(), + ); + + List _buildScaffoldChildren( ) => + [_buildUpperColumn(), _buildLowerColumnWrapper()]; + + Widget _buildUpperColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => + [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; + + Widget _buildIconWrapper() => Align( + alignment: Alignment.topLeft, + child: _buildIcon(), + ); + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/logo_purple.svg', + height: 25, + ); + + Widget _buildLowerColumnWrapper( ) => Expanded( + child: _buildLowerColumn(), + ); + + Widget _buildLowerColumn( ) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildLowerColumnChildren(), + ); + + List _buildLowerColumnChildren( ) => [ + _buildTitle(), + verticalSpaceMedium, + _buildImageWrapper(), + verticalSpaceMedium, + _buildSafeWrapper() + ]; + + + Widget _buildTitle() => Text.rich( + TextSpan( + text: 'እንግሊዝኛ\n', + style: style25P600, + children: [ + TextSpan( + text: 'በማንኛውም', + style: style25P400, + ), + TextSpan( + text: ' እድሜ ', + style: style25P600, + ), + TextSpan( + text: 'ይማሩ!', + style: style25P400, + ), + ], + ), + ); + + Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); + + Widget _buildImageClipper() => ClipRRect( + borderRadius: BorderRadius.circular(25), + child: _buildImage(), + ); + + Widget _buildImage() => Image.asset( + 'assets/images/landing_2.png', + fit: BoxFit.cover, + ); + + Widget _buildSafeWrapper( ) => + SafeArea(child: _buildContinueButtonWrapper()); + + Widget _buildContinueButtonWrapper( ) => Align( + alignment: Alignment.bottomCenter, + child: _buildLoadingTextContainer(), + ); + + + + Widget _buildLoadingTextContainer() => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildLoadingTextWrapper(), + ); + + Widget _buildLoadingTextWrapper() => Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildLoadingTextChildren(), + ); + + List _buildLoadingTextChildren() => [ + _buildLoadingText(), + horizontalSpaceSmall, + _buildIndicatorWrapper(), + ]; + + Widget _buildLoadingText() => + Text('${label ?? LocaleKeys.loading.tr()} ...', style: style16P600); + + Widget _buildIndicatorWrapper() => SizedBox( + width: 16, + height: 16, + child: _buildIndicator(), + ); + + Widget _buildIndicator() => + const CustomCircularProgressIndicator(color: kcPrimaryColor); + + +} + diff --git a/lib/ui/views/startup/screens/third_startup_screen.dart b/lib/ui/views/startup/screens/third_startup_screen.dart new file mode 100644 index 0000000..ad988d5 --- /dev/null +++ b/lib/ui/views/startup/screens/third_startup_screen.dart @@ -0,0 +1,153 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; + +import '../../../common/translations/locale_keys.g.dart'; +import '../../../widgets/custom_circular_progress_indicator.dart'; +import '../startup_viewmodel.dart'; + +class ThirdStartupScreen extends ViewModelWidget { + final String? label; + + const ThirdStartupScreen({super.key,this.label}); + + @override + Widget build(BuildContext context, StartupViewModel viewModel) => + _buildScaffoldWrapper(); + + Widget _buildScaffoldWrapper( ) => Scaffold( + backgroundColor:kcWhite, + body: _buildScaffoldPadding(), + ); + Widget _buildScaffoldPadding( ) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildScaffold(), + ); + + Widget _buildScaffold( ) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildScaffoldChildren(), + ); + + List _buildScaffoldChildren( ) => + [_buildUpperColumn(), _buildLowerColumnWrapper()]; + + Widget _buildUpperColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => + [verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge]; + + Widget _buildIconWrapper() => Align( + alignment: Alignment.topLeft, + child: _buildIcon(), + ); + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/logo_purple.svg', + height: 25, + ); + + Widget _buildLowerColumnWrapper( ) => Expanded( + child: _buildLowerColumn(), + ); + + Widget _buildLowerColumn( ) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildLowerColumnChildren(), + ); + + List _buildLowerColumnChildren( ) => [ + _buildTitle(), + verticalSpaceMedium, + _buildImageWrapper(), + verticalSpaceMedium, + _buildSafeWrapper() + ]; + + + Widget _buildTitle() => Text.rich( + TextSpan( + text: 'እንግሊዝኛ\n', + style: style25P600, + children: [ + TextSpan( + text: 'በማንኛውም', + style: style25P400, + ), + TextSpan( + text: ' ቦታ ', + style: style25P600, + ), + TextSpan( + text: 'ይማሩ!', + style: style25P400, + ), + ], + ), + ); + + Widget _buildImageWrapper() => Expanded(child: _buildImageClipper()); + + Widget _buildImageClipper() => ClipRRect( + borderRadius: BorderRadius.circular(25), + child: _buildImage(), + ); + + Widget _buildImage() => Image.asset( + 'assets/images/landing_3.png', + fit: BoxFit.cover, + ); + + Widget _buildSafeWrapper( ) => + SafeArea(child: _buildContinueButtonWrapper()); + + Widget _buildContinueButtonWrapper( ) => Align( + alignment: Alignment.bottomCenter, + child: _buildLoadingTextContainer(), + ); + + + + Widget _buildLoadingTextContainer() => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildLoadingTextWrapper(), + ); + + Widget _buildLoadingTextWrapper() => Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildLoadingTextChildren(), + ); + + List _buildLoadingTextChildren() => [ + _buildLoadingText(), + horizontalSpaceSmall, + _buildIndicatorWrapper(), + ]; + + Widget _buildLoadingText() => + Text('${label ?? LocaleKeys.loading.tr()} ...', style: style16P600); + + Widget _buildIndicatorWrapper() => SizedBox( + width: 16, + height: 16, + child: _buildIndicator(), + ); + + Widget _buildIndicator() => + const CustomCircularProgressIndicator(color: kcPrimaryColor); + + +} + diff --git a/lib/ui/views/startup/startup_view.dart b/lib/ui/views/startup/startup_view.dart index 967e5fe..a79078f 100644 --- a/lib/ui/views/startup/startup_view.dart +++ b/lib/ui/views/startup/startup_view.dart @@ -1,9 +1,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/views/startup/screens/first_startup_screen.dart'; +import 'package:yimaru_app/ui/views/startup/screens/second_startup_screen.dart'; +import 'package:yimaru_app/ui/views/startup/screens/third_startup_screen.dart'; import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart'; import '../../common/app_colors.dart'; @@ -13,6 +17,7 @@ import 'startup_viewmodel.dart'; class StartupView extends StackedView { final String? label; + const StartupView({Key? key, this.label}) : super(key: key); @override @@ -24,83 +29,36 @@ class StartupView extends StackedView { _buildScaffoldWrapper(viewModel); Widget _buildScaffoldWrapper(StartupViewModel viewModel) => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffoldState(viewModel), + backgroundColor: kcPrimaryColor, + body: _buildStartupScreens(viewModel), ); - Widget _buildScaffoldState(StartupViewModel viewModel) => - viewModel.busy(StateObjects.startupView) - ? _buildStartUpView() - : _buildScaffold(); - - Widget _buildStartUpView() => - StartupView(label: LocaleKeys.checking_user_info.tr()); - - Widget _buildScaffold() => Stack( - children: _buildScaffoldChildren(), + Widget _buildStartupScreens(StartupViewModel viewModel) => FlutterCarousel( + options: FlutterCarouselOptions( + autoPlay: true, + viewportFraction: 1, + showIndicator: false, + height: double.maxFinite, + ), + items: _buildScreens(), ); - List _buildScaffoldChildren() => [ - _buildBackground(), - _buildColumn(), + List _buildScreens() => [ + _buildFirstStartup(), + _buildSecondStartup(), + _buildThirdWelcome(), ]; - Widget _buildBackground() => Image.asset( - 'assets/images/loading.png', - fit: BoxFit.fill, - width: double.maxFinite, - height: double.maxFinite, + Widget _buildFirstStartup() => FirstStartupScreen( + label: label, ); - Widget _buildColumn() => Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildUpperColumnChildren(), + Widget _buildSecondStartup() => SecondStartupScreen( + label: label, ); - List _buildUpperColumnChildren() => - [_buildIconWrapper(), _buildSafeWrapper()]; - - Widget _buildSafeWrapper() => SafeArea(child: _buildLoadingTextContainer()); - - Widget _buildLoadingTextContainer() => Padding( - padding: const EdgeInsets.only(bottom: 50), - child: _buildLoadingTextWrapper(), - ); - - Widget _buildLoadingTextWrapper() => Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: _buildLoadingTextChildren(), - ); - - List _buildLoadingTextChildren() => [ - _buildLoadingText(), - horizontalSpaceSmall, - _buildIndicatorWrapper(), - ]; - - Widget _buildLoadingText() => - Text('${label ?? LocaleKeys.loading.tr()} ...', style: style16W600); - - Widget _buildIndicatorWrapper() => SizedBox( - width: 16, - height: 16, - child: _buildIndicator(), - ); - - Widget _buildIndicator() => - const CustomCircularProgressIndicator(color: kcWhite); - - Widget _buildIconWrapper() => Padding( - padding: const EdgeInsets.only(top: 120), - child: _buildIcon(), - ); - - Widget _buildIcon() => SvgPicture.asset( - 'assets/icons/logo.svg', - height: 50, + Widget _buildThirdWelcome() => ThirdStartupScreen( + label: label, ); @override diff --git a/lib/ui/views/startup/startup_viewmodel.dart b/lib/ui/views/startup/startup_viewmodel.dart index 58bf30d..dab3195 100644 --- a/lib/ui/views/startup/startup_viewmodel.dart +++ b/lib/ui/views/startup/startup_viewmodel.dart @@ -8,6 +8,7 @@ import '../../../app/app.router.dart'; import '../../../models/user.dart'; import '../../../services/api_service.dart'; import '../../../services/image_downloader_service.dart'; +import '../../../services/in_app_notification_service.dart'; import '../../../services/localization_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/enmus.dart'; @@ -28,6 +29,7 @@ class StartupViewModel extends ReactiveViewModel { final _imageDownloaderService = locator(); + @override List get listenableServices => [_onboardingService, _authenticationService]; @@ -133,8 +135,6 @@ class StartupViewModel extends ReactiveViewModel { } } - // Remote api call - // Onboarding fields Future getOnboardingFields() async { bool response = await _onboardingService.getOnboardingFields(); @@ -144,4 +144,6 @@ class StartupViewModel extends ReactiveViewModel { await replaceWithFailure(); } } + + } diff --git a/lib/ui/widgets/learn_lesson_tile.dart b/lib/ui/widgets/learn_lesson_tile.dart index 7bfc4f9..d51e01e 100644 --- a/lib/ui/widgets/learn_lesson_tile.dart +++ b/lib/ui/widgets/learn_lesson_tile.dart @@ -37,9 +37,13 @@ class LearnLessonTile extends ViewModelWidget { Widget _buildContainerWrapper(LearnLessonViewModel viewModel) => GestureDetector( - onTap: viewModel.user?.subscriptionStatus?.toLowerCase() == 'active' ? !(lesson.access?.isAccessible ?? false) - ? onPracticeTap - : null: !first ? onPracticeTap:null, + onTap: viewModel.user?.subscriptionStatus?.toLowerCase() == 'active' + ? !(lesson.access?.isAccessible ?? false) + ? onPracticeTap + : null + : !first + ? onPracticeTap + : null, child: _buildContainer(viewModel), ); diff --git a/lib/ui/widgets/notification_card.dart b/lib/ui/widgets/notification_card.dart new file mode 100644 index 0000000..a63239f --- /dev/null +++ b/lib/ui/widgets/notification_card.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:yimaru_app/models/in_app_notification.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; + +class NotificationCard extends StatelessWidget { + final InAppNotification notification; + + const NotificationCard({super.key, required this.notification}); + + @override + Widget build(BuildContext context) => _buildContainer(); + + + + Widget _buildContainer() => Container( + height: 100, + width: double.maxFinite, + margin: const EdgeInsets.symmetric(horizontal: 15), + padding: const EdgeInsets.symmetric(horizontal: 15,vertical: 15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: (notification.isRead ?? false) + ? kcGreen.withOpacity(0.05) + : kcPrimaryColor.withOpacity(0.05), + border: Border.all( + color: (notification.isRead ?? false) + ? kcGreen.withOpacity(0.1) + : kcPrimaryColor.withOpacity(0.1), + ), + ), + child: _buildRow(), + ); + + Widget _buildRow() => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildRowChildren(), + ); + + List _buildRowChildren()=> [ + _buildIcon(), + horizontalSpaceSmall, + _buildColumnWrapper() + ]; + + Widget _buildIcon() => const Icon( + Icons.notifications_none, + size: 35, + color: kcMediumGrey, + ); + + Widget _buildColumnWrapper()=> Expanded(child: _buildColumn()); + + Widget _buildColumn() => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildColumnChildren(), + ); + + List _buildColumnChildren() => + [ _buildTitle(), _buildSubtitle()]; + + Widget _buildTitle() => Text( + notification.payload?.headline ?? '', + style: style16DG600, + ); + + Widget _buildSubtitle() => Text( + notification.payload?.message ?? '', + maxLines: 2, + style: style14MG400, + ); +} diff --git a/lib/ui/widgets/notification_icon.dart b/lib/ui/widgets/notification_icon.dart new file mode 100644 index 0000000..a2b8814 --- /dev/null +++ b/lib/ui/widgets/notification_icon.dart @@ -0,0 +1,34 @@ +import 'package:badges/badges.dart' as badges; +import 'package:flutter/material.dart'; +import 'package:badges/badges.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import '../common/app_colors.dart'; +class NotificationIcon extends StatelessWidget { + final String count; + final GestureTapCallback? onTap; + const NotificationIcon({super.key,this.onTap,required this.count}); + + @override + Widget build(BuildContext context) => _buildNotificationIconWrapper(); + + + Widget _buildNotificationIconWrapper() => Align( + alignment: Alignment.bottomRight, + child: _buildNotificationButton()); + + Widget _buildNotificationButton() => + GestureDetector( + onTap: onTap, + child: _buildNotificationBadge(), + ); + + Widget _buildNotificationBadge()=> badges.Badge( + badgeContent: Text(count,style: style12W600,), + + child: _buildNotificationIcon(), + ); + Widget _buildNotificationIcon() => const Icon( + Icons.notifications_none, + color: kcDarkGrey, + ); +} diff --git a/lib/ui/widgets/profile_app_bar.dart b/lib/ui/widgets/profile_app_bar.dart index 1473d2e..0a500d6 100644 --- a/lib/ui/widgets/profile_app_bar.dart +++ b/lib/ui/widgets/profile_app_bar.dart @@ -7,13 +7,16 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart'; import '../common/app_colors.dart'; import '../common/translations/locale_keys.g.dart'; +import 'notification_icon.dart'; class ProfileAppBar extends StatelessWidget { final String? name; + final String unreadCount; final String? profileImage; + final GestureTapCallback? onTap; const ProfileAppBar( - {super.key, required this.name, required this.profileImage}); + {super.key, this.onTap, required this.name,required this.unreadCount, required this.profileImage}); @override Widget build(BuildContext context) => _buildStack(); @@ -91,11 +94,8 @@ class ProfileAppBar extends StatelessWidget { style: style14DG400, ); - Widget _buildNotificationIconWrapper() => - Align(alignment: Alignment.bottomRight, child: _buildNotificationIcon()); - Widget _buildNotificationIcon() => const Icon( - Icons.notifications_none, - color: kcDarkGrey, - ); + Widget _buildNotificationIconWrapper() => + NotificationIcon(count: unreadCount,onTap:onTap ,) + ; } diff --git a/pubspec.lock b/pubspec.lock index 1dc8795..846c325 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -113,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.3.0" + badges: + dependency: "direct main" + description: + name: badges + sha256: cf1c88fb3777df69ccd630b80de5267f54efa4a39381b0404a7c03d56cb7c041 + url: "https://pub.dev" + source: hosted + version: "3.2.0" bloc: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a6f3782..ff8a03f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: yimaru_app publish_to: 'none' -version: 0.1.35+37 +version: 0.1.36+38 description: A new Flutter project. environment: @@ -16,6 +16,7 @@ dependencies: path: ^1.9.1 async: ^2.13.1 pinput: ^6.0.1 + badges: ^3.2.0 stacked: ^3.4.0 iconsax: ^0.0.8 chewie: ^1.13.0 diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart index cd907f9..0b9a7f5 100644 --- a/test/helpers/test_helpers.dart +++ b/test/helpers/test_helpers.dart @@ -11,7 +11,6 @@ import 'package:yimaru_app/services/permission_handler_service.dart'; import 'package:yimaru_app/services/image_picker_service.dart'; import 'package:yimaru_app/services/google_auth_service.dart'; import 'package:yimaru_app/services/image_downloader_service.dart'; -import 'package:yimaru_app/services/notification_service.dart'; import 'package:yimaru_app/services/smart_auth_service.dart'; import 'package:yimaru_app/services/course_service.dart'; import 'package:yimaru_app/services/audio_player_service.dart'; @@ -23,6 +22,8 @@ import 'package:yimaru_app/services/phone_caller_service.dart'; import 'package:yimaru_app/services/learn_service.dart'; import 'package:yimaru_app/services/localization_service.dart'; import 'package:yimaru_app/services/onboarding_service.dart'; +import 'package:yimaru_app/services/in_app_notification_service.dart'; +import 'package:yimaru_app/services/push_notification_service.dart'; // @stacked-import @GenerateMocks( @@ -57,6 +58,10 @@ import 'package:yimaru_app/services/onboarding_service.dart'; MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), + MockSpec( + onMissingStub: OnMissingStub.returnDefault), + MockSpec( + onMissingStub: OnMissingStub.returnDefault), // @stacked-mock-spec ], ) @@ -88,6 +93,8 @@ void registerServices() { getAndRegisterLearnService(); getAndRegisterLocalizationService(); getAndRegisterOnboardingService(); + getAndRegisterInAppNotificationService(); + getAndRegisterPushNotificationService(); // @stacked-mock-register } @@ -208,13 +215,6 @@ MockImageDownloaderService getAndRegisterImageDownloaderService() { return service; } -MockNotificationService getAndRegisterNotificationService() { - _removeRegistrationIfExists(); - final service = MockNotificationService(); - locator.registerSingleton(service); - return service; -} - MockSmartAuthService getAndRegisterSmartAuthService() { _removeRegistrationIfExists(); final service = MockSmartAuthService(); @@ -291,6 +291,20 @@ MockOnboardingService getAndRegisterOnboardingService() { locator.registerSingleton(service); return service; } + +MockInAppNotificationService getAndRegisterInAppNotificationService() { + _removeRegistrationIfExists(); + final service = MockInAppNotificationService(); + locator.registerSingleton(service); + return service; +} + +MockPushNotificationService getAndRegisterPushNotificationService() { + _removeRegistrationIfExists(); + final service = MockPushNotificationService(); + locator.registerSingleton(service); + return service; +} // @stacked-mock-create void _removeRegistrationIfExists() { diff --git a/test/services/in_app_notification_service_test.dart b/test/services/in_app_notification_service_test.dart new file mode 100644 index 0000000..9194e4f --- /dev/null +++ b/test/services/in_app_notification_service_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:yimaru_app/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('InAppNotificationServiceTest -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/services/notification_service_test.dart b/test/services/push_notification_service_test.dart similarity index 83% rename from test/services/notification_service_test.dart rename to test/services/push_notification_service_test.dart index 1b2e73e..e9a50b8 100644 --- a/test/services/notification_service_test.dart +++ b/test/services/push_notification_service_test.dart @@ -4,7 +4,7 @@ import 'package:yimaru_app/app/app.locator.dart'; import '../helpers/test_helpers.dart'; void main() { - group('NotificationServiceTest -', () { + group('PushNotificationServiceTest -', () { setUp(() => registerServices()); tearDown(() => locator.reset()); }); diff --git a/test/viewmodels/notification_viewmodel_test.dart b/test/viewmodels/notification_viewmodel_test.dart new file mode 100644 index 0000000..641081d --- /dev/null +++ b/test/viewmodels/notification_viewmodel_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:yimaru_app/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('NotificationViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +}