ui_framework_kit 1.1.5
ui_framework_kit: ^1.1.5 copied to clipboard
Production-ready Flutter UI kit: adaptive light/dark theming, form widgets, MCQ, shimmer skeletons, HTML/image renderers, and responsive utilities — all in one import.
example/lib/main.dart
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:ui_framework_kit/ui_framework_kit.dart';
void main() {
// Optional: configure a custom font before running the app.
// UiFrameworkConfig.configure(fontFamily: 'Nunito');
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(360, 690),
minTextAdapt: true,
splitScreenMode: true,
builder: (_, __) => MaterialApp(
title: 'ui_framework Example',
debugShowCheckedModeBanner: false,
theme: lightTheme,
darkTheme: darkTheme,
home: const _HomeScreen(),
),
);
}
}
// ---------------------------------------------------------------------------
// Home — navigation list
// ---------------------------------------------------------------------------
class _HomeScreen extends StatelessWidget {
const _HomeScreen();
@override
Widget build(BuildContext context) {
final sections = [
('Typography', Icons.text_fields_outlined, const _TypographyPage()),
('Buttons', Icons.smart_button_outlined, const _ButtonsPage()),
('Form Inputs', Icons.edit_outlined, const _FormPage()),
('Shimmer Skeletons', Icons.blur_on_outlined, const _ShimmerPage()),
('MCQ Widget', Icons.quiz_outlined, const _McqPage()),
('Images & Avatars', Icons.image_outlined, const _ImagePage()),
('Newsfeed / Author Tile', Icons.newspaper_outlined, const _NewsfeedPage()),
('Carousel', Icons.view_carousel_outlined, const _CarouselPage()),
('YouTube Player', Icons.play_circle_outline, const _YouTubePage()),
('Link Previewer', Icons.link_outlined, const _LinkPreviewPage()),
('Web View', Icons.public_outlined, const _WebViewPage()),
('HTML Rendering', Icons.code_outlined, const _HtmlPage()),
('Read More Text', Icons.article_outlined, const _ReadMorePage()),
('Theming & Colors', Icons.palette_outlined, const _ThemePage()),
];
return Scaffold(
appBar: AppBar(
title: const TextWidget('ui_framework_kit', textType: TextTypeConstants.heading),
),
body: ListView.separated(
padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 16.w),
itemCount: sections.length,
separatorBuilder: (_, __) => 8.verticalSpace,
itemBuilder: (ctx, i) {
final (label, icon, page) = sections[i];
return ListTile(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.r)),
tileColor: getColorByTheme(context: ctx, colorClass: AppColors.secondaryColor),
leading: Icon(icon),
title: TextWidget(label, textType: TextTypeConstants.subTitle),
trailing: const Icon(Icons.chevron_right),
onTap: () => Navigator.push(ctx, MaterialPageRoute(builder: (_) => page)),
);
},
),
);
}
}
// ---------------------------------------------------------------------------
// 1. Typography
// ---------------------------------------------------------------------------
class _TypographyPage extends StatelessWidget {
const _TypographyPage();
@override
Widget build(BuildContext context) {
return _DemoPage(title: 'Typography', children: [
_label('Display (w700, 20sp)'),
const TextWidget('The quick brown fox', textType: TextTypeConstants.display),
16.verticalSpace,
_label('Heading (w600, 18sp)'),
const TextWidget('The quick brown fox', textType: TextTypeConstants.heading),
16.verticalSpace,
_label('Title (w500, 16sp)'),
const TextWidget('The quick brown fox', textType: TextTypeConstants.title),
16.verticalSpace,
_label('SubTitle (w500, 14sp)'),
const TextWidget('The quick brown fox', textType: TextTypeConstants.subTitle),
16.verticalSpace,
_label('Normal (w400, 12sp)'),
const TextWidget('The quick brown fox', textType: TextTypeConstants.normal),
16.verticalSpace,
_label('Custom colour, weight, letter-spacing'),
TextWidget(
'Custom styled text',
textType: TextTypeConstants.title,
color: Colors.deepPurple,
fontWeight: FontWeight.w800,
textOptions: const TextOptions(letterSpacing: 1.5),
),
16.verticalSpace,
_label('Overflow / maxLines = 2'),
const TextWidget(
'This is a very long piece of text that should be clamped to two lines '
'because maxLines is set to 2 and overflow is set to ellipsis. '
'More text here to force truncation.',
textType: TextTypeConstants.normal,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
]);
}
}
// ---------------------------------------------------------------------------
// 2. Buttons
// ---------------------------------------------------------------------------
class _ButtonsPage extends StatelessWidget {
const _ButtonsPage();
@override
Widget build(BuildContext context) {
return _DemoPage(title: 'Buttons', children: [
_label('Filled button'),
InputButtonWidget(title: 'Submit', onTap: () {}, bgColor: AppColors.blueColor),
16.verticalSpace,
_label('Border button'),
InputButtonWidget(
title: 'Cancel', isBorderButton: true, bgColor: AppColors.blueColor, onTap: () {}),
16.verticalSpace,
_label('Success / Error variants'),
Row(
children: [
Expanded(
child: InputButtonWidget(
title: 'Approve', bgColor: AppColors.successColor, onTap: () {})),
8.horizontalSpace,
Expanded(
child: InputButtonWidget(
title: 'Reject',
isBorderButton: true,
bgColor: AppColors.errorColor,
onTap: () {})),
],
),
16.verticalSpace,
_label('Loading state'),
const InputButtonWidget(title: 'Saving…', isLoading: true),
16.verticalSpace,
_label('Disabled (no onTap)'),
const InputButtonWidget(title: 'Disabled'),
16.verticalSpace,
_label('Debounce demo — tap rapidly, fires once'),
InputButtonWidget(
title: 'Debounced (2 s)',
bgColor: AppColors.pendingColor,
debounceDuration: const Duration(seconds: 2),
onTap: () {},
),
]);
}
}
// ---------------------------------------------------------------------------
// 3. Form Inputs
// ---------------------------------------------------------------------------
class _FormPage extends StatefulWidget {
const _FormPage();
@override
State<_FormPage> createState() => _FormPageState();
}
class _FormPageState extends State<_FormPage> {
final _formKey = GlobalKey<FormState>();
String? _selectedCountry;
final List<String> _countries = ['Nepal', 'India', 'USA', 'UK', 'Germany', 'Japan'];
@override
Widget build(BuildContext context) {
return _DemoPage(
title: 'Form Inputs',
children: [
Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_label('TextFieldWidget — plain text'),
TextFieldWidget(
labelText: 'Full Name',
hintText: 'Enter your full name',
validator: (v) => (v == null || v.isEmpty) ? 'Required' : null,
),
16.verticalSpace,
_label('Password field (toggleable visibility)'),
const TextFieldWidget(
labelText: 'Password',
hintText: '••••••••',
isPassword: true,
),
16.verticalSpace,
_label('Multiline / TextArea'),
const TextFieldWidget(
labelText: 'Bio',
hintText: 'Tell us about yourself…',
maxLines: 4,
minLines: 3,
),
16.verticalSpace,
_label('DatePickerTextField — built-in Gregorian'),
DatePickerTextField(
labelText: 'Date of Birth',
firstDate: DateTime(1940),
lastDate: DateTime.now(),
onChanged: (_) {},
),
16.verticalSpace,
_label('DatePickerTextField — custom dd/MM/yyyy format'),
DatePickerTextField(
labelText: 'Event Date',
dateFormatter: (d) =>
'${d.day.toString().padLeft(2, '0')}/'
'${d.month.toString().padLeft(2, '0')}/'
'${d.year}',
onChanged: (_) {},
),
16.verticalSpace,
_label('TimePickerTextField'),
TimePickerTextField(
labelText: 'Appointment Time',
onChanged: (_) {},
),
16.verticalSpace,
_label('DropdownWidget<String>'),
DropdownWidget<String>(
hintText: 'Select Country',
itemList: _countries,
initialSelectedValue: _selectedCountry,
showSelected: true,
onPressed: (v) => setState(() => _selectedCountry = v),
),
16.verticalSpace,
_label('CheckBoxWidget (with label)'),
CheckBoxWidget(title: 'Accept terms and conditions', onChanged: (_) {}),
16.verticalSpace,
_label('SwitchWidget'),
SwitchWidget(title: 'Enable notifications', onSelected: (_) {}),
24.verticalSpace,
InputButtonWidget(
title: 'Validate',
onTap: () {
if (_formKey.currentState!.validate()) {
context.showToastMessage(
toastEnum: ToastEnum.success, toastMessage: 'Form is valid!');
}
},
),
],
),
),
],
);
}
}
// ---------------------------------------------------------------------------
// 4. Shimmer Skeletons
// ---------------------------------------------------------------------------
class _ShimmerPage extends StatelessWidget {
const _ShimmerPage();
@override
Widget build(BuildContext context) {
return _DemoPage(
title: 'Shimmer Skeletons',
children: [
for (final type in ShimmerType.values) ...[
_label(type.name),
ShimmerWidget(shimmerType: type),
16.verticalSpace,
],
],
);
}
}
// ---------------------------------------------------------------------------
// 5. MCQ Widget
// ---------------------------------------------------------------------------
class _McqPage extends StatefulWidget {
const _McqPage();
@override
State<_McqPage> createState() => _McqPageState();
}
class _McqPageState extends State<_McqPage> {
late MCQConfig _config;
@override
void initState() {
super.initState();
_reset();
}
void _reset() {
_config = MCQConfig(
id: 'q1',
questionNoToDisplay: 1,
question: 'Which of the following is a <b>compiled</b> language?',
answers: [
MCQAnswerConfig(id: 'a', answer: 'Python'),
MCQAnswerConfig(id: 'b', answer: 'JavaScript'),
MCQAnswerConfig(id: 'c', answer: 'Go'),
MCQAnswerConfig(id: 'd', answer: 'Ruby'),
],
);
}
@override
Widget build(BuildContext context) {
return _DemoPage(title: 'MCQ Widget', children: [
McqWidget(
mcqConfig: _config,
selectedAnswerCallback: (questionId, answer, index) {
setState(() {
_config = MCQConfig(
id: _config.id,
questionNoToDisplay: _config.questionNoToDisplay,
question: _config.question,
answers: _config.answers.asMap().entries.map((e) {
final isCorrect = e.key == 2; // 'Go' is correct
return e.value.copyWith(
isSelected: e.key == index,
isCorrectAnswer: e.key == index && isCorrect,
isIncorrectAnswer: e.key == index && !isCorrect,
);
}).toList(),
isToSelectAnswer: false,
);
});
},
),
16.verticalSpace,
TextWidget(
'Tap an answer. Green = correct, Red = incorrect.',
textType: TextTypeConstants.normal,
color: getColorByTheme(context: context, colorClass: AppColors.greyColor),
),
12.verticalSpace,
InputButtonWidget(
title: 'Reset',
isBorderButton: true,
bgColor: AppColors.greyColor,
onTap: () => setState(_reset)),
]);
}
}
// ---------------------------------------------------------------------------
// 6. Images & Avatars
// ---------------------------------------------------------------------------
class _ImagePage extends StatelessWidget {
const _ImagePage();
static const _img =
'https://images.unsplash.com/photo-1501854140801-50d01698950b?w=640&q=80';
@override
Widget build(BuildContext context) {
return _DemoPage(title: 'Images & Avatars', children: [
_label('CachedNetworkImageWidget'),
CachedNetworkImageWidget(imageUrl: _img, height: 180.h, width: double.infinity),
16.verticalSpace,
_label('CircularCachedNetworkImage — loaded & fallback'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(children: [
CircularCachedNetworkImage(imageUrl: _img, size: 64.w, fallBackString: 'Alice'),
4.verticalSpace,
const TextWidget('loaded', textType: TextTypeConstants.normal),
]),
Column(children: [
CircularCachedNetworkImage(
imageUrl: 'https://invalid/404.png', size: 64.w, fallBackString: 'Bob Smith'),
4.verticalSpace,
const TextWidget('initials fallback', textType: TextTypeConstants.normal),
]),
Column(children: [
CircularCachedNetworkImage(imageUrl: '', size: 64.w, fallBackString: 'C'),
4.verticalSpace,
const TextWidget('single letter', textType: TextTypeConstants.normal),
]),
],
),
]);
}
}
// ---------------------------------------------------------------------------
// 7. Newsfeed / Author Tile — multiple use-cases
// ---------------------------------------------------------------------------
class _NewsfeedPage extends StatelessWidget {
const _NewsfeedPage();
static const _avatar = 'https://i.pravatar.cc/150?img=12';
static const _avatar2 = 'https://i.pravatar.cc/150?img=47';
static const _avatar3 = 'https://i.pravatar.cc/150?img=33';
static const _avatar4 = 'https://i.pravatar.cc/150?img=68';
// Landscape photos for posts
static const _photo1 =
'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=900&q=80';
static const _photo2 =
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=900&q=80';
static const _photo3 =
'https://images.unsplash.com/photo-1551632811-561732d1e306?w=900&q=80';
static const _photo4 =
'https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?w=900&q=80';
static const _photo5 =
'https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?w=900&q=80';
// ── helpers ───────────────────────────────────────────────────────────────
Widget _postHeader(
BuildContext context, {
required String avatarUrl,
required String name,
required String timeAgo,
bool verified = false,
}) {
return AuthorListTileWidget(
authorListTileModel: AuthorListTileModel(
authorImageConfig: AuthorImageConfig(
imageUrl: avatarUrl,
isToDisplayAuthor: true,
imageSize: 42.w,
),
titleConfig: AuthorTitleConfig(
title: name,
titleFontWeight: FontWeight.w700,
suffixWidget: verified
? const Icon(Icons.verified, color: Colors.blue, size: 15)
: null,
),
descConfig: AuthorTitleConfig(
title: timeAgo,
titleFontColor: AppColors.greyColor,
),
suffixWidget: IconButton(
icon: const Icon(Icons.more_horiz),
onPressed: () {},
),
),
);
}
Widget _actionBar(
BuildContext context, {
required int likes,
required int comments,
}) {
final grey = getColorByTheme(context: context, colorClass: AppColors.greyColor);
return Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
child: Row(
children: [
_ActionChip(
icon: Icons.favorite_border_rounded,
label: '$likes',
color: grey,
),
12.horizontalSpace,
_ActionChip(
icon: Icons.chat_bubble_outline_rounded,
label: '$comments',
color: grey,
),
12.horizontalSpace,
_ActionChip(
icon: Icons.share_outlined,
label: 'Share',
color: grey,
),
const Spacer(),
Icon(Icons.bookmark_border_rounded, size: 20.w, color: grey),
],
),
);
}
Widget _postCard(BuildContext context, {required List<Widget> children}) {
return Card(
margin: EdgeInsets.only(bottom: 16.h),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
side: BorderSide(
color: getColorByTheme(context: context, colorClass: AppColors.borderColor),
),
),
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
);
}
@override
Widget build(BuildContext context) {
return _DemoPage(
title: 'Newsfeed / Author Tile',
children: [
// ── Use case 1: Social post — text only with action bar ──────────
_label('Text post'),
_postCard(context, children: [
_postHeader(context,
avatarUrl: _avatar,
name: 'Aarav Sharma',
timeAgo: '2 hours ago · Public',
verified: true),
Padding(
padding: EdgeInsets.fromLTRB(12.w, 0, 12.w, 4.h),
child: const TextWidget(
'Just shipped a new Flutter UI framework to pub.dev! '
'Check it out and let me know what you think.',
textType: TextTypeConstants.subTitle,
),
),
_actionBar(context, likes: 128, comments: 24),
]),
// ── Use case 2: Post with single full-width image ────────────────
_label('Post with single image'),
_postCard(context, children: [
_postHeader(context,
avatarUrl: _avatar2,
name: 'Priya Patel',
timeAgo: '5 hours ago'),
Padding(
padding: EdgeInsets.fromLTRB(12.w, 0, 12.w, 8.h),
child: const TextWidget(
'Sunrise from the summit. Worth every step.',
textType: TextTypeConstants.subTitle,
),
),
AspectRatio(
aspectRatio: 16 / 9,
child: CachedNetworkImageWidget(
imageUrl: _photo1,
fit: BoxFit.cover,
),
),
_actionBar(context, likes: 342, comments: 51),
]),
// ── Use case 3: Post with 2 images side-by-side ──────────────────
_label('Post with 2 images'),
_postCard(context, children: [
_postHeader(context,
avatarUrl: _avatar3,
name: 'Rajan Thapa',
timeAgo: 'Yesterday'),
Padding(
padding: EdgeInsets.fromLTRB(12.w, 0, 12.w, 8.h),
child: const TextWidget(
'Weekend hike — the views were breathtaking!',
textType: TextTypeConstants.subTitle,
),
),
SizedBox(
height: 180.h,
child: Row(
children: [
Expanded(
child: CachedNetworkImageWidget(
imageUrl: _photo2,
fit: BoxFit.cover,
),
),
2.horizontalSpace,
Expanded(
child: CachedNetworkImageWidget(
imageUrl: _photo3,
fit: BoxFit.cover,
),
),
],
),
),
_actionBar(context, likes: 64, comments: 8),
]),
// ── Use case 4: Post with image mosaic (3 + overflow badge) ──────
_label('Post with 4-image mosaic'),
_postCard(context, children: [
_postHeader(context,
avatarUrl: _avatar4,
name: 'Sunita Rai',
timeAgo: '2 days ago'),
Padding(
padding: EdgeInsets.fromLTRB(12.w, 0, 12.w, 8.h),
child: const TextWidget(
'Photo dump from the Flutter conference.',
textType: TextTypeConstants.subTitle,
),
),
SizedBox(
height: 200.h,
child: Row(
children: [
// large left image
Expanded(
flex: 2,
child: CachedNetworkImageWidget(
imageUrl: _photo1,
fit: BoxFit.cover,
height: 200.h,
),
),
2.horizontalSpace,
// two stacked right images
Expanded(
child: Column(
children: [
Expanded(
child: CachedNetworkImageWidget(
imageUrl: _photo4,
fit: BoxFit.cover,
width: double.infinity,
),
),
2.verticalSpace,
Expanded(
child: Stack(
fit: StackFit.expand,
children: [
CachedNetworkImageWidget(
imageUrl: _photo5,
fit: BoxFit.cover,
),
// "+2 more" overlay on the last tile
Container(
color: Colors.black54,
alignment: Alignment.center,
child: const TextWidget(
'+2',
textType: TextTypeConstants.title,
color: Colors.white,
),
),
],
),
),
],
),
),
],
),
),
_actionBar(context, likes: 203, comments: 47),
]),
// ── Use case 5: Post with link preview ───────────────────────────
_label('Post with link preview'),
_postCard(context, children: [
_postHeader(context,
avatarUrl: _avatar,
name: 'Aarav Sharma',
timeAgo: '3 days ago',
verified: true),
Padding(
padding: EdgeInsets.fromLTRB(12.w, 0, 12.w, 8.h),
child: const TextWidget(
'This article on Dart 3 macros is a must-read for every Flutter dev.',
textType: TextTypeConstants.subTitle,
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w),
child: const LinkPreviewWidget(
link: 'https://dart.dev/language/macros',
),
),
_actionBar(context, likes: 56, comments: 12),
]),
// ── Use case 6: News article list item ───────────────────────────
_sectionDivider(),
_label('News article list item'),
AuthorListTileWidget(
authorListTileModel: AuthorListTileModel(
authorDecorationConfig: AuthorDecorationConfig(isBorderRequired: true),
authorImageConfig: AuthorImageConfig(
imageUrl: 'https://i.pravatar.cc/150?img=5',
isToDisplayAuthor: true,
imageSize: 40.w,
),
titleConfig: AuthorTitleConfig(
title: 'Flutter Weekly Newsletter',
titleFontSize: 13,
titleFontWeight: FontWeight.w600,
),
descConfig: AuthorTitleConfig(
title: "What's new in Flutter 3.29 — deep dive into Impeller and "
'the new animation framework.',
maxLines: 2,
),
suffixWidget: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: getColorByTheme(
context: context, colorClass: AppColors.blueColor)
.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(4.r),
),
child: TextWidget(
'Tech',
textType: TextTypeConstants.normal,
color: getColorByTheme(
context: context, colorClass: AppColors.blueColor),
),
),
),
),
12.verticalSpace,
// ── Use case 7: Comment row ──────────────────────────────────────
_sectionDivider(),
_label('Comment row'),
AuthorListTileWidget(
authorListTileModel: AuthorListTileModel(
authorImageConfig: AuthorImageConfig(
imageUrl: _avatar2,
isToDisplayAuthor: true,
imageSize: 36.w,
),
titleConfig: AuthorTitleConfig(
title: 'Priya Patel',
titleFontWeight: FontWeight.w600,
titleFontSize: 13,
),
descConfig: AuthorTitleConfig(
title: "Amazing work! I've been waiting for this. The MCQ widget alone is worth it.",
maxLines: 3,
),
suffixWidget: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.thumb_up_alt_outlined, size: 16),
4.verticalSpace,
const TextWidget('24', textType: TextTypeConstants.normal),
],
),
),
),
12.verticalSpace,
// ── Use case 8: Contact list ─────────────────────────────────────
_sectionDivider(),
_label('Contact list'),
...['Alice Johnson', 'Bob Marley', 'Carol White'].asMap().entries.map((e) {
return Column(
children: [
AuthorListTileWidget(
authorListTileModel: AuthorListTileModel(
authorImageConfig: AuthorImageConfig(
imageUrl: 'https://i.pravatar.cc/150?img=${10 + e.key}',
isToDisplayAuthor: true,
imageSize: 40.w,
removeAuthorImagePadding: true,
),
titleConfig: AuthorTitleConfig(
title: e.value,
titleFontWeight: FontWeight.w600,
),
descConfig: AuthorTitleConfig(title: '+1 555-010${e.key}'),
suffixWidget: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.call_outlined, size: 20),
onPressed: () {}),
IconButton(
icon: const Icon(Icons.message_outlined, size: 20),
onPressed: () {}),
],
),
),
),
DividerWidget(),
],
);
}),
12.verticalSpace,
// ── Use case 9: Chat list ────────────────────────────────────────
_sectionDivider(),
_label('Chat list'),
...{
_avatar: ('Aarav Sharma', 'Sure! See you tomorrow', '2m ago', 3),
_avatar2: ('Priya Patel', 'Can you review my PR?', '1h ago', 0),
_avatar3: ('Rajan Thapa', 'Sent you the design files', '3h ago', 1),
}.entries.map((e) {
final (name, lastMsg, time, unread) = e.value;
return Column(
children: [
AuthorListTileWidget(
authorListTileModel: AuthorListTileModel(
authorImageConfig: AuthorImageConfig(
imageUrl: e.key,
isToDisplayAuthor: true,
imageSize: 44.w,
removeAuthorImagePadding: true,
),
titleConfig: AuthorTitleConfig(
title: name,
titleFontWeight: FontWeight.w600,
suffixWidget: TextWidget(
time,
textType: TextTypeConstants.normal,
color: getColorByTheme(
context: context, colorClass: AppColors.greyColor),
),
),
descConfig: AuthorTitleConfig(
title: lastMsg,
titleFontColor: AppColors.greyColor,
maxLines: 1,
),
suffixWidget: unread > 0
? Container(
margin: EdgeInsets.only(right: 4.w),
padding: EdgeInsets.symmetric(
horizontal: 7.w, vertical: 3.h),
decoration: BoxDecoration(
color: getColorByTheme(
context: context,
colorClass: AppColors.blueColor),
borderRadius: BorderRadius.circular(20.r),
),
child: TextWidget(
'$unread',
textType: TextTypeConstants.normal,
color: Colors.white,
),
)
: null,
),
),
DividerWidget(),
],
);
}),
],
);
}
}
class _ActionChip extends StatelessWidget {
final IconData icon;
final String label;
final Color color;
const _ActionChip({
required this.icon,
required this.label,
required this.color,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 18.w, color: color),
4.horizontalSpace,
TextWidget(label, textType: TextTypeConstants.normal, color: color),
],
),
);
}
}
// ---------------------------------------------------------------------------
// 8. Carousel
// ---------------------------------------------------------------------------
class _CarouselPage extends StatefulWidget {
const _CarouselPage();
@override
State<_CarouselPage> createState() => _CarouselPageState();
}
class _CarouselPageState extends State<_CarouselPage> {
int _currentIndex = 0;
static const _banners = [
(
'https://images.unsplash.com/photo-1517816743773-6e0fd518b4a6?w=800&q=80',
'Mountain Sunrise',
'Nepal Himalayas'
),
(
'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&q=80',
'Alpine Lake',
'Swiss Alps'
),
(
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=800&q=80',
'Golden Valley',
'Yosemite, USA'
),
];
@override
Widget build(BuildContext context) {
return _DemoPage(title: 'Carousel', children: [
_label('Image carousel with indicators'),
CarouselSlider(
options: CarouselOptions(
height: 200.h,
autoPlay: true,
autoPlayInterval: const Duration(seconds: 3),
enlargeCenterPage: true,
viewportFraction: 0.9,
onPageChanged: (index, _) => setState(() => _currentIndex = index),
),
items: _banners.map((b) {
final (url, title, sub) = b;
return ClipRRect(
borderRadius: BorderRadius.circular(12.r),
child: Stack(
fit: StackFit.expand,
children: [
CachedNetworkImageWidget(imageUrl: url, fit: BoxFit.cover),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black.withValues(alpha: 0.7),
Colors.transparent
],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
TextWidget(title,
textType: TextTypeConstants.title,
color: Colors.white,
fontWeight: FontWeight.w700),
TextWidget(sub,
textType: TextTypeConstants.normal, color: Colors.white70),
],
),
),
),
],
),
);
}).toList(),
),
12.verticalSpace,
// Dot indicators
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _banners.asMap().entries.map((e) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
margin: EdgeInsets.symmetric(horizontal: 3.w),
width: _currentIndex == e.key ? 20.w : 8.w,
height: 8.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
color: _currentIndex == e.key
? getColorByTheme(context: context, colorClass: AppColors.blueColor)
: getColorByTheme(context: context, colorClass: AppColors.greyColor),
),
);
}).toList(),
),
24.verticalSpace,
_label('Card carousel (viewportFraction = 0.75)'),
CarouselSlider(
options: CarouselOptions(
height: 120.h,
viewportFraction: 0.75,
enableInfiniteScroll: true,
),
items: [
('Breaking News', 'Flutter 4.0 lands with Material You'),
('Tutorial', 'Build a production MCQ app in Flutter'),
('Deep Dive', 'Riverpod vs BLoC — 2025 comparison'),
].map((item) {
final (tag, headline) = item;
return Container(
margin: EdgeInsets.symmetric(horizontal: 6.w),
padding: EdgeInsets.all(14.w),
decoration: BoxDecoration(
color: getColorByTheme(context: context, colorClass: AppColors.cardColor),
borderRadius: BorderRadius.circular(10.r),
border: Border.all(
color: getColorByTheme(context: context, colorClass: AppColors.borderColor),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
decoration: BoxDecoration(
color: getColorByTheme(context: context, colorClass: AppColors.blueColor)
.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(4.r),
),
child: TextWidget(tag,
textType: TextTypeConstants.normal,
color: getColorByTheme(context: context, colorClass: AppColors.blueColor)),
),
8.verticalSpace,
TextWidget(headline,
textType: TextTypeConstants.subTitle,
fontWeight: FontWeight.w600,
maxLines: 2,
overflow: TextOverflow.ellipsis),
],
),
);
}).toList(),
),
]);
}
}
// ---------------------------------------------------------------------------
// 9. YouTube Player
// ---------------------------------------------------------------------------
class _YouTubePage extends StatelessWidget {
const _YouTubePage();
static const _videos = [
('Flutter Intro (Google)', 'https://www.youtube.com/watch?v=fq4N0hgOWzU'),
('Dart in 100 Seconds', 'https://www.youtube.com/watch?v=NrO0CJCbYLA'),
('Flutter Riverpod Deep Dive', 'https://www.youtube.com/watch?v=yMgtTtDQoEA'),
];
@override
Widget build(BuildContext context) {
return _DemoPage(title: 'YouTube Player', children: [
for (final v in _videos) ...[
_label(v.$1),
ClipRRect(
borderRadius: BorderRadius.circular(8.r),
child: YoutubePlayerWidget(
youtubePlayerConfig: YoutubePlayerConfig(youtubeUrl: v.$2),
),
),
16.verticalSpace,
],
]);
}
}
// ---------------------------------------------------------------------------
// 10. Link Previewer
// ---------------------------------------------------------------------------
class _LinkPreviewPage extends StatelessWidget {
const _LinkPreviewPage();
static const _links = [
'https://flutter.dev',
'https://pub.dev/packages/flutter_screenutil',
'https://dart.dev',
];
@override
Widget build(BuildContext context) {
return _DemoPage(title: 'Link Previewer', children: [
for (final link in _links) ...[
_label(link),
Container(
decoration: BoxDecoration(
border: Border.all(
color: getColorByTheme(context: context, colorClass: AppColors.borderColor)),
borderRadius: BorderRadius.circular(8.r),
),
padding: EdgeInsets.all(8.w),
child: LinkPreviewWidget(link: link, width: double.infinity),
),
16.verticalSpace,
],
]);
}
}
// ---------------------------------------------------------------------------
// 11. Web View
// ---------------------------------------------------------------------------
class _WebViewPage extends StatelessWidget {
const _WebViewPage();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const TextWidget('Web View', textType: TextTypeConstants.heading),
),
body: WebViewWidget(
url: 'https://flutter.dev',
showProgressBar: true,
),
);
}
}
// ---------------------------------------------------------------------------
// 12. HTML Rendering
// ---------------------------------------------------------------------------
class _HtmlPage extends StatelessWidget {
const _HtmlPage();
static const _html = '''
<h2>Rich HTML Content</h2>
<p>The <strong>RenderHtmlWidget</strong> renders <em>formatted</em> HTML inside Flutter:</p>
<ul>
<li><b>Bold text</b> and <i>italic text</i></li>
<li>Inline <code>code snippets</code></li>
<li>Ordered & unordered lists</li>
</ul>
<ol>
<li>First numbered item</li>
<li>Second numbered item</li>
<li>Third numbered item</li>
</ol>
<p>Block quote example:</p>
<blockquote>
"Code is like humor. When you have to explain it, it's bad." — Cory House
</blockquote>
<p>Visit <a href="https://flutter.dev">Flutter.dev</a> for the official docs.</p>
''';
@override
Widget build(BuildContext context) {
return _DemoPage(title: 'HTML Rendering', children: [
_label('RenderHtmlWidget'),
RenderHtmlWidget(htmlContent: _html),
24.verticalSpace,
_label('Inline HTML inside MCQ answers'),
McqWidget(
mcqConfig: MCQConfig(
id: 'html_q',
questionNoToDisplay: 1,
question:
'The value of <code>2<sup>8</sup></code> is:',
answers: [
MCQAnswerConfig(id: 'a', answer: '128'),
MCQAnswerConfig(id: 'b', answer: '<b>256</b>', isCorrectAnswer: true),
MCQAnswerConfig(id: 'c', answer: '512'),
],
),
selectedAnswerCallback: (_, __, ___) {},
),
]);
}
}
// ---------------------------------------------------------------------------
// 13. Read More Text
// ---------------------------------------------------------------------------
class _ReadMorePage extends StatelessWidget {
const _ReadMorePage();
static const _longText = "Flutter is Google's UI toolkit for building "
'beautiful, natively compiled applications for mobile, web, desktop, '
'and embedded devices from a single codebase. Flutter works with '
'existing code, is used by developers and organizations around the '
'world, and is free and open source. It was first described in 2015 '
'and was released in version 1.0 on December 4, 2018. Flutter is used '
'internally by Google in multiple products, including Google Pay, '
'and it has been growing in popularity since its original release.';
@override
Widget build(BuildContext context) {
return _DemoPage(title: 'Read More Text', children: [
_label('Trim by length (240 chars)'),
ReadMoreText(
_longText,
trimMode: TrimMode.Length,
trimLength: 120,
colorClickableText:
getColorByTheme(context: context, colorClass: AppColors.blueColor),
style: context.textTheme.bodyMedium,
),
24.verticalSpace,
_label('Trim by lines (2 lines)'),
ReadMoreText(
_longText,
trimMode: TrimMode.Line,
trimLines: 2,
colorClickableText:
getColorByTheme(context: context, colorClass: AppColors.blueColor),
style: context.textTheme.bodyMedium,
),
]);
}
}
// ---------------------------------------------------------------------------
// 15. Theming & Colors
// ---------------------------------------------------------------------------
class _ThemePage extends StatelessWidget {
const _ThemePage();
static final _palette = <(String, ColorModel)>[
('primaryColor', AppColors.primaryColor),
('secondaryColor', AppColors.secondaryColor),
('blueColor', AppColors.blueColor),
('successColor', AppColors.successColor),
('errorColor', AppColors.errorColor),
('warningColor', AppColors.warningColor),
('pendingColor', AppColors.pendingColor),
('greyColor', AppColors.greyColor),
('cardColor', AppColors.cardColor),
('backgroundColor', AppColors.backgroundColor),
('hrTitleColor', AppColors.hrTitileColor),
('infoColor', AppColors.infoColor),
];
@override
Widget build(BuildContext context) {
return _DemoPage(title: 'Theming & Colors', children: [
_label('Adaptive color swatches (light ↔ dark)'),
..._palette.map((entry) {
final (name, model) = entry;
final resolved = getColorByTheme(context: context, colorClass: model);
return Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Row(
children: [
Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: resolved,
border: Border.all(
color: getColorByTheme(
context: context, colorClass: AppColors.borderColor)),
borderRadius: BorderRadius.circular(6.r),
),
),
12.horizontalSpace,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextWidget(name, textType: TextTypeConstants.subTitle),
TextWidget(
'#${resolved.toARGB32().toRadixString(16).padLeft(8, '0').toUpperCase()}',
textType: TextTypeConstants.normal,
color: getColorByTheme(context: context, colorClass: AppColors.greyColor),
),
],
),
],
),
);
}),
24.verticalSpace,
_label('Toast notifications'),
Wrap(
spacing: 8.w,
runSpacing: 8.h,
children: [
for (final t in ToastEnum.values)
InputButtonWidget(
title: t.name,
bgColor: switch (t) {
ToastEnum.success => AppColors.successColor,
ToastEnum.error => AppColors.errorColor,
ToastEnum.warning => AppColors.warningColor,
ToastEnum.info => AppColors.infoColor,
},
onTap: () =>
context.showToastMessage(toastEnum: t, toastMessage: '${t.name} toast'),
),
],
),
]);
}
}
// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
class _DemoPage extends StatelessWidget {
final String title;
final List<Widget> children;
const _DemoPage({required this.title, required this.children});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextWidget(title, textType: TextTypeConstants.heading),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
),
);
}
}
Widget _label(String text) => Padding(
padding: EdgeInsets.only(bottom: 6.h),
child: TextWidget(
text,
textType: TextTypeConstants.normal,
color: Colors.grey,
fontWeight: FontWeight.w600,
),
);
Widget _sectionDivider() => Padding(
padding: EdgeInsets.symmetric(vertical: 12.h),
child: Divider(
thickness: 0.5,
color: Colors.grey.withValues(alpha: 0.3),
),
);