diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1779fb0..cae299d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -5,6 +5,8 @@ PODS: - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) + - image_picker_ios (0.0.1): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -31,6 +33,7 @@ PODS: DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) - Flutter (from `Flutter`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -50,6 +53,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/app_links/ios" Flutter: :path: Flutter + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" screen_brightness_ios: @@ -73,6 +78,7 @@ SPEC CHECKSUMS: app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index f92cbf3..8c772cf 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,12 @@ + NSPhotoLibraryUsageDescription + Pick your favorite place image. + NSCameraUsageDescription + Pick your favorite place image. + NSMicrophoneUsageDescription + Allow this app to use microphone CFBundleURLTypes diff --git a/lib/main.dart b/lib/main.dart index 4067c9a..b84de05 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:movie_app/cubits/my_list/my_list_cubit.dart'; import 'package:movie_app/cubits/route_stack/route_stack_cubit.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -33,7 +34,9 @@ void main() async { } final supabase = Supabase.instance.client; -const tmdbApiKey = 'a29284b32c092cc59805c9f5513d3811'; +const tmdbApiKey = 'tmdbApiKey'; +const baseAvatarUrl = + 'https://kpaxjjmelbqpllxenpxz.supabase.co/storage/v1/object/public/avatar/'; class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -56,6 +59,15 @@ class MyApp extends StatelessWidget { ), home: const SplashScreen(), debugShowCheckedModeBanner: false, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('vi'), + ], + locale: const Locale('vi'), ); } } diff --git a/lib/screens/auth/sign_in.dart b/lib/screens/auth/sign_in.dart index 8e6489f..47a8429 100644 --- a/lib/screens/auth/sign_in.dart +++ b/lib/screens/auth/sign_in.dart @@ -90,7 +90,7 @@ class _SignInState extends State { return Align( alignment: const Alignment(0, -0.15), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), + padding: const EdgeInsets.symmetric(horizontal: 20), child: Form( key: _formKey, child: Column( diff --git a/lib/screens/auth/sign_up.dart b/lib/screens/auth/sign_up.dart index 2280208..5246ed4 100644 --- a/lib/screens/auth/sign_up.dart +++ b/lib/screens/auth/sign_up.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; +import 'package:gap/gap.dart'; import 'package:movie_app/main.dart'; +import 'package:movie_app/utils/common_variables.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class SignUpScreen extends StatefulWidget { @@ -36,15 +37,18 @@ class _SignInState extends State { final enteredEmail = _emailController.text; final enteredPassword = _passwordController.text; + // print(_dobController.text); + try { await supabase.auth.signUp( email: enteredEmail, password: enteredPassword, emailRedirectTo: 'io.supabase.movie-app://login-callback/', data: { + 'password': enteredPassword, 'full_name': enteredUsername, 'dob': enteredDob, - 'avatar_url': 'https://i.imgur.com/zBr1CQ3.png', + 'avatar_url': 'default_avt.png', }, ); @@ -84,6 +88,18 @@ class _SignInState extends State { } } + Future _openDatePicker(BuildContext context) async { + DateTime? chosenDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + if (chosenDate != null) { + _dobController.text = vnDateFormat.format(chosenDate); + } + } + @override void dispose() { _usernameControllber.dispose(); @@ -95,8 +111,9 @@ class _SignInState extends State { @override Widget build(BuildContext context) { - return Align( - alignment: const Alignment(0, -0.2), + final screenSize = MediaQuery.sizeOf(context); + + return SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Form( @@ -104,6 +121,116 @@ class _SignInState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ + Gap(screenSize.height * 0.1), + Container( + height: 150, + width: 150, + decoration: BoxDecoration( + color: const Color.fromARGB(255, 51, 51, 51), + borderRadius: BorderRadius.circular(10), + ), + clipBehavior: Clip.antiAlias, + child: Stack( + alignment: Alignment.bottomRight, + children: [ + Image.network( + 'https://kpaxjjmelbqpllxenpxz.supabase.co/storage/v1/object/public/avatar/default_avt.png', + height: 150, + width: 150, + fit: BoxFit.cover, + frameBuilder: ( + BuildContext context, + Widget child, + int? frame, + bool wasSynchronouslyLoaded, + ) { + if (wasSynchronouslyLoaded) { + return child; + } + return AnimatedOpacity( + opacity: frame == null ? 0 : 1, + duration: const Duration( + milliseconds: 500), // Adjust the duration as needed + curve: Curves.easeInOut, + child: child, // Adjust the curve as needed + ); + }, + // https://api.flutter.dev/flutter/widgets/Image/loadingBuilder.html + loadingBuilder: ( + BuildContext context, + Widget child, + ImageChunkEvent? loadingProgress, + ) { + if (loadingProgress == null) { + return child; + } + // print("loadingProgress: $loadingProgress"); + return Center( + child: SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator( + // value: loadingProgress.expectedTotalBytes != null + // ? loadingProgress.cumulativeBytesLoaded / + // loadingProgress.expectedTotalBytes! + // : null, + color: Theme.of(context).colorScheme.primary, + strokeCap: StrokeCap.round, + ), + ), + ); + }, + ), + Container( + height: 48, + width: 48, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + child: const Icon( + Icons.image_rounded, + color: Colors.white, + ), + ) + // IconButton( + // onPressed: _pickAvatar, + // style: IconButton.styleFrom( + // backgroundColor: Theme.of(context).colorScheme.primary, + // foregroundColor: Colors.white, + // padding: const EdgeInsets.all(12), + // shape: const RoundedRectangleBorder( + // borderRadius: BorderRadius.only( + // topLeft: Radius.circular(10), + // bottomRight: Radius.circular(10), + // ), + // ), + // ), + // icon: const Icon(Icons.image_rounded), + // ) + ], + ), + ), + const Gap(8), + const Text( + 'Sau khi đăng nhập ', + style: TextStyle( + color: Color(0xFFACACAC), + fontSize: 15, + ), + ), + const Text( + 'Bạn có thể thay đổi Ảnh đại diện.', + style: TextStyle( + color: Color(0xFFACACAC), + fontSize: 15, + ), + ), + const Gap(24), TextFormField( controller: _usernameControllber, decoration: const InputDecoration( @@ -115,6 +242,7 @@ class _SignInState extends State { borderSide: BorderSide.none, ), contentPadding: EdgeInsets.fromLTRB(16, 20, 16, 12), + errorStyle: TextStyle(fontSize: 14), ), style: const TextStyle(color: Colors.white), autocorrect: false, @@ -130,10 +258,43 @@ class _SignInState extends State { const SizedBox( height: 12, ), - _DobTextFormField(dobController: _dobController), + GestureDetector( + onTap: () => _openDatePicker(context), + child: TextFormField( + controller: _dobController, + enabled: false, + mouseCursor: SystemMouseCursors.click, + decoration: const InputDecoration( + filled: true, + fillColor: Color.fromARGB(255, 51, 51, 51), + hintText: 'dd/MM/yyyy', + hintStyle: TextStyle(color: Color(0xFFACACAC)), + border: OutlineInputBorder( + borderSide: BorderSide.none, + ), + contentPadding: EdgeInsets.fromLTRB(16, 20, 16, 12), + suffixIcon: Padding( + padding: EdgeInsets.symmetric(horizontal: 14), + child: Icon( + Icons.edit_calendar, + color: Color(0xFFACACAC), + ), + ), + errorStyle: TextStyle(fontSize: 14), + ), + style: const TextStyle(color: Colors.white), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Bạn chưa nhập Ngày sinh'; // 68 44 + } + return null; + }, + ), + ), const SizedBox( height: 12, ), + // _DobTextFormField(dobController: _dobController), TextFormField( controller: _emailController, decoration: const InputDecoration( @@ -145,6 +306,7 @@ class _SignInState extends State { borderSide: BorderSide.none, ), contentPadding: EdgeInsets.fromLTRB(16, 20, 16, 12), + errorStyle: TextStyle(fontSize: 14), ), style: const TextStyle(color: Colors.white), autocorrect: false, @@ -161,9 +323,7 @@ class _SignInState extends State { height: 12, ), _PasswordTextFormField(passwordController: _passwordController), - const SizedBox( - height: 20, - ), + const Gap(50), _isProcessing ? const Padding( padding: EdgeInsets.symmetric(vertical: 14), @@ -198,111 +358,6 @@ class _SignInState extends State { } } -class _DobTextFormField extends StatefulWidget { - const _DobTextFormField({required this.dobController}); - final TextEditingController dobController; - - @override - State<_DobTextFormField> createState() => _DobTextFormFieldState(); -} - -class _DobTextFormFieldState extends State<_DobTextFormField> { - final _dobFocusNode = FocusNode(); - bool isHighlightDatePickerButton = false; - - void _onFocusChange() { - setState(() { - isHighlightDatePickerButton = _dobFocusNode.hasFocus; - }); - } - - @override - void initState() { - super.initState(); - - _dobFocusNode.addListener(_onFocusChange); - } - - @override - void dispose() { - _dobFocusNode.removeListener(_onFocusChange); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: TextFormField( - controller: widget.dobController, - focusNode: _dobFocusNode, - decoration: const InputDecoration( - filled: true, - fillColor: Color.fromARGB(255, 51, 51, 51), - hintText: 'Ngày sinh (dd/mm/yyyy)', - hintStyle: TextStyle(color: Color(0xFFACACAC)), - border: OutlineInputBorder( - borderSide: BorderSide.none, - ), - contentPadding: EdgeInsets.fromLTRB(16, 20, 16, 12), - ), - style: const TextStyle(color: Colors.white), - autocorrect: false, - enableSuggestions: false, // No work - keyboardType: TextInputType.datetime, // Trick: disable suggestions - validator: (value) { - if (value == null || value.isEmpty) { - return 'Bạn chưa nhập Ngày sinh'; - } - // Validate input day of birth - // print(value); - List dateParts = value.split('/'); - String formattedDate = - '${dateParts[2]}-${dateParts[1].padLeft(2, '0')}-${dateParts[0].padLeft(2, '0')}'; - final parsedDate = DateTime.tryParse(formattedDate); - // print('formattedDate = $formattedDate'); - // print('parsedDate = $parsedDate'); - if (parsedDate == null) { - return 'Không khớp định dạng dd/mm/yyyy'; - } - return null; - }, - ), - ), - const SizedBox( - width: 12, - ), - IconButton.filled( - onPressed: () async { - final selectedDob = await showDatePicker( - context: context, - initialDate: DateTime.now(), - firstDate: DateTime(1900), - lastDate: DateTime.now(), - ); - if (selectedDob != null) { - widget.dobController.text = DateFormat('dd/MM/yyyy').format(selectedDob); - } - }, - icon: const Icon(Icons.edit_calendar), - style: IconButton.styleFrom( - backgroundColor: isHighlightDatePickerButton - ? Theme.of(context).colorScheme.primary - : const Color.fromARGB(255, 51, 51, 51), - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), - padding: const EdgeInsets.all(16), - ), - ), - ], - ); - } -} - class _PasswordTextFormField extends StatefulWidget { const _PasswordTextFormField({required this.passwordController}); final TextEditingController passwordController; @@ -336,6 +391,7 @@ class _PasswordTextFormFieldState extends State<_PasswordTextFormField> { : const Icon(Icons.visibility), ), suffixIconColor: const Color(0xFFACACAC), + errorStyle: const TextStyle(fontSize: 14), border: const OutlineInputBorder( borderSide: BorderSide.none, ), diff --git a/lib/screens/change_password.dart b/lib/screens/change_password.dart index 0e9d519..24ee52f 100644 --- a/lib/screens/change_password.dart +++ b/lib/screens/change_password.dart @@ -175,31 +175,32 @@ class _ChangePasswordScreenState extends State { }, ), const Gap(40), - SizedBox( - width: double.infinity, - child: FilledButton( - onPressed: _changePassword, - style: FilledButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - child: _isProcessing - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - strokeCap: StrokeCap.round, + _isProcessing + ? const Padding( + padding: EdgeInsets.symmetric(vertical: 14), + child: SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 3, + ), + ), + ) + : SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: _changePassword, + style: FilledButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), ), - ) - : const Text( + ), + child: const Text( 'ĐỔI MẬT KHẨU', style: TextStyle(fontWeight: FontWeight.bold), ), - ), - ), + ), + ), Gap(MediaQuery.sizeOf(context).height * 0.15), ], ), diff --git a/lib/screens/main/new_hot.dart b/lib/screens/main/new_hot.dart index 8f14556..3b57efc 100644 --- a/lib/screens/main/new_hot.dart +++ b/lib/screens/main/new_hot.dart @@ -1,3 +1,4 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:movie_app/main.dart'; import 'package:movie_app/screens/film_detail.dart'; @@ -280,50 +281,16 @@ class NotificationNewFilm extends StatelessWidget { clipBehavior: Clip.antiAlias, child: Stack( children: [ - Image.network( - 'https://image.tmdb.org/t/p/original/$backdropPath', + CachedNetworkImage( + imageUrl: 'https://image.tmdb.org/t/p/original/$backdropPath', fit: BoxFit.cover, - frameBuilder: ( - BuildContext context, - Widget child, - int? frame, - bool wasSynchronouslyLoaded, - ) { - if (wasSynchronouslyLoaded) { - return child; - } - return AnimatedOpacity( - opacity: frame == null ? 0 : 1, - duration: const Duration( - milliseconds: 500), // Adjust the duration as needed - curve: Curves.easeInOut, - child: child, // Adjust the curve as needed - ); - }, - // https://api.flutter.dev/flutter/widgets/Image/loadingBuilder.html - loadingBuilder: ( - BuildContext context, - Widget child, - ImageChunkEvent? loadingProgress, - ) { - if (loadingProgress == null) { - return child; - } - return Center( - child: SizedBox( - width: 32, - height: 32, - child: CircularProgressIndicator( - // value: loadingProgress.expectedTotalBytes != null - // ? loadingProgress.cumulativeBytesLoaded / - // loadingProgress.expectedTotalBytes! - // : null, - color: Theme.of(context).colorScheme.primary, - strokeCap: StrokeCap.round, - ), - ), - ); - }, + // fadeInDuration: là thời gian xuất hiện của Image khi đã load xong + fadeInDuration: const Duration(milliseconds: 500), + // fadeOutDuration: là thời gian biến mất của placeholder khi Image khi đã load xong + fadeOutDuration: const Duration(milliseconds: 1000), + placeholder: (context, url) => const Center( + child: CircularProgressIndicator(), + ), ), Positioned( top: 10, diff --git a/lib/screens/main/profile.dart b/lib/screens/main/profile.dart index cb758fd..9544725 100644 --- a/lib/screens/main/profile.dart +++ b/lib/screens/main/profile.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -10,6 +11,7 @@ import 'package:movie_app/data/profile_data.dart'; import 'package:movie_app/onboarding/onboarding.dart'; import 'package:movie_app/screens/change_password.dart'; import 'package:movie_app/screens/my_list_films.dart'; +import 'package:movie_app/screens/update_user_info.dart'; import 'package:page_transition/page_transition.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -171,9 +173,37 @@ class _ProfileScreenState extends State { } } -class Header extends StatelessWidget { +class Header extends StatefulWidget { const Header({super.key}); + @override + State
createState() => _HeaderState(); +} + +class _HeaderState extends State
{ + void _updateUserInfo() { + context.read().push('/update_user_info'); + Navigator.of(context) + .push( + PageTransition( + child: const UpdateUserInfo(), + type: PageTransitionType.rightToLeft, + duration: 300.ms, + reverseDuration: 300.ms, + settings: const RouteSettings(name: '/update_user_info'), + ), + ) + .then( + (hasChanged) { + context.read().pop(); + // Nếu có nhấn nút "Cập nhật" thì khi quay lại trang Profile sẽ setState để update thông tin + if (hasChanged == true) { + setState(() {}); + } + }, + ); + } + void _deleteUser(BuildContext context) async { bool isProcessingDeleteUser = false; @@ -303,43 +333,58 @@ class Header extends StatelessWidget { child: SizedBox( width: 80, height: 80, - child: Image.network( - profileData['avatar_url'], + child: CachedNetworkImage( + imageUrl: + '$baseAvatarUrl${profileData['avatar_url']}?t=${DateTime.now()}', + fit: BoxFit.cover, + // fadeInDuration: là thời gian xuất hiện của Image khi đã load xong + fadeInDuration: const Duration(milliseconds: 400), + // fadeOutDuration: là thời gian biến mất của placeholder khi Image khi đã load xong + fadeOutDuration: const Duration(milliseconds: 800), + placeholder: (context, url) => const Padding( + padding: EdgeInsets.all(26), + child: CircularProgressIndicator( + strokeCap: StrokeCap.round, + strokeWidth: 3, + ), + ), ), ), ), const Gap(20), - SizedBox( - height: 80, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Email: ${currentUser!.email!}', - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: Colors.white, - fontSize: 16, + Expanded( + child: SizedBox( + height: 80, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Email: ${currentUser!.email!}', + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), ), - ), - Text( - 'Tên: ${profileData['full_name']}', - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: Colors.white, - fontSize: 16, + Text( + 'Tên: ${profileData['full_name']}', + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), ), - ), - Text( - 'Ngày sinh: ${profileData['dob']}', - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: Colors.white, - fontSize: 16, + Text( + 'Ngày sinh: ${profileData['dob']}', + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), ), - ), - ], + ], + ), ), ) ], @@ -350,7 +395,7 @@ class Header extends StatelessWidget { Row( children: [ InkWell( - onTap: () {}, + onTap: _updateUserInfo, borderRadius: BorderRadius.circular(8), child: Container( width: 44, diff --git a/lib/screens/update_user_info.dart b/lib/screens/update_user_info.dart new file mode 100644 index 0000000..49ca06e --- /dev/null +++ b/lib/screens/update_user_info.dart @@ -0,0 +1,315 @@ +import 'dart:io'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:movie_app/data/profile_data.dart'; +import 'package:movie_app/main.dart'; +import 'package:movie_app/utils/common_variables.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:path/path.dart' as p; + +class UpdateUserInfo extends StatefulWidget { + const UpdateUserInfo({super.key}); + + @override + State createState() => _UpdateUserInfoState(); +} + +class _UpdateUserInfoState extends State { + final _formKey = GlobalKey(); + + // Hình ảnh lấy từ device + File? chosenAvatar; + + final _usernameControllber = TextEditingController(text: profileData['full_name']); + final _dobController = TextEditingController(text: profileData['dob']); + + bool _isProcessing = false; + + Future _pickAvatar() async { + final XFile? pickedImage = await ImagePicker().pickImage(source: ImageSource.gallery); + if (pickedImage != null) { + setState(() { + chosenAvatar = File(pickedImage.path); + }); + } + } + + Future _openDatePicker(BuildContext context) async { + DateTime? chosenDate = await showDatePicker( + context: context, + initialDate: vnDateFormat.parse(profileData['dob']), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + if (chosenDate != null) { + _dobController.text = vnDateFormat.format(chosenDate); + } + } + + Future _updateUserInfo(BuildContext context) async { + final isValid = _formKey.currentState!.validate(); + + if (!isValid) { + return; + } + + setState(() { + _isProcessing = true; + }); + + final enteredUsername = _usernameControllber.text; + final enteredDob = _dobController.text; + + String extensionImage = ""; + + if (chosenAvatar != null) { + extensionImage = p.extension(chosenAvatar!.path); + // print(extension); + // print('${supabase.auth.currentUser!.email!}$extension'); + /* + https://stackoverflow.com/questions/74302341/supabase-bucket-new-row-violates-row-level-security-policy-for-table-objects + upsert need DELETE and UPDATE + */ + await supabase.storage.from('avatar').upload( + '${supabase.auth.currentUser!.email!}$extensionImage', + chosenAvatar!, + fileOptions: const FileOptions(upsert: true), + ); + /* + upload() trả về chuỗi sau: avatar/hlgame174@gmail.com.jpg + avatar_url: + https://kpaxjjmelbqpllxenpxz.supabase.co/storage/v1/object/public/avatar/hlgame174@gmail.com.jpg + */ + } + + try { + /* + Cập nhật vào Database server + - Cập nhật thông tin vào bảng profile + - Cập nhật avatar ở Storage + */ + + final updatedInfo = { + 'full_name': enteredUsername, + 'dob': enteredDob, + }; + + if (chosenAvatar != null) { + updatedInfo.addAll( + {'avatar_url': '${supabase.auth.currentUser!.email!}$extensionImage'}); + } + + print('updatedInfo = $updatedInfo'); + + await supabase.from('profile').update(updatedInfo).eq( + 'id', + supabase.auth.currentUser!.id, + ); + + // Cập nhật vào local + profileData['full_name'] = enteredUsername; + profileData['dob'] = enteredDob; + + if (chosenAvatar != null) { + profileData['avatar_url'] = '${supabase.auth.currentUser!.email!}$extensionImage'; + } + + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Cập nhật thông tin thành công.'), + behavior: SnackBarBehavior.floating, + duration: Duration(seconds: 3), + ), + ); + // ignore: use_build_context_synchronously + Navigator.of(context).pop(true); + } catch (e) { + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Cập nhật thất bại. Vui lòng thử lại sau!'), + ), + ); + } + + setState(() { + _isProcessing = false; + }); + } + + @override + Widget build(BuildContext context) { + final screenSize = MediaQuery.sizeOf(context); + + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.black, + foregroundColor: Colors.white, + ), + body: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: screenSize.width * 0.05), + child: Column( + children: [ + Gap(0.1 * screenSize.height), + Container( + height: 150, + width: 150, + decoration: BoxDecoration( + color: const Color.fromARGB(255, 51, 51, 51), + borderRadius: BorderRadius.circular(10), + ), + clipBehavior: Clip.antiAlias, + child: Stack( + alignment: Alignment.bottomRight, + children: [ + chosenAvatar == null + ? CachedNetworkImage( + imageUrl: + '$baseAvatarUrl${profileData['avatar_url']}?t=${DateTime.now()}', + height: 150, + width: 150, + fit: BoxFit.cover, + // fadeInDuration: là thời gian xuất hiện của Image khi đã load xong + fadeInDuration: const Duration(milliseconds: 400), + // fadeOutDuration: là thời gian biến mất của placeholder khi Image khi đã load xong + fadeOutDuration: const Duration(milliseconds: 800), + placeholder: (context, url) => const Padding( + padding: EdgeInsets.all(56), + child: CircularProgressIndicator( + strokeCap: StrokeCap.round, + strokeWidth: 3, + ), + ), + ) + : Image.file( + chosenAvatar!, + height: 150, + width: 150, + fit: BoxFit.cover, + ), + IconButton( + onPressed: _pickAvatar, + style: IconButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Colors.white, + padding: const EdgeInsets.all(12), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + ), + icon: const Icon(Icons.image_rounded), + ) + ], + ), + ), + const Gap(40), + TextField( + decoration: InputDecoration( + filled: true, + fillColor: const Color.fromARGB(255, 51, 51, 51), + hintText: supabase.auth.currentUser!.email, + hintStyle: const TextStyle(color: Color(0xFFACACAC)), + border: const OutlineInputBorder( + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.fromLTRB(16, 20, 16, 12), + ), + enabled: false, + style: const TextStyle(color: Colors.white), + ), + const Gap(20), + Form( + key: _formKey, + child: TextFormField( + controller: _usernameControllber, + decoration: const InputDecoration( + filled: true, + fillColor: Color.fromARGB(255, 51, 51, 51), + hintText: 'Tên của bạn', + hintStyle: TextStyle(color: Color(0xFFACACAC)), + border: OutlineInputBorder( + borderSide: BorderSide.none, + ), + contentPadding: EdgeInsets.fromLTRB(16, 20, 16, 12), + errorStyle: TextStyle(fontSize: 14), + ), + style: const TextStyle(color: Colors.white), + autocorrect: false, + enableSuggestions: false, // No work+ + keyboardType: TextInputType.emailAddress, // Trick: disable suggestions + validator: (value) { + if (value == null || value.isEmpty) { + return 'Bạn chưa nhập Tên'; + } + return null; + }, + ), + ), + const Gap(10), + GestureDetector( + onTap: () => _openDatePicker(context), + child: TextField( + controller: _dobController, + enabled: false, + mouseCursor: SystemMouseCursors.click, + decoration: const InputDecoration( + filled: true, + fillColor: Color.fromARGB(255, 51, 51, 51), + border: OutlineInputBorder( + borderSide: BorderSide.none, + ), + contentPadding: EdgeInsets.fromLTRB(16, 20, 16, 12), + suffixIcon: Padding( + padding: EdgeInsets.symmetric(horizontal: 14), + child: Icon( + Icons.edit_calendar, + color: Color(0xFFACACAC), + ), + ), + errorStyle: TextStyle(fontSize: 14), + ), + style: const TextStyle(color: Colors.white), + ), + ), + const Gap(40), + _isProcessing + ? const Padding( + padding: EdgeInsets.symmetric(vertical: 14), + child: SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 3, + ), + ), + ) + : SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () => _updateUserInfo(context), + style: FilledButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + ), + child: const Text( + 'CẬP NHẬT', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/film_detail/reviews_bottom_sheet.dart b/lib/widgets/film_detail/reviews_bottom_sheet.dart index ca73452..bedf504 100644 --- a/lib/widgets/film_detail/reviews_bottom_sheet.dart +++ b/lib/widgets/film_detail/reviews_bottom_sheet.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:movie_app/data/profile_data.dart'; @@ -158,8 +159,21 @@ class _ReviewsBottomSheetState extends State { borderRadius: BorderRadius.circular(10), child: SizedBox( width: 47, - child: Image.network( - profileData['avatar_url'], + height: 47, + child: CachedNetworkImage( + imageUrl: '$baseAvatarUrl${profileData['avatar_url']}', + fit: BoxFit.cover, + // fadeInDuration: là thời gian xuất hiện của Image khi đã load xong + fadeInDuration: const Duration(milliseconds: 400), + // fadeOutDuration: là thời gian biến mất của placeholder khi Image khi đã load xong + fadeOutDuration: const Duration(milliseconds: 800), + placeholder: (context, url) => const Padding( + padding: EdgeInsets.all(12), + child: CircularProgressIndicator( + strokeCap: StrokeCap.round, + strokeWidth: 3, + ), + ), ), ), ), @@ -217,8 +231,21 @@ class _ReviewsBottomSheetState extends State { borderRadius: BorderRadius.circular(10), child: SizedBox( width: 44, - child: Image.network( - widget.reviews[index].avatarUrl, + height: 44, + child: CachedNetworkImage( + imageUrl: '$baseAvatarUrl${widget.reviews[index].avatarUrl}', + fit: BoxFit.cover, + // fadeInDuration: là thời gian xuất hiện của Image khi đã load xong + fadeInDuration: const Duration(milliseconds: 400), + // fadeOutDuration: là thời gian biến mất của placeholder khi Image khi đã load xong + fadeOutDuration: const Duration(milliseconds: 800), + placeholder: (context, url) => const Padding( + padding: EdgeInsets.all(12), + child: CircularProgressIndicator( + strokeCap: StrokeCap.round, + strokeWidth: 3, + ), + ), ), ), ), diff --git a/lib/widgets/film_detail/segment_compose.dart b/lib/widgets/film_detail/segment_compose.dart index 11ad5d3..a7d03a9 100644 --- a/lib/widgets/film_detail/segment_compose.dart +++ b/lib/widgets/film_detail/segment_compose.dart @@ -81,12 +81,12 @@ class _SegmentComposeState extends State { // print('casts = ' + casts); - String characters = ''; - for (var element in _castData) { - characters += element['role'] + ', '; - } + // String characters = ''; + // for (var element in _castData) { + // characters += element['role'] + ', '; + // } - print('characters = ' + characters); + // print('characters = $characters'); } Future _fetchCrewData() async { diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index f6f23bf..7299b5c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index f16b4c3..786ff5c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index bae7dc8..1d842d1 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import app_links +import file_selector_macos import path_provider_foundation import screen_brightness_macos import shared_preferences_foundation @@ -17,6 +18,7 @@ import wakelock_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index a84ebdb..f006a27 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,6 +1,8 @@ PODS: - app_links (1.0.0): - FlutterMacOS + - file_selector_macos (0.0.1): + - FlutterMacOS - FlutterMacOS (1.0.0) - FMDB (2.7.5): - FMDB/standard (= 2.7.5) @@ -28,6 +30,7 @@ PODS: DEPENDENCIES: - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) + - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`) @@ -45,6 +48,8 @@ SPEC REPOS: EXTERNAL SOURCES: app_links: :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos + file_selector_macos: + :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos FlutterMacOS: :path: Flutter/ephemeral path_provider_foundation: @@ -66,6 +71,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: app_links: 4481ed4d71f384b0c3ae5016f4633aa73d32ff67 + file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 diff --git a/pubspec.lock b/pubspec.lock index 68dafa1..31bdc70 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f + url: "https://pub.dev" + source: hosted + version: "3.3.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257" + url: "https://pub.dev" + source: hosted + version: "1.1.0" carousel_slider: dependency: "direct main" description: @@ -65,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + url: "https://pub.dev" + source: hosted + version: "0.3.3+8" crypto: dependency: transitive description: @@ -93,10 +125,10 @@ packages: dependency: "direct main" description: name: dio - sha256: "01870acd87986f768e0c09cc4d7a19a59d814af7b34cbeb0b437d2c33bdfea4c" + sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" url: "https://pub.dev" source: hosted - version: "5.3.4" + version: "5.4.0" fake_async: dependency: transitive description: @@ -121,6 +153,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "0aa47a725c346825a2bd396343ce63ac00bda6eff2fbc43eabe99737dede8262" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" flutter: dependency: "direct main" description: flutter @@ -142,6 +206,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.3" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" flutter_lints: dependency: "direct dev" description: @@ -150,6 +222,19 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da + url: "https://pub.dev" + source: hosted + version: "2.0.17" flutter_test: dependency: "direct dev" description: flutter @@ -232,6 +317,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "7d7f2768df2a8b0a3cefa5ef4f84636121987d403130e70b17ef7e2cf650ba84" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: d6a6e78821086b0b737009b09363018309bbc6de3fd88cc5c26bc2bb44a4957f + url: "https://pub.dev" + source: hosted + version: "0.8.8+2" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "50bc9ae6a77eea3a8b11af5eb6c661eeb858fdd2f734c2a4fd17086922347ef7" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "76ec722aeea419d03aa915c2c96bf5b47214b053899088c9abb4086ceecf97a7" + url: "https://pub.dev" + source: hosted + version: "0.8.8+4" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: ed9b00e63977c93b0d2d2b343685bed9c324534ba5abafbb3dfbd6a780b1b514 + url: "https://pub.dev" + source: hosted + version: "2.9.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" intl: dependency: "direct main" description: @@ -304,6 +453,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + url: "https://pub.dev" + source: hosted + version: "2.0.0" page_transition: dependency: "direct main" description: @@ -313,7 +470,7 @@ packages: source: hosted version: "2.1.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" @@ -589,6 +746,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" sqflite: dependency: "direct main" description: @@ -689,10 +854,10 @@ packages: dependency: transitive description: name: url_launcher - sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba + sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.2" url_launcher_android: dependency: transitive description: @@ -737,10 +902,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "138bd45b3a456dcfafc46d1a146787424f8d2edfbf2809c9324361e58f851cf7" + sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" url_launcher_windows: dependency: transitive description: @@ -749,6 +914,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921 + url: "https://pub.dev" + source: hosted + version: "4.2.1" vector_math: dependency: transitive description: @@ -777,10 +950,10 @@ packages: dependency: transitive description: name: video_player_avfoundation - sha256: bc923884640d6dc403050586eb40713cdb8d1d84e6886d8aca50ab04c59124c2 + sha256: "01a57940e1dabc8769ccd457c4ae9ea50274e7d5a7617f7820dae5fe1d8436ae" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" video_player_platform_interface: dependency: transitive description: @@ -873,10 +1046,10 @@ packages: dependency: transitive description: name: webview_flutter_platform_interface - sha256: adb8c03c2be231bea5a8ed0e9039e9d18dbb049603376beaefa15393ede468a5 + sha256: "68e86162aa8fc646ae859e1585995c096c95fc2476881fa0c4a8d10f56013a5a" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.8.0" webview_flutter_wkwebview: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index aa661b3..1d87ac7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,11 @@ dependencies: path_provider: ^2.1.1 sqflite: ^2.3.0 gap: ^3.0.1 + flutter_localizations: + sdk: flutter + image_picker: ^1.0.4 + path: ^1.8.3 + cached_network_image: ^3.3.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 076e067..d42317a 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,12 +7,15 @@ #include "generated_plugin_registrant.h" #include +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { AppLinksPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); ScreenBrightnessWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 712119b..e56152e 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links + file_selector_windows screen_brightness_windows url_launcher_windows )