import 'dart:async'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/providers/user.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/views/profile/edit_profile/widgets/profile_photo.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text_field.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; class EditProfile extends StatefulWidget { const EditProfile({Key? key}) : super(key: key); @override State createState() => _EditProfileState(); } class _EditProfileState extends State with TickerProviderStateMixin { Timer? _timer; late String fullName; String? username; String? email; bool _usernameIsAvailible = true; final _formKey = GlobalKey(); late AnimationController _slideController; late AnimationController _fadeController; late AnimationController _buttonController; late AnimationController _scaleController; late Animation _appBarSlideAnimation; late Animation _photoSlideAnimation; late Animation _field1SlideAnimation; late Animation _field2SlideAnimation; late Animation _field3SlideAnimation; late Animation _field4SlideAnimation; late Animation _fadeAnimation; late Animation _buttonFadeAnimation; late Animation _buttonScaleAnimation; late Animation _scaleAnimation; @override void initState() { super.initState(); final user = context.read().user; fullName = user.fullName; username = user.username; email = user.email; _initializeAnimations(); _startAnimations(); } void _initializeAnimations() { _slideController = AnimationController( duration: const Duration(milliseconds: 1200), vsync: this, ); _fadeController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _buttonController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _scaleController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _appBarSlideAnimation = Tween( begin: const Offset(0, -1), end: Offset.zero, ).animate(CurvedAnimation( parent: _slideController, curve: const Interval(0.0, 0.3, curve: Curves.easeOutCubic), )); _photoSlideAnimation = Tween( begin: const Offset(1.5, 0), end: Offset.zero, ).animate(CurvedAnimation( parent: _slideController, curve: const Interval(0.1, 0.5, curve: Curves.elasticOut), )); _field1SlideAnimation = Tween( begin: const Offset(-1.2, 0), end: Offset.zero, ).animate(CurvedAnimation( parent: _slideController, curve: const Interval(0.2, 0.5, curve: Curves.easeOutCubic), )); _field2SlideAnimation = Tween( begin: const Offset(-1.2, 0), end: Offset.zero, ).animate(CurvedAnimation( parent: _slideController, curve: const Interval(0.3, 0.6, curve: Curves.easeOutCubic), )); _field3SlideAnimation = Tween( begin: const Offset(-1.2, 0), end: Offset.zero, ).animate(CurvedAnimation( parent: _slideController, curve: const Interval(0.4, 0.7, curve: Curves.easeOutCubic), )); _field4SlideAnimation = Tween( begin: const Offset(-1.2, 0), end: Offset.zero, ).animate(CurvedAnimation( parent: _slideController, curve: const Interval(0.5, 0.8, curve: Curves.easeOutCubic), )); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _fadeController, curve: Curves.easeIn, )); _buttonFadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _buttonController, curve: Curves.easeIn, )); _buttonScaleAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _buttonController, curve: Curves.elasticOut, )); _scaleAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _scaleController, curve: Curves.elasticOut, )); } void _startAnimations() { _slideController.forward(); _fadeController.forward(); Future.delayed(const Duration(milliseconds: 800), () { if (mounted) { _buttonController.forward(); } }); Future.delayed(const Duration(milliseconds: 200), () { if (mounted) { _scaleController.forward(); } }); } @override void dispose() { _slideController.dispose(); _fadeController.dispose(); _buttonController.dispose(); _scaleController.dispose(); _timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Consumer( builder: (context, state, child) => Material( child: Stack( children: [ Container( padding: const EdgeInsets.only(bottom: 80), child: Form( key: _formKey, child: DidvanScaffold( padding: const EdgeInsets.only( top: 16, left: 0, right: 0, ), appBarData: null, showSliversFirst: true, slivers: [ SliverAppBar( pinned: true, backgroundColor: Theme.of(context).colorScheme.surface, automaticallyImplyLeading: false, leadingWidth: 200, leading: SlideTransition( position: _appBarSlideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: Padding( padding: const EdgeInsetsDirectional.only(start: 0.0), child: SvgPicture.asset( Assets.horizontalLogoWithText, fit: BoxFit.contain, height: 80, ), ), ), ), actions: [ SlideTransition( position: _appBarSlideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: IconButton( onPressed: () { Navigator.of(context) .pushNamed(Routes.bookmarks); }, icon: SvgPicture.asset( 'lib/assets/icons/hugeicons_telescope-01.svg')), ), ), SlideTransition( position: _appBarSlideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: IconButton( onPressed: () => Navigator.of(context).pop(), icon: SvgPicture.asset( 'lib/assets/icons/arrow-left.svg', color: const Color.fromARGB(255, 102, 102, 102), )), ), ), const SizedBox(width: 8), ], ), ], children: [ SlideTransition( position: _photoSlideAnimation, child: ScaleTransition( scale: _scaleAnimation, child: FadeTransition( opacity: _fadeAnimation, child: const ProfilePhoto(), ), ), ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.all(25.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SlideTransition( position: _field1SlideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: DidvanTextField( title: 'نام و نام‌ خانوادگی', hintText: 'نام و نام خانوادگی', initialValue: state.user.fullName, onChanged: (value) { _setButtonState(); fullName = value; }, prefixSvgPath: 'lib/assets/icons/profile2.svg', suffixSvgPath: 'lib/assets/icons/edit-3.svg', ), ), ), const SizedBox(height: 16), SlideTransition( position: _field2SlideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: DidvanTextField( title: 'شماره تلفن همراه', enabled: false, hintText: state.user.phoneNumber, prefixSvgPath: 'lib/assets/icons/mobile.svg', ), ), ), const SizedBox(height: 16), SlideTransition( position: _field3SlideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DidvanTextField( title: 'نام کاربری', hintText: 'انتخاب نام کاربری (اختیاری)', onChanged: _onUsernameChanged, initialValue: state.user.username, acceptSpace: false, prefixSvgPath: 'lib/assets/icons/user.svg', suffixSvgPath: 'lib/assets/icons/edit-3.svg', ), Padding( padding: const EdgeInsets.symmetric( vertical: 8.0), child: DidvanText( 'نام کاربری می‌تواند شامل کاراکترهای انگلیسی و اعداد باشد.', style: Theme.of(context) .textTheme .labelMedium ?.copyWith( color: const Color.fromARGB( 255, 184, 184, 184)), ), ), AnimatedVisibility( duration: DesignConfig.lowAnimationDuration, isVisible: !_usernameIsAvailible, child: DidvanText( 'نام کاربری در دسترس نمی‌باشد', style: Theme.of(context).textTheme.bodySmall, color: Theme.of(context).colorScheme.error, ), ), ], ), ), ), const SizedBox(height: 16), SlideTransition( position: _field4SlideAnimation, child: FadeTransition( opacity: _fadeAnimation, child: DidvanTextField( title: 'ایمیل', hintText: 'مثال: example@email.com', onChanged: (value) { _setButtonState(); email = value; }, prefixSvgPath: 'lib/assets/icons/sms.svg', suffixSvgPath: 'lib/assets/icons/edit-3.svg', initialValue: state.user.email, validator: (value) { if (value.isEmpty) return null; bool emailValid = RegExp( r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") .hasMatch(value); if (!emailValid) { return 'ایمیل وارد شده معتبر نمی‌باشد'; } return null; }, ), ), ), ], ), ), ], ), ), ), Positioned( left: 20, right: 20, bottom: 20, child: FadeTransition( opacity: _buttonFadeAnimation, child: ScaleTransition( scale: _buttonScaleAnimation, child: DidvanButton( key: UniqueKey(), title: 'ذخیره تغییرات', onPressed: () { if (_formKey.currentState!.validate() && _usernameIsAvailible) { state.editProfile(fullName, username, email); } }, imagepath: 'lib/assets/icons/verify.svg', enabled: _buttonEnabled, ), ), ), ), ], ), ), ); } void _setButtonState() { if (!_buttonEnabled) { setState(() {}); } } bool get _buttonEnabled { final user = context.read().user; return !(user.email == email && user.fullName == fullName && user.username == username); } void _onUsernameChanged(String value) { _usernameIsAvailible = true; _setButtonState(); username = value; _timer?.cancel(); if (value.length < 5) { return; } _timer = Timer(const Duration(seconds: 1), () { context.read().checkUsername(value).then((value) { if (value == false) { setState(() { _usernameIsAvailible = false; }); } }); }); } }