From 9386195560f96dc484d26c2b4612ed76b60feb8c Mon Sep 17 00:00:00 2001
From: sidhdhi canopas <122426509+cp-sidhdhi-p@users.noreply.github.com>
Date: Fri, 24 May 2024 08:48:34 +0530
Subject: [PATCH] Redesign profile screens (#20)
* redesign profile screens
* added config for image cropper
* use action button
---
.../android/app/src/main/AndroidManifest.xml | 5 +
khelo/assets/images/ic_bin.svg | 3 +
khelo/assets/images/ic_check.svg | 3 +
khelo/assets/images/ic_edit.svg | 4 +
khelo/assets/images/ic_profile.svg | 3 +
khelo/assets/images/ic_profile_thin.svg | 3 +
khelo/assets/locales/app_en.arb | 26 +-
khelo/lib/components/action_bottom_sheet.dart | 48 +-
.../lib/components/profile_image_avatar.dart | 107 ++++
.../lib/domain/formatter/date_formatter.dart | 11 +-
khelo/lib/gen/assets.gen.dart | 20 +
khelo/lib/ui/flow/main/main_screen.dart | 2 +-
khelo/lib/ui/flow/profile/profile_screen.dart | 55 +-
.../edit_profile/edit_profile_screen.dart | 602 +++++++++---------
khelo/pubspec.yaml | 2 +-
style/lib/text/app_text_field.dart | 22 +-
style/lib/theme/colors.dart | 10 +
17 files changed, 542 insertions(+), 384 deletions(-)
create mode 100644 khelo/assets/images/ic_bin.svg
create mode 100644 khelo/assets/images/ic_check.svg
create mode 100644 khelo/assets/images/ic_edit.svg
create mode 100644 khelo/assets/images/ic_profile.svg
create mode 100644 khelo/assets/images/ic_profile_thin.svg
create mode 100644 khelo/lib/components/profile_image_avatar.dart
diff --git a/khelo/android/app/src/main/AndroidManifest.xml b/khelo/android/app/src/main/AndroidManifest.xml
index 4fb5ecb5..60387f8f 100644
--- a/khelo/android/app/src/main/AndroidManifest.xml
+++ b/khelo/android/app/src/main/AndroidManifest.xml
@@ -24,6 +24,11 @@
+
+
+
+
diff --git a/khelo/assets/images/ic_check.svg b/khelo/assets/images/ic_check.svg
new file mode 100644
index 00000000..8fdb4c3c
--- /dev/null
+++ b/khelo/assets/images/ic_check.svg
@@ -0,0 +1,3 @@
+
diff --git a/khelo/assets/images/ic_edit.svg b/khelo/assets/images/ic_edit.svg
new file mode 100644
index 00000000..b965c959
--- /dev/null
+++ b/khelo/assets/images/ic_edit.svg
@@ -0,0 +1,4 @@
+
diff --git a/khelo/assets/images/ic_profile.svg b/khelo/assets/images/ic_profile.svg
new file mode 100644
index 00000000..9a9f8a56
--- /dev/null
+++ b/khelo/assets/images/ic_profile.svg
@@ -0,0 +1,3 @@
+
diff --git a/khelo/assets/images/ic_profile_thin.svg b/khelo/assets/images/ic_profile_thin.svg
new file mode 100644
index 00000000..7a7624cb
--- /dev/null
+++ b/khelo/assets/images/ic_profile_thin.svg
@@ -0,0 +1,3 @@
+
diff --git a/khelo/assets/locales/app_en.arb b/khelo/assets/locales/app_en.arb
index d89d962f..80c8e022 100644
--- a/khelo/assets/locales/app_en.arb
+++ b/khelo/assets/locales/app_en.arb
@@ -557,24 +557,24 @@
"my_stat_stats_stumping_title": " Stumping",
"@_PROFILE": {
},
- "tab_profile_title": "profile",
- "edit_profile_screen_title": "Edit profile",
+ "tab_profile_title": "Profile",
+ "edit_profile_screen_title": "Edit Profile",
"edit_profile_select_birth_date_placeholder": "Select your date of birth",
- "edit_profile_name_placeholder": "NAME",
- "edit_profile_email_placeholder": "EMAIL",
- "edit_profile_location_placeholder": "LOCATION",
- "edit_profile_dob_placeholder": "DOB",
- "edit_profile_gender_placeholder": "GENDER",
- "edit_profile_player_role_placeholder": "PLAYER ROLE",
- "edit_profile_batting_style_placeholder": "BATTING STYLE",
- "edit_profile_bowling_style_placeholder": "BOWLING STYLE",
+ "edit_profile_name_placeholder": "Name",
+ "edit_profile_email_placeholder": "Email",
+ "edit_profile_location_placeholder": "Location",
+ "edit_profile_dob_placeholder": "Date of birth",
+ "edit_profile_gender_placeholder": "Gender",
+ "edit_profile_player_role_placeholder": "Player role",
+ "edit_profile_batting_style_placeholder": "Batting style",
+ "edit_profile_bowling_style_placeholder": "Bowling style",
"edit_profile_gender_male_title": "Male",
"edit_profile_gender_female_title": "Female",
"edit_profile_gender_other_title": "Other",
- "edit_profile_save_title": "SAVE",
+ "edit_profile_save_title": "Save",
"profile_complete_profile_btn_title": "Complete Profile",
- "profile_complete_your_profile_title": "Complete Your Profile",
- "profile_complete_profile_description_title": "Enhance your profile by filling in the remaining details. Let's make it more comprehensive and appealing.",
+ "profile_complete_your_profile_title": "Complete your profile",
+ "profile_complete_profile_description": "Enhance your profile by filling in the remaining details. Let's make it more comprehensive and appealing.",
"image_picker_choose_option_title": "Choose an option.",
"image_picker_crop_image_title": "Crop Image",
"batting_style_right_hand_bat_title": "Right hand bat",
diff --git a/khelo/lib/components/action_bottom_sheet.dart b/khelo/lib/components/action_bottom_sheet.dart
index 8488deb8..a8003a71 100644
--- a/khelo/lib/components/action_bottom_sheet.dart
+++ b/khelo/lib/components/action_bottom_sheet.dart
@@ -17,26 +17,29 @@ Future showActionBottomSheet({
),
),
useRootNavigator: useRootNavigator,
+ isScrollControlled: true,
context: context,
- builder: (context) => Container(
- decoration: BoxDecoration(
- borderRadius: const BorderRadius.vertical(
- top: Radius.circular(16),
+ builder: (context) => SingleChildScrollView(
+ child: Container(
+ decoration: BoxDecoration(
+ borderRadius: const BorderRadius.vertical(
+ top: Radius.circular(16),
+ ),
+ color: context.colorScheme.surface,
),
- color: context.colorScheme.surface,
- ),
- padding: EdgeInsets.only(
- bottom: context.mediaQueryPadding.bottom,
- ),
- child: ColumnBuilder.separated(
- separatorBuilder: (index) => Divider(
- height: 0,
- thickness: 1,
- color: context.colorScheme.outline,
+ padding: EdgeInsets.only(
+ bottom: context.mediaQueryPadding.bottom,
+ ),
+ child: ColumnBuilder.separated(
+ separatorBuilder: (index) => Divider(
+ height: 0,
+ thickness: 1,
+ color: context.colorScheme.outline,
+ ),
+ itemBuilder: (index) => items[index],
+ itemCount: items.length,
+ mainAxisSize: MainAxisSize.min,
),
- itemBuilder: (index) => items[index],
- itemCount: items.length,
- mainAxisSize: MainAxisSize.min,
),
),
);
@@ -70,12 +73,13 @@ class BottomSheetAction extends StatelessWidget {
visible: icon != null,
child: const SizedBox(width: 20),
),
- Text(
- title,
- style: AppTextStyle.subtitle2
- .copyWith(color: context.colorScheme.textPrimary),
+ Expanded(
+ child: Text(
+ title,
+ style: AppTextStyle.body1
+ .copyWith(color: context.colorScheme.textPrimary),
+ ),
),
- const Spacer(),
Visibility(
visible: child != null,
child: child ?? const SizedBox(),
diff --git a/khelo/lib/components/profile_image_avatar.dart b/khelo/lib/components/profile_image_avatar.dart
new file mode 100644
index 00000000..278386f0
--- /dev/null
+++ b/khelo/lib/components/profile_image_avatar.dart
@@ -0,0 +1,107 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:khelo/gen/assets.gen.dart';
+import 'package:style/animations/on_tap_scale.dart';
+import 'package:style/extensions/context_extensions.dart';
+import 'package:style/indicator/progress_indicator.dart';
+
+class ProfileImageAvatar extends StatelessWidget {
+ final double size;
+ final String? imageUrl;
+ final String placeHolderImage;
+ final bool isLoading;
+ final Function() onEditButtonTap;
+
+ const ProfileImageAvatar({
+ super.key,
+ required this.size,
+ this.imageUrl,
+ required this.placeHolderImage,
+ required this.isLoading,
+ required this.onEditButtonTap,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Center(
+ child: SizedBox(
+ height: size,
+ width: size,
+ child: Stack(
+ children: [
+ _roundedImageView(context),
+ _editImageButton(context, onTap: onEditButtonTap)
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _roundedImageView(BuildContext context) {
+ return Container(
+ height: size,
+ width: size,
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ image: imageUrl != null && !isLoading
+ ? DecorationImage(
+ image: CachedNetworkImageProvider(imageUrl!),
+ fit: BoxFit.cover)
+ : null,
+ color: context.colorScheme.primary),
+ child: _imagePlaceHolder(context),
+ );
+ }
+
+ Widget? _imagePlaceHolder(BuildContext context) {
+ return imageUrl == null && !isLoading
+ ? SvgPicture.asset(
+ Assets.images.icProfileThin,
+ height: size / 2,
+ width: size / 2,
+ colorFilter: ColorFilter.mode(
+ context.colorScheme.textInversePrimary,
+ BlendMode.srcATop,
+ ),
+ )
+ : isLoading
+ ? AppProgressIndicator(color: context.colorScheme.surface)
+ : null;
+ }
+
+ Widget _editImageButton(
+ BuildContext context, {
+ required Function() onTap,
+ }) {
+ return Align(
+ alignment: Alignment.bottomRight,
+ child: OnTapScale(
+ onTap: onTap,
+ child: Stack(
+ alignment: Alignment.center,
+ children: [
+ Container(
+ height: 32,
+ width: 32,
+ decoration: BoxDecoration(
+ color: context.colorScheme.surface,
+ border: Border.all(color: context.colorScheme.textPrimary),
+ shape: BoxShape.circle),
+ ),
+ SvgPicture.asset(
+ Assets.images.icEdit,
+ height: 18,
+ width: 18,
+ colorFilter: ColorFilter.mode(
+ context.colorScheme.textPrimary,
+ BlendMode.srcATop,
+ ),
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/khelo/lib/domain/formatter/date_formatter.dart b/khelo/lib/domain/formatter/date_formatter.dart
index d9325908..61cdb334 100644
--- a/khelo/lib/domain/formatter/date_formatter.dart
+++ b/khelo/lib/domain/formatter/date_formatter.dart
@@ -2,7 +2,7 @@ import 'package:flutter/cupertino.dart';
import 'package:intl/intl.dart';
import 'package:khelo/domain/extensions/context_extensions.dart';
-enum DateFormatType { dateAndTime, date, time}
+enum DateFormatType { dateAndTime, date, time, shortDate }
extension DateFormatter on DateTime {
String format(BuildContext context, DateFormatType type) {
@@ -14,13 +14,12 @@ extension DateFormatter on DateTime {
'EEE, dd MMM yyyy ${context.is24HourFormat ? 'HH:mm' : 'hh:mm a'}')
.format(this);
case DateFormatType.date:
- return DateFormat(
- 'EEE, MMM dd yyyy')
- .format(this);
+ return DateFormat('EEE, MMM dd yyyy').format(this);
case DateFormatType.time:
- return DateFormat(
- context.is24HourFormat ? 'HH:mm' : 'hh:mm a')
+ return DateFormat(context.is24HourFormat ? 'HH:mm' : 'hh:mm a')
.format(this);
+ case DateFormatType.shortDate:
+ return DateFormat('dd MMM yyyy').format(this);
}
}
}
diff --git a/khelo/lib/gen/assets.gen.dart b/khelo/lib/gen/assets.gen.dart
index fcf3f47e..3f4f4000 100644
--- a/khelo/lib/gen/assets.gen.dart
+++ b/khelo/lib/gen/assets.gen.dart
@@ -19,6 +19,9 @@ class $AssetsImagesGen {
AssetGenImage get icBatsman =>
const AssetGenImage('assets/images/ic_batsman.png');
+ /// File path: assets/images/ic_bin.svg
+ String get icBin => 'assets/images/ic_bin.svg';
+
/// File path: assets/images/ic_bowler.png
AssetGenImage get icBowler =>
const AssetGenImage('assets/images/ic_bowler.png');
@@ -27,6 +30,12 @@ class $AssetsImagesGen {
AssetGenImage get icCamera =>
const AssetGenImage('assets/images/ic_camera.png');
+ /// File path: assets/images/ic_check.svg
+ String get icCheck => 'assets/images/ic_check.svg';
+
+ /// File path: assets/images/ic_edit.svg
+ String get icEdit => 'assets/images/ic_edit.svg';
+
/// File path: assets/images/ic_gallery.png
AssetGenImage get icGallery =>
const AssetGenImage('assets/images/ic_gallery.png');
@@ -47,6 +56,12 @@ class $AssetsImagesGen {
AssetGenImage get icOtherOfficial =>
const AssetGenImage('assets/images/ic_other_official.png');
+ /// File path: assets/images/ic_profile.svg
+ String get icProfile => 'assets/images/ic_profile.svg';
+
+ /// File path: assets/images/ic_profile_thin.svg
+ String get icProfileThin => 'assets/images/ic_profile_thin.svg';
+
/// File path: assets/images/ic_scorer.png
AssetGenImage get icScorer =>
const AssetGenImage('assets/images/ic_scorer.png');
@@ -73,13 +88,18 @@ class $AssetsImagesGen {
List get values => [
icArrowDown,
icBatsman,
+ icBin,
icBowler,
icCamera,
+ icCheck,
+ icEdit,
icGallery,
icLeatherBall,
icMicroPhone,
icOtherBall,
icOtherOfficial,
+ icProfile,
+ icProfileThin,
icScorer,
icStreamer,
icTennisBall,
diff --git a/khelo/lib/ui/flow/main/main_screen.dart b/khelo/lib/ui/flow/main/main_screen.dart
index 70afe233..635faaec 100644
--- a/khelo/lib/ui/flow/main/main_screen.dart
+++ b/khelo/lib/ui/flow/main/main_screen.dart
@@ -126,7 +126,7 @@ class _MainScreenState extends ConsumerState
TabItem(
tabIcon: const Icon(Icons.person_outlined),
tabActiveIcon: const Icon(Icons.person),
- tabLabel: context.l10n.tab_profile_title,
+ tabLabel: context.l10n.tab_profile_title.toLowerCase(),
route: '',
onTap: () {
_materialPageController.jumpToPage(3);
diff --git a/khelo/lib/ui/flow/profile/profile_screen.dart b/khelo/lib/ui/flow/profile/profile_screen.dart
index 33766648..f3ac247d 100644
--- a/khelo/lib/ui/flow/profile/profile_screen.dart
+++ b/khelo/lib/ui/flow/profile/profile_screen.dart
@@ -54,7 +54,7 @@ class ProfileScreen extends ConsumerWidget {
child: Text(
context.l10n.common_sign_out_title,
style: AppTextStyle.button
- .copyWith(color: context.colorScheme.primary),
+ .copyWith(color: context.colorScheme.alert),
))
],
body: Builder(
@@ -83,29 +83,29 @@ class ProfileScreen extends ConsumerWidget {
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
- border: Border.all(color: context.colorScheme.containerHigh),
- borderRadius: BorderRadius.circular(12)),
+ color: context.colorScheme.containerNormal,
+ borderRadius: BorderRadius.circular(16)),
child: Row(
children: [
Container(
- height: 90,
- width: 90,
+ height: 80,
+ width: 80,
alignment: Alignment.center,
decoration: BoxDecoration(
- color: context.colorScheme.containerLow,
- shape: BoxShape.circle,
- image: state.currentUser?.profile_img_url != null
- ? DecorationImage(
- image: CachedNetworkImageProvider(
- state.currentUser!.profile_img_url!),
- fit: BoxFit.cover)
- : null,
- border: Border.all(color: context.colorScheme.containerHigh)),
+ color: context.colorScheme.containerHigh,
+ shape: BoxShape.circle,
+ image: state.currentUser?.profile_img_url != null
+ ? DecorationImage(
+ image: CachedNetworkImageProvider(
+ state.currentUser!.profile_img_url!),
+ fit: BoxFit.cover)
+ : null,
+ ),
child: state.currentUser?.profile_img_url == null
? Text(state.currentUser?.nameInitial ?? '?',
- style: AppTextStyle.header1.copyWith(
- color: context.colorScheme.secondary,
- fontSize: 40,
+ textAlign: TextAlign.center,
+ style: AppTextStyle.header2.copyWith(
+ color: context.colorScheme.textPrimary,
))
: null,
),
@@ -115,7 +115,7 @@ class ProfileScreen extends ConsumerWidget {
Expanded(
child: Text(
state.currentUser?.name ?? context.l10n.common_anonymous_title,
- style: AppTextStyle.header3
+ style: AppTextStyle.subtitle1
.copyWith(color: context.colorScheme.textPrimary),
),
)
@@ -130,28 +130,29 @@ class ProfileScreen extends ConsumerWidget {
currentUser.batting_style == null ||
currentUser.bowling_style == null) {
return Container(
- padding: const EdgeInsets.all(16),
+ padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
- border: Border.all(color: context.colorScheme.containerHigh),
- borderRadius: BorderRadius.circular(12)),
+ border: Border.all(color: context.colorScheme.outline),
+ borderRadius: BorderRadius.circular(16)),
child: Column(
children: [
Text(
context.l10n.profile_complete_your_profile_title,
- style: AppTextStyle.header1
+ textAlign: TextAlign.center,
+ style: AppTextStyle.header4
.copyWith(color: context.colorScheme.textPrimary),
),
const SizedBox(
- height: 8,
+ height: 16,
),
Text(
- context.l10n.profile_complete_profile_description_title,
- style: AppTextStyle.subtitle1
- .copyWith(color: context.colorScheme.textSecondary),
+ context.l10n.profile_complete_profile_description,
textAlign: TextAlign.center,
+ style: AppTextStyle.body1
+ .copyWith(color: context.colorScheme.textSecondary),
),
const SizedBox(
- height: 24,
+ height: 16,
),
PrimaryButton(
onPressed: () => AppRoute.editProfile().push(context),
diff --git a/khelo/lib/ui/flow/settings/edit_profile/edit_profile_screen.dart b/khelo/lib/ui/flow/settings/edit_profile/edit_profile_screen.dart
index c0e5bb8c..e25358f8 100644
--- a/khelo/lib/ui/flow/settings/edit_profile/edit_profile_screen.dart
+++ b/khelo/lib/ui/flow/settings/edit_profile/edit_profile_screen.dart
@@ -1,22 +1,28 @@
-import 'package:cached_network_image/cached_network_image.dart';
import 'package:data/api/user/user_models.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
-import 'package:intl/intl.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:khelo/components/action_bottom_sheet.dart';
import 'package:khelo/components/app_page.dart';
import 'package:khelo/components/confirmation_dialog.dart';
import 'package:khelo/components/error_snackbar.dart';
+import 'package:khelo/components/profile_image_avatar.dart';
import 'package:khelo/domain/extensions/enum_extensions.dart';
import 'package:khelo/domain/extensions/context_extensions.dart';
+import 'package:khelo/domain/formatter/date_formatter.dart';
+import 'package:khelo/gen/assets.gen.dart';
import 'package:khelo/ui/app_route.dart';
import 'package:khelo/ui/flow/settings/edit_profile/edit_profile_view_model.dart';
import 'package:style/animations/on_tap_scale.dart';
+import 'package:style/button/action_button.dart';
import 'package:style/button/bottom_sticky_overlay.dart';
import 'package:style/button/primary_button.dart';
import 'package:style/extensions/context_extensions.dart';
-import 'package:style/indicator/progress_indicator.dart';
+import 'package:style/text/app_text_field.dart';
import 'package:style/text/app_text_style.dart';
+import 'package:style/theme/colors.dart';
import '../../../../components/image_picker_sheet.dart';
@@ -25,15 +31,15 @@ class EditProfileScreen extends ConsumerWidget {
const EditProfileScreen({super.key, required this.isToCreateAccount});
- final double profileViewHeight = 130;
+ final double profileViewHeight = 128;
void _observeActionError(BuildContext context, WidgetRef ref) {
ref.listen(editProfileStateProvider.select((value) => value.actionError),
- (previous, next) {
- if (next != null) {
- showErrorSnackBar(context: context, error: next);
- }
- });
+ (previous, next) {
+ if (next != null) {
+ showErrorSnackBar(context: context, error: next);
+ }
+ });
}
void _observeIsSaved(BuildContext context, WidgetRef ref) {
@@ -58,362 +64,338 @@ class EditProfileScreen extends ConsumerWidget {
_observeIsSaved(context, ref);
return PopScope(
- canPop: !state.isButtonEnable,
- onPopInvoked: (didPop) {
- notifier.onBackBtnPressed();
- context.pop();
+ onPopInvoked: (didPop) async {
+ await notifier.onBackBtnPressed();
},
child: AppPage(
title: context.l10n.edit_profile_screen_title,
actions: [
if (!isToCreateAccount) ...[
- IconButton(
+ actionButton(context,
onPressed: () => showConfirmationDialog(context,
title: context.l10n.common_delete_title,
message: context.l10n.alert_confirm_default_message(
context.l10n.common_delete_title.toLowerCase()),
confirmBtnText: context.l10n.common_delete_title,
onConfirm: notifier.onDeleteTap),
- icon: const Icon(Icons.delete_outline))
+ icon: SvgPicture.asset(
+ Assets.images.icBin,
+ height: 24,
+ width: 24,
+ fit: BoxFit.contain,
+ colorFilter: ColorFilter.mode(
+ context.colorScheme.primary, BlendMode.srcATop),
+ )),
]
],
- body: Material(
- color: Colors.transparent,
- child: Builder(
- builder: (context) {
- return Stack(
- children: [
- ListView(
- padding: context.mediaQueryPadding +
- const EdgeInsets.symmetric(
- vertical: 8.0, horizontal: 16.0) +
- BottomStickyOverlay.padding,
- children: [
- _profileImageView(context, notifier, state),
- const SizedBox(
- height: 16,
- ),
- _textInputField(
- context,
- notifier,
- state,
- context.l10n.edit_profile_name_placeholder,
- state.nameController),
- const SizedBox(
- height: 16,
- ),
- _textInputField(
- context,
- notifier,
- state,
- context.l10n.edit_profile_email_placeholder,
- state.emailController),
- const SizedBox(
- height: 16,
- ),
- _textInputField(
- context,
- notifier,
- state,
- context.l10n.edit_profile_location_placeholder,
- state.locationController),
- _sectionTitle(
- context, context.l10n.edit_profile_dob_placeholder),
- OnTapScale(
- onTap: () => _selectDate(context, notifier, state),
- child: Text.rich(TextSpan(children: [
- WidgetSpan(
- child: Padding(
- padding: const EdgeInsets.only(right: 8.0),
- child: Icon(Icons.cake_rounded,
- color: context.colorScheme.textPrimary),
- )),
- TextSpan(
- text: DateFormat.yMMMMd().format(state.dob),
- style: AppTextStyle.subtitle1.copyWith(
- color: context.colorScheme.textSecondary)),
- ])),
- ),
- _sectionTitle(context,
- context.l10n.edit_profile_gender_placeholder),
- _genderOptionView(context, notifier, state),
- const SizedBox(
- height: 16,
- ),
- Wrap(
- alignment: WrapAlignment.center,
- children: [
- DropdownButton(
- alignment: Alignment.center,
- value: state.playerRole,
- dropdownColor:
- context.colorScheme.containerLowOnSurface,
- isExpanded: false,
- hint: Text(
- context.l10n
- .edit_profile_player_role_placeholder,
- style: AppTextStyle.header4.copyWith(
- color: context.colorScheme.textDisabled)),
- items: PlayerRole.values.map((PlayerRole items) {
- return DropdownMenuItem(
- value: items,
- child: Text(
- items.getString(context),
- style: AppTextStyle.body1.copyWith(
- color: context.colorScheme.textPrimary),
- ),
- );
- }).toList(),
- onChanged: (PlayerRole? newValue) {
- if (newValue != null &&
- newValue != state.playerRole) {
- notifier.onPlayerRoleChange(newValue);
- }
- }),
- ],
- ),
- const SizedBox(
- height: 16,
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: [
- DropdownButton(
- value: state.battingStyle,
- dropdownColor:
- context.colorScheme.containerLowOnSurface,
- hint: Text(
- context.l10n
- .edit_profile_batting_style_placeholder,
- style: AppTextStyle.header4.copyWith(
- color: context.colorScheme.textDisabled)),
- items:
- BattingStyle.values.map((BattingStyle items) {
- return DropdownMenuItem(
- value: items,
- child: Text(
- items.getString(context),
- style: AppTextStyle.body1.copyWith(
- color: context.colorScheme.textPrimary),
- ),
- );
- }).toList(),
- onChanged: (BattingStyle? newValue) {
- if (newValue != null &&
- newValue != state.battingStyle) {
- notifier.onBattingStyleChange(newValue);
- }
- }),
- DropdownButton(
- value: state.bowlingStyle,
- dropdownColor:
- context.colorScheme.containerLowOnSurface,
- hint: Text(
- context.l10n
- .edit_profile_bowling_style_placeholder,
- style: AppTextStyle.header4.copyWith(
- color: context.colorScheme.textDisabled)),
- items:
- BowlingStyle.values.map((BowlingStyle items) {
- return DropdownMenuItem(
- value: items,
- child: Text(
- items.getString(context),
- style: AppTextStyle.body1.copyWith(
- color: context.colorScheme.textPrimary),
- ),
- );
- }).toList(),
- onChanged: (BowlingStyle? newValue) {
- if (newValue != null &&
- newValue != state.bowlingStyle) {
- notifier.onBowlingStyleChange(newValue);
- }
- })
- ],
- ),
- const SizedBox(
- height: 24,
- ),
- ],
- ),
- _stickyButton(context, notifier, state)
- ],
- );
- },
- ),
- ),
- ),
- );
- }
-
- Widget _profileImageView(
- BuildContext context,
- EditProfileViewNotifier notifier,
- EditProfileState state,
- ) {
- return Center(
- child: SizedBox(
- height: profileViewHeight,
- width: profileViewHeight,
- child: Stack(
- children: [
- Container(
- height: profileViewHeight,
- width: profileViewHeight,
- alignment: Alignment.center,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- image: state.imageUrl != null && !state.isImageUploading
- ? DecorationImage(
- image: CachedNetworkImageProvider(state.imageUrl!),
- fit: BoxFit.cover)
- : null,
- color: context.colorScheme.primary),
- child: state.imageUrl == null && !state.isImageUploading
- ? Icon(
- Icons.person,
- size: profileViewHeight / 2,
- color: context.colorScheme.textSecondary,
- )
- : state.isImageUploading
- ? AppProgressIndicator(
- color: context.colorScheme.surface,
- )
- : null,
- ),
- OnTapScale(
- onTap: () async {
- final imagePath =
- await ImagePickerSheet.show(context, true);
- if (imagePath != null) {
- notifier.onImageChange(imagePath);
- }
- },
- child: Align(
- alignment: Alignment.bottomRight,
- child: Container(
- height: profileViewHeight / 3,
- width: profileViewHeight / 3,
- decoration: BoxDecoration(
- color: context.colorScheme.surface,
- border:
- Border.all(color: context.colorScheme.textSecondary),
- shape: BoxShape.circle),
- child: Icon(
- Icons.edit,
- size: profileViewHeight / 5,
- color: context.colorScheme.textSecondary,
- ),
+ body: Builder(
+ builder: (context) {
+ return Stack(
+ children: [
+ ListView(
+ padding: context.mediaQueryPadding +
+ const EdgeInsets.all(16.0) +
+ BottomStickyOverlay.padding,
+ children: [
+ ProfileImageAvatar(
+ size: profileViewHeight,
+ placeHolderImage: Assets.images.icProfileThin,
+ imageUrl: state.imageUrl,
+ isLoading: state.isImageUploading,
+ onEditButtonTap: () async {
+ final imagePath = await ImagePickerSheet.show(
+ context, true);
+ if (imagePath != null) {
+ notifier.onImageChange(imagePath);
+ }
+ }),
+ const SizedBox(height: 24),
+ _userContactDetailsView(context, notifier, state),
+ const SizedBox(height: 24),
+ _userPersonalDetailsView(context, notifier, state),
+ const SizedBox(height: 24),
+ _userPlayStyleView(context, notifier, state),
+ const SizedBox(height: 24),
+ ],
),
- ),
- ),
- ],
+ _stickyButton(context, notifier, state)
+ ],
+ );
+ },
),
),
);
}
- Widget _textInputField(
+ Widget _userContactDetailsView(
BuildContext context,
EditProfileViewNotifier notifier,
EditProfileState state,
- String label,
- TextEditingController controller,
) {
- return TextField(
- controller: controller,
- onChanged: (value) => notifier.onValueChange(),
- style:
- AppTextStyle.header4.copyWith(color: context.colorScheme.textPrimary),
- decoration: InputDecoration(
- labelText: label,
- labelStyle: AppTextStyle.header4
- .copyWith(color: context.colorScheme.textDisabled),
- border: const OutlineInputBorder(),
- ),
- );
- }
-
- Widget _sectionTitle(BuildContext context, String title) {
return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ _textInputField(context, notifier,
+ placeholderText: context.l10n.edit_profile_name_placeholder,
+ controller: state.nameController),
const SizedBox(
- height: 16,
- ),
- Text(
- title,
- style: AppTextStyle.body2
- .copyWith(color: context.colorScheme.textSecondary),
+ height: 8,
),
+ _textInputField(context, notifier,
+ placeholderText: context.l10n.edit_profile_email_placeholder,
+ controller: state.emailController),
const SizedBox(
height: 8,
),
+ _textInputField(context, notifier,
+ placeholderText: context.l10n.edit_profile_location_placeholder,
+ controller: state.locationController),
],
);
}
- Future _selectDate(
+ Widget _textInputField(
BuildContext context,
- EditProfileViewNotifier notifier,
- EditProfileState state,
- ) async {
- final DateTime? picked = await showDatePicker(
- context: context,
- helpText: context.l10n.edit_profile_select_birth_date_placeholder,
- initialDate: state.dob,
- firstDate: DateTime(1920),
- lastDate: DateTime.now());
- if (picked != null && picked != state.dob) {
- notifier.onDateSelect(selectedDate: picked);
- }
+ EditProfileViewNotifier notifier, {
+ required String placeholderText,
+ required TextEditingController controller,
+ }) {
+ return AppTextField(
+ controller: controller,
+ onChanged: (value) => notifier.onValueChange(),
+ style: AppTextStyle.subtitle3
+ .copyWith(color: context.colorScheme.textPrimary),
+ borderRadius: BorderRadius.circular(12),
+ borderType: AppTextFieldBorderType.outline,
+ backgroundColor: context.colorScheme.containerLow,
+ borderColor: BorderColor(Colors.transparent, Colors.transparent),
+ hintText: placeholderText,
+ hintStyle: AppTextStyle.subtitle3
+ .copyWith(color: context.colorScheme.textDisabled),
+ );
}
- Widget _genderOptionView(
+ Widget _userPersonalDetailsView(
BuildContext context,
EditProfileViewNotifier notifier,
EditProfileState state,
) {
return Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- _radioBtnCell(context, notifier, state, UserGender.male),
- _radioBtnCell(context, notifier, state, UserGender.female),
- _radioBtnCell(context, notifier, state, UserGender.other),
+ Expanded(
+ child: _selectionDropDownButton(
+ context,
+ headerText: context.l10n.edit_profile_dob_placeholder,
+ title: state.dob.format(context, DateFormatType.shortDate),
+ placeholder: context.l10n.edit_profile_dob_placeholder,
+ onTap: () => _selectDate(context, notifier, state),
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: _selectionDropDownButton(
+ context,
+ headerText: context.l10n.edit_profile_gender_placeholder,
+ title: state.gender?.getString(context),
+ placeholder: context.l10n.edit_profile_gender_placeholder,
+ showTrailingIcon: false,
+ onTap: () {
+ showActionBottomSheet(
+ context: context,
+ items: [UserGender.male, UserGender.female, UserGender.other]
+ .map((gender) => BottomSheetAction(
+ title: gender.getString(context),
+ child: showCheckMark(
+ context,
+ showCheck: state.gender == gender,
+ ),
+ onTap: () {
+ context.pop();
+ notifier.onGenderSelect(gender: gender);
+ },
+ ))
+ .toList());
+ },
+ ),
+ ),
],
);
}
- Widget _radioBtnCell(
+ Widget _userPlayStyleView(
BuildContext context,
EditProfileViewNotifier notifier,
EditProfileState state,
- UserGender gender,
) {
- return OnTapScale(
- onTap: () => notifier.onGenderSelect(gender: gender),
- child: Wrap(
- direction: Axis.horizontal,
- crossAxisAlignment: WrapCrossAlignment.center,
- children: [
- Radio(
- value: gender,
- groupValue: state.gender,
- activeColor: context.colorScheme.primary,
- onChanged: (value) {
- if (value != null) {
- notifier.onGenderSelect(gender: value);
- }
- }),
+ return Column(
+ children: [
+ _selectionDropDownButton(
+ context,
+ title: state.playerRole?.getString(context),
+ placeholder: context.l10n.edit_profile_player_role_placeholder,
+ onTap: () {
+ showActionBottomSheet(
+ context: context,
+ items: PlayerRole.values
+ .map((role) => BottomSheetAction(
+ title: role.getString(context),
+ child: showCheckMark(
+ context,
+ showCheck: state.playerRole == role,
+ ),
+ onTap: () {
+ context.pop();
+ notifier.onPlayerRoleChange(role);
+ },
+ ))
+ .toList());
+ },
+ ),
+ const SizedBox(height: 24),
+ _selectionDropDownButton(
+ context,
+ title: state.battingStyle?.getString(context),
+ placeholder: context.l10n.edit_profile_batting_style_placeholder,
+ onTap: () {
+ showActionBottomSheet(
+ context: context,
+ items: BattingStyle.values
+ .map((style) => BottomSheetAction(
+ title: style.getString(context),
+ child: showCheckMark(
+ context,
+ showCheck: state.battingStyle == style,
+ ),
+ onTap: () {
+ context.pop();
+ notifier.onBattingStyleChange(style);
+ },
+ ))
+ .toList());
+ },
+ ),
+ const SizedBox(height: 24),
+ _selectionDropDownButton(
+ context,
+ title: state.bowlingStyle?.getString(context),
+ placeholder: context.l10n.edit_profile_bowling_style_placeholder,
+ onTap: () {
+ showActionBottomSheet(
+ context: context,
+ items: BowlingStyle.values
+ .map((style) => BottomSheetAction(
+ title: style.getString(context),
+ child: showCheckMark(
+ context,
+ showCheck: state.bowlingStyle == style,
+ ),
+ onTap: () {
+ context.pop();
+ notifier.onBowlingStyleChange(style);
+ },
+ ))
+ .toList());
+ },
+ ),
+ ],
+ );
+ }
+
+ Widget? showCheckMark(
+ BuildContext context, {
+ required bool showCheck,
+ }) {
+ return showCheck
+ ? SvgPicture.asset(
+ Assets.images.icCheck,
+ height: 24,
+ width: 24,
+ colorFilter: ColorFilter.mode(
+ context.colorScheme.primary,
+ BlendMode.srcATop,
+ ),
+ )
+ : null;
+ }
+
+ Widget _selectionDropDownButton(
+ BuildContext context, {
+ String? headerText,
+ String? title,
+ required String placeholder,
+ bool showTrailingIcon = true,
+ required Function() onTap,
+ }) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ if (headerText != null) ...[
Text(
- gender.getString(context),
- style: AppTextStyle.button.copyWith(
- color: context.colorScheme.textSecondary, fontSize: 16),
+ headerText,
+ style: AppTextStyle.caption
+ .copyWith(color: context.colorScheme.textDisabled),
),
+ const SizedBox(height: 4),
],
- ),
+ OnTapScale(
+ onTap: onTap,
+ child: Container(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12),
+ border: Border.all(color: context.colorScheme.outline)),
+ child: Row(
+ children: [
+ Expanded(
+ child: Text(
+ title ?? placeholder,
+ style: AppTextStyle.subtitle3.copyWith(
+ color: title != null
+ ? context.colorScheme.textPrimary
+ : context.colorScheme.textSecondary),
+ ),
+ ),
+ if (showTrailingIcon) ...[
+ SvgPicture.asset(
+ Assets.images.icArrowDown,
+ height: 18,
+ width: 18,
+ colorFilter: ColorFilter.mode(
+ context.colorScheme.textPrimary,
+ BlendMode.srcATop,
+ ),
+ )
+ ]
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+
+ Future _selectDate(
+ BuildContext context,
+ EditProfileViewNotifier notifier,
+ EditProfileState state,
+ ) async {
+ final DateTime? picked = await showDatePicker(
+ context: context,
+ helpText: context.l10n.edit_profile_select_birth_date_placeholder,
+ initialDate: state.dob,
+ firstDate: DateTime(1920),
+ lastDate: DateTime.now(),
+ builder: (context, child) {
+ return Theme(
+ data: context.brightness == Brightness.dark
+ ? materialThemeDataDark
+ : materialThemeDataLight,
+ child: child!,
+ );
+ },
);
+ if (picked != null && picked != state.dob) {
+ notifier.onDateSelect(selectedDate: picked);
+ }
}
Widget _stickyButton(
diff --git a/khelo/pubspec.yaml b/khelo/pubspec.yaml
index bf823427..f001803f 100644
--- a/khelo/pubspec.yaml
+++ b/khelo/pubspec.yaml
@@ -93,7 +93,7 @@ flutter:
uses-material-design: true
assets:
- - assets/images/.
+ - assets/images/
# To add assets to your application, add an assets section, like this:
# assets:
diff --git a/style/lib/text/app_text_field.dart b/style/lib/text/app_text_field.dart
index 807db344..8e086af9 100644
--- a/style/lib/text/app_text_field.dart
+++ b/style/lib/text/app_text_field.dart
@@ -12,10 +12,12 @@ class AppTextField extends StatelessWidget {
final int? minLines;
final int? maxLength;
final TextStyle? style;
+ final Color? backgroundColor;
final bool expands;
final bool enabled;
final BorderRadius? borderRadius;
final AppTextFieldBorderType borderType;
+ final BorderColor? borderColor;
final double borderWidth;
final TextInputAction? textInputAction;
final String? hintText;
@@ -40,10 +42,12 @@ class AppTextField extends StatelessWidget {
this.minLines,
this.maxLength,
this.style,
+ this.backgroundColor,
this.expands = false,
this.enabled = true,
this.onChanged,
this.borderType = AppTextFieldBorderType.underline,
+ this.borderColor,
this.borderWidth = 1,
this.textInputAction,
this.borderRadius,
@@ -85,6 +89,7 @@ class AppTextField extends StatelessWidget {
Widget _textField(BuildContext context) => Material(
color: Colors.transparent,
+ borderRadius: borderRadius,
child: TextField(
controller: controller,
onChanged: onChanged,
@@ -117,6 +122,8 @@ class AppTextField extends StatelessWidget {
isCollapsed: isCollapsed,
hintText: hintText,
hintStyle: hintStyle,
+ fillColor: backgroundColor,
+ filled: backgroundColor != null,
focusedBorder: _border(context, true),
enabledBorder: _border(context, false),
contentPadding: contentPadding ??
@@ -141,8 +148,8 @@ class AppTextField extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: focused
- ? context.colorScheme.primary
- : context.colorScheme.outline,
+ ? borderColor?.focusColor ?? context.colorScheme.primary
+ : borderColor?.unFocusColor ?? context.colorScheme.outline,
width: borderWidth,
),
);
@@ -150,8 +157,8 @@ class AppTextField extends StatelessWidget {
return UnderlineInputBorder(
borderSide: BorderSide(
color: focused
- ? context.colorScheme.primary
- : context.colorScheme.outline,
+ ? borderColor?.focusColor ?? context.colorScheme.primary
+ : borderColor?.unFocusColor ?? context.colorScheme.outline,
width: borderWidth,
),
);
@@ -163,4 +170,11 @@ enum AppTextFieldBorderType {
none,
outline,
underline,
+}
+
+class BorderColor {
+ Color? focusColor;
+ Color? unFocusColor;
+
+ BorderColor(this.focusColor, this.unFocusColor);
}
\ No newline at end of file
diff --git a/style/lib/theme/colors.dart b/style/lib/theme/colors.dart
index 3154c712..6d741729 100644
--- a/style/lib/theme/colors.dart
+++ b/style/lib/theme/colors.dart
@@ -41,6 +41,11 @@ final ThemeData _materialDarkTheme = ThemeData.dark(useMaterial3: true);
final ThemeData materialThemeDataLight = _materialLightTheme.copyWith(
primaryColor: primaryColor,
dividerColor: outlineColor,
+ datePickerTheme: _materialLightTheme.datePickerTheme.copyWith(
+ backgroundColor: surfaceLightColor,
+ headerForegroundColor: textPrimaryLightColor,
+ dividerColor: outlineColor,
+ ),
colorScheme: _materialLightTheme.colorScheme.copyWith(
primary: primaryColor,
secondary: secondaryColor,
@@ -63,6 +68,11 @@ final ThemeData materialThemeDataLight = _materialLightTheme.copyWith(
final ThemeData materialThemeDataDark = _materialDarkTheme.copyWith(
primaryColor: primaryColor,
dividerColor: outlineColor,
+ datePickerTheme: _materialDarkTheme.datePickerTheme.copyWith(
+ backgroundColor: surfaceDarkColor,
+ headerForegroundColor: textPrimaryDarkColor,
+ dividerColor: outlineColor,
+ ),
colorScheme: _materialDarkTheme.colorScheme.copyWith(
primary: primaryColor,
secondary: secondaryColor,