getsupportpage/lib/supportscreen.dart
2025-05-22 10:21:29 +03:00

759 lines
28 KiB
Dart

// This Flutter file defines a "Get Support" screen with various interactive UI elements.
// It includes reusable widgets for displaying support options, FAQ items, a custom AppBar,
// and reusable separator widgets.
// The screen allows users to navigate to dummy pages for demonstration purposes.
// It uses the 'google_fonts' package for custom typography (Poppins).
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
// --- Dummy Page for Navigation ---
/// A simple [StatelessWidget] used as a placeholder page for navigation demonstrations.
/// It displays a title in the AppBar and a message in the body, both indicating the page's purpose.
class DummyPage extends StatelessWidget {
/// The title to be displayed in the AppBar and body of the dummy page.
final String title;
/// Creates a [DummyPage].
///
/// The [title] parameter is required and will be used to identify the page.
const DummyPage({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
title,
style: GoogleFonts.poppins(color: Colors.white), // Using Poppins font
),
backgroundColor:
Colors.deepOrange, // A distinct color for the dummy page AppBar
iconTheme: const IconThemeData(
color: Colors.white,
), // Ensures back arrow is white
),
body: Center(
child: Text(
'This is the $title page.',
style: GoogleFonts.poppins(fontSize: 20), // Using Poppins font
),
),
);
}
}
// --- Reusable UI Components ---
/// A [StatelessWidget] that represents a circular support option with an icon and a label.
/// Tapping on this widget triggers an [onTap] callback.
///
/// This widget is used to display quick actions like "Raise a Ticket" or "Request a Callback".
/// It features a circular gradient background for the icon and a text label below it.
class SupportOption extends StatelessWidget {
/// The icon to display within the circular background.
final IconData iconData;
/// The text label displayed below the icon. Can contain newlines for multi-line labels.
final String label;
/// The background color of the circle.
/// Note: This is currently overridden by a `BoxDecoration` with a gradient in the implementation.
/// It's kept for potential future use or simpler styling options.
final Color circleBackgroundColor;
/// The color of the icon.
final Color iconColor;
/// The callback function that is called when the widget is tapped.
final VoidCallback onTap;
/// The size of the icon.
final double iconSize;
/// The optional [TextStyle] for the label. If null, a default Poppins style is used.
final TextStyle? labelStyle;
/// Creates a [SupportOption] widget.
/// All parameters are required except [iconColor], [iconSize], and [labelStyle], which have defaults.
const SupportOption({
super.key,
required this.iconData,
required this.label,
required this.circleBackgroundColor,
required this.onTap,
this.iconColor = Colors.white,
this.iconSize = 25,
this.labelStyle,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap, // Makes the entire widget tappable
child: Column(
mainAxisSize:
MainAxisSize
.min, // Ensures the Column takes minimum vertical space needed by its children
children: [
// Circular icon container
Container(
width: 56, // Fixed width for the circle
height:
56, // Fixed height for the circle, ensuring it's perfectly round
padding: const EdgeInsets.all(
12,
), // Padding around the icon inside the circle
decoration: BoxDecoration(
shape: BoxShape.circle, // Makes the container circular
gradient: const LinearGradient(
// Applies a gradient background to the circle
begin: Alignment.bottomLeft,
end: Alignment.topRight,
colors: <Color>[
Color(0xFFF57C00),
Color(0xFFFFB74D),
], // Orange gradient
),
),
child: Icon(
iconData,
color: iconColor,
size: iconSize,
), // The icon itself
),
const SizedBox(height: 8), // Spacing between the icon and the label
// Label text
Text(
label,
textAlign:
TextAlign.center, // Ensures multi-line labels are centered
style:
labelStyle ?? // Use custom style if provided, otherwise default
GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500),
),
],
),
);
}
}
/// A [StatelessWidget] that represents an item in a Frequently Asked Questions (FAQ) list.
/// It is typically used within a [ListView] or [Column] to display individual FAQ entries.
/// Features include a leading icon in a [CircleAvatar], a text title, and an optional trailing widget.
class FaqItem extends StatelessWidget {
/// The icon to display at the beginning (leading edge) of the FAQ item.
final IconData icon;
/// The color of the leading icon. This color is also used to derive the
/// semi-transparent background color of the [CircleAvatar] holding the icon.
final Color iconColor;
/// The main text or title of the FAQ item.
final String text;
/// The callback function that is called when the FAQ item is tapped.
final VoidCallback onTap;
/// The optional [TextStyle] for the main text. If null, a default Poppins style is used.
final TextStyle? textStyle;
/// An optional widget to display at the end (trailing edge) of the FAQ item,
/// commonly an arrow icon indicating navigability.
final Widget? trailing;
/// The optional size for the leading icon.
/// A special condition exists for `Icons.payment` with `Colors.green` to use a different default size.
final double? iconSize;
/// The optional radius for the [CircleAvatar] that contains the leading icon.
final double? avatarRadius;
/// Creates an [FaqItem] widget.
/// The [icon], [iconColor], [text], and [onTap] parameters are required.
const FaqItem({
super.key,
required this.icon,
required this.iconColor,
required this.text,
required this.onTap,
this.textStyle,
this.trailing,
this.iconSize,
this.avatarRadius,
});
@override
Widget build(BuildContext context) {
// This condition allows for specific icon sizing if needed.
// For more complex scenarios, this logic might be better handled by passing exact sizes.
final bool isPaymentsIcon =
icon == Icons.payment && iconColor == Colors.green;
return ListTile(
// `contentPadding` controls the internal padding of the ListTile.
// `left: 16.0` provides standard leading padding.
// `right: 16.0` provides padding on the right, ensuring the trailing widget
// (if any) isn't flush against the screen edge.
contentPadding: const EdgeInsets.only(left: 16.0, right: 16.0),
leading: CircleAvatar(
backgroundColor: iconColor.withOpacity(
0.15,
), // Background for the icon, derived from iconColor with reduced opacity
radius: avatarRadius ?? 10, // Default radius is 10, can be overridden
child: Icon(
icon,
color: iconColor,
size:
iconSize ??
(isPaymentsIcon
? 10
: 12), // Default size logic, can be overridden
),
),
title: Text(
text,
style:
textStyle ?? // Use custom style if provided, otherwise default
GoogleFonts.poppins(
fontWeight: FontWeight.w600, // Medium-bold weight for emphasis
),
maxLines: 1, // Ensures the FAQ title is on a single line
overflow: TextOverflow.ellipsis, // Displays "..." if the text overflows
),
onTap: onTap, // Makes the ListTile tappable
trailing: trailing, // The widget displayed at the end of the ListTile
);
}
}
// --- New Reusable Component Structures for GetSupportScreen ---
/// A reusable [AppBar] widget designed for this application, featuring a
/// consistent gradient background, title styling, and icon theming.
/// It implements [PreferredSizeWidget] which is necessary for a widget to be used
/// as an [AppBar] in a [Scaffold].
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
/// The text to be displayed as the title of the AppBar.
final String titleText;
/// An optional widget to display before the title, typically an [IconButton] for navigation (e.g., back button).
final Widget? leading;
/// An optional list of widgets to display after the title, typically [IconButton]s for actions.
final List<Widget>? actions;
/// Creates a [CustomAppBar].
/// The [titleText] is required.
const CustomAppBar({
super.key,
required this.titleText,
this.leading,
this.actions,
});
@override
Widget build(BuildContext context) {
return AppBar(
leading: leading,
actions: actions,
// `flexibleSpace` allows a widget to be placed behind the AppBar's primary content (title, actions).
// Here, it's used to create the gradient background.
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft, // Gradient starts from the left
end: Alignment.centerRight, // Gradient ends at the right
colors: [
// Defines the colors for the gradient
Color(0xFFF57C00), // Darker orange
Color(0xFFFFB74D), // Lighter orange (middle color)
Color(0xFFF57C00), // Darker orange again for a symmetrical feel
],
stops: [
// Defines the distribution of colors along the gradient line
0.0, // Start color at the beginning
0.5, // Middle color at the midpoint
1.0, // End color at the end
],
),
),
),
backgroundColor:
Colors
.transparent, // Makes the AppBar's own background transparent, so the `flexibleSpace` gradient is visible.
elevation:
0, // Removes the default shadow under the AppBar for a flatter look.
title: Text(
titleText,
style: GoogleFonts.poppins(
color: Colors.white, // Title text color
fontWeight: FontWeight.bold, // Bold weight for the title
),
),
centerTitle: true, // Centers the title horizontally within the AppBar.
iconTheme: const IconThemeData(
color:
Colors
.white, // Ensures that all icons in the AppBar (leading, actions) are white by default.
),
);
}
/// Defines the preferred size (height) of this AppBar.
/// `kToolbarHeight` is a Flutter constant representing the standard height of an AppBar.
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}
/// A data class designed to hold the configuration for a single [SupportOption] widget.
/// Using data classes like this helps in separating the data/content from the UI structure,
/// making it easier to manage lists of similar items and pass data to reusable sections.
class SupportOptionData {
final IconData iconData;
final String label;
final VoidCallback onTap;
SupportOptionData({
required this.iconData,
required this.label,
required this.onTap,
});
}
/// A reusable [StatelessWidget] that displays a horizontal row of [SupportOption] widgets.
/// It takes a list of [SupportOptionData] to dynamically configure each option in the row.
/// This component is used for the top section of the "Get Support" screen.
class SupportOptionsSection extends StatelessWidget {
/// A list of [SupportOptionData] objects, each defining the properties for one support option.
final List<SupportOptionData> options;
/// Creates a [SupportOptionsSection].
/// The [options] list is required.
const SupportOptionsSection({super.key, required this.options});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
top: 20.0, // Space above the entire section
left: 8.0, // Horizontal padding for the section content
right: 8.0,
),
// `LayoutBuilder` can be used for more complex responsive layouts,
// but here `IntrinsicHeight` and `Row` with `Flexible` children handle the distribution.
child: LayoutBuilder(
builder: (context, constraints) {
return IntrinsicHeight(
child: Wrap(
alignment: WrapAlignment.spaceBetween,
spacing: 12, // Horizontal space between items
runSpacing: 16, // Vertical space between rows if wrapped
children: options.map((optionData) {
return SizedBox(
width: (constraints.maxWidth / 4) - 12, // 4 items per row, adjust as needed
child: SupportOption(
iconData: optionData.iconData,
label: optionData.label,
circleBackgroundColor: const Color(0xFFF57C00),
iconColor: Colors.white,
onTap: optionData.onTap,
),
);
}).toList(),
),
);
},
),
);
}
}
/// A data class to hold the configuration for a single [FaqItem] displayed within an [FaqSection].
/// This includes information like the icon, color, text, tap action, and whether a larger
/// separator should be used after this item.
class FaqSectionItem {
final IconData icon;
final Color iconColor;
final String text;
final VoidCallback onTap;
/// If true, the [FaqSection] will use the `largerSpaceSeparator` (if provided) after this item.
/// Otherwise, it will use the `defaultSeparator` (if provided).
final bool useLargerSeparatorAfter;
FaqSectionItem({
required this.icon,
required this.iconColor,
required this.text,
required this.onTap,
this.useLargerSeparatorAfter =
false, // Defaults to using the smaller/default separator
});
}
/// A reusable [StatelessWidget] that displays a section for Frequently Asked Questions.
/// It includes a section title and a list of [FaqItem] widgets.
/// The separators between FAQ items are passed in as parameters, allowing for flexible styling.
class FaqSection extends StatelessWidget {
/// The title text for the FAQ section (e.g., "Frequently Asked Questions (FAQs)").
final String sectionTitle;
/// A list of [FaqSectionItem] objects, each defining one FAQ entry.
final List<FaqSectionItem> items;
/// The size of the icon within each [FaqItem]. Defaults to 12.
final double faqIconSize;
/// The radius of the [CircleAvatar] within each [FaqItem]. Defaults to 12.
final double faqAvatarRadius;
/// The widget to use as a default separator between FAQ items.
/// If null, `SizedBox.shrink()` is used, effectively showing no separator.
final Widget? defaultSeparator;
/// The widget to use as a separator when a larger space is desired between certain FAQ items
/// (as indicated by `FaqSectionItem.useLargerSeparatorAfter`).
/// If null, `SizedBox.shrink()` is used.
final Widget? largerSpaceSeparator;
/// Creates an [FaqSection].
/// [sectionTitle] and [items] are required.
/// Separator widgets are optional and can be customized by the parent.
const FaqSection({
super.key,
required this.sectionTitle,
required this.items,
this.faqIconSize = 12,
this.faqAvatarRadius = 12,
this.defaultSeparator,
this.largerSpaceSeparator,
});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white, // Background color for the entire FAQ section
padding: const EdgeInsets.only(
top: 8.0, // Reduced top padding for the section
bottom: 16.0, // Maintained bottom padding for the section
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment
.start, // Aligns children (title, FAQ items list) to the left
children: [
// Section Title
Padding(
padding: const EdgeInsets.only(
left: 16.0, // Horizontal padding for the title
right: 16.0,
bottom:
16.0, // Space between the title and the first FAQ item (remains 16.0)
),
child: Text(
sectionTitle,
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.bold,
color: const Color.fromARGB(
255,
90,
92,
121,
), // A specific dark grey/blue color for the title
),
),
),
// List of FAQ Items and their Separators
Column(
children: [
// Using a `for` loop with the spread operator (`...`) to dynamically build
// the list of FaqItems and their corresponding separators.
for (int i = 0; i < items.length; i++) ...[
FaqItem(
// Create an FaqItem for the current data item
icon: items[i].icon,
iconColor: items[i].iconColor,
text: items[i].text,
onTap: items[i].onTap,
iconSize:
faqIconSize, // Use the passed-in or default icon size
avatarRadius:
faqAvatarRadius, // Use the passed-in or default avatar radius
trailing: IconButton(
// Standard trailing arrow icon for all FAQ items in this section
padding:
EdgeInsets
.zero, // Removes default padding from IconButton to make it compact
constraints:
const BoxConstraints(), // Removes default size constraints, allowing icon size to dominate
icon: Icon(
Icons.arrow_forward_ios, // iOS-style forward arrow
size: 11, // Specific small size for the arrow
color:
Colors
.grey
.shade600, // A medium grey color for the arrow
),
onPressed:
items[i]
.onTap, // Assumes the arrow tap triggers the same action as the item tap
),
),
// Conditionally add a separator after the current item, but not after the last item
if (i < items.length - 1)
items[i]
.useLargerSeparatorAfter // Check if this item requires a larger separator
? (largerSpaceSeparator ??
const SizedBox.shrink()) // Use provided larger separator, or an empty box if null
: (defaultSeparator ??
const SizedBox.shrink()), // Use provided default separator, or an empty box if null
],
],
),
],
),
);
}
}
/// A reusable widget for a thick, styled divider.
/// This is typically used to separate major content sections on a page.
/// It provides consistent styling for such separators.
class ThickSectionSeparator extends StatelessWidget {
const ThickSectionSeparator({super.key});
@override
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.symmetric(
vertical: 18.0,
), // Vertical spacing around the divider
child: Divider(
thickness: 7, // A very thick line for a prominent separation
color: Color(
0xFFF5F5F5,
), // A light grey color, often matching subtle background variations
height:
1, // The actual height of the divider line itself. Padding controls the space around it.
),
);
}
}
// --- Main Screen Widget ---
/// The main screen widget for the "Get Support" feature.
/// This screen is composed of several reusable sections:
/// - A [CustomAppBar] for the top navigation bar.
/// - A [SupportOptionsSection] displaying quick actions.
/// - A [ThickSectionSeparator] visually dividing major parts of the screen.
/// - An [FaqSection] listing frequently asked questions.
///
/// This screen demonstrates how to compose a complex UI by combining smaller, reusable widgets
/// and managing their data and styling.
class GetSupportScreen extends StatelessWidget {
const GetSupportScreen({super.key});
/// A helper method to navigate to a [DummyPage] with a given [title].
/// This method is passed as a callback to various interactive elements
/// (like [SupportOptionData] and [FaqSectionItem]) to handle their tap actions.
void _navigateToDummy(BuildContext context, String title) {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => DummyPage(title: title)),
);
}
@override
Widget build(BuildContext context) {
/* --- Data and Style Definitions for Reusable Sections ---
* Define the data that will be passed to the reusable sections.
* Also, define common styling elements like dividers here to be passed down.
* This approach keeps the UI structure (the `build` method) cleaner by separating
* data and specific styling configurations from the main widget tree composition.
* For larger applications, these definitions might live in separate files or theme/style managers.
*/
// Common Divider Styles for the FAQ Section.
// These are defined here and passed to the FaqSection widget, promoting reusability
// and centralized style management for this screen.
const double faqDividerThickness = 0.2;
// MODIFIED: Changed to a darker grey for better visibility
final Color faqDividerColor =
Colors.grey.shade400; // (0xFFBDBDBD) - More visible than shade300
const double faqDividerIndent =
16.0; // Indent from left, aligns with FaqItem content
const double faqDividerEndIndent =
16.0; // Indent from right, before FaqItem trailing icon
final Widget defaultFaqSeparator = Divider(
height: 10, // Total vertical space for this separator (line + spacing)
thickness: faqDividerThickness,
color: faqDividerColor,
indent: faqDividerIndent,
endIndent: faqDividerEndIndent,
);
final Widget largerFaqSeparator = Divider(
height: 16, // More vertical space for this separator
thickness: faqDividerThickness,
color: faqDividerColor,
indent: faqDividerIndent,
endIndent: faqDividerEndIndent,
);
// Data for the Support Options Section
final List<SupportOptionData> supportOptions = [
SupportOptionData(
iconData: Icons.article_outlined,
label: 'Raise a Ticket',
onTap: () => _navigateToDummy(context, 'Raise a Ticket'),
),
SupportOptionData(
iconData: Icons.phone_callback_outlined,
label: 'Request a\nCallback',
onTap: () => _navigateToDummy(context, 'Request a Callback'),
),
SupportOptionData(
iconData: Icons.headset_mic_outlined,
label: 'Call Customer\nCare',
onTap: () => _navigateToDummy(context, 'Call Customer Care'),
),
SupportOptionData(
iconData: Icons.more_horiz,
label: 'View Raised\nTickets',
onTap: () => _navigateToDummy(context, 'View Raised Tickets'),
),
];
// Data for the FAQ Section
final List<FaqSectionItem> faqItems = [
FaqSectionItem(
icon: Icons.local_fire_department,
iconColor: Colors.orange,
text: 'FREQUENTLY ASKED QUESTIONS',
onTap: () => _navigateToDummy(context, 'Frequently Asked Questions'),
// `useLargerSeparatorAfter` defaults to false, so a `defaultFaqSeparator` will be used.
),
FaqSectionItem(
icon: Icons.account_balance_wallet,
iconColor: Colors.blue,
text: 'ACCOUNT MANAGEMENT',
onTap: () => _navigateToDummy(context, 'Account Management'),
),
FaqSectionItem(
icon: Icons.payment,
iconColor: Colors.green,
text: 'PAYMENTS',
onTap: () => _navigateToDummy(context, 'Payments'),
useLargerSeparatorAfter:
true, // A `largerFaqSeparator` will be used after this item.
),
FaqSectionItem(
icon: Icons.monetization_on,
iconColor: Colors.amber,
text: 'CARDS',
onTap: () => _navigateToDummy(context, 'Cards'),
useLargerSeparatorAfter: true,
),
FaqSectionItem(
icon: Icons.account_box,
iconColor: Colors.blue,
text: 'LOOP LOANS',
onTap: () => _navigateToDummy(context, 'LOOP LOANS'),
useLargerSeparatorAfter: true,
),
FaqSectionItem(
icon: Icons.account_balance,
iconColor: Colors.orange,
text: 'LOOP OVERDRAFT',
onTap: () => _navigateToDummy(context, 'LOOP OVERDRAFT'),
useLargerSeparatorAfter: true,
),
FaqSectionItem(
icon: Icons.credit_card,
iconColor: Colors.blue,
text: 'LOOP FLEX (Buy Now Pay Later(BNPL))',
onTap:
() => _navigateToDummy(
context,
'LOOP FLEX (Buy Now Pay Later(BNPL))',
),
useLargerSeparatorAfter: true,
),
FaqSectionItem(
icon: Icons.account_balance,
iconColor: Colors.green,
text: 'LOOP GOALS',
onTap: () => _navigateToDummy(context, 'LOOP GOALS'),
// For the last item, `useLargerSeparatorAfter` is effectively ignored as no separator is drawn after the last item.
),
];
// The main Scaffold for the GetSupportScreen
return Scaffold(
appBar: CustomAppBar(
// Using the reusable custom AppBar
titleText: 'Get Support',
leading: IconButton(
// Providing a custom leading widget (back button)
icon: const Icon(
Icons.arrow_back_ios, // iOS-style back arrow
),
onPressed: () {
// Navigate to a generic "Back Page" for demonstration purposes
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const DummyPage(title: 'Back Page'),
),
);
},
),
),
// `SingleChildScrollView` allows the content within its `child` (the Container and Column)
// to be scrollable if the content's height exceeds the available screen height.
body: SingleChildScrollView(
child: Center(
// Center the constrained content horizontally
child: ConstrainedBox(
constraints: const BoxConstraints(
// Set the minimum width to 600
maxWidth:
600, // Optionally, also set the maxWidth to 600 for a fixed width
),
child: Container(
color: Colors.white,
child: Column(
// Arranges the main sections of the screen vertically
children: [
// First major section: Support Options
SupportOptionsSection(options: supportOptions),
// Reusable Thick divider separating the support options from the FAQ section
const ThickSectionSeparator(),
// Second major section: Frequently Asked Questions
FaqSection(
sectionTitle: 'Frequently Asked Questions (FAQs)',
items: faqItems,
defaultSeparator:
defaultFaqSeparator, // Pass the defined default separator style
largerSpaceSeparator:
largerFaqSeparator, // Pass the defined larger separator style
// `faqIconSize` and `faqAvatarRadius` will use their default values as defined in `FaqSection`,
// but they could be overridden here if needed for this specific instance of FaqSection.
// e.g., faqIconSize: 14,
),
],
),
),
),
),
),
);
}
}