diff --git a/api/lib/cecr_unwomen_web/controllers/user_controller.ex b/api/lib/cecr_unwomen_web/controllers/user_controller.ex index cf6525a..89bd4f7 100644 --- a/api/lib/cecr_unwomen_web/controllers/user_controller.ex +++ b/api/lib/cecr_unwomen_web/controllers/user_controller.ex @@ -169,6 +169,7 @@ defmodule CecrUnwomenWeb.UserController do def update_info(conn, params) do user_id = conn.assigns.user.user_id + response = Repo.get_by(User, id: user_id) |> case do nil -> Helper.response_json_message(false, "Không tìm thấy người dùng!", 300) @@ -176,7 +177,17 @@ defmodule CecrUnwomenWeb.UserController do keys = ["first_name", "last_name", "date_of_birth", "email", "gender", "location"] data_changes = Enum.reduce(keys, %{}, fn key, acc -> key_atom = String.to_atom(key) - if params[key], do: Map.put(acc, key_atom, params[key]), else: acc + value = case key do + "date_of_birth" -> + if (!is_nil(params[key])) do + String.split(params[key], "T") + |> List.first() + |> Date.from_iso8601!() + end + + _ -> params[key] + end + if params[key], do: Map.put(acc, key_atom, value), else: acc end) Ecto.Changeset.change(user, data_changes) |> Repo.update diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index f994f30..e8dde2c 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -496,9 +496,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 4; - DEVELOPMENT_TEAM = PHG66TGA97; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PHG66TGA97; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = WazNet; @@ -512,6 +514,7 @@ PRODUCT_BUNDLE_IDENTIFIER = vn.sparc.waznet; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = build_testflight_waznet; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -688,9 +691,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 4; - DEVELOPMENT_TEAM = PHG66TGA97; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PHG66TGA97; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = WazNet; @@ -704,6 +709,7 @@ PRODUCT_BUNDLE_IDENTIFIER = vn.sparc.waznet; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = build_testflight_waznet; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -720,9 +726,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 4; - DEVELOPMENT_TEAM = PHG66TGA97; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PHG66TGA97; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = WazNet; @@ -736,6 +744,7 @@ PRODUCT_BUNDLE_IDENTIFIER = vn.sparc.waznet; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = build_testflight_waznet; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/mobile/lib/constants/color_constants.dart b/mobile/lib/constants/color_constants.dart index b251505..6bcb5d2 100644 --- a/mobile/lib/constants/color_constants.dart +++ b/mobile/lib/constants/color_constants.dart @@ -45,4 +45,7 @@ class ColorConstants { // letterSpacing: 0.1 ); } + + TextStyle placeholderStyle() => fastStyle(14, FontWeight.w400, textPlaceholder); + TextStyle highlightPlaceHolderStyle() => fastStyle(14, FontWeight.w500, textSubHeader); } diff --git a/mobile/lib/features/authentication/bloc/authentication_bloc.dart b/mobile/lib/features/authentication/bloc/authentication_bloc.dart index 52857b0..7d11e51 100644 --- a/mobile/lib/features/authentication/bloc/authentication_bloc.dart +++ b/mobile/lib/features/authentication/bloc/authentication_bloc.dart @@ -10,6 +10,7 @@ class AuthenticationBloc extends Bloc on(_onAuthSubscription); on(_onLogoutRequest); on(_onAutoLogin); + on(_onUpdateInfo); } Future _onAuthSubscription(AuthSubscription event, Emitter emit) async { @@ -59,4 +60,13 @@ class AuthenticationBloc extends Bloc emit(state.copyWith(status: AuthenticationStatus.error)); } } + + Future _onUpdateInfo(UpdateInfo event, Emitter emit) async { + try { + emit(state.copyWith(status: AuthenticationStatus.authorized, user: event.user)); + } catch (e) { + print('udpate into :$e'); + emit(state.copyWith(status: AuthenticationStatus.error)); + } + } } diff --git a/mobile/lib/features/authentication/bloc/authentication_event.dart b/mobile/lib/features/authentication/bloc/authentication_event.dart index c62d0d1..b82cc93 100644 --- a/mobile/lib/features/authentication/bloc/authentication_event.dart +++ b/mobile/lib/features/authentication/bloc/authentication_event.dart @@ -1,4 +1,10 @@ -abstract class AuthenticationEvent {} +import 'package:cecr_unwomen/features/authentication/models/user.dart'; +import 'package:equatable/equatable.dart'; + +abstract class AuthenticationEvent extends Equatable { + @override + List get props => []; +} class AuthSubscription extends AuthenticationEvent {} @@ -6,3 +12,13 @@ class LogoutRequest extends AuthenticationEvent {} class AutoLogin extends AuthenticationEvent {} +class UpdateInfo extends AuthenticationEvent { + final User user; + + UpdateInfo(this.user); + + @override + List get props => [user]; +} + + diff --git a/mobile/lib/features/authentication/bloc/authentication_state.dart b/mobile/lib/features/authentication/bloc/authentication_state.dart index 30cbcd1..dd21e24 100644 --- a/mobile/lib/features/authentication/bloc/authentication_state.dart +++ b/mobile/lib/features/authentication/bloc/authentication_state.dart @@ -1,7 +1,7 @@ import 'package:cecr_unwomen/features/authentication/models/user.dart'; import 'package:equatable/equatable.dart'; -enum AuthenticationStatus { unknown, authorized, unauthorized, loading, error } +enum AuthenticationStatus { unknown, authorized, unauthorized, loading, error} class AuthenticationState extends Equatable { const AuthenticationState({ @@ -23,5 +23,5 @@ class AuthenticationState extends Equatable { } @override - List get props => [status]; + List get props => [status, user]; } diff --git a/mobile/lib/features/authentication/models/user.dart b/mobile/lib/features/authentication/models/user.dart index c9c961e..0ba54bc 100644 --- a/mobile/lib/features/authentication/models/user.dart +++ b/mobile/lib/features/authentication/models/user.dart @@ -6,7 +6,7 @@ class User extends Equatable { final String lastName; final String? email; final String phoneNumber; - final int? dateOfBirth; + final DateTime? dateOfBirth; final String? avatarUrl; final Gender gender; final int roleId; @@ -31,9 +31,11 @@ class User extends Equatable { lastName: json["last_name"], email: json["email"] ?? "", phoneNumber: json["phone_number"], - dateOfBirth: json["date_of_birth"], + dateOfBirth: json["date_of_birth"] != null + ? DateTime.tryParse(json["date_of_birth"]) + : null, avatarUrl: json["avatar_url"], - gender: _getGenderBasedOnInt(json["gender"]), + gender: getGenderBasedOnInt(json["gender"]), roleId: json["role_id"], location: json["location"] ); @@ -44,25 +46,72 @@ class User extends Equatable { "last_name": lastName, "email": email, "phone_number": phoneNumber, - "birth": dateOfBirth, - "gender": gender, + "date_of_birth": dateOfBirth?.toIso8601String(), + "gender": convertGenderToInt(gender), "avatar_url": avatarUrl, "role_id": roleId, "location": location }; + + User clone(Map data) => User.fromJson(data); + + User copyWith({ + String? id, + String? firstName, + String? lastName, + String? email, + String? phoneNumber, + DateTime? dateOfBirth, + String? avatarUrl, + Gender? gender, + int? roleId, + String? location, + }) { + return User( + id: id ?? this.id, + firstName: firstName ?? this.firstName, + lastName: lastName ?? this.lastName, + email: email ?? this.email, + phoneNumber: phoneNumber ?? this.phoneNumber, + dateOfBirth: dateOfBirth ?? this.dateOfBirth, + avatarUrl: avatarUrl ?? this.avatarUrl, + gender: gender ?? this.gender, + roleId: roleId ?? this.roleId, + location: location ?? this.location, + ); + } + @override - List get props => []; + List get props => [ + id, firstName, lastName, email, phoneNumber, dateOfBirth, avatarUrl, gender, roleId, location + ]; } enum Gender { male, female, other } -Gender _getGenderBasedOnInt(int? gender) { +int convertGenderToInt(Gender gender) { + switch (gender) { + case Gender.male: return 1; + case Gender.female: return 2; + default: return 0; + } +} + +Gender getGenderBasedOnInt(int? gender) { switch (gender) { case 1: return Gender.male; case 2: return Gender.female; default: return Gender.other; + } +} + +String convertGenderToString(Gender gender) { + switch (gender) { + case Gender.male: return "Nam"; + case Gender.female: return "Nữ"; + default: return "Không xác định"; } } diff --git a/mobile/lib/features/home/view/contribution_screen.dart b/mobile/lib/features/home/view/contribution_screen.dart index cb8b4b6..d37e766 100644 --- a/mobile/lib/features/home/view/contribution_screen.dart +++ b/mobile/lib/features/home/view/contribution_screen.dart @@ -629,10 +629,7 @@ class _UserContributionWidgetState extends State { } num countTotal() { - return widget.oneDayData.entries.fold(0, (previousValue, element) { - if (!element.key.contains("reduced")) return previousValue; - return previousValue + element.value; - }); + return widget.oneDayData["kg_co2e_reduced"] ?? 0; } String? getAvatarUrl() { diff --git a/mobile/lib/features/home/view/home_screen.dart b/mobile/lib/features/home/view/home_screen.dart index 5b9f64f..932ecc4 100644 --- a/mobile/lib/features/home/view/home_screen.dart +++ b/mobile/lib/features/home/view/home_screen.dart @@ -7,7 +7,7 @@ import 'package:cecr_unwomen/features/home/view/component/header_widget.dart'; import 'package:cecr_unwomen/features/home/view/component/tab_bar_widget.dart'; import 'package:cecr_unwomen/features/home/view/contribution_screen.dart'; import 'package:cecr_unwomen/features/home/view/statistic_screen.dart'; -import 'package:cecr_unwomen/features/home/view/user_info.dart'; +import 'package:cecr_unwomen/features/user/view/user_info.dart'; import 'package:cecr_unwomen/temp_api.dart'; import 'package:cecr_unwomen/utils.dart'; import 'package:cecr_unwomen/widgets/circle_avatar.dart'; @@ -236,7 +236,12 @@ class _HomeScreenState extends State { ..add(OpenMessageTerminated()) ..add(ReceiveMessageForeground()), child: _currentIndex == 0 ? adminWidget - : _currentIndex == 1 ? StatisticScreen(householdStatisticData: householdData["statistic"] ?? {}, scraperStatisticData: scraperData["statistic"] ?? {}) + : _currentIndex == 1 ? + StatisticScreen( + householdStatisticData: householdData["statistic"] ?? {}, + scraperStatisticData: scraperData["statistic"] ?? {}, + roleId: roleId, + ) : const UserInfo(), ), ); @@ -259,6 +264,21 @@ class CardInfoWidget extends StatelessWidget { ), child: Stack( children: [ + Positioned( + right: -5, + bottom: -20, + child: ShaderMask( + shaderCallback: (Rect bounds) { + return const LinearGradient( + colors: [Color(0xFFFFFCF0), Color(0xFFC8E6C9)], // Define your gradient colors here + tileMode: TileMode.clamp, + begin: Alignment.centerLeft, + end: Alignment.bottomRight, + ).createShader(bounds); + }, + child: icon + ), + ), Container( padding: const EdgeInsets.all(14), child: Column( @@ -278,21 +298,6 @@ class CardInfoWidget extends StatelessWidget { ], ), ), - Positioned( - right: -5, - bottom: -20, - child: ShaderMask( - shaderCallback: (Rect bounds) { - return const LinearGradient( - colors: [Color(0xFFFFFCF0), Color(0xFFC8E6C9)], // Define your gradient colors here - tileMode: TileMode.clamp, - begin: Alignment.centerLeft, - end: Alignment.bottomRight, - ).createShader(bounds); - }, - child: icon - ), - ), ], ), ); diff --git a/mobile/lib/features/home/view/statistic_screen.dart b/mobile/lib/features/home/view/statistic_screen.dart index 47ddd8f..545659e 100644 --- a/mobile/lib/features/home/view/statistic_screen.dart +++ b/mobile/lib/features/home/view/statistic_screen.dart @@ -7,9 +7,10 @@ import 'package:flutter/material.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; class StatisticScreen extends StatefulWidget { - const StatisticScreen({super.key, required this.householdStatisticData, required this.scraperStatisticData}); + const StatisticScreen({super.key, required this.householdStatisticData, required this.scraperStatisticData, required this.roleId}); final Map householdStatisticData; final Map scraperStatisticData; + final int roleId; @override State createState() => _StatisticScreenState(); @@ -27,7 +28,11 @@ class _StatisticScreenState extends State { @override Widget build(BuildContext context) { - final Map allData = isHouseholdTab ? (widget.householdStatisticData) : (widget.scraperStatisticData); + final Map allData = widget.roleId == 1 ? + isHouseholdTab ? (widget.householdStatisticData) : (widget.scraperStatisticData) + : widget.roleId == 2 ? (widget.householdStatisticData) + : widget.roleId == 3 ? (widget.scraperStatisticData) + : {}; return Column( children: [ HeaderWidget( @@ -58,8 +63,7 @@ class _StatisticScreenState extends State { BarWidget(isHousehold: isHouseholdTab, changeBar: changeBar), if (allData["overall_data_one_month"] != null && allData["overall_data_one_month"].isNotEmpty) ...allData["overall_data_one_month"].map((e) { - final int roleIdUser = isHouseholdTab ? 2 : 3; - return UserContributionWidget(oneDayData: {...e, "role_id": roleIdUser}); + return UserContributionWidget(oneDayData: {...e, "role_id": widget.roleId}); }).toList() else const Center( diff --git a/mobile/lib/features/login/view/login_screen.dart b/mobile/lib/features/login/view/login_screen.dart index 24a9514..2207f57 100644 --- a/mobile/lib/features/login/view/login_screen.dart +++ b/mobile/lib/features/login/view/login_screen.dart @@ -185,7 +185,7 @@ class _PhoneNumberBoxState extends State { FocusScope.of(context).unfocus(); }, padding: const EdgeInsets.only(left: 12), - placeholder: "Nhập số điện thoại của bạn", + placeholder: "Nhập số điện thoại", keyboardType: TextInputType.phone, placeholderStyle: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: colorConstants.textPlaceholder, fontFamily: "Inter" @@ -348,20 +348,69 @@ class _RegisterBoxState extends State { ), ); } - - Future _pickDate(BuildContext context) async { - final DateTime? picked = await showDatePicker( + void _showDatePicker() { + showCupertinoModalPopup( context: context, - initialDate: _selectedDate, - firstDate: DateTime(1900), - lastDate: DateTime.now(), + builder: (BuildContext context) => Material( + borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)), + child: ClipRRect( + borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)), + child: Container( + height: 300, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 3), + color: const Color(0xffFFFFFF), + child: Column( + children: [ + Container( + height: 5, + width: 45, + margin: const EdgeInsets.only(top: 6, bottom: 12), + decoration: BoxDecoration( + color: const Color(0xffC1C1C2), + borderRadius: BorderRadius.circular(100) + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () { + registerData["birth"] = ""; + setState(() => _selectedDate = null); + Navigator.pop(context); + } , + child: Text("Huỷ", style: colorConstants.fastStyle(16, FontWeight.w500, const Color(0xff4CAF50)),) + ), + Text("Chọn thời gian", style: colorConstants.fastStyle(16, FontWeight.w700, const Color(0xff29292A)),), + InkWell( + onTap: () { + if (_selectedDate != null) { + setState(() => _selectedDate); + registerData["birth"] = DateFormat("yyyy-MM-dd").format(_selectedDate!); + } + Navigator.pop(context); + } , + child: Text("Lưu", style: colorConstants.fastStyle(16, FontWeight.w500, const Color(0xff4CAF50)),) + ) + ], + ), + Flexible( + child: CupertinoDatePicker( + initialDateTime: _selectedDate ?? DateTime.now(), + mode: CupertinoDatePickerMode.date, + use24hFormat: true, + onDateTimeChanged: (DateTime newDate) { + _selectedDate = newDate; + }, + dateOrder: DatePickerDateOrder.dmy, + ), + ) + ], + ) + ), + ), + ), ); - if (picked != null && picked != _selectedDate) { - setState(() { - _selectedDate = picked; - }); - registerData["birth"] = DateFormat("yyyy-MM-dd").format(_selectedDate!); - } } @override @@ -370,10 +419,13 @@ class _RegisterBoxState extends State { return BackgroundWithTransparentBox( child: Column(children: [ + const SizedBox(height: 10), Align( alignment: Alignment.centerLeft, child: InkWell( - onTap: () => widget.callbackEditPhoneNumber(), + onTap: () { + widget.callbackEditPhoneNumber(); + }, child: Icon(PhosphorIcons.regular.arrowLeft, size: 24, color: colorConstants.textHeader)), ), Text("Hoàn tất đăng ký", @@ -423,7 +475,7 @@ class _RegisterBoxState extends State { InkWell( radius: 8, // canRequestFocus: false, - onTap: () => _pickDate(context), + onTap: () => _showDatePicker(), child: Container( height: 44, padding: const EdgeInsets.symmetric(horizontal: 16), diff --git a/mobile/lib/features/user/repository/user_api.dart b/mobile/lib/features/user/repository/user_api.dart index 66e5bf8..17cdf32 100644 --- a/mobile/lib/features/user/repository/user_api.dart +++ b/mobile/lib/features/user/repository/user_api.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:cecr_unwomen/http.dart'; import 'package:dio/dio.dart'; @@ -7,4 +9,10 @@ class UserApi { final Response res = await dioConfigInterceptor.post(url, data: {"user_id": "f47cc61f-6e66-4822-835a-e0ed2485997e"}); return res.data; } + + static Future updateInfo(Map data) async { + const String url = "/user/update_info"; + final Response res = await dioConfigInterceptor.post(url, data: jsonEncode(data)); + return res.data["data"]; + } } \ No newline at end of file diff --git a/mobile/lib/features/user/view/screen/app_info.dart b/mobile/lib/features/user/view/screen/app_info.dart new file mode 100644 index 0000000..211e797 --- /dev/null +++ b/mobile/lib/features/user/view/screen/app_info.dart @@ -0,0 +1,75 @@ +import 'package:cecr_unwomen/constants/color_constants.dart'; +import 'package:cecr_unwomen/features/home/view/component/header_widget.dart'; +import 'package:cecr_unwomen/widgets/navigation_button.dart'; +import 'package:flutter/material.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +class AppInfo extends StatelessWidget { + final ColorConstants colorConstants = ColorConstants(); + AppInfo({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFF4F4F5), + body: SafeArea( + top: false, + child: Column( + children: [ + HeaderWidget( + child: Padding( + padding: const EdgeInsets.only(top: 25), + child: Row( + children: [ + InkWell( + child: Icon(PhosphorIcons.regular.arrowLeft, size: 20, color: const Color(0xff29292A),), + onTap: () => Navigator.pop(context), + ), + const SizedBox(width: 16,), + Text( + 'Về chúng tôi', + style: colorConstants.fastStyle(16, FontWeight.w600, const Color(0xff29292A)), + ), + ], + ), + ), + ), + const SizedBox(height: 30,), + SizedBox( + height: 100, + child: Image.asset("assets/icon/logo_green.png",) + ), + Text("Waznet", style: colorConstants.fastStyle(26, FontWeight.w900, const Color(0xff4CAF50))), + const SizedBox(height: 20,), + Text("Phiên bản 0.0.0", style: colorConstants.placeholderStyle()), + Text.rich( + TextSpan( + children: [ + TextSpan(text: "Một sản phẩm của ", style: colorConstants.placeholderStyle()), + TextSpan(text: "SPARC ", style: colorConstants.highlightPlaceHolderStyle()), + TextSpan(text: "và ", style: colorConstants.placeholderStyle()), + TextSpan(text: "CECR", style: colorConstants.highlightPlaceHolderStyle()) + ] + ) + ), + const SizedBox(height: 30,), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: NavigationButton( + icon: PhosphorIcons.regular.globe, + text: "Trang web", + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: NavigationButton( + icon: PhosphorIcons.regular.briefcase, + text: "Đối tác", + ), + ) + ], + ), + ) + ); + } +} \ No newline at end of file diff --git a/mobile/lib/features/user/view/screen/change_info_screen.dart b/mobile/lib/features/user/view/screen/change_info_screen.dart new file mode 100644 index 0000000..54316bb --- /dev/null +++ b/mobile/lib/features/user/view/screen/change_info_screen.dart @@ -0,0 +1,431 @@ +import 'package:cecr_unwomen/constants/color_constants.dart'; +import 'package:cecr_unwomen/features/authentication/authentication.dart'; +import 'package:cecr_unwomen/features/authentication/models/user.dart'; +import 'package:cecr_unwomen/features/home/view/component/header_widget.dart'; +import 'package:cecr_unwomen/features/home/view/component/toast_content.dart'; +import 'package:cecr_unwomen/features/user/repository/user_api.dart'; +import 'package:cecr_unwomen/widgets/circle_avatar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:intl/intl.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +class ChangeInfoScreen extends StatefulWidget { + const ChangeInfoScreen({super.key}); + + @override + State createState() => _ChangeInfoScreenState(); +} + +class _ChangeInfoScreenState extends State { + final TextEditingController firstNameController = TextEditingController(); + final TextEditingController lastNameController = TextEditingController(); + final TextEditingController phoneController = TextEditingController(); + final TextEditingController addressController = TextEditingController(); + final TextEditingController birthDateController = TextEditingController(); + DateTime? birthDate; + Gender? gender; + final ColorConstants colorConstants = ColorConstants(); + late User user; + late User userClone; + FToast fToast = FToast(); + + + @override + void initState() { + super.initState(); + user = context.read().state.user!; + userClone = user.clone(user.toJson()); + firstNameController.text = userClone.firstName; + lastNameController.text = userClone.lastName; + phoneController.text = userClone.phoneNumber; + addressController.text = userClone.location ?? ''; + birthDateController.text = userClone.dateOfBirth != null ? DateFormat("dd/MM/yyyy").format(userClone.dateOfBirth!) : ""; + birthDate = userClone.dateOfBirth; + gender = userClone.gender; + fToast.init(context); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Scaffold( + backgroundColor: const Color(0xFFF4F4F5), + body: SafeArea( + top: false, + child: Column( + children: [ + HeaderWidget( + child: Padding( + padding: const EdgeInsets.only(top: 25), + child: Row( + children: [ + InkWell( + child: Icon(PhosphorIcons.regular.arrowLeft, size: 20, color: const Color(0xff29292A),), + onTap: () => Navigator.pop(context), + ), + const SizedBox(width: 16,), + Text( + 'Sửa thông tin', + style: colorConstants.fastStyle(16, FontWeight.w600, const Color(0xff29292A)), + ), + ], + ), + ), + ), + + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Stack( + children: [ + CustomCircleAvatar( + size: 104, + avatarUrl: userClone.avatarUrl, + ), + Positioned( + right: 0, + bottom: 0, + child: Container( + padding: const EdgeInsets.all(8), + decoration: const BoxDecoration( + color: Color(0xFFE8F5E9), + shape: BoxShape.circle, + ), + child: const Icon(Icons.camera_alt, size: 20), + ), + ), + ], + ), + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: _buildTextField( + label: 'Họ', + controller: firstNameController, + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildTextField( + label: 'Tên', + controller: lastNameController, + ), + ), + ], + ), + const SizedBox(height: 16), + _buildTextField( + label: 'Số điện thoại', + controller: phoneController, + keyboardType: TextInputType.phone, + ), + const SizedBox(height: 16), + _buildDateField( + label: 'Ngày sinh', + value: birthDate, + onTap: () => _showDatePicker() + ), + const SizedBox(height: 16), + _buildGenderSelection(), + const SizedBox(height: 16), + _buildTextField( + label: 'Địa chỉ', + controller: addressController, + maxLines: 2, + hintText: addressController.text.isEmpty ? "Nhập địa chỉ" : null + ), + ], + ), + ), + ), + + // Update Button + Padding( + padding: const EdgeInsets.all(16), + child: ElevatedButton( + onPressed: () async { + userClone = userClone.copyWith( + firstName: firstNameController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + location: addressController.text, + gender: gender, + dateOfBirth: birthDate + ); + Map updatedUser = await UserApi.updateInfo(userClone.toJson()); + if (context.mounted) { + if (updatedUser.isNotEmpty) { + context.read().add(UpdateInfo(User.fromJson(updatedUser))); + fToast.showToast( + child: const ToastContent( + isSuccess: true, + title: 'Cập nhật thành công' + ), + gravity: ToastGravity.BOTTOM + ); + } else { + fToast.showToast( + child: const ToastContent( + isSuccess: false, + title: 'Cập nhật thất bại. Vui lòng thử lại sau' + ), + gravity: ToastGravity.BOTTOM + ); + } + Navigator.pop(context); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF4CAF50), + minimumSize: const Size.fromHeight(50), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + 'Cập nhật', + style: colorConstants.fastStyle(16, FontWeight.w700,const Color(0xffFFFFFF)), + ), + ), + ), + ], + ), + ), + ), + ); + } + + + + void _showDatePicker() { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) => Material( + borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)), + child: ClipRRect( + borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)), + child: Container( + height: 300, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 3), + color: const Color(0xffFFFFFF), + child: Column( + children: [ + Container( + height: 5, + width: 45, + margin: const EdgeInsets.only(top: 6, bottom: 12), + decoration: BoxDecoration( + color: const Color(0xffC1C1C2), + borderRadius: BorderRadius.circular(100) + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () { + birthDate = DateTime.parse(birthDateController.text); + Navigator.pop(context); + } , + child: Text("Huỷ", style: colorConstants.fastStyle(16, FontWeight.w500, const Color(0xff4CAF50)),) + ), + Text("Chọn thời gian", style: colorConstants.fastStyle(16, FontWeight.w700, const Color(0xff29292A)),), + InkWell( + onTap: () { + birthDate = birthDate ?? DateTime.now(); + birthDateController.text = DateFormat("dd/MM/yyyy").format(birthDate!); + // setState(() => userClone = userClone.copyWith(dateOfBirth: birthDate)); + Navigator.pop(context); + } , + child: Text("Lưu", style: colorConstants.fastStyle(16, FontWeight.w500, const Color(0xff4CAF50)),) + ) + ], + ), + Flexible( + child: CupertinoDatePicker( + initialDateTime: userClone.dateOfBirth ?? DateTime.now(), + mode: CupertinoDatePickerMode.date, + use24hFormat: true, + onDateTimeChanged: (DateTime newDate) { + birthDate = newDate; + }, + dateOrder: DatePickerDateOrder.dmy, + ), + ) + ], + ) + ), + ), + ), + ); + } + + + Widget _buildTextField({ + required String label, + required TextEditingController controller, + TextInputType? keyboardType, + int maxLines = 1, + String? hintText + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: colorConstants.fastStyle(14, FontWeight.w600,const Color(0xff666667)), + ), + const SizedBox(height: 8), + TextFormField( + style: colorConstants.fastStyle(16, FontWeight.w400,const Color(0xff333334)), + controller: controller, + keyboardType: keyboardType, + maxLines: maxLines, + decoration: InputDecoration( + filled: true, + fillColor: Colors.white, + hintText: hintText, + hintStyle: colorConstants.fastStyle(14, FontWeight.w400,const Color(0xffC1C1C2)), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: Color(0xFF4CAF50)), + ), + ), + ), + ], + ); + } + + Widget _buildDateField({ + required String label, + required DateTime? value, + required VoidCallback onTap, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: colorConstants.fastStyle(14, FontWeight.w600,const Color(0xff666667)), + ), + const SizedBox(height: 8), + TextFormField( + readOnly: true, + onTap: onTap, + style: colorConstants.fastStyle(16, FontWeight.w400,const Color(0xff333334)), + controller: birthDateController, + decoration: InputDecoration( + suffixIcon: Icon(PhosphorIcons.regular.calendarBlank, size: 20, color: const Color(0xff4CAF50),), + filled: true, + fillColor: Colors.white, + hintText: "Chọn ngày sinh", + hintStyle: colorConstants.fastStyle(14, FontWeight.w400,const Color(0xffC1C1C2)), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide(color: Color(0xFF4CAF50)), + ), + ), + ), + ], + ); + } + + Widget _buildGenderSelection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Giới tính', + style: colorConstants.fastStyle(14, FontWeight.w600,const Color(0xff666667)), + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: _buildGenderOption('Nam', Gender.male), + ), + const SizedBox(width: 16), + Expanded( + child: _buildGenderOption('Nữ', Gender.female), + ), + const SizedBox(width: 16), + Expanded( + child: _buildGenderOption('Khác', Gender.other), + ), + ], + ), + ], + ); + } + + Widget _buildGenderOption(String label, Gender value) { + final isSelected = gender == value; + return InkWell( + onTap: () { + setState(() => gender = value); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon( + isSelected ? Icons.radio_button_checked : Icons.radio_button_off, + color: isSelected ? const Color(0xFF4CAF50) : const Color(0xffC1C1C2), + weight: 2, + ), + const SizedBox(width: 8), + Text(label), + ], + ), + ), + ); + } + + @override + void dispose() { + firstNameController.dispose(); + lastNameController.dispose(); + phoneController.dispose(); + addressController.dispose(); + birthDateController.dispose(); + super.dispose(); + } +} \ No newline at end of file diff --git a/mobile/lib/features/home/view/user_info.dart b/mobile/lib/features/user/view/user_info.dart similarity index 56% rename from mobile/lib/features/home/view/user_info.dart rename to mobile/lib/features/user/view/user_info.dart index c30a8bf..f4979c5 100644 --- a/mobile/lib/features/home/view/user_info.dart +++ b/mobile/lib/features/user/view/user_info.dart @@ -2,8 +2,11 @@ import 'package:cecr_unwomen/constants/color_constants.dart'; import 'package:cecr_unwomen/features/authentication/bloc/authentication_bloc.dart'; import 'package:cecr_unwomen/features/authentication/bloc/authentication_event.dart'; import 'package:cecr_unwomen/features/authentication/models/user.dart'; +import 'package:cecr_unwomen/features/user/view/screen/app_info.dart'; +import 'package:cecr_unwomen/features/user/view/screen/change_info_screen.dart'; import 'package:cecr_unwomen/utils.dart'; import 'package:cecr_unwomen/widgets/circle_avatar.dart'; +import 'package:cecr_unwomen/widgets/navigation_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; @@ -20,7 +23,7 @@ class _UserInfoState extends State { @override Widget build(BuildContext context) { - final User user = context.watch().state.user!; + final User user = context.watch().state.user!; return SafeArea( child: SingleChildScrollView( child: Container( @@ -45,7 +48,14 @@ class _UserInfoState extends State { color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(12), - onTap: () => Utils.showDialogWarningError(context, false, "Chức năng đang được phát triển"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => BlocProvider.value( + value: BlocProvider.of(this.context), + child: const ChangeInfoScreen())) + ); + }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( @@ -57,19 +67,30 @@ class _UserInfoState extends State { ), ), const SizedBox(height: 20), - UserInfoItem( + NavigationButton( text: "Về chúng tôi", icon: PhosphorIcons.regular.users, - onTap: () => Utils.showDialogWarningError(context, false, "Chức năng đang được phát triển"), + onTap: () => Navigator.push( + context, + MaterialPageRoute(builder: (context) => AppInfo()) + ), ), - UserInfoItem( - text: "Đổi mật khẩu", + NavigationButton( + text: "Thay đổi mật khẩu", icon: PhosphorIcons.regular.lock, onTap: () => Utils.showDialogWarningError(context, false, "Chức năng đang được phát triển"), ), - UserInfoItem( + NavigationButton( + text: "Xác thực sinh trắc học", + icon: PhosphorIcons.regular.fingerprint, + hasSwitch: true, + onToggleSwitch: (p0) { + return Utils.showDialogWarningError(context, false, "Chức năng đang được phát triển"); + }, + ), + NavigationButton( text: "Đăng xuất", - isLogout: true, + isWarning: true, icon: PhosphorIcons.regular.signOut, onTap: () { context.read().add(LogoutRequest()); @@ -82,57 +103,3 @@ class _UserInfoState extends State { ); } } - -class UserInfoItem extends StatelessWidget { - const UserInfoItem({super.key, required this.text, required this.icon, this.isLogout = false, this.isBiometric = false, this.onTap}); - final String text; - final IconData icon; - final bool isLogout; - final bool isBiometric; - final Function? onTap; - - @override - Widget build(BuildContext context) { - final Color iconColor = isLogout ? const Color(0xFFFF4F3F) : const Color(0xFF4CAF50); - final Color iconBgColor = isLogout ? const Color(0xFFFFE8D8) : const Color(0xFFE8F5E9).withOpacity(0.7); - return Material( - color: Colors.transparent, - child: Container( - margin: const EdgeInsets.only(bottom: 12), - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () => onTap == null ? null : onTap!(), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - children: [ - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: iconBgColor, - shape: BoxShape.circle, - ), - child: Icon(icon, size: 20, color: iconColor), - ), - const SizedBox(width: 14), - Expanded( - child: Text(text, style: TextStyle( - color: !isLogout ? const Color(0xFF333334) : const Color(0xFFFF4F3F), - fontSize: 16, - fontWeight: FontWeight.w600) - ), - ), - Icon(PhosphorIcons.regular.caretRight, size: 20, color: const Color(0xFF4D4D4E)), - ], - ), - ), - ), - ), - ); - } -} diff --git a/mobile/lib/http.dart b/mobile/lib/http.dart index 359e9f6..4f68cdb 100644 --- a/mobile/lib/http.dart +++ b/mobile/lib/http.dart @@ -19,6 +19,7 @@ final Interceptor tokenInterceptor = QueuedInterceptorsWrapper( if (accessToken == null || accessExp == null || refreshToken == null) { // return handler.next(options); // logout + await AuthRepository.logout(); return; } diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 3df7fee..8ebb50a 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -6,6 +6,7 @@ import 'package:firebase_core/firebase_core.dart'; import 'features/authentication/authentication.dart'; import 'firebase_options.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -51,12 +52,12 @@ class MyApp extends StatelessWidget { ), localizationsDelegates: const [ // S.delegate, - // GlobalMaterialLocalizations.delegate, - // GlobalWidgetsLocalizations.delegate, - // GlobalCupertinoLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [ - Locale('en'), // English + // Locale('en'), // English Locale('vi'), ], ); diff --git a/mobile/lib/widgets/navigation_button.dart b/mobile/lib/widgets/navigation_button.dart new file mode 100644 index 0000000..ed5da39 --- /dev/null +++ b/mobile/lib/widgets/navigation_button.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_switch/flutter_switch.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; + +class NavigationButton extends StatelessWidget { + const NavigationButton({super.key, required this.text, required this.icon, this.isWarning = false, this.hasSwitch = false, this.onTap, this.onToggleSwitch, this.valueSwitch}); + final String text; + final IconData icon; + final bool isWarning; + final bool hasSwitch; + final bool? valueSwitch; + final Function(bool)? onToggleSwitch; + final Function? onTap; + + @override + Widget build(BuildContext context) { + final Color iconColor = isWarning ? const Color(0xFFFF4F3F) : const Color(0xFF4CAF50); + final Color iconBgColor = isWarning ? const Color(0xFFFFE8D8) : const Color(0xFFE8F5E9).withOpacity(0.7); + return Material( + color: Colors.transparent, + child: Container( + margin: const EdgeInsets.only(bottom: 12), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () => onTap == null ? null : onTap!(), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: iconBgColor, + shape: BoxShape.circle, + ), + child: Icon(icon, size: 20, color: iconColor), + ), + const SizedBox(width: 14), + Expanded( + child: Text(text, style: TextStyle( + color: !isWarning ? const Color(0xFF333334) : const Color(0xFFFF4F3F), + fontSize: 16, + fontWeight: FontWeight.w600) + ), + ), + hasSwitch + ? FlutterSwitch( + width: 55, + height: 31, + value: valueSwitch ?? false, + onToggle: onToggleSwitch ?? (v) {}, + activeColor: const Color(0xff4CAF50), + ) + : Icon(PhosphorIcons.regular.caretRight, size: 20, color: const Color(0xFF4D4D4E)), + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile/linux/flutter/generated_plugin_registrant.cc b/mobile/linux/flutter/generated_plugin_registrant.cc index e71a16d..10e19fe 100644 --- a/mobile/linux/flutter/generated_plugin_registrant.cc +++ b/mobile/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_localization_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalizationPlugin"); + flutter_localization_plugin_register_with_registrar(flutter_localization_registrar); } diff --git a/mobile/linux/flutter/generated_plugins.cmake b/mobile/linux/flutter/generated_plugins.cmake index 2e1de87..2284757 100644 --- a/mobile/linux/flutter/generated_plugins.cmake +++ b/mobile/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_localization ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/mobile/macos/Flutter/GeneratedPluginRegistrant.swift b/mobile/macos/Flutter/GeneratedPluginRegistrant.swift index c5d822f..2c0f94f 100644 --- a/mobile/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/mobile/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,10 +7,12 @@ import Foundation import firebase_core import firebase_messaging +import flutter_localization import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) + FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a0a9b23..994b1d5 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -3,7 +3,7 @@ description: "WazNet (Waste zero, Net zero) is a mobile app that helps users tra # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 0.0.1+12 +version: 0.0.1+13 environment: sdk: ^3.5.3 @@ -11,6 +11,9 @@ dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter + cupertino_icons: ^1.0.8 phosphor_flutter: 2.0.0 firebase_core: ^3.6.0 @@ -24,9 +27,11 @@ dependencies: flutter_spinkit: ^5.2.1 salomon_bottom_bar: ^3.3.2 flutter_native_splash: ^2.4.3 - collection: 1.18.0 - intl: 0.18.0 + collection: ^1.18.0 + intl: ^0.19.0 fluttertoast: ^8.2.8 + flutter_localization: ^0.3.0 + flutter_switch: ^0.3.2 dev_dependencies: flutter_test: diff --git a/mobile/windows/flutter/generated_plugin_registrant.cc b/mobile/windows/flutter/generated_plugin_registrant.cc index 1a82e7d..0a04f2f 100644 --- a/mobile/windows/flutter/generated_plugin_registrant.cc +++ b/mobile/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + FlutterLocalizationPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi")); } diff --git a/mobile/windows/flutter/generated_plugins.cmake b/mobile/windows/flutter/generated_plugins.cmake index fa8a39b..b70a297 100644 --- a/mobile/windows/flutter/generated_plugins.cmake +++ b/mobile/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST firebase_core + flutter_localization ) list(APPEND FLUTTER_FFI_PLUGIN_LIST