Welcome back to our series on creating UI in Flutter! In this article, we will continue our exploration of the reward screen UI and focus on creating the reward detail screen UI in Flutter. This screen is typically opened when a user clicks on a particular reward card and displays more detailed information about that reward. We will learn how to create this screen using Flutter widgets and layout techniques. So, if you haven’t already, be sure to check out our previous article on creating the main reward screen UI before diving into this one!
Understanding the Rewards Detail UI of Google Pay
When we open a particular reward from the rewards screen, we are taken to the rewards detail screen. This screen has a transparent background with some opacity. In the top right corner of the screen, there are two icons, one for closing the screen and another for displaying additional information. At the bottom of the screen, there is a button for redeeming the reward.
The main body of the screen consists of an animated container that changes its size and position as we scroll. The container contains information about the reward, such as the title and description. Additionally, there is a list view on the side, which is also animated as we scroll.
Implementing the Reward Detail Screen UI in Flutter

One of the key elements of the reward detail screen is the transparent background with some opacity. However, setting the background color to transparent is not as simple as just setting it in the Scaffold widget. If you do that, you may find that your screen turns black instead of transparent.
The reason for this is that when you navigate to a new screen, it creates a new layer on top of the previous screen. If you set the background color to transparent without any other modifications, you will be able to see the previous screen through the transparent layer, but the new layer will still be black.
Making Transparent Background
To resolve this, we are adding the CupertinoModalPopup
widget in the onTap
function of the rewards card, which will open the GPayRewardDetailScreen
and also make the background transparent.
onTap: (){
showCupertinoModalPopup(context: context, builder:
(context) => GPayRewardDetailScreen()
);
},
This code snippet is handling the onTap event for the reward card. When the user taps on the reward card, it shows a modal popup using the showCupertinoModalPopup
method. The builder
parameter of this method takes a function that returns the widget to be displayed in the modal popup. In this case, it’s the GPayRewardDetailScreen
widget, which is the reward detail screen that we want to display when the user taps on a reward card.
now in the stateful class GPayRewardDetailScreen
inside the scaffold, we set our background color to transparent with opacity
backgroundColor: Colors.transparent.withOpacity(0.5),
Creating the App bar
appBar: AppBar(
// Set the background color of the app bar to transparent
backgroundColor: Colors.transparent,
// Remove the shadow effect from the app bar
elevation: 0,
// Add a close button icon to the left of the app bar. When pressed, it pops the current screen and goes back to the previous screen.
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
Navigator.pop(context);
},
),
// Add a row of buttons to the right of the app bar. In this case, there are three buttons: a "Sponsored" text, an info icon button, and a more vertical icon button.
actions: [
Row(
children: [
const SizedBox(width: 4.0),
const Text(
'Sponsored',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.normal,
),
),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.info_outline,
color: Colors.white,
),
),
const SizedBox(width: 16.0),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.more_vert,
color: Colors.white,
),
),
],
),
],
),
This is the AppBar
widget used in the rewards detail screen of the app.
backgroundColor: Colors.transparent,
sets the background color of the app bar to transparent, which gives it a more immersive look.elevation: 0,
removes the shadow effect from the app bar.leading: IconButton(...),
adds a close button icon to the left of the app bar. When pressed, it pops the current screen and goes back to the previous screen.actions: Row(...),
adds a row of buttons to the right of the app bar. In this case, there are three buttons: a “Sponsored” text, an info icon button, and a more vertical icon button.
Creating the Body for Detail Screen
In the Rewards Detail screen, the initState
method is used to initialize some variables and set their initial values. Similarly, the dispose
method is used to release any resources that were allocated by the Rewards Detail screen. The main function of the Rewards Detail screen is build
, which returns a Scaffold widget with the UI elements of the screen.
Setting Up the Screen and Lifecycle Methods
In this section of code, we define some variables and methods that will be used to control the size and behavior of our rewards container in the Rewards Detail screen.
First, we define the initial height of our rewards container to be 250 pixels using a double variable named _containerHeight
. We also set the maximum height to be 300 pixels and the minimum height to be 200 pixels using final variables named _maxContainerHeight
and _minContainerHeight
, respectively.
double _containerHeight = 250.0;
final double _maxContainerHeight = 300.0;
final double _minContainerHeight = 200.0;
We then create a ScrollController using ScrollController()
to keep track of the scroll position of the rewards container. This will allow us to dynamically adjust the size of the container based on the user’s scrolling behavior.
final ScrollController _scrollController = ScrollController();
Next, we define a boolean variable named _minSizedContainer
and set it to true. This variable will help us keep track of whether the rewards container is currently at its minimum size.
bool _minSizedContainer = true;
Moving on, we use the initState()
method to initialize our ScrollController and add a listener to it using _scrollController.addListener(_scrollListener)
. This listener will call _scrollListener()
whenever the user scrolls the rewards container.
@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
}
In the _scrollListener()
method, we check the direction of the user’s scrolling behavior using _scrollController.position.userScrollDirection
. If the user is scrolling forward (i.e. scrolling down), we increase the size of the container to _maxContainerHeight
and set _minSizedContainer
to false. Conversely, if the user is scrolling backward (i.e. scrolling up), we decrease the size of the container to _minContainerHeight
and set _minSizedContainer
to true.
void _scrollListener() {
if (_scrollController.position.userScrollDirection ==
ScrollDirection.forward) {
setState(() {
_containerHeight = _maxContainerHeight;
_minSizedContainer = false;
});
} else if (_scrollController.position.userScrollDirection ==
ScrollDirection.reverse) {
setState(() {
_containerHeight = _minContainerHeight;
_minSizedContainer = true;
});
}
}
Finally, we use the dispose()
method to remove the listener from our ScrollController to avoid memory leaks.
@override
void dispose() {
_scrollController.removeListener(_scrollListener);
super.dispose();
}
Body Structure
In the body of our Rewards Detail screen, we have a SafeArea widget that ensures our content is safely positioned within the visible screen area.
We use a Column widget to organize our content vertically, with each child widget being added sequentially. The first child is a SizedBox widget that creates some space between the top of the screen and the next child.
Next, we have a FittedBox widget that automatically scales its child to fit within its bounds. Inside the FittedBox, we have an AnimatedContainer that animates its size change when the user taps on the rewards container. We pass in the current and maximum container heights as parameters to our custom buildAnimatedRewardsContainer function, which builds the rewards container with the appropriate height.
We then add another SizedBox to create some space between the rewards container and the next child.
Finally, we call our custom buildDraggableDetailsContainer function to build the details container, which can be dragged up and down using a gesture detector. We pass in the current ScrollController as a parameter to this function to enable scrolling within the container.
body: SafeArea(
child: Column(
children: [
// Add a SizedBox to give some vertical space above the rewards container
SizedBox(height: _minSizedContainer ? 50 : 80),
// Use FittedBox and AnimatedContainer to create an animated rewards container
FittedBox(
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: _containerHeight,
width: _containerHeight,
alignment: Alignment.center,
child: buildAnimatedRewardsContainer(
animatedHeight: _containerHeight,
maxHeight: _maxContainerHeight),
),
),
// Add another SizedBox to give some vertical space between the rewards container and the details container
SizedBox(height: _minSizedContainer ? 30 : 60),
// Use the buildDraggableDetailsContainer function to create a draggable details container
buildDraggableDetailsContainer(context,
ScrollController: _scrollController),
],
),
),
building Animated Reward Container
Widget buildAnimatedRewardsContainer(
{required double animatedHeight, required double maxHeight}) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: animatedHeight,
width: animatedHeight,
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(20)),
child: FittedBox(
child: SizedBox(
height: maxHeight / 2,
width: maxHeight / 2,
child: Stack(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
"Flat ₹5000",
style: TextStyle(
fontWeight: FontWeight.normal, fontSize: 18),
),
Expanded(
child: Text(
'''Bonus cash on1st deposit on My11Circle''',
maxLines: 3,
)),
]),
),
const CircleAvatar(
radius: 100 * 0.11,
backgroundColor: Colors.teal,
child: CircleAvatar(
// backgroundImage: AssetImage('assets/appdev.png'),
backgroundColor: Colors.white,
radius: 100 * 0.1,
),
),
],
),
),
],
),
),
),
);
}
The buildAnimatedRewardsContainer
function returns an AnimatedContainer widget that displays a reward card.
The function takes two required parameters: animatedHeight
and maxHeight
. animatedHeight
specifies the height of the AnimatedContainer, which is set to grow or shrink based on user interaction with the screen. maxHeight
specifies the maximum height the container can reach.
Inside the AnimatedContainer, we create a BoxDecoration to give it a white background color and rounded corners using BorderRadius.circular. We use FittedBox to ensure that the contents of the AnimatedContainer are scaled proportionally to fit within the constraints of the container.
Inside the FittedBox, we create a SizedBox with height and width set to half of the maxHeight
parameter. We then create a Stack widget that holds the contents of the reward card.
Inside the Stack, we add a Padding widget to give some space between the edges of the container and the contents of the card. We use a Column widget to display the reward details and the reward image. We use MainAxisAlignment.spaceBetween to evenly distribute the columns vertically.
Inside the first Column, we use another Column widget to display the reward details. We use mainAxisAlignment.spaceBetween to evenly distribute the text vertically. We add two Text widgets to display the reward amount and a description of the reward.
Inside the second Column, we use a CircleAvatar widget to display the reward image. We set the backgroundColor to teal and add another CircleAvatar widget to display a smaller white circle inside it.
Finally, we return the AnimatedContainer widget with its contents as described above. The duration of the animation is set to 300 milliseconds.
building Draggable Detail Container
To create a custom Rewards Detail screen similar to Google Pay, we can use our own implementation instead of relying on the built-in DraggableBottomSheet. The DraggableBottomSheet may not provide enough customization options and may also cause issues on some devices.
Widget buildDraggableDetailsContainer(BuildContext context,
{required ScrollController}){
return Expanded(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
bottomLeft: Radius.zero,
bottomRight: Radius.zero,
),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
controller: ScrollController,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
child: Row(
children: [
const CircleAvatar(
radius: 100 * 0.11,
backgroundColor: Colors.teal,
child: CircleAvatar(
// backgroundImage: AssetImage('assets/appdev.png'),
backgroundColor: Colors.white,
radius: 100 * 0.1,
),
),
const SizedBox(width: 8),
Text(
myCoupon.title,
style: const TextStyle(),
),
],
),
),
Container(
child: Row(
children: [
CircleAvatar(
child: IconButton(
onPressed: () {},
icon: Icon(Icons.thumb_up_off_alt_outlined))),
SizedBox(
width: 8,
),
CircleAvatar(
child: IconButton(
onPressed: () {},
icon: Icon(Icons.thumb_down_off_alt_outlined)))
],
),
)
],
),
const SizedBox(height: 16),
Text(
myCoupon.description,
style:
const TextStyle(fontSize: 20, fontWeight: FontWeight.normal),
),
const SizedBox(height: 16),
const Text(
"Copy code and use at checkout",
style: TextStyle(),
),
const SizedBox(height: 4),
Stack(
children: [
Container(
alignment: Alignment.centerLeft,
width: MediaQuery.of(context).size.width,
height: 30,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: const BorderRadius.only(
topRight: Radius.zero,
topLeft: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.zero,
)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
myCoupon.couponCode,
style: const TextStyle(fontSize: 16),
),
),
),
Container(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: () {
Clipboard.setData(
ClipboardData(text: myCoupon.couponCode));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Coupon code copied to clipboard.'),
),
);
},
style: ElevatedButton.styleFrom(
primary: Colors.blue.shade700,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: const Text('Copy'),
),
),
],
),
const SizedBox(height: 16),
const Text(
"Details",
style: TextStyle(fontWeight: FontWeight.bold),
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: myCoupon.details
.map((detail) => Text(
detail,
textAlign: TextAlign.left,
))
.toList(),
),
const SizedBox(height: 100),
const SizedBox(height: 16),
],
),
),
),
);
}
The code snippet provided is a function called Widget buildDraggableDetailsContainer
which returns a widget that displays details of a coupon, including its title, description, coupon code, and additional details.
The widget is built using Flutter, a popular open-source mobile application development framework that allows developers to build natively compiled applications for mobile, web, and desktop platforms from a single codebase. Flutter widgets are built using a reactive programming model which makes it easy to build complex, high-performance UIs.
The buildDraggableDetailsContainer
function takes two arguments – a BuildContext
object and a ScrollController
object. The BuildContext
object is required by the function to access the current build context of the widget. The ScrollController
object is used to control the scrolling behavior of the ListView
widget that displays the coupon details.
The function returns an Expanded
widget that occupies all the available space within the parent container. The parent container is decorated with a BoxDecoration
object that sets the background color of the container to white and applies rounded corners to the top-left and top-right edges of the container. The remaining edges are set to zero.
Inside the container, there is a Padding
widget that adds 16 pixels of padding on all sides of its child. The child of the Padding
widget is a ListView
widget that displays the coupon details. The ListView
widget is given the ScrollController
object to control its scrolling behavior.
The coupon details are displayed within the ListView
widget using various child widgets such as Row
, Stack
, and Column
. The first child widget of the ListView
widget is a Row
widget that displays the coupon title, a CircleAvatar
widget, and a Container
widget that displays the coupon’s like and dislike icons.
The second child widget of the ListView
widget is a Text
widget that displays the coupon description. The third child widget of the ListView
widget is a Stack
widget that displays the coupon code and a copy button. The fourth child widget of the ListView
widget is a Text
widget that displays the coupon details title.
The fifth child widget of the ListView
widget is a Column
widget that displays the coupon details as a list of Text
widgets. Finally, the last child widget of the ListView
widget is a SizedBox
widget that adds some empty space at the bottom of the widget.
Overall, the Widget buildDraggableDetailsContainer
function returns a widget that is both aesthetically pleasing and functional, allowing users to view all the details of a coupon in an easy-to-read format.
Creating the Redeem Button
To create the “Redeem Now” button in our Rewards Detail screen, we can use the bottomNavigationBar
property of the Scaffold widget. We wrap the button in a Container with a white background color and a height of 60 pixels, centered horizontally using Center and some padding using EdgeInsets.symmetric.
Inside the Container, we create another Container that will hold our button, with a blue background color and rounded corners. We add a GestureDetector to handle taps on the button and define our redemption logic inside the onTap property.
We then create a Row widget that holds an Icon widget for the button icon, followed by a SizedBox with a width of 8 pixels for some spacing, and a Text widget for the button text with a white color, normal weight, and font size of 16 pixels. We center the row vertically and make it only as tall as its contents using MainAxisAlignment.center and MainAxisSize.min.
bottomNavigationBar: Container(
color: Colors.white,
height: 60,
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
child: Container(
height: 40,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.blue.shade700,
borderRadius: BorderRadius.circular(30.0),
),
child: GestureDetector(
onTap: () {
// Add your logic here for when the button is pressed
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: const [
Icon(
Icons.redeem_outlined,
color: Colors.white,
),
SizedBox(width: 8.0),
Text(
'Redeem Now',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.normal,
fontSize: 16.0,
),
),
],
),
),
),
),
),
),
Output
Full Source Code
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'basicsliver.dart';
class GPayRewardDetailScreen extends StatefulWidget {
@override
State<GPayRewardDetailScreen> createState() => _GPayRewardDetailScreenState();
}
class _GPayRewardDetailScreenState extends State<GPayRewardDetailScreen> {
double _containerHeight = 250.0;
final double _maxContainerHeight = 300.0;
final double _minContainerHeight = 200.0;
final ScrollController _scrollController = ScrollController();
bool _minSizedContainer = true;
@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
}
@override
void dispose() {
_scrollController.removeListener(_scrollListener);
super.dispose();
}
void _scrollListener() {
if (_scrollController.position.userScrollDirection ==
ScrollDirection.forward) {
setState(() {
_containerHeight = _maxContainerHeight;
_minSizedContainer = false;
});
} else if (_scrollController.position.userScrollDirection ==
ScrollDirection.reverse) {
setState(() {
_containerHeight = _minContainerHeight;
_minSizedContainer = true;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent.withOpacity(0.5),
appBar: AppBar(
// Set the background color of the app bar to transparent
backgroundColor: Colors.transparent,
// Remove the shadow effect from the app bar
elevation: 0,
// Add a close button icon to the left of the app bar. When pressed, it pops the current screen and goes back to the previous screen.
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
Navigator.pop(context);
},
),
// Add a row of buttons to the right of the app bar. In this case, there are three buttons: a "Sponsored" text, an info icon button, and a more vertical icon button.
actions: [
Row(
children: [
const SizedBox(width: 4.0),
const Text(
'Sponsored',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.normal,
),
),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.info_outline,
color: Colors.white,
),
),
const SizedBox(width: 16.0),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.more_vert,
color: Colors.white,
),
),
],
),
],
),
body: SafeArea(
child: Column(
children: [
SizedBox(height: _minSizedContainer ? 50 : 80),
FittedBox(
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: _containerHeight,
width: _containerHeight,
alignment: Alignment.center,
child: buildAnimatedRewardsContainer(
animatedHeight: _containerHeight,
maxHeight: _maxContainerHeight),
),
),
SizedBox(height: _minSizedContainer ? 30 : 60),
buildDraggableDetailsContainer(context,
ScrollController: _scrollController),
],
),
),
bottomNavigationBar: Container(
color: Colors.white,
height: 60,
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
child: Container(
height: 40,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.blue.shade700,
borderRadius: BorderRadius.circular(30.0),
),
child: GestureDetector(
onTap: () {
// Add your logic here for when the button is pressed
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: const [
Icon(
Icons.redeem_outlined,
color: Colors.white,
),
SizedBox(width: 8.0),
Text(
'Redeem Now',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.normal,
fontSize: 16.0,
),
),
],
),
),
),
),
),
),
);
}
}
Widget buildAnimatedRewardsContainer(
{required double animatedHeight, required double maxHeight}) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: animatedHeight,
width: animatedHeight,
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(20)),
child: FittedBox(
child: SizedBox(
height: maxHeight / 2,
width: maxHeight / 2,
child: Stack(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
"Flat ₹5000",
style: TextStyle(
fontWeight: FontWeight.normal, fontSize: 18),
),
Expanded(
child: Text(
'''Bonus cash on1st deposit on My11Circle''',
maxLines: 3,
)),
]),
),
const CircleAvatar(
radius: 100 * 0.11,
backgroundColor: Colors.teal,
child: CircleAvatar(
// backgroundImage: AssetImage('assets/appdev.png'),
backgroundColor: Colors.white,
radius: 100 * 0.1,
),
),
],
),
),
],
),
),
),
);
}
Widget buildDraggableDetailsContainer(BuildContext context,
{required ScrollController}){
return Expanded(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
bottomLeft: Radius.zero,
bottomRight: Radius.zero,
),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
controller: ScrollController,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
child: Row(
children: [
const CircleAvatar(
radius: 100 * 0.11,
backgroundColor: Colors.teal,
child: CircleAvatar(
// backgroundImage: AssetImage('assets/appdev.png'),
backgroundColor: Colors.white,
radius: 100 * 0.1,
),
),
const SizedBox(width: 8),
Text(
myCoupon.title,
style: const TextStyle(),
),
],
),
),
Container(
child: Row(
children: [
CircleAvatar(
child: IconButton(
onPressed: () {},
icon: Icon(Icons.thumb_up_off_alt_outlined))),
SizedBox(
width: 8,
),
CircleAvatar(
child: IconButton(
onPressed: () {},
icon: Icon(Icons.thumb_down_off_alt_outlined)))
],
),
)
],
),
const SizedBox(height: 16),
Text(
myCoupon.description,
style:
const TextStyle(fontSize: 20, fontWeight: FontWeight.normal),
),
const SizedBox(height: 16),
const Text(
"Copy code and use at checkout",
style: TextStyle(),
),
const SizedBox(height: 4),
Stack(
children: [
Container(
alignment: Alignment.centerLeft,
width: MediaQuery.of(context).size.width,
height: 30,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: const BorderRadius.only(
topRight: Radius.zero,
topLeft: Radius.circular(20),
bottomLeft: Radius.circular(20),
bottomRight: Radius.zero,
)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
myCoupon.couponCode,
style: const TextStyle(fontSize: 16),
),
),
),
Container(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: () {
Clipboard.setData(
ClipboardData(text: myCoupon.couponCode));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Coupon code copied to clipboard.'),
),
);
},
style: ElevatedButton.styleFrom(
primary: Colors.blue.shade700,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: const Text('Copy'),
),
),
],
),
const SizedBox(height: 16),
const Text(
"Details",
style: TextStyle(fontWeight: FontWeight.bold),
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: myCoupon.details
.map((detail) => Text(
detail,
textAlign: TextAlign.left,
))
.toList(),
),
const SizedBox(height: 100),
const SizedBox(height: 16),
],
),
),
),
);
}
class Coupon {
final String title;
final String description;
final String couponCode;
final List<String> details;
Coupon({
required this.title,
required this.description,
required this.details,
required this.couponCode,
});
}
Coupon myCoupon = Coupon(
couponCode: "FFRT456SE32",
title: "My11Circle",
description: "Congrats! Flat ₹5000 Bonus cash on!st Deposit on My11Circle",
details: [
"* Go to the store",
"* Show this coupon to the cashier",
"* Enjoy your discount"
]);
Conclusion
In conclusion, creating a rewards detail screen like Google Pay can greatly enhance the user experience and increase engagement with your app. By including details about the reward, such as its title, description, coupon code, and usage instructions, users can easily understand the benefits of the reward and how to redeem it.
The layout of the screen is also important, as it should be visually appealing and easy to navigate. Using containers, rows, and columns, along with appropriate padding and spacing, can help organize the information and make it easily accessible to the user.
Additionally, adding interactive elements, such as the ability to copy the coupon code to the clipboard or liking and disliking the reward, can further engage users and increase their satisfaction with the app.
Overall, the rewards detail screen is an essential component of any rewards program and should be designed with the user in mind. By incorporating the above elements, you can create a seamless and enjoyable experience for your users and encourage them to continue using your app and engaging with your brand.
How to Make Reward Screen UI in Flutter Like Google Pay Complete Guide