feat: implemenat widget modal and show dialog

This commit is contained in:
pahmiudahgede 2025-05-27 08:36:25 +07:00
parent c87d820a9d
commit de01c1acce
7 changed files with 394 additions and 21 deletions

View File

@ -24,10 +24,10 @@ FontWeight superBold = FontWeight.w900;
// =====================text behavior===================== // =====================text behavior=====================
class Tulisan { class Tulisan {
static TextStyle heading({Color? color}) { static TextStyle heading({Color? color, double? fontsize}) {
return GoogleFonts.spaceGrotesk( return GoogleFonts.spaceGrotesk(
fontSize: 24.sp, fontSize: fontsize?.sp ?? 24.sp,
fontWeight: FontWeight.bold, fontWeight: extraBold,
color: color ?? blackNavyColor, color: color ?? blackNavyColor,
); );
} }
@ -35,7 +35,7 @@ class Tulisan {
static TextStyle body({Color? color, double? fontsize}) { static TextStyle body({Color? color, double? fontsize}) {
return GoogleFonts.spaceMono( return GoogleFonts.spaceMono(
fontSize: fontsize?.sp ?? 16.sp, fontSize: fontsize?.sp ?? 16.sp,
fontWeight: FontWeight.w400, fontWeight: regular,
color: color ?? blackNavyColor, color: color ?? blackNavyColor,
); );
} }
@ -43,7 +43,7 @@ class Tulisan {
static TextStyle subheading({Color? color, double? fontsize}) { static TextStyle subheading({Color? color, double? fontsize}) {
return GoogleFonts.spaceGrotesk( return GoogleFonts.spaceGrotesk(
fontSize: fontsize?.sp ?? 18.sp, fontSize: fontsize?.sp ?? 18.sp,
fontWeight: FontWeight.w500, fontWeight: medium,
color: color ?? blackNavyColor, color: color ?? blackNavyColor,
); );
} }
@ -55,7 +55,7 @@ class Tulisan {
}) { }) {
return GoogleFonts.spaceGrotesk( return GoogleFonts.spaceGrotesk(
fontSize: fontsize?.sp ?? 16.sp, fontSize: fontsize?.sp ?? 16.sp,
fontWeight: fontWeight ?? FontWeight.w400, fontWeight: fontWeight ?? regular,
color: color ?? blackNavyColor, color: color ?? blackNavyColor,
); );
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rijig_mobile/core/utils/guide.dart'; import 'package:rijig_mobile/core/utils/guide.dart';
import 'package:rijig_mobile/widget/tabbar_custom.dart'; import 'package:rijig_mobile/widget/tabbar_custom.dart';
import 'package:rijig_mobile/widget/unhope_handler.dart';
class ActivityScreen extends StatefulWidget { class ActivityScreen extends StatefulWidget {
const ActivityScreen({super.key}); const ActivityScreen({super.key});
@ -49,11 +50,11 @@ class _ActivityScreenState extends State<ActivityScreen> {
), ),
), ),
), ),
body: const TabBarView( body: TabBarView(
children: [ children: [
Center(child: Text('Proses Page')), Center(child: InfoStateWidget(type: InfoStateType.emptyData)),
Center(child: Text('Gak Eroh Page')), Center(child: InfoStateWidget(type: InfoStateType.emptyData)),
Center(child: Text('Dibatalkan Page')), Center(child: InfoStateWidget(type: InfoStateType.emptyData)),
], ],
), ),
), ),

View File

@ -13,7 +13,9 @@ import 'package:rijig_mobile/features/home/presentation/components/about_comp.da
import 'package:rijig_mobile/features/home/presentation/components/article_list.dart'; import 'package:rijig_mobile/features/home/presentation/components/article_list.dart';
import 'package:rijig_mobile/globaldata/about/about_vmod.dart'; import 'package:rijig_mobile/globaldata/about/about_vmod.dart';
import 'package:rijig_mobile/globaldata/article/article_vmod.dart'; import 'package:rijig_mobile/globaldata/article/article_vmod.dart';
import 'package:rijig_mobile/widget/buttoncard.dart';
import 'package:rijig_mobile/widget/card_withicon.dart'; import 'package:rijig_mobile/widget/card_withicon.dart';
import 'package:rijig_mobile/widget/showmodal.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
const HomeScreen({super.key}); const HomeScreen({super.key});
@ -98,7 +100,40 @@ class _HomeScreenState extends State<HomeScreen> {
icon: Iconsax.timer, icon: Iconsax.timer,
text: 'Process', text: 'Process',
number: '1', number: '1',
onTap: () {
CustomModalDialog.show(
context: context,
variant: ModalVariant.textVersion,
title: 'Hapus Akun',
content:
'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
buttonCount: 2,
button1: CardButtonOne(
textButton: "Ya, Hapus",
onTap: () {}, onTap: () {},
fontSized: 14,
colorText: whiteColor,
color: primaryColor,
borderRadius: 10,
horizontal: double.infinity,
vertical: 50,
loadingTrue: false,
usingRow: false,
),
button2: CardButtonOne(
textButton: "Batal",
onTap: () => router.pop(),
fontSized: 14,
colorText: primaryColor,
color: Colors.transparent,
borderRadius: 10,
horizontal: double.infinity,
vertical: 50,
loadingTrue: false,
usingRow: false,
),
);
},
), ),
], ],
), ),

View File

@ -6,6 +6,7 @@ import 'package:rijig_mobile/core/router.dart';
import 'package:rijig_mobile/core/utils/guide.dart'; import 'package:rijig_mobile/core/utils/guide.dart';
import 'package:rijig_mobile/features/auth/presentation/viewmodel/logout_vmod.dart'; import 'package:rijig_mobile/features/auth/presentation/viewmodel/logout_vmod.dart';
import 'package:rijig_mobile/widget/buttoncard.dart'; import 'package:rijig_mobile/widget/buttoncard.dart';
import 'package:rijig_mobile/widget/custom_bottom_sheet.dart';
class ButtonLogout extends StatefulWidget { class ButtonLogout extends StatefulWidget {
const ButtonLogout({super.key}); const ButtonLogout({super.key});
@ -29,17 +30,54 @@ class _ButtonLogoutState extends State<ButtonLogout> {
borderRadius: 10, borderRadius: 10,
horizontal: double.infinity, horizontal: double.infinity,
vertical: 50, vertical: 50,
onTap: () async { onTap:
await viewModel.logout(); () => CustomBottomSheet.show(
context: context,
title: "Logout Sekarang?",
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Yakin ingin logout dari akun ini?"),
// tambahan konten
],
),
button1: CardButtonOne(
textButton: "Logout",
onTap: () {},
fontSized: 14,
colorText: Colors.white,
color: Colors.red,
borderRadius: 10,
horizontal: double.infinity,
vertical: 50,
loadingTrue: false,
usingRow: false,
),
button2: CardButtonOne(
textButton: "Batal",
onTap: () => router.pop(),
fontSized: 14,
colorText: Colors.red,
color: Colors.white,
borderRadius: 10,
horizontal: double.infinity,
vertical: 50,
loadingTrue: false,
usingRow: false,
),
),
if (viewModel.errorMessage == null) { // onTap: () async {
router.go("/login"); // await viewModel.logout();
} else {
ScaffoldMessenger.of(context).showSnackBar( // if (viewModel.errorMessage == null) {
SnackBar(content: Text(viewModel.errorMessage!)), // router.go("/login");
); // } else {
} // ScaffoldMessenger.of(context).showSnackBar(
}, // SnackBar(content: Text(viewModel.errorMessage!)),
// );
// }
// },
loadingTrue: viewModel.isLoading, loadingTrue: viewModel.isLoading,
usingRow: false, usingRow: false,
), ),

View File

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:rijig_mobile/core/router.dart';
import 'package:rijig_mobile/core/utils/guide.dart';
class CustomBottomSheet extends StatelessWidget {
final String title;
final Widget content;
final Widget button1;
final Widget? button2;
const CustomBottomSheet({
super.key,
required this.title,
required this.content,
required this.button1,
this.button2,
});
static void show({
required BuildContext context,
required String title,
required Widget content,
required Widget button1,
Widget? button2,
Duration duration = const Duration(milliseconds: 380),
}) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
transitionAnimationController: AnimationController(
vsync: Navigator.of(context),
duration: duration,
),
builder: (context) {
return CustomBottomSheet(
title: title,
content: content,
button1: button1,
button2: button2,
);
},
);
}
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
Container(
padding: PaddingCustom().paddingAll(15),
margin: const EdgeInsets.only(top: 30),
decoration: BoxDecoration(
color: whiteColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(15)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Tulisan.subheading(fontsize: 20)),
const Gap(12),
content,
const Gap(24),
if (button2 != null)
Column(children: [button1, const Gap(12), button2!])
else
button1,
],
),
),
Positioned(
top: -25,
right: 5,
child: GestureDetector(
onTap: () => router.pop(),
child: Container(
padding: PaddingCustom().paddingAll(10),
decoration: BoxDecoration(
color: whiteColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.close, size: 25),
),
),
),
],
);
}
}

140
lib/widget/showmodal.dart Normal file
View File

@ -0,0 +1,140 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:rijig_mobile/core/router.dart';
import 'package:rijig_mobile/core/utils/guide.dart';
enum ModalVariant { textVersion, imageVersion }
class CustomModalDialog extends StatelessWidget {
final ModalVariant variant;
final String title;
final String content;
final String? imageAsset;
final int buttonCount;
final Widget? button1;
final Widget? button2;
const CustomModalDialog({
super.key,
required this.variant,
required this.title,
required this.content,
this.imageAsset,
this.buttonCount = 0,
this.button1,
this.button2,
});
static void show({
required BuildContext context,
required ModalVariant variant,
required String title,
required String content,
String? imageAsset,
int buttonCount = 0,
Widget? button1,
Widget? button2,
}) {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: 'Dismiss',
barrierColor: Colors.black54,
transitionDuration: const Duration(milliseconds: 190),
pageBuilder: (_, __, ___) => const SizedBox.shrink(),
transitionBuilder: (_, animation, __, child) {
return Transform.scale(
scale: animation.value,
child: Opacity(
opacity: animation.value,
child: Center(
child: Dialog(
backgroundColor: Colors.transparent,
insetPadding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 24,
),
child: CustomModalDialog(
variant: variant,
title: title,
content: content,
imageAsset: imageAsset,
buttonCount: buttonCount,
button1: button1,
button2: button2,
),
),
),
),
);
},
);
}
@override
Widget build(BuildContext context) {
final modalContent = Container(
padding: PaddingCustom().paddingHorizontalVertical(20, 24),
margin: const EdgeInsets.only(top: 30),
decoration: BoxDecoration(
color: whiteColor,
borderRadius: BorderRadius.circular(24),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (variant == ModalVariant.imageVersion && imageAsset != null)
Padding(
padding: PaddingCustom().paddingOnly(bottom: 20),
child: Image.asset(
imageAsset!,
width: MediaQuery.of(context).size.width * 0.6,
fit: BoxFit.contain,
),
),
Align(
alignment: Alignment.centerLeft,
child: Text(title, style: Tulisan.subheading(fontsize: 19)),
),
Gap(8),
Align(
alignment: Alignment.centerLeft,
child: Text(content, style: Tulisan.customText(fontsize: 14)),
),
Gap(24),
if (buttonCount == 1 && button1 != null) button1!,
if (buttonCount == 2 && button1 != null && button2 != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(child: button2!),
Gap(16),
Expanded(child: button1!),
],
),
],
),
);
return Stack(
clipBehavior: Clip.none,
children: [
modalContent,
Positioned(
top: -15,
right: 5,
child: GestureDetector(
onTap: () => router.pop(),
child: CircleAvatar(
radius: 18,
backgroundColor: whiteColor,
child: Icon(Icons.close, color: blackNavyColor),
),
),
),
],
);
}
}

View File

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
enum InfoStateType { dataNotFound, emptyData, comingSoon }
class InfoStateWidget extends StatelessWidget {
final InfoStateType type;
final String? imageAsset;
final String? description;
const InfoStateWidget({
super.key,
required this.type,
this.imageAsset,
this.description,
});
String _getDefaultDescription() {
switch (type) {
case InfoStateType.dataNotFound:
return 'Data tidak ditemukan';
case InfoStateType.emptyData:
return 'Belum ada data yang tersedia';
case InfoStateType.comingSoon:
return 'Fitur ini segera hadir';
}
}
String _getDefaultImageAsset() {
switch (type) {
case InfoStateType.dataNotFound:
return 'assets/image/empty_data.png';
case InfoStateType.emptyData:
return 'assets/image/empty_data.png';
case InfoStateType.comingSoon:
return 'assets/image/empty_data.png';
}
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
return Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
imageAsset ?? _getDefaultImageAsset(),
width: screenWidth * 0.5,
height: screenHeight * 0.3,
fit: BoxFit.contain,
),
const SizedBox(height: 20),
Text(
description ?? _getDefaultDescription(),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
);
}
}