From 77e677b4f8de704d512ea0b6ab906155b890ab2f Mon Sep 17 00:00:00 2001 From: Vinayakkadge04 Date: Tue, 14 Oct 2025 17:31:59 +0530 Subject: [PATCH] worked on profile-module --- assets/icons/change_language.png | Bin 0 -> 1777 bytes assets/icons/contact_us.png | Bin 0 -> 2261 bytes assets/icons/faq.png | Bin 0 -> 1171 bytes assets/icons/privacy.png | Bin 0 -> 1999 bytes assets/icons/terms_and_condition.png | Bin 0 -> 703 bytes assets/icons/user_profile.png | Bin 0 -> 2392 bytes lib/common_package/custom_expansiontile.dart | 858 ++++++++++++++++++ lib/common_package/custom_text.dart | 43 + lib/common_package/custom_textfield.dart | 64 ++ lib/contact_us/contact_us_view.dart | 275 ++++++ lib/core/app_router.dart | 48 +- lib/core/route_constants.dart | 9 + lib/edit_profile/edit_profile_view.dart | 197 ++++ lib/faq/faq_view.dart | 185 ++++ lib/home/views/home_page_view.dart | 14 +- lib/privacy/privacy_view.dart | 69 ++ lib/profile/profile_page_view.dart | 213 +++++ .../terms_and_condition_view.dart | 69 ++ 18 files changed, 2037 insertions(+), 7 deletions(-) create mode 100644 assets/icons/change_language.png create mode 100644 assets/icons/contact_us.png create mode 100644 assets/icons/faq.png create mode 100644 assets/icons/privacy.png create mode 100644 assets/icons/terms_and_condition.png create mode 100644 assets/icons/user_profile.png create mode 100644 lib/common_package/custom_expansiontile.dart create mode 100644 lib/common_package/custom_text.dart create mode 100644 lib/common_package/custom_textfield.dart create mode 100644 lib/contact_us/contact_us_view.dart create mode 100644 lib/edit_profile/edit_profile_view.dart create mode 100644 lib/faq/faq_view.dart create mode 100644 lib/privacy/privacy_view.dart create mode 100644 lib/profile/profile_page_view.dart create mode 100644 lib/terms_and_condition/terms_and_condition_view.dart diff --git a/assets/icons/change_language.png b/assets/icons/change_language.png new file mode 100644 index 0000000000000000000000000000000000000000..bd3ef4f15ed602ac90eda283c1c59d63e659e796 GIT binary patch literal 1777 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 literal 0 HcmV?d00001 diff --git a/assets/icons/privacy.png b/assets/icons/privacy.png new file mode 100644 index 0000000000000000000000000000000000000000..6ad2b2722672f5e36637e589933bdcb9dea8b29e GIT binary patch literal 1999 zcmV;=2Qc`FP)@~0drDELIAGL9O(c600d`2O+f$vv5yP z+cp%(-vi^X8832!Dkq3@f;cCry>DtJov!1Yz|ILAuiMy&+v&FH2`Zl;>IuS}pzchj zjx7ra9z}Ubi4+MCq(BM3l_}8@g+K8AkN6^h$Fs^HbPO4W53ffM28=zHAba#00!BiJ zO9-I9#d!oBqB8w0FU%*do?7$2ONp_ z8^7}p;0OyBAXVE#bvZp~1*uG1zHz~{7bbv)sW#0*O}kYA>X~ZOEXK535x}mgHch*x z-KYRfn`+avVcOOOSd6JQO>?GgO#p{XwP~!Hb}a#1FjXn>FGTr0w2mFqHWI)kQ{8xf zhSy=(V*-!W^*mKC^`KSinYNYyE}AL{kUqW+Uxp|kRH5)Gw2X;qmkQvzsZI+p;Y;}Q z&YHM8RM&j~tyA5!3kB#(?p`Fo1PV_8Y?nzmg4V;6LW<*c2B*mRRo>gV7azw|iGOz? zjVu!&%}w~zo{Ym&+or8r1j=W5tzqWkyHxmujsO!DW7;xWNBLyBB|MluExE8BEHOz< z4C!w&Z6-i@1w4jE!QQRJ6ZOZ7%&X~xaqVZHphK9nfIQkVZI!N4DC$p+S^CYm_OB@0 z{FlMT#d)X-=#jv4CY#p+)*9*qUsh=10%+cdZNgn~3rNN*eHg09*E`VdzQ39t z6P~#fMvy)XRYW4w5t#;e8+vgYY9+P*|3Q0#K9jLzJ@w z3}1mB(H8%;U-ahOX(_^d`_vZY0K}__)bC1dT34v_x{L#4r&`J)K4fVk@ zUTrxwx4!~&!w*q9GtOs231jos8vT+|mX=+q09%3J;BU%+F+9@z7wS=XTXs&V0CZEP zl)Qu$=K6UU>Mq+=8RTV^y(213&aI;4GGkWzt7VXt1XyJ=HmUs(o9`K~a$@D_=_AD2 z%W=9!yl&c{_6vkJRnM&~K%(eMnejO9#~$cWx%S830UD@*np0R2lDuDk)jWb$q|g#z zlL<28m1qUy4NJ6%1jK23_3zO+IZjD6;#*g_`7t~)*)2wu&8hCTWkwra+#I~52}V6| z09%`NOsKJ8Nfd8ypo!5UJ%Qo{EGdSeM=SHmd>=?Z<<;QwUe|of)10~BTsk5I}vSwJcb(7 z5`fauE*mmUu&uK}p&IRaQM4UpRcdcl5F4_Tox4M(!XMTs{Bh`+KiXA=+5!-3EN+Go zbcmsZlNvjOMVtKm5G-It0BVLq6#LMDZi;wE;Dhj70t>;6t2$ZFG*$#4wmBcT-c2zH zJ2eWQx*M!M+Qgaw#3ny`>#846wsR}-W37z|RpHZb1dCV|fLOmcKXh$k@0T##skhzZZ5SnFyf)to9_PNqQAOg8r6XS`_t$j^jNX zsd!&ETZxkGh9hWzMp^(t7d#wi3$15H^rVk*ysusOS4|c^(U<@PWk%`;yoQ!YQp^(; zFdN(NQ)=C4;S-GtK&T(U+vu*KSg&gRCkwTnhzoz4H~)Oe#ynseQ>= zfF~CBy>6}WiPi)llnA>@gg>ESW3B=D)_z-sPqZokK{@%_ScD$9MI>5?`O~y60Fk7q zH1^s92SC8V2uJ&ic)0UI1R&y!W$X!>Oa3iihw4_g)w5~B!UULb6TUo#@iV^KCZ6MD zyKhIAg$qC==1i@j2kp?h*%s0GeLxjxR4wjfx9~uOGAQJ%qj=&%;S)}@fHd(VLYjH( zU_@iMHii*x;+z0PY|wN^YsVX|)UP~U2l2M<0D2QRDZqr(BL__%uEtfkdF^7sPjFg* h2?j!N(+4cK%{N|FW~zBWc3%Jh002ovPDHLkV1k4ehaUg{ literal 0 HcmV?d00001 diff --git a/assets/icons/terms_and_condition.png b/assets/icons/terms_and_condition.png new file mode 100644 index 0000000000000000000000000000000000000000..a479dbd190148fa6761dd121db68d8b1bf8096c4 GIT binary patch literal 703 zcmeAS@N?(olHy`uVBq!ia0vp^2|%34!3HEXk1UxBq&N#aB8wRqbi6^BajEUJl|Vtp z;vjb?hIQv;UIIBR>5jgR3=A9lx&I`xGB7ZOc)B=-R4~51doWknQKaFac`|pB-#doO zjIt^r4qObIJXx19Enq5o((tHJgh93?q~T@4%LD33%9V1jWdclge>}Ev?)L1z_f;>w z(P;m>_++1ZR5#ETDA;}Gs_zAz*Z$9oUhKUeez9@Eyi@w2vfs}+ub92d6Ly2B-QB&SX^<12=|b7K3AcHzceRlZlaK02zQvtobVMYV(Ej#@~0drDELIAGL9O(c600d`2O+f$vv5yPop~?Uww80Rdc@;|E0W0klr`J8*?@ zJb;$b8Ue2V6ZKKyAtLEGymg&qJ z7hp3AeilkIO>h43Lwp7e)2IM!<5hrf&1pi|NOIG3@IxF!Lo^})*ZMnxR|M(>JPcxu z`gId#$+-ZVU+b4)S_9It5g`GGC~(N_v*?uy)VqgQ_zZ@M)H}XMaEOx@U^`k;rq;tD zd>&lxqCR#pWUVd0brgjG;V;yhF})PlBt5CyFVqr%3ttgFP?J-J`XW>oU|aYF^+c#7 zz_##bsV>6W0^}Vo{_&=$7+^O!h`&h}9m9Q- zY1DnxyvIOafiF-$-HYSPQiql~$YZBFFjx3y7xdY>w$)y<##6Hjdj#?f%-YeHWjf_j z0^B}3j-_uTukkvnJJA_V6yKVOVjl!Spk(*r?UU8Vt-y=vL3q6vpPzz5ZljY^({DIk z$5}KL!DAk4|{JF1B)OI^;*o`m&`hAg}#mq`czD4CfFMn2NmA z>@5_)j;94jJ=&NBDK7+uZL%hK19C+99A%z`0!Y{XgZMlKVfK~g(TSW7kXF&6kLLv7 z#Wt|+fnp@!0qItt5T4tzo&9|FgPV*yCk#P|JJgbcYj8O-C)M4?R03!sqtAdY`;I+gDw3!igb*2?FFaP;G%*`e6k&AENJRGOCm zxcGf+f;`p6n@o9g=*?&SUI>d{tNw@CoV1L2wxp{+3uEfatliO>f} z*wERQ29$L8#cYfW+Lbvd7vc8kq*j1*n*+venZr;36yB6rgGiBzSctnj{pO3G%57g3 zX>dN0k2E;tD9YRlphyo|X^qB2J3B1IamqP{|UcnwnX3 zfDcri9pm(^0P)kHmdAX zW44=TCnj6GVx_5E@sCd(v{u^DIi+c_-m_o<*rohj5NQM_n1KAROvFVJV(*v#hJRdNFhH!ic za;oA1DY^;((_P4tCEu4!zPXP6*H=1|-Wy0J-{ABG@uC%{9BY)y!PiY88Pu&&JvoTK zO^Ww((?+xI@+UNubHP>cbyElgbx)ZeW|A4(z(wHxTdsus96=hrz#PZt1O9HanfYnrf1y@9VfVM1AI@2xNaW0cvTvP zl{@j3njWgtha^J9Y>}lhz^oL&HoYnx@&gf}YO2n%8DL7g5L8ZbMEZ2^`*HFuM5ziIM&tel>?biUAnwY`|k zk}bOnR!?KuQj;`(+*b9}%*O5?ubX_Tk{en5^pDLy9%>z&Jb0+$(o1DOJ{LJS$;>e* zGeUTJmZNpz4e(X%V7(gaUbP%4-6cYmdzNGIEK8;ED<@HsH5VOgd$PLCMoXsf@e(rq z>uv`US7H>;Csu(nd%kq&%w^rdimww_Vh78$3t*&PHoNa3eVY6tg)=wf5P-Un2 z94cy~7F#cbsOzL?L|dLBMje_VD?0#(&@R0Ata=M6WYdHiB9Ct3mfVdU&-UW)TzdqT z!kYjE<7a=#iNS@g6 +class CustomExpansionTile extends StatefulWidget { + /// Creates a single-line [ListTile] with an expansion arrow icon that expands or collapses + /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must + /// be non-null. + const CustomExpansionTile({ + super.key, + this.leading, + required this.title, + this.subtitle, + this.onExpansionChanged, + this.children = const [], + this.trailing, + this.showTrailingIcon = true, + this.initiallyExpanded = false, + this.maintainState = false, + this.tilePadding, + this.expandedCrossAxisAlignment, + this.expandedAlignment, + this.childrenPadding, + this.backgroundColor, + this.collapsedBackgroundColor, + this.textColor, + this.collapsedTextColor, + this.iconColor, + this.collapsedIconColor, + this.shape, + this.collapsedShape, + this.clipBehavior, + this.controller, + this.dense, + this.visualDensity, + this.minTileHeight, + this.enableFeedback = true, + this.enabled = true, + this.expansionAnimationStyle, + this.internalAddSemanticForOnTap = false, + this.borderRadius, + this.controlAffinity, + }) : assert( + expandedCrossAxisAlignment != CrossAxisAlignment.baseline, + 'CrossAxisAlignment.baseline is not supported since the expanded children ' + 'are aligned in a column, not a row. Try to use another constant.', + ); + + /// A widget to display before the title. + /// + /// Typically a [CircleAvatar] widget. + /// + /// Depending on the value of [controlAffinity], the [leading] widget + /// may replace the rotating expansion arrow icon. + final Widget? leading; + + /// The primary content of the list item. + /// + /// Typically a [Text] widget. + final Widget title; + + /// Additional content displayed below the title. + /// + /// Typically a [Text] widget. + final Widget? subtitle; + + /// Called when the tile expands or collapses. + /// + /// When the tile starts expanding, this function is called with the value + /// true. When the tile starts collapsing, this function is called with + /// the value false. + /// + /// Instead of providing this property, consider adding this callback as a + /// listener to a provided [controller]. + final ValueChanged? onExpansionChanged; + + /// The widgets that are displayed when the tile expands. + /// + /// Typically [ListTile] widgets. + final List children; + + /// The color to display behind the sublist when expanded. + /// + /// If this property is null then [ExpansionTileThemeData.backgroundColor] is used. If that + /// is also null then Colors.transparent is used. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? backgroundColor; + + /// When not null, defines the background color of tile when the sublist is collapsed. + /// + /// If this property is null then [ExpansionTileThemeData.collapsedBackgroundColor] is used. + /// If that is also null then Colors.transparent is used. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? collapsedBackgroundColor; + + /// A widget to display after the title. + /// + /// Depending on the value of [controlAffinity], the [trailing] widget + /// may replace the rotating expansion arrow icon. + final Widget? trailing; + + /// Specifies if the [ExpansionTile] should build a default trailing icon if [trailing] is null. + final bool showTrailingIcon; + + /// Specifies if the list tile is initially expanded (true) or collapsed (false). + /// + /// Alternatively, a provided [controller] can be used to initially expand the + /// tile if [ExpansibleController.expand] is called before this widget is built. + /// + /// Defaults to false. + final bool initiallyExpanded; + + /// Specifies whether the state of the children is maintained when the tile expands and collapses. + /// + /// When true, the children are kept in the tree while the tile is collapsed. + /// When false (default), the children are removed from the tree when the tile is + /// collapsed and recreated upon expansion. + final bool maintainState; + + /// Specifies padding for the [ListTile]. + /// + /// Analogous to [ListTile.contentPadding], this property defines the insets for + /// the [leading], [title], [subtitle] and [trailing] widgets. It does not inset + /// the expanded [children] widgets. + /// + /// If this property is null then [ExpansionTileThemeData.tilePadding] is used. If that + /// is also null then the tile's padding is `EdgeInsets.symmetric(horizontal: 16.0)`. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final EdgeInsetsGeometry? tilePadding; + + /// Specifies the alignment of [children], which are arranged in a column when + /// the tile is expanded. + /// + /// The internals of the expanded tile make use of a [Column] widget for + /// [children], and [Align] widget to align the column. The [expandedAlignment] + /// parameter is passed directly into the [Align]. + /// + /// Modifying this property controls the alignment of the column within the + /// expanded tile, not the alignment of [children] widgets within the column. + /// To align each child within [children], see [expandedCrossAxisAlignment]. + /// + /// The width of the column is the width of the widest child widget in [children]. + /// + /// If this property is null then [ExpansionTileThemeData.expandedAlignment]is used. If that + /// is also null then the value of [expandedAlignment] is [Alignment.center]. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Alignment? expandedAlignment; + + /// Specifies the alignment of each child within [children] when the tile is expanded. + /// + /// The internals of the expanded tile make use of a [Column] widget for + /// [children], and the `crossAxisAlignment` parameter is passed directly into + /// the [Column]. + /// + /// Modifying this property controls the cross axis alignment of each child + /// within its [Column]. The width of the [Column] that houses [children] will + /// be the same as the widest child widget in [children]. The width of the + /// [Column] might not be equal to the width of the expanded tile. + /// + /// To align the [Column] along the expanded tile, use the [expandedAlignment] + /// property instead. + /// + /// When the value is null, the value of [expandedCrossAxisAlignment] is + /// [CrossAxisAlignment.center]. + final CrossAxisAlignment? expandedCrossAxisAlignment; + + /// Specifies padding for [children]. + /// + /// If this property is null then [ExpansionTileThemeData.childrenPadding] is used. If that + /// is also null then the value of [childrenPadding] is [EdgeInsets.zero]. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final EdgeInsetsGeometry? childrenPadding; + + /// The icon color of tile's expansion arrow icon when the sublist is expanded. + /// + /// Used to override to the [ListTileThemeData.iconColor]. + /// + /// If this property is null then [ExpansionTileThemeData.iconColor] is used. If that + /// is also null then the value of [ColorScheme.primary] is used. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? iconColor; + + /// The icon color of tile's expansion arrow icon when the sublist is collapsed. + /// + /// Used to override to the [ListTileThemeData.iconColor]. + /// + /// If this property is null then [ExpansionTileThemeData.collapsedIconColor] is used. If that + /// is also null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurface] is used. Otherwise, + /// defaults to [ThemeData.unselectedWidgetColor] color. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? collapsedIconColor; + + /// The color of the tile's titles when the sublist is expanded. + /// + /// Used to override to the [ListTileThemeData.textColor]. + /// + /// If this property is null then [ExpansionTileThemeData.textColor] is used. If that + /// is also null then and [ThemeData.useMaterial3] is true, color of the [TextTheme.bodyLarge] + /// will be used for the [title] and [subtitle]. Otherwise, defaults to [ColorScheme.primary] color. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? textColor; + + /// The color of the tile's titles when the sublist is collapsed. + /// + /// Used to override to the [ListTileThemeData.textColor]. + /// + /// If this property is null then [ExpansionTileThemeData.collapsedTextColor] is used. + /// If that is also null and [ThemeData.useMaterial3] is true, color of the + /// [TextTheme.bodyLarge] will be used for the [title] and [subtitle]. Otherwise, + /// defaults to color of the [TextTheme.titleMedium]. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? collapsedTextColor; + + /// The tile's border shape when the sublist is expanded. + /// + /// If this property is null, the [ExpansionTileThemeData.shape] is used. If that + /// is also null, a [Border] with vertical sides default to [ThemeData.dividerColor] is used + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final ShapeBorder? shape; + + /// The tile's border shape when the sublist is collapsed. + /// + /// If this property is null, the [ExpansionTileThemeData.collapsedShape] is used. If that + /// is also null, a [Border] with vertical sides default to Color [Colors.transparent] is used + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final ShapeBorder? collapsedShape; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// If this is not null and a custom collapsed or expanded shape is provided, + /// the value of [clipBehavior] will be used to clip the expansion tile. + /// + /// If this property is null, the [ExpansionTileThemeData.clipBehavior] is used. If that + /// is also null, defaults to [Clip.antiAlias]. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Clip? clipBehavior; + + /// Typically used to force the expansion arrow icon to the tile's leading or trailing edge. + /// + /// By default, the value of [controlAffinity] is [ListTileControlAffinity.platform], + /// which means that the expansion arrow icon will appear on the tile's trailing edge. + final ListTileControlAffinity? controlAffinity; + + /// If provided, the controller can be used to expand and collapse tiles. + /// + /// In cases where control over the tile's state is needed from a callback + /// triggered by a widget within the tile, [ExpansibleController.of] may be + /// more convenient than supplying a controller. + final ExpansionTileController? controller; + + /// {@macro flutter.material.ListTile.dense} + final bool? dense; + final BorderRadius? borderRadius; + /// Defines how compact the expansion tile's layout will be. + /// + /// {@macro flutter.material.themedata.visualDensity} + final VisualDensity? visualDensity; + + /// {@macro flutter.material.ListTile.minTileHeight} + final double? minTileHeight; + + /// {@macro flutter.material.ListTile.enableFeedback} + final bool? enableFeedback; + + /// Whether this expansion tile is interactive. + /// + /// If false, the internal [ListTile] will be disabled, changing its + /// appearance according to the theme and disabling user interaction. + /// + /// Even if disabled, the expansion can still be toggled programmatically + /// through an [ExpansionTileController]. + final bool enabled; + + /// Used to override the expansion animation curve and duration. + /// + /// If [AnimationStyle.duration] is provided, it will be used to override + /// the expansion animation duration. If it is null, then [AnimationStyle.duration] + /// from the [ExpansionTileThemeData.expansionAnimationStyle] will be used. + /// Otherwise, defaults to 200ms. + /// + /// If [AnimationStyle.curve] is provided, it will be used to override + /// the expansion animation curve. If it is null, then [AnimationStyle.curve] + /// from the [ExpansionTileThemeData.expansionAnimationStyle] will be used. + /// Otherwise, defaults to [Curves.easeIn]. + /// + /// If [AnimationStyle.reverseCurve] is provided, it will be used to override + /// the collapse animation curve. If it is null, then [AnimationStyle.reverseCurve] + /// from the [ExpansionTileThemeData.expansionAnimationStyle] will be used. + /// Otherwise, the same curve will be used as for expansion. + /// + /// To disable the theme animation, use [AnimationStyle.noAnimation]. + /// + /// {@tool dartpad} + /// This sample showcases how to override the [ExpansionTile] expansion + /// animation curve and duration using [AnimationStyle]. + /// + /// ** See code in examples/api/lib/material/expansion_tile/expansion_tile.2.dart ** + /// {@end-tool} + final AnimationStyle? expansionAnimationStyle; + + /// Whether to add button:true to the semantics if onTap is provided. + /// This is a temporary flag to help changing the behavior of ListTile onTap semantics. + /// + // TODO(hangyujin): Remove this flag after fixing related g3 tests and flipping + // the default value to true. + final bool internalAddSemanticForOnTap; + + @override + State createState() => _customExpansionTileState(); +} + + +class _customExpansionTileState extends State { + static final Animatable _easeInTween = CurveTween( + curve: Curves.easeIn); + static final Animatable _easeOutTween = CurveTween( + curve: Curves.easeOut); + static final Animatable _halfTween = Tween( + begin: 0.0, end: 0.5); + + final ShapeBorderTween _borderTween = ShapeBorderTween(); + final ColorTween _headerColorTween = ColorTween(); + final ColorTween _iconColorTween = ColorTween(); + final ColorTween _backgroundColorTween = ColorTween(); + + late Animation _iconTurns; + late Animation _border; + late Animation _headerColor; + late Animation _iconColor; + late Animation _backgroundColor; + + late ExpansionTileThemeData _expansionTileTheme; + late ExpansionTileController _tileController; + Timer? _timer; + late Curve _curve; + late Curve? _reverseCurve; + late Duration _duration; + + @override + void initState() { + super.initState(); + _curve = Curves.easeIn; + _duration = _kExpand; + _tileController = widget.controller ?? ExpansionTileController(); + if (widget.initiallyExpanded) { + _tileController.expand(); + } + _tileController.addListener(_onExpansionChanged); + } + + @override + void dispose() { + _tileController.removeListener(_onExpansionChanged); + if (widget.controller == null) { + _tileController.dispose(); + } + _timer?.cancel(); + _timer = null; + super.dispose(); + } + + void _onExpansionChanged() { + final TextDirection textDirection = WidgetsLocalizations + .of(context) + .textDirection; + final MaterialLocalizations localizations = MaterialLocalizations.of( + context); + final String stateHint = _tileController.isExpanded + ? localizations.collapsedHint + : localizations.expandedHint; + + if (defaultTargetPlatform == TargetPlatform.iOS) { + // TODO(tahatesser): This is a workaround for VoiceOver interrupting + // semantic announcements on iOS. https://github.com/flutter/flutter/issues/122101. + _timer?.cancel(); + _timer = Timer(const Duration(seconds: 1), () { + SemanticsService.announce(stateHint, textDirection); + _timer?.cancel(); + _timer = null; + }); + } else { + SemanticsService.announce(stateHint, textDirection); + } + widget.onExpansionChanged?.call(_tileController.isExpanded); + } + + // Platform or null affinity defaults to trailing. + ListTileControlAffinity _effectiveAffinity() { + final ListTileThemeData listTileTheme = ListTileTheme.of(context); + final ListTileControlAffinity affinity = + widget.controlAffinity ?? listTileTheme.controlAffinity ?? + ListTileControlAffinity.trailing; + switch (affinity) { + case ListTileControlAffinity.leading: + return ListTileControlAffinity.leading; + case ListTileControlAffinity.trailing: + case ListTileControlAffinity.platform: + return ListTileControlAffinity.trailing; + } + } + + Widget? _buildIcon(BuildContext context, Animation animation) { + _iconTurns = animation.drive(_halfTween.chain(_easeInTween)); + return RotationTransition( + turns: _iconTurns, child: const Icon(Icons.expand_more)); + } + + Widget? _buildLeadingIcon(BuildContext context, Animation animation) { + if (_effectiveAffinity() != ListTileControlAffinity.leading) { + return null; + } + return _buildIcon(context, animation); + } + + Widget? _buildTrailingIcon(BuildContext context, + Animation animation) { + if (_effectiveAffinity() != ListTileControlAffinity.trailing) { + return null; + } + return _buildIcon(context, animation); + } + + Widget _buildHeader(BuildContext context, Animation animation) { + _iconColor = animation.drive(_iconColorTween.chain(_easeInTween)); + _headerColor = animation.drive(_headerColorTween.chain(_easeInTween)); + final ThemeData theme = Theme.of(context); + final MaterialLocalizations localizations = MaterialLocalizations.of( + context); + final String onTapHint = _tileController.isExpanded + ? localizations.expansionTileExpandedTapHint + : localizations.expansionTileCollapsedTapHint; + String? semanticsHint; + switch (theme.platform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + semanticsHint = _tileController.isExpanded + ? '${localizations.collapsedHint}\n ${localizations + .expansionTileExpandedHint}' + : '${localizations.expandedHint}\n ${localizations + .expansionTileCollapsedHint}'; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + break; + } + + return Semantics( + hint: semanticsHint, + onTapHint: onTapHint, + child: ListTileTheme.merge( + iconColor: _iconColor.value ?? _expansionTileTheme.iconColor, + textColor: _headerColor.value, + child: ListTile( + enabled: widget.enabled, + onTap: _tileController.isExpanded + ? _tileController.collapse + : _tileController.expand, + dense: widget.dense, + visualDensity: widget.visualDensity, + enableFeedback: widget.enableFeedback, + contentPadding: widget.tilePadding ?? _expansionTileTheme.tilePadding, + leading: widget.leading ?? _buildLeadingIcon(context, animation), + title: widget.title, + subtitle: widget.subtitle, + trailing: widget.showTrailingIcon + ? widget.trailing ?? _buildTrailingIcon(context, animation) + : null, + minTileHeight: widget.minTileHeight, + internalAddSemanticForOnTap: widget.internalAddSemanticForOnTap, + ), + ), + ); + } + + Widget _buildBody(BuildContext context, Animation animation) { + return Align( + alignment: + widget.expandedAlignment ?? _expansionTileTheme.expandedAlignment ?? + Alignment.center, + child: Padding( + padding: widget.childrenPadding ?? + _expansionTileTheme.childrenPadding ?? EdgeInsets.zero, + child: Column( + crossAxisAlignment: widget.expandedCrossAxisAlignment ?? + CrossAxisAlignment.center, + children: widget.children, + ), + ), + ); + } + + Widget _buildExpansible(BuildContext context, + Widget header, + Widget body, + Animation animation,) { + _backgroundColor = + animation.drive(_backgroundColorTween.chain(_easeOutTween)); + _border = animation.drive(_borderTween.chain(_easeOutTween)); + final Color backgroundColor = + _backgroundColor.value ?? _expansionTileTheme.backgroundColor ?? + Colors.transparent; + final ShapeBorder expansionTileBorder = _border.value ?? + RoundedRectangleBorder( + borderRadius: widget.borderRadius ?? BorderRadius.circular(5), + side: BorderSide(color: Colors.transparent), + ); + + final Clip clipBehavior = + widget.clipBehavior ?? _expansionTileTheme.clipBehavior ?? + Clip.antiAlias; + + final Decoration decoration = ShapeDecoration( + color: backgroundColor, + shape: expansionTileBorder, + ); + + final Widget tile = Padding( + padding: decoration.padding, + child: Column( + mainAxisSize: MainAxisSize.min, children: [header, body]), + ); + + final bool isShapeProvided = + widget.shape != null || + _expansionTileTheme.shape != null || + widget.collapsedShape != null || + _expansionTileTheme.collapsedShape != null; + + if (isShapeProvided) { + return Material( + clipBehavior: clipBehavior, + color: backgroundColor, + shape: expansionTileBorder, + child: tile, + ); + } + + return DecoratedBox(decoration: decoration, child: tile); + } + + @override + void didUpdateWidget(covariant CustomExpansionTile oldWidget) { + super.didUpdateWidget(oldWidget); + final ThemeData theme = Theme.of(context); + _expansionTileTheme = ExpansionTileTheme.of(context); + final ExpansionTileThemeData defaults = theme.useMaterial3 + ? _ExpansionTileDefaultsM3(context) + : _ExpansionTileDefaultsM2(context); + if (widget.collapsedShape != oldWidget.collapsedShape || + widget.shape != oldWidget.shape) { + _updateShapeBorder(theme); + } + if (widget.collapsedTextColor != oldWidget.collapsedTextColor || + widget.textColor != oldWidget.textColor) { + _updateHeaderColor(defaults); + } + if (widget.collapsedIconColor != oldWidget.collapsedIconColor || + widget.iconColor != oldWidget.iconColor) { + _updateIconColor(defaults); + } + if (widget.backgroundColor != oldWidget.backgroundColor || + widget.collapsedBackgroundColor != oldWidget.collapsedBackgroundColor) { + _updateBackgroundColor(); + } + if (widget.expansionAnimationStyle != oldWidget.expansionAnimationStyle) { + _updateAnimationDuration(); + _updateHeightFactorCurve(); + } + } + + @override + void didChangeDependencies() { + final ThemeData theme = Theme.of(context); + _expansionTileTheme = ExpansionTileTheme.of(context); + final ExpansionTileThemeData defaults = theme.useMaterial3 + ? _ExpansionTileDefaultsM3(context) + : _ExpansionTileDefaultsM2(context); + _updateAnimationDuration(); + _updateShapeBorder(theme); + _updateHeaderColor(defaults); + _updateIconColor(defaults); + _updateBackgroundColor(); + _updateHeightFactorCurve(); + super.didChangeDependencies(); + } + + void _updateAnimationDuration() { + _duration = + widget.expansionAnimationStyle?.duration ?? + _expansionTileTheme.expansionAnimationStyle?.duration ?? + const Duration(milliseconds: 200); + } + + void _updateShapeBorder(ThemeData theme) { + final BorderRadius radius = widget.borderRadius ?? BorderRadius.circular(5); + + _borderTween + ..begin = widget.collapsedShape ?? + _expansionTileTheme.collapsedShape ?? + RoundedRectangleBorder( + borderRadius: radius, + side: const BorderSide(color: Colors.transparent), + ) + ..end = widget.shape ?? + _expansionTileTheme.shape ?? + RoundedRectangleBorder( + borderRadius: radius, + side: BorderSide(color:Colors.transparent), + ); + } + + + void _updateHeaderColor(ExpansionTileThemeData defaults) { + _headerColorTween + ..begin = + widget.collapsedTextColor ?? + _expansionTileTheme.collapsedTextColor ?? + defaults.collapsedTextColor + ..end = widget.textColor ?? _expansionTileTheme.textColor ?? + defaults.textColor; + } + + void _updateIconColor(ExpansionTileThemeData defaults) { + _iconColorTween + ..begin = + widget.collapsedIconColor ?? + _expansionTileTheme.collapsedIconColor ?? + defaults.collapsedIconColor + ..end = widget.iconColor ?? _expansionTileTheme.iconColor ?? + defaults.iconColor; + } + + void _updateBackgroundColor() { + _backgroundColorTween + ..begin = widget.collapsedBackgroundColor ?? + _expansionTileTheme.collapsedBackgroundColor + ..end = widget.backgroundColor ?? _expansionTileTheme.backgroundColor; + } + + void _updateHeightFactorCurve() { + _curve = + widget.expansionAnimationStyle?.curve ?? + _expansionTileTheme.expansionAnimationStyle?.curve ?? + Curves.easeIn; + _reverseCurve = + widget.expansionAnimationStyle?.reverseCurve ?? + _expansionTileTheme.expansionAnimationStyle?.reverseCurve; + } + + @override + Widget build(BuildContext context) { + return Expansible( + controller: _tileController, + curve: _curve, + duration: _duration, + reverseCurve: _reverseCurve, + maintainState: widget.maintainState, + headerBuilder: _buildHeader, + bodyBuilder: _buildBody, + expansibleBuilder: _buildExpansible, + ); + } +} + +class _ExpansionTileDefaultsM2 extends ExpansionTileThemeData { + _ExpansionTileDefaultsM2(this.context); + + final BuildContext context; + late final ThemeData _theme = Theme.of(context); + late final ColorScheme _colorScheme = _theme.colorScheme; + + @override + Color? get textColor => _colorScheme.primary; + + @override + Color? get iconColor => _colorScheme.primary; + + @override + Color? get collapsedTextColor => _theme.textTheme.titleMedium!.color; + + @override + Color? get collapsedIconColor => _theme.unselectedWidgetColor; +} + +// BEGIN GENERATED TOKEN PROPERTIES - ExpansionTile + +// Do not edit by hand. The code between the "BEGIN GENERATED" and +// "END GENERATED" comments are generated from data in the Material +// Design token database by the script: +// dev/tools/gen_defaults/bin/gen_defaults.dart. + +// dart format off +class _ExpansionTileDefaultsM3 extends ExpansionTileThemeData { + _ExpansionTileDefaultsM3(this.context); + + final BuildContext context; + late final ThemeData _theme = Theme.of(context); + late final ColorScheme _colors = _theme.colorScheme; + + @override + Color? get textColor => _colors.onSurface; + + @override + Color? get iconColor => _colors.primary; + + @override + Color? get collapsedTextColor => _colors.onSurface; + + @override + Color? get collapsedIconColor => _colors.onSurfaceVariant; +} +// dart format on + +// END GENERATED TOKEN PROPERTIES - ExpansionTile diff --git a/lib/common_package/custom_text.dart b/lib/common_package/custom_text.dart new file mode 100644 index 0000000..6602eea --- /dev/null +++ b/lib/common_package/custom_text.dart @@ -0,0 +1,43 @@ + +import 'package:flutter/material.dart'; + +class CustomText extends StatelessWidget { + final FontWeight? weight; + final Color? color; + final double? size; + final String text; + final int? maxLines; + final TextOverflow? overflow; + + const CustomText({ + Key? key, + this.weight, + this.color, + this.size, + required this.text, + this.maxLines, + this.overflow, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Text( + text, + style: TextStyle( + fontWeight: FontWeight.lerp( + weight, + FontWeight.values[ + (FontWeight.values.indexOf(weight??FontWeight.w400) + 1) + .clamp(0, FontWeight.values.length - 1) // prevent overflow + ], + 0.5, // t: pick between 0.0 and 1.0 + ), + color: color, + fontSize: size, + ), + maxLines: maxLines, + overflow: overflow, + ); + } +} + diff --git a/lib/common_package/custom_textfield.dart b/lib/common_package/custom_textfield.dart new file mode 100644 index 0000000..c524008 --- /dev/null +++ b/lib/common_package/custom_textfield.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:citycards_customer/common_package/custom_text.dart'; + +class CustomTextField extends StatelessWidget { + final String label; + final String hint; + final TextEditingController controller; + final int? maxLines; + + const CustomTextField({ + super.key, + required this.label, + required this.hint, + required this.controller, + this.maxLines =1 + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText(text: label, size: 14), + const SizedBox(height: 6), + SizedBox( + height: maxLines == 1 ? 42 : null, + child: TextField( + controller: controller, + maxLines: maxLines, + decoration: InputDecoration( + hintText: hint, + hintStyle: const TextStyle( + fontSize: 12, + color: Color(0xFF8E8E8E), + ), + filled: true, + fillColor: const Color(0xFFFFF5F5), + contentPadding: const EdgeInsets.symmetric( + horizontal: 24, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: Color(0xBBC83B61).withOpacity(0.4), + width: .4, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide( + color: Color(0xFFF95F62), + width: 1, + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/contact_us/contact_us_view.dart b/lib/contact_us/contact_us_view.dart new file mode 100644 index 0000000..653c4a0 --- /dev/null +++ b/lib/contact_us/contact_us_view.dart @@ -0,0 +1,275 @@ +import 'package:flutter/material.dart'; +import 'package:citycards_customer/common_package/custom_text.dart'; +import 'package:citycards_customer/common_package/custom_textfield.dart'; + +class ContactUsPage extends StatelessWidget { + const ContactUsPage({super.key}); + + @override + Widget build(BuildContext context) { + final TextEditingController firstNameController = TextEditingController(); + final TextEditingController lastNameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController phoneController = TextEditingController(); + final TextEditingController messageController = TextEditingController(); + + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header bar + Container( + padding: const EdgeInsets.only(bottom: 10), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(color: Color(0xFFD9D9D9), width: 0.7), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset("assets/logo/logo_city_cards.png", scale: 4), + Image.asset("assets/icons/shopping_cart.png", scale: 4), + ], + ), + ), + const SizedBox(height: 22), + + // Back + Title + Row( + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Icon(Icons.arrow_back, size: 24), + ), + SizedBox(width: 8), + Text( + "Contact Us", + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + ), + ], + ), + const SizedBox(height: 22), + + CustomText( + text: + "You can get in touch with us through the below platforms. Our team will contact you shortly", + size: 14, + color: Colors.black.withOpacity(.6), + ), + const SizedBox(height: 20), + + // Customer Support Section + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + decoration: BoxDecoration( + color: Color(0x00000005).withOpacity(.02), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CustomText( + text: "Customer Support", + size: 18, + weight: FontWeight.w500, + ), + const SizedBox(height: 16), + _supportBox( + icon: Icons.phone, + title: "Contact Number", + subtitle: "+1012 3456 789", + action: "Tap to call", + ), + const SizedBox(height: 12), + _supportBox( + icon: Icons.email_rounded, + title: "Email", + subtitle: "citycards24@gmail.com", + action: "Tap to email", + ), + const SizedBox(height: 12), + _supportBox( + icon: Icons.location_on, + title: "Location", + subtitle: + "132 Dartmouth Street Boston, Massachusetts 02156 United States", + action: "View on map", + ), + ], + ), + ), + const SizedBox(height: 24), + + // Text fields + CustomTextField( + label: "First Name", + hint: "Enter your first name", + controller: firstNameController, + ), + CustomTextField( + label: "Last Name", + hint: "Enter your last name", + controller: lastNameController, + ), + CustomTextField( + label: "Email", + hint: "Enter your email address", + controller: emailController, + ), + CustomTextField( + label: "Phone Number", + hint: "Enter your phone number", + controller: phoneController, + ), + + CustomTextField( + label: "Description", + hint: "Write your message here", + maxLines: 4, + controller: messageController, + ), + + + // _descriptionField(messageController), + + const SizedBox(height: 24), + + // Submit Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFF95F62), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(38), + ), + padding: const EdgeInsets.symmetric(vertical: 6), + ), + onPressed: () {}, + child: const CustomText( + text: "Submit Ticket", + size: 16, + weight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ); + } + + // --- Support Info Box --- + Widget _supportBox({ + required IconData icon, + required String title, + required String subtitle, + required String action, + }) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0xFFF95F62), width: 0.8), + color: Colors.white, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(icon, color: const Color(0xFFF95F62), size: 32), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText( + text: title, + size: 11, + weight: FontWeight.w600, + color: Color(0x00000000).withOpacity(.6), + ), + const SizedBox(height: 6), + Text( + subtitle, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Colors.black, + ), + ), + const SizedBox(height: 2), + Text( + action, + style: TextStyle( + fontSize: 11, + color: Color(0xFF000000).withOpacity(.4), + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ], + ), + ); + } + + // --- Description Field --- + Widget _descriptionField(TextEditingController controller) { + return Padding( + padding: const EdgeInsets.only(bottom: 12, left: 12, right: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CustomText(text: "Description", size: 14), + const SizedBox(height: 6), + TextField( + controller: controller, + maxLines: 4, + decoration: InputDecoration( + hintText: "Write your message here", + hintStyle: const TextStyle( + fontSize: 12, + color: Color(0xFF8E8E8E), + ), + filled: true, + fillColor: const Color(0xFFFFF5F5), + contentPadding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide( + color: const Color(0xBBC83B61).withOpacity(0.4), + width: .4, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: const BorderSide( + color: Color(0xFFF95F62), + width: 1, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 732884a..7b336ed 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -1,3 +1,9 @@ +import 'package:citycards_customer/Profile/profile_page_view.dart'; +import 'package:citycards_customer/contact_us/contact_us_view.dart'; +import 'package:citycards_customer/edit_profile/edit_profile_view.dart'; +import 'package:citycards_customer/faq/faq_view.dart'; +import 'package:citycards_customer/privacy/privacy_view.dart'; +import 'package:citycards_customer/terms_and_condition/terms_and_condition_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../home/views/home_page_view.dart'; @@ -19,11 +25,47 @@ class AppRouter { }, ); + case RouteConstants.profile: + return MaterialPageRoute( + builder: (_) { + return const ProfilePage(); + }, + ); + case RouteConstants.editProfile: + return MaterialPageRoute( + builder: (_) { + return const EditProfilePage(); + }, + ); + case RouteConstants.contactUs: + return MaterialPageRoute( + builder: (_) { + return const ContactUsPage(); + }, + ); + case RouteConstants.faq: + return MaterialPageRoute( + builder: (_) { + return const FaqPage(); + }, + ); + case RouteConstants.termsAndCondition: + return MaterialPageRoute( + builder: (_) { + return const TermsAndCondition(); + }, + ); + case RouteConstants.privacyPolicy: + return MaterialPageRoute( + builder: (_) { + return const PrivacyPolicyPage(); + }, + ); + default: return MaterialPageRoute( - builder: (_) => const Scaffold( - body: Center(child: Text('404 - Page Not Found')), - ), + builder: (_) => + const Scaffold(body: Center(child: Text('404 - Page Not Found'))), ); } } diff --git a/lib/core/route_constants.dart b/lib/core/route_constants.dart index d07cddb..32c0909 100644 --- a/lib/core/route_constants.dart +++ b/lib/core/route_constants.dart @@ -1,3 +1,12 @@ class RouteConstants { static const String home = '/home'; + + /* ****************************** Profile Section **************************/ + + static const String profile = '/profile'; + static const String editProfile = '/editProfile'; + static const String contactUs = '/contactUs'; + static const String termsAndCondition = '/termsAndCondition'; + static const String privacyPolicy = '/privacyPolicy'; + static const String faq = '/faq'; } diff --git a/lib/edit_profile/edit_profile_view.dart b/lib/edit_profile/edit_profile_view.dart new file mode 100644 index 0000000..52c3910 --- /dev/null +++ b/lib/edit_profile/edit_profile_view.dart @@ -0,0 +1,197 @@ +import 'package:citycards_customer/common_package/custom_textfield.dart'; +import 'package:flutter/material.dart'; +import 'package:citycards_customer/common_package/custom_text.dart'; + +class EditProfilePage extends StatelessWidget { + const EditProfilePage({super.key}); + + @override + Widget build(BuildContext context) { + final TextEditingController firstNameController = TextEditingController(); + final TextEditingController lastNameController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController phoneController = TextEditingController(); + final TextEditingController addressController = TextEditingController(); + + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Header + Container( + padding: const EdgeInsets.only(bottom: 10), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(color: Color(0xFFD9D9D9), width: 0.7), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset("assets/logo/logo_city_cards.png", scale: 4), + Image.asset("assets/icons/shopping_cart.png", scale: 4), + ], + ), + ), + const SizedBox(height: 22), + + // Back + title + Row( + children: [ + GestureDetector( + onTap: (){ + Navigator.pop(context); + }, + child: Icon(Icons.arrow_back, size: 24)), + SizedBox(width: 8), + Text( + "Edit Profile", + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + ), + ], + ), + const SizedBox(height: 33), + + // Profile Image + const CircleAvatar( + radius: 38, + backgroundImage: AssetImage("assets/images/profile_img.png"), + ), + const SizedBox(height: 18), + const Text( + "Change Profile Picture", + style: TextStyle( + fontSize: 12, + color: Color(0xFFF95F62), + fontWeight: FontWeight.w400, + ), + ), + const SizedBox(height: 40), + + // Personal Information + Align( + alignment: Alignment.centerLeft, + child: CustomText( + text: "Personal Information", + size: 18, + weight: FontWeight.w500, + ), + ), + const SizedBox(height: 12), + + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: CustomTextField( + label: "First Name", + hint: "Enter your first name", + controller: firstNameController, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: CustomTextField( + label: "Last Name", + hint: "Enter your last name", + controller: lastNameController, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: CustomTextField( + label: "Email", + hint: "Enter your email address", + controller: emailController, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: CustomTextField( + label: "Phone Number", + hint: "Enter your phone number", + controller: phoneController, + ), + ), + + const SizedBox(height: 2), + + // Location Details + Align( + alignment: Alignment.centerLeft, + child: CustomText( + text: "Location Details", + size: 18, + weight: FontWeight.w500, + ), + ), + const SizedBox(height: 16), + + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: CustomTextField( + label: "Address 1", + hint: "Enter address manually or tap to search", + controller: addressController, + ), + ), + + const SizedBox(height: 26), + + // Buttons + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: const Color(0xFFF95F62), + side: const BorderSide(color: Colors.transparent), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(38), + ), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + onPressed: () {}, + child: const Text( + "Cancel", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFF95F62), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(38), + ), + padding: const EdgeInsets.symmetric(vertical: 6), + ), + onPressed: () {}, + child: const Text( + "Save", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ), + ], + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ); + } +} diff --git a/lib/faq/faq_view.dart b/lib/faq/faq_view.dart new file mode 100644 index 0000000..3462164 --- /dev/null +++ b/lib/faq/faq_view.dart @@ -0,0 +1,185 @@ +import 'package:citycards_customer/common_package/custom_expansiontile.dart'; +import 'package:citycards_customer/common_package/custom_text.dart'; +import 'package:flutter/material.dart'; + +class FaqPage extends StatelessWidget { + const FaqPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.only(bottom: 10), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(color: Color(0xFFD9D9D9), width: 0.7), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset("assets/logo/logo_city_cards.png", scale: 4), + Image.asset("assets/icons/shopping_cart.png", scale: 4), + ], + ), + ), + const SizedBox(height: 22), + + // Back + Title + Row( + children: [ + GestureDetector( + onTap: (){ + Navigator.pop(context); + }, + child: Icon(Icons.arrow_back, size: 24)), + SizedBox(width: 8), + Text( + "FAQ", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + const SizedBox(height: 34), + + FAQSection(title: "🧭 General FAQs", faqs: generalFAQs), + SizedBox(height: 20), + FAQSection(title: "✈️ Booking & Planning", faqs: bookingFaq), + SizedBox(height: 20), + FAQSection(title: "🌍 Discover & Explore", faqs: discoverFAQs), + ], + ), + ), + ), + ), + ); + } +} + +// Model for FAQ +class FAQItem { + final String question; + final String answer; + + FAQItem({required this.question, required this.answer}); +} + +// Sample FAQ data +final List generalFAQs = [ + FAQItem( + question: "What is CityCards?", + answer: + "You can browse without an account, but signing up allows you to save trips, sync data, and get personalized recommendations.", + ), + FAQItem( + question: "Is the app free to use?", + answer: + "You can browse without an account, but signing up allows you to save trips, sync data, and get personalized recommendations.", + ), + FAQItem( + question: "Do I need an account to use the app?", + answer: + "You can browse without an account, but signing up allows you to save trips, sync data, and get personalized recommendations.", + ), +]; + +final List discoverFAQs = [ + FAQItem( + question: "How does the app recommend destinations?", + answer: + "You can browse without an account, but signing up allows you to save trips, sync data, and get personalized recommendations.", + ), + FAQItem( + question: "Can I create a custom itinerary?", + answer: + "You can browse without an account, but signing up allows you to save trips, sync data, and get personalized recommendations.", + ), + FAQItem( + question: "Does the app work offline?", + answer: + "You can browse without an account, but signing up allows you to save trips, sync data, and get personalized recommendations.", + ), +]; + +final List bookingFaq = [ + FAQItem( + question: "Can I modify or cancel my bookings?", + answer: + "You can browse without an account, but signing up allows you to save trips, sync data, and get personalized recommendations.", + ), + FAQItem( + question: "Can I plan multi-city trips?", + answer: + "You can browse without an account, but signing up allows you to save trips, sync data, and get personalized recommendations.", + ), + FAQItem( + question: "Can I book hotels through the app?", + answer: + "You can browse without an account, but signing up allows you to save trips, sync data, and get personalized recommendations.", + ), +]; + +// Widget for FAQ section +Widget FAQSection({required String title, required List faqs}) { + return Container( + padding: EdgeInsets.symmetric(vertical: 12, horizontal: 8), + decoration: BoxDecoration( + border: Border.all(color: Color(0xFFF95F62)), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Section heading + CustomText( + text: title, + size: 16, + weight: FontWeight.w500, + color: Color(0xFF212121), + ), + SizedBox(height: 12), + + // Dynamic list of questions + Column( + children: faqs.map((faq) { + int index = faqs.indexOf(faq); + return Column( + children: [ + CustomExpansionTile( + minTileHeight: 42, + borderRadius: BorderRadius.circular(5), + backgroundColor: Color(0xFFFEE7E7), + collapsedBackgroundColor: Color(0xFFFEE7E7), + tilePadding: EdgeInsets.symmetric( + horizontal: 14, + vertical: 0, + ), + childrenPadding: EdgeInsets.all(12), + title: Text(faq.question, style: TextStyle(fontSize: 14)), + children: [ + Text( + faq.answer, + style: TextStyle(color: Color(0xFF5C5C5C), fontSize: 14), + ), + ], + ), + if (index != faqs.length - 1) SizedBox(height: 8), // spacing + ], + ); + }).toList(), + ), + ], + ), + ); +} diff --git a/lib/home/views/home_page_view.dart b/lib/home/views/home_page_view.dart index 9893294..1de5db9 100644 --- a/lib/home/views/home_page_view.dart +++ b/lib/home/views/home_page_view.dart @@ -1,3 +1,4 @@ +import 'package:citycards_customer/core/route_constants.dart'; import 'package:citycards_customer/home/views/explore_cities_card.dart'; import 'package:flutter/material.dart'; @@ -132,10 +133,15 @@ class _HomePageState extends State { ), ), const SizedBox(width: 8), - const CircleAvatar( - backgroundColor: Color(0xffFFDFDF), - backgroundImage: - AssetImage("assets/images/profile_img.png"), + GestureDetector( + onTap: (){ + Navigator.pushNamed(context, RouteConstants.profile); + }, + child: const CircleAvatar( + backgroundColor: Color(0xffFFDFDF), + backgroundImage: + AssetImage("assets/images/profile_img.png"), + ), ), ], ), diff --git a/lib/privacy/privacy_view.dart b/lib/privacy/privacy_view.dart new file mode 100644 index 0000000..00f2332 --- /dev/null +++ b/lib/privacy/privacy_view.dart @@ -0,0 +1,69 @@ +import 'package:citycards_customer/common_package/custom_text.dart'; +import 'package:flutter/material.dart'; + +class PrivacyPolicyPage extends StatelessWidget { + const PrivacyPolicyPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(bottom: 10), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(color: Color(0xFFD9D9D9), width: 0.7), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset("assets/logo/logo_city_cards.png", scale: 4), + Image.asset("assets/icons/shopping_cart.png", scale: 4), + ], + ), + ), + const SizedBox(height: 22), + + // Back + Title + Row( + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Icon(Icons.arrow_back, size: 24), + ), + SizedBox(width: 8), + Text( + "Privacy Policy", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SizedBox(height: 32), + CustomText( + text: + "Your use of our website is governed by the following terms and conditions (“Terms of Use”), as well as the CARDONE CAPITAL Privacy Policy and other operating rules, minimum qualifications and cautions posted throughout the website or presented to you individually during the course of your use of the website (collectively, the “Terms”). \n\n" + "The Terms govern your use of the website and CARDONE CAPITAL reserves the right to update or replace the Terms any time without notice. You are advised to review the Terms for any changes when you visit the website even if you have not received a notification of changes as you are bound by them even if you have not reviewed them. \n\n" + "Your viewing and use of the website after such change constitutes your acceptance of the Terms and any changes to such terms. If at any time you do not want to be bound by the Terms you should logout, exit and cease using the website immediately.", + size: 14, + weight: FontWeight.w400, + color: Color(0xFF000000).withOpacity(.6), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/profile/profile_page_view.dart b/lib/profile/profile_page_view.dart new file mode 100644 index 0000000..cc72e69 --- /dev/null +++ b/lib/profile/profile_page_view.dart @@ -0,0 +1,213 @@ +import 'package:citycards_customer/common_package/custom_text.dart'; +import 'package:citycards_customer/core/route_constants.dart'; +import 'package:flutter/material.dart'; + +class ProfilePage extends StatelessWidget { + const ProfilePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(bottom: 10), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Color(0xFFD9D9D9), width: .7), + ), + ), + + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset("assets/logo/logo_city_cards.png", scale: 4), + Image.asset("assets/icons/shopping_cart.png", scale: 4), + ], + ), + ), + SizedBox(height: 22), + + Row( + children: [ + GestureDetector( + onTap: (){ + Navigator.pop(context); + }, + child: Icon(Icons.arrow_back, size: 24)), + SizedBox(width: 8), + Text("My profile", style: TextStyle(fontSize: 12)), + ], + ), + SizedBox(height: 29), + // Profile Image and Name + Row( + children: [ + const CircleAvatar( + radius: 38, + backgroundImage: AssetImage( + "assets/images/profile_img.png", + ), + ), + SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Laysha Adams', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + // SizedBox(height: 4,), + Row( + children: [ + Icon( + Icons.location_on_sharp, + color: Color(0xFF8E8E8E), + size: 14, + ), + SizedBox(width: 4), + const Text( + 'Louisiana, United States', + style: TextStyle( + fontSize: 12, + color: Color(0xFF8E8E8E), + ), + ), + ], + ), + ], + ), + ], + ), + + const SizedBox(height: 30), + + // Account Settings Section + Align( + alignment: Alignment.centerLeft, + child: CustomText( + text: "Account Settings", + weight: FontWeight.w500, + size: 18, + ), + ), + const SizedBox(height: 10), + + _buildListTile( + icon: "assets/icons/user_profile.png", + title: 'Edit profile', + onTap: () { + Navigator.pushNamed(context, RouteConstants.editProfile); + }, + ), + _buildListTile( + icon: "assets/icons/change_language.png", + title: 'Change language', + onTap: () {}, + ), + + const SizedBox(height: 24), + + // Support & Legal Section + Align( + alignment: Alignment.centerLeft, + child: CustomText( + text: "Support & Legal", + weight: FontWeight.w500, + size: 18, + ), + ), + const SizedBox(height: 10), + + _buildListTile( + icon: "assets/icons/contact_us.png", + title: 'Contact Us', + onTap: () { + Navigator.pushNamed(context, RouteConstants.contactUs); + }, + ), + _buildListTile( + icon: "assets/icons/terms_and_condition.png", + title: 'Terms and Conditions', + onTap: () { + Navigator.pushNamed( + context, + RouteConstants.termsAndCondition, + ); + }, + ), + _buildListTile( + icon: "assets/icons/faq.png", + title: 'FAQ', + onTap: () { + Navigator.pushNamed(context, RouteConstants.faq); + }, + ), + _buildListTile( + icon: "assets/icons/privacy.png", + title: 'Privacy Policy', + onTap: () { + Navigator.pushNamed(context, RouteConstants.privacyPolicy); + }, + ), + + const SizedBox(height: 22), + + // Logout Button + SizedBox( + width: double.infinity, + child: OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: Color(0xFFF95F62), + side: const BorderSide(color: Color(0xFFF95F62)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(38), + ), + padding: const EdgeInsets.symmetric(vertical: 6), + ), + onPressed: () {}, + child: const Text( + 'Log out', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildListTile({ + required String icon, + required String title, + required VoidCallback onTap, + }) { + return Container( + height: 64, + decoration: BoxDecoration( + border: Border.all(color: Colors.black.withOpacity(.10)), + borderRadius: BorderRadius.circular(15), + ), + margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), + child: ListTile( + leading: Image.asset(icon, scale: 4), + title: Text( + title, + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + ), + trailing: const Icon(Icons.arrow_forward_ios, size: 16), + onTap: onTap, + ), + ); + } +} diff --git a/lib/terms_and_condition/terms_and_condition_view.dart b/lib/terms_and_condition/terms_and_condition_view.dart new file mode 100644 index 0000000..b648e63 --- /dev/null +++ b/lib/terms_and_condition/terms_and_condition_view.dart @@ -0,0 +1,69 @@ +import 'package:citycards_customer/common_package/custom_text.dart'; +import 'package:flutter/material.dart'; + +class TermsAndCondition extends StatelessWidget { + const TermsAndCondition({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(bottom: 10), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(color: Color(0xFFD9D9D9), width: 0.7), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset("assets/logo/logo_city_cards.png", scale: 4), + Image.asset("assets/icons/shopping_cart.png", scale: 4), + ], + ), + ), + const SizedBox(height: 22), + + // Back + Title + Row( + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Icon(Icons.arrow_back, size: 24), + ), + SizedBox(width: 8), + Text( + "Terms and Conditons", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SizedBox(height: 32), + CustomText( + text: + "Your use of our website is governed by the following terms and conditions (“Terms of Use”), as well as the CARDONE CAPITAL Privacy Policy and other operating rules, minimum qualifications and cautions posted throughout the website or presented to you individually during the course of your use of the website (collectively, the “Terms”). \n\n" + "The Terms govern your use of the website and CARDONE CAPITAL reserves the right to update or replace the Terms any time without notice. You are advised to review the Terms for any changes when you visit the website even if you have not received a notification of changes as you are bound by them even if you have not reviewed them. \n\n" + "Your viewing and use of the website after such change constitutes your acceptance of the Terms and any changes to such terms. If at any time you do not want to be bound by the Terms you should logout, exit and cease using the website immediately.", + size: 14, + weight: FontWeight.w400, + color: Color(0xFF000000).withOpacity(.6), + ), + ], + ), + ), + ), + ), + ); + } +}