didvan-app/lib/views/profile/edit_profile/edit_profile.dart

451 lines
16 KiB
Dart

import 'dart:async';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.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<EditProfile> createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile>
with TickerProviderStateMixin {
Timer? _timer;
late String fullName;
String? username;
String? email;
bool _usernameIsAvailible = true;
final _formKey = GlobalKey<FormState>();
late AnimationController _slideController;
late AnimationController _fadeController;
late AnimationController _buttonController;
late AnimationController _scaleController;
late Animation<Offset> _appBarSlideAnimation;
late Animation<Offset> _photoSlideAnimation;
late Animation<Offset> _field1SlideAnimation;
late Animation<Offset> _field2SlideAnimation;
late Animation<Offset> _field3SlideAnimation;
late Animation<Offset> _field4SlideAnimation;
late Animation<double> _fadeAnimation;
late Animation<double> _buttonFadeAnimation;
late Animation<double> _buttonScaleAnimation;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
final user = context.read<UserProvider>().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<Offset>(
begin: const Offset(0, -1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _slideController,
curve: const Interval(0.0, 0.3, curve: Curves.easeOutCubic),
));
_photoSlideAnimation = Tween<Offset>(
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<Offset>(
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<Offset>(
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<Offset>(
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<Offset>(
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<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _fadeController,
curve: Curves.easeIn,
));
_buttonFadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _buttonController,
curve: Curves.easeIn,
));
_buttonScaleAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _buttonController,
curve: Curves.elasticOut,
));
_scaleAnimation = Tween<double>(
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<UserProvider>(
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:
Theme.of(context).colorScheme.caption,
)),
),
),
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<UserProvider>().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<UserProvider>().checkUsername(value).then((value) {
if (value == false) {
setState(() {
_usernameIsAvailible = false;
});
}
});
});
}
}