first commit

This commit is contained in:
Aditya Siregar
2025-07-30 22:38:44 +07:00
commit 73320561b0
444 changed files with 64633 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import '../constants/colors.dart';
enum ButtonStyle { filled, outlined }
class Button extends StatelessWidget {
const Button.filled({
super.key,
required this.onPressed,
required this.label,
this.style = ButtonStyle.filled,
this.color = AppColors.primary,
this.textColor = Colors.white,
this.width,
this.height = 50.0,
this.borderRadius = 16.0,
this.icon,
this.disabled = false,
this.fontSize = 16.0,
});
const Button.outlined({
super.key,
required this.onPressed,
required this.label,
this.style = ButtonStyle.outlined,
this.color = Colors.transparent,
this.textColor = AppColors.primary,
this.width,
this.height = 50.0,
this.borderRadius = 16.0,
this.icon,
this.disabled = false,
this.fontSize = 16.0,
});
final Function() onPressed;
final String label;
final ButtonStyle style;
final Color color;
final Color textColor;
final double? width;
final double height;
final double borderRadius;
final Widget? icon;
final bool disabled;
final double fontSize;
@override
Widget build(BuildContext context) {
return SizedBox(
height: height,
width: width,
child: style == ButtonStyle.filled
? ElevatedButton(
onPressed: disabled ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: color,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius),
),
padding: const EdgeInsets.symmetric(horizontal: 16.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
icon ?? const SizedBox.shrink(),
if (icon != null) const SizedBox(width: 10.0),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
label,
style: TextStyle(
color: disabled ? Colors.grey : textColor,
fontSize: fontSize,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
),
),
],
),
)
: OutlinedButton(
onPressed: disabled ? null : onPressed,
style: OutlinedButton.styleFrom(
backgroundColor: color,
side: const BorderSide(color: Colors.grey),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius),
),
padding: const EdgeInsets.symmetric(horizontal: 16.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
icon ?? const SizedBox.shrink(),
if (icon != null) const SizedBox(width: 10.0),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
label,
style: TextStyle(
color: disabled ? Colors.grey : textColor,
fontSize: fontSize,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
),
),
],
),
),
);
}
}
+9
View File
@@ -0,0 +1,9 @@
export 'buttons.dart';
export 'custom_dropdown.dart';
export 'custom_screen_manager.dart';
export 'custom_text_field.dart';
export 'image_picker_widget.dart';
export 'menu_button.dart';
export 'search_input.dart';
export 'spaces.dart';
export 'tab_custom.dart';
@@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/assets/assets.gen.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
class CustomDatePicker extends StatefulWidget {
final void Function(DateTime selectedDate)? onDateSelected;
final DateTime initialDate;
final Widget? prefix;
const CustomDatePicker({
super.key,
required this.initialDate,
this.onDateSelected,
this.prefix,
});
@override
State<CustomDatePicker> createState() => _CustomDatePickerState();
}
class _CustomDatePickerState extends State<CustomDatePicker> {
late DateTime selectedDate;
@override
void initState() {
selectedDate = widget.initialDate;
super.initState();
}
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = picked;
});
if (widget.onDateSelected != null) {
widget.onDateSelected!(picked);
}
}
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 500,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: GestureDetector(
onTap: () => _selectDate(context),
child: AbsorbPointer(
child: TextFormField(
style: const TextStyle(
color: AppColors.black,
fontWeight: FontWeight.bold,
),
readOnly: true,
controller: TextEditingController(
text: selectedDate.toFormattedDate2(),
),
decoration: InputDecoration(
suffixIcon: Padding(
padding: const EdgeInsets.all(16.0),
child: Assets.icons.calendar.svg(),
),
prefix: widget.prefix,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6.0),
borderSide: const BorderSide(color: AppColors.stroke),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6.0),
borderSide: const BorderSide(color: AppColors.stroke),
),
),
),
),
),
),
],
),
),
],
);
}
}
+55
View File
@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'spaces.dart';
class CustomDropdown extends StatelessWidget {
final String? value;
final List<String> items;
final String label;
final Function(String? value)? onChanged;
const CustomDropdown({
super.key,
required this.value,
required this.items,
required this.label,
this.onChanged,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
const SpaceHeight(12.0),
DropdownButtonFormField<String>(
value: value,
onChanged: onChanged,
items: items.map((String item) {
return DropdownMenuItem<String>(
value: item,
child: Text(item),
);
}).toList(),
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16.0),
borderSide: const BorderSide(color: Colors.grey),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16.0),
borderSide: const BorderSide(color: Colors.grey),
),
),
),
],
);
}
}
@@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
class CustomScreenManager extends StatelessWidget {
final Widget mobile;
final Widget desktop;
const CustomScreenManager({
super.key,
required this.mobile,
required this.desktop,
});
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
if (screenWidth < 1200) {
return mobile;
} else {
return desktop;
}
}
}
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'spaces.dart';
class CustomTextField extends StatelessWidget {
final TextEditingController controller;
final String label;
final Function(String value)? onChanged;
final bool obscureText;
final TextInputType? keyboardType;
final TextInputAction? textInputAction;
final TextCapitalization? textCapitalization;
final bool showLabel;
final Widget? prefixIcon;
final Widget? suffixIcon;
final bool readOnly;
const CustomTextField({
super.key,
required this.controller,
required this.label,
this.onChanged,
this.obscureText = false,
this.keyboardType,
this.textInputAction,
this.textCapitalization,
this.showLabel = true,
this.prefixIcon,
this.suffixIcon,
this.readOnly = false,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showLabel) ...[
Text(
label,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
const SpaceHeight(12.0),
],
TextFormField(
controller: controller,
onChanged: onChanged,
obscureText: obscureText,
keyboardType: keyboardType,
textInputAction: textInputAction,
textCapitalization: textCapitalization ?? TextCapitalization.none,
readOnly: readOnly,
decoration: InputDecoration(
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16.0),
borderSide: const BorderSide(color: Colors.grey),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16.0),
borderSide: const BorderSide(color: Colors.grey),
),
hintText: label,
),
),
],
);
}
}
+23
View File
@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
class DashedLine extends StatelessWidget {
const DashedLine({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: List.generate(
400 ~/ 10,
(index) => Expanded(
child: Container(
color: index % 2 == 1 ? Colors.transparent : Colors.grey,
height: 1,
),
),
),
),
);
}
}
@@ -0,0 +1,130 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../assets/assets.gen.dart';
import '../constants/colors.dart';
import '../constants/variables.dart';
import 'buttons.dart';
import 'spaces.dart';
class ImagePickerWidget extends StatefulWidget {
final String label;
final void Function(XFile? file) onChanged;
final bool showLabel;
final String? initialImageUrl;
const ImagePickerWidget({
super.key,
required this.label,
required this.onChanged,
this.showLabel = true,
this.initialImageUrl,
});
@override
State<ImagePickerWidget> createState() => _ImagePickerWidgetState();
}
class _ImagePickerWidgetState extends State<ImagePickerWidget> {
String? imagePath;
bool hasInitialImage = false;
@override
void initState() {
super.initState();
hasInitialImage = widget.initialImageUrl != null;
}
Future<void> _pickImage() async {
final pickedFile = await ImagePicker().pickImage(
source: ImageSource.gallery,
);
setState(() {
if (pickedFile != null) {
imagePath = pickedFile.path;
hasInitialImage = false; // Clear initial image when new image is picked
widget.onChanged(pickedFile);
} else {
debugPrint('No image selected.');
widget.onChanged(null);
}
});
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.showLabel) ...[
Text(
widget.label,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
const SpaceHeight(12.0),
],
Container(
padding: const EdgeInsets.all(6.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0),
border: Border.all(color: AppColors.primary),
),
child: Row(
children: [
SizedBox(
width: 80.0,
height: 80.0,
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: imagePath != null
? Image.file(
File(imagePath!),
fit: BoxFit.cover,
)
: hasInitialImage && widget.initialImageUrl != null
? CachedNetworkImage(
imageUrl: widget.initialImageUrl!.contains('http')
? widget.initialImageUrl!
: '${Variables.baseUrl}/${widget.initialImageUrl}',
placeholder: (context, url) =>
const Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => Container(
padding: const EdgeInsets.all(16.0),
color: AppColors.black.withOpacity(0.05),
child: Assets.icons.image.svg(),
),
fit: BoxFit.cover,
)
: Container(
padding: const EdgeInsets.all(16.0),
color: AppColors.black.withOpacity(0.05),
child: Assets.icons.image.svg(),
),
),
),
const Spacer(),
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Button.filled(
height: 30.0,
width: 140.0,
onPressed: _pickImage,
label: 'Choose Photo',
fontSize: 12.0,
borderRadius: 5.0,
),
),
],
),
),
],
);
}
}
+76
View File
@@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../constants/colors.dart';
import 'components.dart';
class MenuButton extends StatelessWidget {
final String iconPath;
final String label;
final bool isActive;
final VoidCallback onPressed;
final bool isImage;
final double size;
const MenuButton({
super.key,
required this.iconPath,
required this.label,
this.isActive = false,
required this.onPressed,
this.isImage = false,
this.size = 90,
});
@override
Widget build(BuildContext context) {
return Flexible(
child: InkWell(
onTap: onPressed,
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
child: Container(
width: context.deviceWidth,
padding: const EdgeInsets.all(15.0),
decoration: BoxDecoration(
color: isActive ? AppColors.primary : AppColors.white,
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
boxShadow: [
BoxShadow(
offset: const Offset(0, 4),
blurRadius: 20.0,
blurStyle: BlurStyle.outer,
spreadRadius: 0,
color: AppColors.black.withOpacity(0.1),
),
],
),
child: Column(
children: [
isImage
? Image.asset(iconPath,
width: size, height: size, fit: BoxFit.contain)
: SvgPicture.asset(
iconPath,
colorFilter: ColorFilter.mode(
isActive ? AppColors.white : AppColors.primary,
BlendMode.srcIn,
),
),
const SpaceHeight(10.0),
Text(
label,
style: TextStyle(
color: isActive ? AppColors.white : AppColors.primary,
fontSize: 12,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
);
}
}
+52
View File
@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import '../constants/colors.dart';
class SearchInput extends StatelessWidget {
final TextEditingController controller;
final Function(String value)? onChanged;
final VoidCallback? onTap;
final String hintText;
const SearchInput({
super.key,
required this.controller,
this.onChanged,
this.onTap,
this.hintText = 'Cari di sini',
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(8.0),
),
child: TextFormField(
onTap: onTap,
readOnly: onTap != null,
controller: controller,
onChanged: onChanged,
decoration: InputDecoration(
hintText: hintText,
prefixIcon: const Icon(
Icons.search,
color: AppColors.primary,
),
contentPadding: const EdgeInsets.all(16.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: const BorderSide(color: Colors.grey),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: const BorderSide(color: Colors.grey),
),
),
),
);
}
}
+17
View File
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class SpaceHeight extends StatelessWidget {
final double height;
const SpaceHeight(this.height, {super.key});
@override
Widget build(BuildContext context) => SizedBox(height: height);
}
class SpaceWidth extends StatelessWidget {
final double width;
const SpaceWidth(this.width, {super.key});
@override
Widget build(BuildContext context) => SizedBox(width: width);
}
+71
View File
@@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import '../constants/colors.dart';
class TabCustom extends StatelessWidget {
final List<TabMenu> children;
final EdgeInsetsGeometry? padding;
const TabCustom({
super.key,
required this.children,
this.padding,
});
@override
Widget build(BuildContext context) {
return Container(
margin: padding,
decoration: BoxDecoration(
border: Border.all(color: AppColors.primary, width: 1.5),
borderRadius: const BorderRadius.all(Radius.circular(9.0)),
),
child: Row(
children: children,
),
);
}
}
class TabMenu extends StatelessWidget {
final String label;
final VoidCallback onTap;
final bool isActive;
const TabMenu({
super.key,
required this.label,
required this.onTap,
this.isActive = false,
});
@override
Widget build(BuildContext context) {
return Flexible(
child: InkWell(
onTap: onTap,
child: Container(
width: context.deviceWidth,
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 15.0),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
color: isActive ? AppColors.primary : AppColors.white,
),
child: Center(
child: Text(
label,
style: TextStyle(
color: isActive ? AppColors.white : AppColors.primary,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
),
),
),
);
}
}