451 lines
16 KiB
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;
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|