init
This commit is contained in:
commit
e833bcaec4
224 files changed
+11812
No files matched your search
@@ -0,0 +1,90 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'src/models/dashboard_models/dashboard_card.model.dart';
|
||||
|
||||
final demoUsersList = List.generate(
|
||||
20,
|
||||
(i) => UsersModel.fromMap({
|
||||
'id': i.toString(),
|
||||
'avatar':
|
||||
'https://cdn.dribbble.com/users/1500210/avatars/normal/ebcc769d2f155e72aa024bdafadc28cc.jpg?1629718724&resize=80x80',
|
||||
'name': 'user $i',
|
||||
'dateOfBirth': DateTime.now().toLocal(),
|
||||
'gender': i.isEven ? "male" : "female",
|
||||
'email': 'user$i@codestein.dev',
|
||||
'phone': (Random.secure().nextInt(9999999) + 1999999).toString(),
|
||||
'status': i.isEven,
|
||||
}),
|
||||
);
|
||||
|
||||
class UsersModel {
|
||||
final String id;
|
||||
final String avatar;
|
||||
final String name;
|
||||
final DateTime dateOfBirth;
|
||||
final String gender;
|
||||
final String email;
|
||||
final String phone;
|
||||
bool status;
|
||||
|
||||
UsersModel({
|
||||
required this.id,
|
||||
required this.avatar,
|
||||
required this.name,
|
||||
required this.dateOfBirth,
|
||||
required this.gender,
|
||||
required this.email,
|
||||
required this.phone,
|
||||
this.status = false,
|
||||
});
|
||||
|
||||
factory UsersModel.fromMap(Map<String, dynamic> map) {
|
||||
return UsersModel(
|
||||
id: map['id'],
|
||||
avatar: map['avatar'],
|
||||
name: map['name'],
|
||||
dateOfBirth: map['dateOfBirth'],
|
||||
gender: map['gender'],
|
||||
email: map['email'],
|
||||
phone: map['phone'],
|
||||
status: map['status'] ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// final dashboardCardModelValues = [
|
||||
// DashboardCardModel.fromJson({
|
||||
// 'name': 'Projects',
|
||||
// 'icon': Icons.wine_bar,
|
||||
// 'value': 327,
|
||||
// 'persentage': 32.1,
|
||||
// 'persentageValue': '2.1',
|
||||
// 'notes': '8k social visitors',
|
||||
// }),
|
||||
// DashboardCardModel.fromJson({
|
||||
// 'name': 'Stock Qty',
|
||||
// 'icon': Icons.add_chart_rounded,
|
||||
// 'value': 275,
|
||||
// 'persentage': 64.1,
|
||||
// 'persentageValue': '3.1',
|
||||
// 'notes': 'Total 424,567 deliveries',
|
||||
// }),
|
||||
// DashboardCardModel.fromJson({
|
||||
// 'name': 'C APEX',
|
||||
// 'icon': Icons.apple_rounded,
|
||||
// 'value': 648,
|
||||
// 'persentage': 72.1,
|
||||
// 'persentageValue': '12.1',
|
||||
// 'notes': 'Counted in Millions',
|
||||
// }),
|
||||
// DashboardCardModel.fromJson({
|
||||
// 'name': 'Saving',
|
||||
// 'icon': Icons.data_saver_off_rounded,
|
||||
// 'value': 18478,
|
||||
// 'persentage': 24.1,
|
||||
// 'persentageValue': '0.1',
|
||||
// 'notes': 'Reports by states and ganders',
|
||||
// }),
|
||||
// ];
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'src/classes/constents.class.dart';
|
||||
import 'src/platform/platform_functions.dart';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'src/classes/functions.class.dart';
|
||||
import 'src/classes/routes.class.dart';
|
||||
|
||||
// void main() async {
|
||||
// WidgetsFlutterBinding.ensureInitialized();
|
||||
// PlatformFunctions.init();
|
||||
// await initializeProviders();
|
||||
|
||||
// runApp(const MyApp());
|
||||
// }
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetMaterialApp(
|
||||
title: 'Dashboard',
|
||||
debugShowCheckedModeBanner: false,
|
||||
getPages: appRoutes,
|
||||
initialRoute: appRoutes.first.name,
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.stylus,
|
||||
PointerDeviceKind.unknown
|
||||
},
|
||||
),
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: primaryColor).copyWith(
|
||||
surfaceTint: Colors.transparent,
|
||||
),
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: primaryColor,
|
||||
selectionColor: primaryColor.withOpacity(.3),
|
||||
),
|
||||
useMaterial3: true,
|
||||
textTheme: GoogleFonts.poppinsTextTheme(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const Color primaryColor = Colors.blueAccent;
|
||||
|
||||
const Color navbarColor = Colors.white;
|
||||
const Color drawerColor = Colors.white;
|
||||
|
||||
const bool toastAutoDissmiss = true;
|
||||
const Duration toastDuration = Duration(milliseconds: 200);
|
||||
const Duration toastDissmissDuration = Duration(seconds: 4);
|
||||
|
||||
// this is the maximum width the main container can take as width => in web called (fixed, fluid)
|
||||
// it can be double.infinity or fixed double like 1600.0
|
||||
const double maxContainerWidth = 1600;
|
||||
|
||||
// if true a BreadCampWidget will be showen at the top of the main container
|
||||
// you can add or edit this widget as you like, you will find it in the /src/widgets/app_widgets.
|
||||
const bool showBreadCamp = false;
|
||||
|
||||
const List<BoxShadow> cardShadow = [
|
||||
BoxShadow(
|
||||
color: Color(0x1F6E9AE6),
|
||||
offset: Offset(0, 12),
|
||||
blurRadius: 20,
|
||||
spreadRadius: .05,
|
||||
),
|
||||
];
|
||||
@@ -0,0 +1,9 @@
|
||||
enum AlertType { primary, success, danger, warning, info }
|
||||
|
||||
enum ToastType { primary, success, danger, warning, info }
|
||||
|
||||
enum ScreenSizeType { desktop, tablet, mobile }
|
||||
|
||||
enum DissmissType { auto, click, swipe }
|
||||
|
||||
enum DrawerItemType { link, menu }
|
||||
@@ -0,0 +1,165 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'constents.class.dart';
|
||||
import 'enums.class.dart';
|
||||
import 'routes.class.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../providers/alert.provider.dart';
|
||||
import '../providers/auth.provider.dart';
|
||||
import '../providers/routes.provider.dart';
|
||||
import '../providers/search.provider.dart';
|
||||
import '../providers/toast.provider.dart';
|
||||
|
||||
String imgUrl(String url) {
|
||||
if (kIsWeb) return 'images/$url';
|
||||
return 'images/$url';
|
||||
}
|
||||
|
||||
Widget svg(String name, {double size = 20, Color? color}) {
|
||||
try {
|
||||
final String assetName = 'images/svgs/$name.svg';
|
||||
return SvgPicture.asset(
|
||||
assetName,
|
||||
width: size,
|
||||
height: size,
|
||||
colorFilter: ColorFilter.mode(color ?? primaryColor, BlendMode.srcIn),
|
||||
);
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
alert(
|
||||
String message, {
|
||||
AlertType type = AlertType.primary,
|
||||
Duration autoDismissAfter = Duration.zero,
|
||||
}) {
|
||||
Get.find<AlertProvider>().setAlert(
|
||||
message,
|
||||
type: type,
|
||||
autoDismissAfter: autoDismissAfter,
|
||||
);
|
||||
}
|
||||
|
||||
toast(
|
||||
String? message, {
|
||||
ToastType type = ToastType.primary,
|
||||
VoidCallback? callback,
|
||||
}) {
|
||||
Get.find<ToastProvider>().addToast(message, type: type, callback: callback);
|
||||
}
|
||||
|
||||
void showModel(String type) {
|
||||
final r = Random.secure().nextInt(5);
|
||||
switch (r) {
|
||||
case 0:
|
||||
alert(AlertType.primary.toString(), type: AlertType.primary);
|
||||
break;
|
||||
case 1:
|
||||
alert(AlertType.success.toString(), type: AlertType.success);
|
||||
break;
|
||||
case 2:
|
||||
alert(AlertType.warning.toString(), type: AlertType.warning);
|
||||
break;
|
||||
case 3:
|
||||
alert(AlertType.danger.toString(), type: AlertType.danger);
|
||||
break;
|
||||
case 4:
|
||||
alert(AlertType.info.toString(), type: AlertType.info);
|
||||
break;
|
||||
default:
|
||||
alert(AlertType.primary.toString(), type: AlertType.primary);
|
||||
}
|
||||
// Get.defaultDialog(content: SelectableText(type));
|
||||
}
|
||||
|
||||
Future<void> initializeProviders() async {
|
||||
Get.put(AuthProvider(), permanent: true);
|
||||
Get.put(AlertProvider(), permanent: true);
|
||||
Get.put(RoutesProvider());
|
||||
Get.put(SearchProvider());
|
||||
Get.put(ToastProvider());
|
||||
}
|
||||
|
||||
Future<void> removeProviders() async {
|
||||
await Get.delete<RoutesProvider>(force: true);
|
||||
await Get.delete<SearchProvider>(force: true);
|
||||
await Get.delete<ToastProvider>(force: true);
|
||||
}
|
||||
|
||||
closeDrawerMenu(String item) {
|
||||
for (var child in drawerMenu) {
|
||||
if (child.type == DrawerItemType.menu) {
|
||||
child.isOpen = false;
|
||||
for (var subChild in child.children) {
|
||||
if (subChild.route == item) {
|
||||
child.isOpen = true;
|
||||
} else if (subChild.type == DrawerItemType.menu) {
|
||||
subChild.isOpen = false;
|
||||
for (var subsubChild in subChild.children) {
|
||||
if (subsubChild.route == item) {
|
||||
child.isOpen = true;
|
||||
subChild.isOpen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int menuIndex(String item) {
|
||||
for (var child in drawerMenu) {
|
||||
if (child.type == DrawerItemType.menu) {
|
||||
for (var subChild in child.children) {
|
||||
if (subChild.type == DrawerItemType.menu) {
|
||||
for (var subsubChild in subChild.children) {
|
||||
if (subsubChild.route == item) {
|
||||
return drawerMenu.indexOf(child);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (subChild.route == item) {
|
||||
return drawerMenu.indexOf(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (child.route == item) {
|
||||
return drawerMenu.indexOf(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void focusSearch() {
|
||||
Get.find<SearchProvider>().focus();
|
||||
}
|
||||
|
||||
String formatDateTime(DateTime value) {
|
||||
int hours = value.hour;
|
||||
String letters = 'AM';
|
||||
if (hours > 12) {
|
||||
hours -= 12;
|
||||
letters = "PM";
|
||||
}
|
||||
String convertedDateTime = value.year.toString();
|
||||
convertedDateTime += "-${value.month.toString().padLeft(2, '0')}";
|
||||
convertedDateTime += "-${value.day.toString().padLeft(2, '0')}";
|
||||
convertedDateTime += " ${hours.toString().padLeft(2, '0')}";
|
||||
convertedDateTime += "-${value.minute.toString().padLeft(2, '0')} $letters";
|
||||
return convertedDateTime;
|
||||
}
|
||||
|
||||
String formatDate(DateTime value) {
|
||||
String convertedDateTime = value.year.toString();
|
||||
convertedDateTime += "-${value.month.toString().padLeft(2, '0')}";
|
||||
convertedDateTime += "-${value.day.toString().padLeft(2, '0')}";
|
||||
return convertedDateTime;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import 'package:u_vineyard/pages/login.page.dart';
|
||||
import 'package:u_vineyard/pages/new.vineyard.page.dart';
|
||||
|
||||
import '../models/app_models/drawer_item.model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../middlewares/auth.middleware.dart';
|
||||
import '../screens/dashboard/dashboard.screen.dart';
|
||||
import '../screens/main/main.screen.dart';
|
||||
|
||||
List<GetPage<dynamic>> appRoutes = [
|
||||
GetPage(
|
||||
title: 'Dashboard',
|
||||
name: MainScreen.route,
|
||||
page: () => const MainScreen(),
|
||||
middlewares: [AuthMiddleware()],
|
||||
),
|
||||
GetPage(
|
||||
title: 'Login',
|
||||
name: LoginPage.route,
|
||||
page: () => const LoginPage(),
|
||||
middlewares: [GuestMiddleware()],
|
||||
)
|
||||
];
|
||||
|
||||
Map<String, Widget Function()> internalRoutes = {
|
||||
'/dashboard': () => const DashboadScreen(),
|
||||
'/vineyards/new-vineyard': () => const NewVineyardScreen(), //const SizedBox(child: Text('1')),
|
||||
'/vineyards/list': () => const SizedBox(child: Text('2')),
|
||||
'/vineyards/edit': () => const SizedBox(child: Text('3')),
|
||||
'/settings/account': () => const SizedBox(child: Text('5')),
|
||||
'/menu2/1': () => const SizedBox(child: Text('menu 2-1')),
|
||||
'/menu2/2': () => const SizedBox(child: Text('menu 2-2')),
|
||||
'/menu2/3': () => const SizedBox(child: Text('menu 2-3')),
|
||||
'/menu2/4': () => const SizedBox(child: Text('menu 2-4')),
|
||||
'/menu2/menu3/1': () => const SizedBox(child: Text('menu 2-3-1')),
|
||||
'/menu2/menu3/2': () => const SizedBox(child: Text('menu 2-3-2')),
|
||||
'/menu2/menu3/3': () => const SizedBox(child: Text('menu 2-3-3')),
|
||||
'/menu2/menu3/4': () => const SizedBox(child: Text('menu 2-3-4')),
|
||||
};
|
||||
|
||||
final List<Map<String, dynamic>> drawerMenuMap = [
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Dashboard',
|
||||
'icon': 'dashboard',
|
||||
'route': '/dashboard',
|
||||
},
|
||||
{
|
||||
'type': 'menu',
|
||||
'name': 'Vigne',
|
||||
'icon': 'menu',
|
||||
'route': '/vineyards',
|
||||
'children': [
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Aggiungi Vigna',
|
||||
'icon': 'dot',
|
||||
'route': '/new-vineyard',
|
||||
},
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Lista Vigne',
|
||||
'icon': 'dot',
|
||||
'route': '/list',
|
||||
},
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Modifica Vigna',
|
||||
'icon': 'dot',
|
||||
'route': '/edit',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'type': 'menu',
|
||||
'name': 'Menu2',
|
||||
'icon': 'menu',
|
||||
'route': '/menu2',
|
||||
'children': [
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Menu2 Item 1',
|
||||
'icon': 'dot',
|
||||
'route': '/1',
|
||||
},
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Menu2 Item 2',
|
||||
'icon': 'dot',
|
||||
'route': '/2',
|
||||
},
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Menu2 Item 3',
|
||||
'icon': 'dot',
|
||||
'route': '/3',
|
||||
},
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Menu2 Item 4',
|
||||
'icon': 'dot',
|
||||
'route': '/4',
|
||||
},
|
||||
{
|
||||
'type': 'menu',
|
||||
'name': 'Menu2 menu',
|
||||
'icon': 'menu',
|
||||
'route': '/menu3',
|
||||
'children': [
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Menu3 Item 1',
|
||||
'icon': 'dot',
|
||||
'route': '/1',
|
||||
},
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Menu3 Item 2',
|
||||
'icon': 'dot',
|
||||
'route': '/2',
|
||||
},
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Menu3 Item 3',
|
||||
'icon': 'dot',
|
||||
'route': '/3',
|
||||
},
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Menu3 Item 4',
|
||||
'icon': 'dot',
|
||||
'route': '/4',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'type': 'menu',
|
||||
'name': 'Settings',
|
||||
'icon': 'settings',
|
||||
'route': '/settings',
|
||||
'children': [
|
||||
{
|
||||
'type': 'link',
|
||||
'name': 'Account',
|
||||
'icon': 'account',
|
||||
'route': '/account',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
final List<DrawerItem> drawerMenu =
|
||||
drawerMenuMap.map(DrawerItem.fromJson).toList();
|
||||
@@ -0,0 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
const escape = SingleActivator(LogicalKeyboardKey.escape);
|
||||
const enter = SingleActivator(LogicalKeyboardKey.enter);
|
||||
const controlF = SingleActivator(LogicalKeyboardKey.keyF, control: true);
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../providers/auth.provider.dart';
|
||||
|
||||
class AuthMiddleware extends GetMiddleware {
|
||||
final authProvider = Get.find<AuthProvider>();
|
||||
|
||||
@override
|
||||
RouteSettings? redirect(String? route) {
|
||||
return authProvider.isAuth || route == '/login'
|
||||
? null
|
||||
: const RouteSettings(name: '/login');
|
||||
}
|
||||
}
|
||||
|
||||
class GuestMiddleware extends GetMiddleware {
|
||||
final authProvider = Get.find<AuthProvider>();
|
||||
|
||||
@override
|
||||
RouteSettings? redirect(String? route) {
|
||||
return authProvider.isAuth && route != '/'
|
||||
? const RouteSettings(name: '/')
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// ignore_for_file: hash_and_equals
|
||||
|
||||
import '../../classes/enums.class.dart';
|
||||
|
||||
class DrawerItem {
|
||||
bool isOpen;
|
||||
final DrawerItemType type;
|
||||
final String name;
|
||||
final String icon;
|
||||
final String route;
|
||||
final List<DrawerItem> children;
|
||||
|
||||
DrawerItem({
|
||||
this.isOpen = false,
|
||||
this.type = DrawerItemType.link,
|
||||
required this.name,
|
||||
required this.icon,
|
||||
required this.route,
|
||||
this.children = const [],
|
||||
});
|
||||
|
||||
factory DrawerItem.fromJson(Map<String, dynamic> json, {String? parent}) {
|
||||
if (parent is String) json['route'] = parent + json['route'];
|
||||
final List<Map<String, dynamic>> childrenMap = json['children'] ?? [];
|
||||
final List<DrawerItem> children = [];
|
||||
for (var child in childrenMap) {
|
||||
children.add(DrawerItem.fromJson(child, parent: json['route']));
|
||||
}
|
||||
return DrawerItem(
|
||||
type: children.isEmpty ? DrawerItemType.link : DrawerItemType.menu,
|
||||
name: json['name'],
|
||||
icon: json['icon'] ?? '',
|
||||
route: json['route'],
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is DrawerItem && other.name == name && other.route == route;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class UserModel {
|
||||
final String name;
|
||||
final String email;
|
||||
final String image;
|
||||
|
||||
UserModel(this.name, this.email, this.image);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DashboardCardModel {
|
||||
final String name;
|
||||
final IconData icon;
|
||||
final int value;
|
||||
double? persentage;
|
||||
String? persentageValue;
|
||||
String? notes;
|
||||
|
||||
DashboardCardModel({
|
||||
required this.name,
|
||||
required this.icon,
|
||||
required this.value,
|
||||
this.persentage,
|
||||
this.persentageValue,
|
||||
this.notes,
|
||||
});
|
||||
|
||||
factory DashboardCardModel.fromJson(json) {
|
||||
return DashboardCardModel(
|
||||
name: json['name'],
|
||||
icon: json['icon'],
|
||||
value: json['value'],
|
||||
persentage: json['persentage'],
|
||||
persentageValue: json['persentageValue'],
|
||||
notes: json['notes'],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
|
||||
|
||||
class GridColumnModel {
|
||||
final String name;
|
||||
final double width;
|
||||
final bool allowFiltering;
|
||||
final bool allowSorting;
|
||||
final ColumnWidthMode columnWidthMode;
|
||||
|
||||
GridColumnModel({
|
||||
required this.name,
|
||||
this.width = double.nan,
|
||||
this.allowFiltering = true,
|
||||
this.allowSorting = true,
|
||||
this.columnWidthMode = ColumnWidthMode.none,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class GeneralSearchModel {
|
||||
GeneralSearchModel();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../classes/functions.class.dart';
|
||||
|
||||
class PlatformFunctions {
|
||||
static void init() {
|
||||
try {
|
||||
keyListener();
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
static keyListener() {
|
||||
RawKeyboard.instance.addListener((event) {
|
||||
final isKeyDown = event is RawKeyDownEvent;
|
||||
if (isKeyDown &&
|
||||
event.logicalKey.keyId == 102 &&
|
||||
event.isControlPressed) {
|
||||
focusSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../classes/enums.class.dart';
|
||||
import '../widgets/notifications_widgets/alert.widget.dart';
|
||||
|
||||
class AlertProvider extends GetxController {
|
||||
final Rx<Alert?> _alert = Rx<Alert?>(null);
|
||||
Widget? get alert => _alert.value;
|
||||
|
||||
setAlert(
|
||||
String message, {
|
||||
AlertType type = AlertType.info,
|
||||
Duration autoDismissAfter = Duration.zero,
|
||||
bool dismissable = true,
|
||||
}) {
|
||||
if (message.isEmpty) {
|
||||
_alert.value = null;
|
||||
} else {
|
||||
_alert.value = Alert(
|
||||
message,
|
||||
type: type,
|
||||
dismissible: dismissable,
|
||||
autoDismissAfter: autoDismissAfter,
|
||||
onDismiss: () {
|
||||
_alert.value = null;
|
||||
update();
|
||||
},
|
||||
);
|
||||
}
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:u_vineyard/entities/user.dart';
|
||||
import 'package:u_vineyard/pages/login.page.dart';
|
||||
import 'package:u_vineyard/services/api.user.service.dart';
|
||||
import 'package:u_vineyard/services/auth.service.dart';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../classes/functions.class.dart';
|
||||
|
||||
class AuthProvider extends GetxController {
|
||||
final Rx<User?> _profile = Rx<User?>(null);
|
||||
|
||||
final RxBool _isAuth = RxBool(AuthService.instance.token != null);
|
||||
|
||||
bool get isAuth => _isAuth.value;
|
||||
User? get profile => _profile.value;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// Chiama la funzione per recuperare il profilo all'inizializzazione del controller
|
||||
fetchProfileData();
|
||||
}
|
||||
Future<void> toggleAuth(bool value) async {
|
||||
_isAuth.value = value;
|
||||
if(!value) {
|
||||
await removeProviders();
|
||||
} else {
|
||||
// Se l'utente si autentica, recupera i dati del profilo
|
||||
await fetchProfileData();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> fetchProfileData() async {
|
||||
final userService = UserService();
|
||||
// Assumiamo che l'ID utente sia 1
|
||||
final fetchedUser = await userService.userProfile(1);
|
||||
|
||||
// Aggiorna la variabile reattiva
|
||||
_profile.value = fetchedUser;
|
||||
}
|
||||
void logout() async {
|
||||
_isAuth.value = false;
|
||||
AuthService.instance.clear();
|
||||
update();
|
||||
await removeProviders();
|
||||
Get.offAllNamed(LoginPage.route);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import '../providers/auth.provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../classes/functions.class.dart';
|
||||
import '../classes/routes.class.dart';
|
||||
import '../models/app_models/drawer_item.model.dart';
|
||||
import '../screens/errors/404.screen.dart';
|
||||
|
||||
class Routes {
|
||||
static void to(Widget Function() screen, {dynamic arguments}) {
|
||||
Get.find<RoutesProvider>().to(screen, arguments: arguments);
|
||||
}
|
||||
|
||||
static void toNamed(
|
||||
String newRoute, {
|
||||
duplicate = false,
|
||||
dynamic arguments,
|
||||
}) {
|
||||
Get.find<RoutesProvider>().toNamed(
|
||||
newRoute,
|
||||
arguments: arguments,
|
||||
duplicate: duplicate,
|
||||
);
|
||||
closeDrawerMenu(newRoute);
|
||||
}
|
||||
}
|
||||
|
||||
class RoutesProvider extends GetxController {
|
||||
GlobalKey<ScaffoldState>? mainKey = GlobalKey();
|
||||
final RxInt _selectedIndex = RxInt(0);
|
||||
final RxString _current = RxString('/dashboard');
|
||||
final Rx<Widget Function()?> _currentWidget = Rx<Widget Function()?>(null);
|
||||
final Rx<dynamic> _arguments = Rx<dynamic>(null);
|
||||
|
||||
int get currentIndex => _selectedIndex.value;
|
||||
String get current => _current.value;
|
||||
dynamic get arguments => _arguments.value;
|
||||
bool get isDrawerOpen => mainKey?.currentState?.isDrawerOpen ?? false;
|
||||
|
||||
Widget Function() get currentWidget => getWidget();
|
||||
DrawerItem get currentMenu => drawerMenu[currentIndex];
|
||||
|
||||
set currentWidget(Widget Function()? v) => _currentWidget.value = v;
|
||||
set currentIndex(int v) => _selectedIndex.value = v;
|
||||
|
||||
Widget Function() getWidget() {
|
||||
final auth = Get.find<AuthProvider>();
|
||||
if (!auth.isAuth) {
|
||||
auth.logout();
|
||||
return () => const Error404Screen();
|
||||
}
|
||||
return _currentWidget.value != null
|
||||
? _currentWidget.value!
|
||||
: (internalRoutes[current] ?? () => const Error404Screen());
|
||||
}
|
||||
|
||||
void toNamed(String newRoute, {duplicate = false, dynamic arguments}) {
|
||||
if (newRoute.startsWith('/')) {
|
||||
if (duplicate) {
|
||||
_arguments.value = arguments;
|
||||
_current.value = newRoute;
|
||||
_currentWidget.value = null;
|
||||
update();
|
||||
} else if (newRoute != _current.value) {
|
||||
_arguments.value = arguments;
|
||||
_current.value = newRoute;
|
||||
_currentWidget.value = null;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
currentIndex = menuIndex(newRoute);
|
||||
update();
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
to(Widget Function() screen, {dynamic arguments}) {
|
||||
_current.value = '-';
|
||||
_arguments.value = arguments;
|
||||
currentWidget = screen;
|
||||
update();
|
||||
}
|
||||
|
||||
void toggleDrawer() {
|
||||
if (mainKey?.currentState?.isDrawerOpen ?? false) {
|
||||
mainKey?.currentState?.openDrawer();
|
||||
} else {
|
||||
mainKey?.currentState?.closeDrawer();
|
||||
}
|
||||
}
|
||||
|
||||
void closeDrawer() {
|
||||
try {
|
||||
mainKey?.currentState?.closeDrawer();
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import '../classes/enums.class.dart';
|
||||
import '../models/general_search.model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../classes/functions.class.dart';
|
||||
import '../screens/search/general_search.screen.dart';
|
||||
import 'routes.provider.dart';
|
||||
|
||||
class SearchProvider extends GetxController {
|
||||
final searchController = TextEditingController();
|
||||
final node = FocusNode();
|
||||
final RxBool _isLoading = RxBool(false);
|
||||
|
||||
bool get isLoading => _isLoading.value;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
searchController.dispose();
|
||||
node.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void loading(bool value) {
|
||||
_isLoading.value = value;
|
||||
update();
|
||||
}
|
||||
|
||||
search(String value) async {
|
||||
if (searchController.text.isEmpty) return;
|
||||
loading(true);
|
||||
await Future.delayed(const Duration(seconds: 1), () {
|
||||
if (value.toLowerCase() == 'test') {
|
||||
Routes.to(() => const GeneralSerch(<GeneralSearchModel>[]));
|
||||
searchController.clear();
|
||||
} else {
|
||||
toast(
|
||||
'$value Not found',
|
||||
type: ToastType.warning,
|
||||
callback: () => print('clicked'),
|
||||
);
|
||||
focus();
|
||||
}
|
||||
});
|
||||
|
||||
loading(false);
|
||||
}
|
||||
|
||||
void unfocus() {
|
||||
node.unfocus();
|
||||
update();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
node.unfocus();
|
||||
searchController.clear();
|
||||
update();
|
||||
}
|
||||
|
||||
void focus() {
|
||||
try {
|
||||
node.requestFocus();
|
||||
searchController.selection = TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: searchController.text.length,
|
||||
);
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../classes/constents.class.dart';
|
||||
import '../classes/enums.class.dart';
|
||||
import '../widgets/notifications_widgets/toast.widget.dart';
|
||||
|
||||
class ToastProvider extends GetxController {
|
||||
late GlobalKey<AnimatedListState> listKey;
|
||||
|
||||
@override
|
||||
onInit() {
|
||||
super.onInit();
|
||||
setKey(GlobalKey<AnimatedListState>());
|
||||
}
|
||||
|
||||
setKey(GlobalKey<AnimatedListState> key) {
|
||||
listKey = key;
|
||||
}
|
||||
|
||||
final Rx<List<ToastModel>> _toasts = Rx<List<ToastModel>>([]);
|
||||
|
||||
List<ToastModel> get toasts => _toasts.value;
|
||||
|
||||
addToast(String? message,
|
||||
{ToastType type = ToastType.primary, VoidCallback? callback}) {
|
||||
final item = ToastModel(type: type, message: message, callback: callback);
|
||||
_toasts.value.insert(0, item);
|
||||
listKey.currentState?.insertItem(0, duration: toastDuration);
|
||||
update();
|
||||
}
|
||||
|
||||
removeToast(ToastModel item, DissmissType type) {
|
||||
try {
|
||||
final index = _toasts.value.indexOf(item);
|
||||
_toasts.value.removeAt(index);
|
||||
listKey.currentState?.removeItem(
|
||||
index,
|
||||
(__, animation) =>
|
||||
type == DissmissType.swipe ? const SizedBox() : buildItem(item, animation),
|
||||
duration: toastDuration,
|
||||
);
|
||||
update();
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildItem(ToastModel item, Animation<double> animation) {
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: Toast(item, removeToast),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
import '../../../providers/auth.provider.dart';
|
||||
import '../../../widgets/responsive_widgets/responsive.widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../classes/enums.class.dart';
|
||||
import '../../../classes/functions.class.dart';
|
||||
import '../../../providers/alert.provider.dart';
|
||||
import '../../../widgets/simple_loader.widget.dart';
|
||||
|
||||
class LoginScreenOld extends StatefulWidget {
|
||||
static const route = '/login';
|
||||
const LoginScreenOld({super.key});
|
||||
|
||||
@override
|
||||
State<LoginScreenOld> createState() => _LoginScreenOldState();
|
||||
}
|
||||
|
||||
class _LoginScreenOldState extends State<LoginScreenOld> {
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
|
||||
bool isVisible = false;
|
||||
bool isLoading = false;
|
||||
String? error;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
emailController.dispose();
|
||||
passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
update(VoidCallback func) {
|
||||
if (mounted) setState(func);
|
||||
}
|
||||
|
||||
bool validate() {
|
||||
final email = emailController.text;
|
||||
final pass = passwordController.text;
|
||||
if (email.isEmpty) {
|
||||
return !setError('The email address is required');
|
||||
}
|
||||
if (!email.isEmail) {
|
||||
return !setError('The email address is inviled');
|
||||
}
|
||||
if (pass.isEmpty) {
|
||||
return !setError('The password is required');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
login() async {
|
||||
setError('');
|
||||
update(() => isLoading = true);
|
||||
final state = validate();
|
||||
|
||||
if(Get.find<AuthProvider>().isAuth) {
|
||||
Get.offAllNamed('/');
|
||||
return;
|
||||
}
|
||||
|
||||
if (state) {
|
||||
TextInput.finishAutofillContext();
|
||||
await Future.delayed(const Duration(seconds: 1), () async {
|
||||
await Get.find<AuthProvider>().toggleAuth(true);
|
||||
Get.offAllNamed('/');
|
||||
});
|
||||
initializeProviders();
|
||||
}
|
||||
update(() => isLoading = false);
|
||||
}
|
||||
|
||||
togglePass() {
|
||||
if (mounted) setState(() => isVisible = !isVisible);
|
||||
}
|
||||
|
||||
bool setError(String error) {
|
||||
final alerter = Get.find<AlertProvider>();
|
||||
alerter.setAlert(
|
||||
error,
|
||||
type: AlertType.danger,
|
||||
dismissable: false,
|
||||
);
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
if (error.isEmpty) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ResponsiveWidget(
|
||||
builder: (cnx, screen) => Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: !screen.isMobile
|
||||
? BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(imgUrl('login.jpg')),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
physics:
|
||||
screen.isBig ? const NeverScrollableScrollPhysics() : null,
|
||||
child: Container(
|
||||
width: !screen.isMobile ? 550 : null,
|
||||
decoration: BoxDecoration(
|
||||
color: !screen.isMobile ? Colors.white : null,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 50),
|
||||
Text(
|
||||
'Welcome Back',
|
||||
style:
|
||||
Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xff0f0f0f),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
GetBuilder<AlertProvider>(
|
||||
init: AlertProvider(),
|
||||
builder: (alerter) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 20,
|
||||
left: 20,
|
||||
right: 20,
|
||||
),
|
||||
child: alerter.alert ?? const SizedBox(),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||
child: AutofillGroup(
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: emailController,
|
||||
autofocus: true,
|
||||
autofillHints: const [AutofillHints.email],
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Email',
|
||||
icon: SizedBox(
|
||||
width: 20,
|
||||
child: svg(
|
||||
'profile',
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: const Color(0xFFf0f1f5),
|
||||
border: const OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFf0f1f5),
|
||||
),
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFf0f1f5),
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (value) => setError(''),
|
||||
onEditingComplete:
|
||||
TextInput.finishAutofillContext,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
onSubmitted: (_) => login(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
TextField(
|
||||
autofillHints: const [AutofillHints.password],
|
||||
controller: passwordController,
|
||||
autofocus: false,
|
||||
obscureText: !isVisible,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
icon: SizedBox(
|
||||
width: 20,
|
||||
child: svg(
|
||||
'password',
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10),
|
||||
child: IconButton(
|
||||
onPressed: togglePass,
|
||||
icon: isVisible
|
||||
? svg('eye.slash.fill',
|
||||
size: 16,
|
||||
color: Colors.grey.shade800)
|
||||
: svg('eye.fill',
|
||||
size: 15,
|
||||
color: Colors.grey.shade800),
|
||||
),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: const Color(0xFFf0f1f5),
|
||||
border: const OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFf0f1f5),
|
||||
),
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFf0f1f5),
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (value) => setError(''),
|
||||
onEditingComplete:
|
||||
TextInput.finishAutofillContext,
|
||||
textInputAction: TextInputAction.go,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
onSubmitted: (_) => login(),
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
SizedBox(
|
||||
width: 300,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(5),
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: isLoading ? null : login,
|
||||
child: AnimatedCrossFade(
|
||||
crossFadeState: !isLoading
|
||||
? CrossFadeState.showFirst
|
||||
: CrossFadeState.showSecond,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
alignment: Alignment.center,
|
||||
firstChild: const Text('Login'),
|
||||
secondChild: const Center(
|
||||
child: SimpleLoader(
|
||||
strokeWidth: 3,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 50, width: 200),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_core/src/get_main.dart';
|
||||
import 'package:u_vineyard/template/src/models/dashboard_models/dashboard_card.model.dart';
|
||||
import 'package:u_vineyard/template/src/providers/auth.provider.dart';
|
||||
import 'package:u_vineyard/widgets/map.widget.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../widgets/data_tables_widgets/data_table_card.widget.dart';
|
||||
import '../../widgets/menu_list.widget.dart';
|
||||
import '../../widgets/responsive_widgets/responsive_grid.widget.dart';
|
||||
import '../../widgets/responsive_widgets/repesponsive_grid_item.widget.dart';
|
||||
import 'dashboard_widgets/dashboard_card.widget.dart';
|
||||
|
||||
class DashboadScreen extends StatefulWidget {
|
||||
const DashboadScreen({super.key});
|
||||
|
||||
@override
|
||||
State<DashboadScreen> createState() => _DashboadScreenState();
|
||||
}
|
||||
|
||||
class _VineyardDetails {
|
||||
var vineyards = Get.find<AuthProvider>().profile?.owners?.expand((owner) => owner.vineyards ?? []).toList() ?? [];
|
||||
int get numberOfVineyards => vineyards.length;
|
||||
double get totalArea => vineyards.fold(0.0, (sum, vineyard) => sum! + (vineyard.hectares ?? 0.0)) ?? 0.0;
|
||||
}
|
||||
|
||||
class _DashboadScreenState extends State<DashboadScreen> {
|
||||
final dashboardCardModelValues = [
|
||||
DashboardCardModel.fromJson({
|
||||
'name': 'Vigneti',
|
||||
'icon': Icons.eco_rounded,
|
||||
'value': _VineyardDetails().numberOfVineyards,
|
||||
}),
|
||||
DashboardCardModel.fromJson({
|
||||
'name': 'Ettari',
|
||||
'icon': Icons.grass_rounded,
|
||||
'value': _VineyardDetails().totalArea.toInt(),
|
||||
'persentage': 64.1,
|
||||
'persentageValue': '3.1',
|
||||
'notes': 'Total 424,567 deliveries',
|
||||
}),
|
||||
];
|
||||
//double _calculatedArea = 0.0;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveGrid(
|
||||
childrenBuilder: (cnx, screen) {
|
||||
return [
|
||||
...dashboardCardModelValues
|
||||
.map(
|
||||
(item) => ResponsiveGridItem(
|
||||
animationIndex: dashboardCardModelValues.indexOf(item),
|
||||
crossAxisCellCount: screen.setAxis(6, 9, 6, 6),
|
||||
child: DashboardStatisticCard(
|
||||
item,
|
||||
callback: () {},
|
||||
isSmall: screen.isSmall,
|
||||
actions: [
|
||||
if (dashboardCardModelValues.indexOf(item) == 0)
|
||||
MenuListItem(
|
||||
callback: () {},
|
||||
svgName: 'menu',
|
||||
title: 'Lista',
|
||||
),
|
||||
MenuListItem(
|
||||
callback: () {},
|
||||
svgName: 'menu',
|
||||
title: 'Aggiungi',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
// ResponsiveGridItem(
|
||||
// animationIndex: 4,
|
||||
// crossAxisCellCount: screen.setAxis(6, 9, 6, 6),
|
||||
// child: CardWidget(
|
||||
// height: 420,
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Row(
|
||||
// children: [
|
||||
// FittedBox(
|
||||
// fit: BoxFit.scaleDown,
|
||||
// child: SelectableText(
|
||||
// 'Shot per distance',
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .headlineSmall
|
||||
// ?.copyWith(fontWeight: FontWeight.w700),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// const Spacer(),
|
||||
// const AnimationRadialChart(),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
ResponsiveGridItem(
|
||||
animationIndex: 7,
|
||||
crossAxisCellCount: 24,
|
||||
child: MainMapWidget(),
|
||||
),
|
||||
// ResponsiveGridItem(
|
||||
// animationIndex: 5,
|
||||
// crossAxisCellCount: screen.setAxis(6, 9, 6, 6),
|
||||
// child: CardWidget(
|
||||
// height: 420,
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Row(
|
||||
// children: [
|
||||
// FittedBox(
|
||||
// fit: BoxFit.scaleDown,
|
||||
// child: SelectableText(
|
||||
// 'Revenue',
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .headlineSmall
|
||||
// ?.copyWith(fontWeight: FontWeight.w700),
|
||||
// ),
|
||||
// ),
|
||||
// const Spacer(),
|
||||
// const SelectableText('Last 7 days')
|
||||
// ],
|
||||
// ),
|
||||
// const Spacer(),
|
||||
// const AnimationSplineChart(),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ResponsiveGridItem(
|
||||
// animationIndex: 12,
|
||||
// crossAxisCellCount: screen.setAxis(12, 18, 24, 24),
|
||||
// child: CardWidget(
|
||||
// height: 420,
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Row(
|
||||
// children: [
|
||||
// FittedBox(
|
||||
// fit: BoxFit.scaleDown,
|
||||
// child: SelectableText(
|
||||
// 'Revenue',
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .headlineSmall
|
||||
// ?.copyWith(fontWeight: FontWeight.w700),
|
||||
// ),
|
||||
// ),
|
||||
// const Spacer(),
|
||||
// const SelectableText('Last 7 days VS period week')
|
||||
// ],
|
||||
// ),
|
||||
// const Spacer(),
|
||||
// const AnimationSplineChart(),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
ResponsiveGridItem(
|
||||
animationIndex: 7,
|
||||
crossAxisCellCount: 24,
|
||||
child: DataTableCard(screen.isBig, screen.is4K, screen.isMobile),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
];
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../models/dashboard_models/dashboard_card.model.dart';
|
||||
import '../../../widgets/animated_text.widget.dart';
|
||||
import '../../../widgets/card.widget.dart';
|
||||
import '../../../widgets/menu_list.widget.dart';
|
||||
import '../../../widgets/rounded_progress_indicator.widget.dart';
|
||||
|
||||
class DashboardStatisticCard extends StatelessWidget {
|
||||
const DashboardStatisticCard(
|
||||
this.item, {
|
||||
super.key,
|
||||
this.isSmall = false,
|
||||
this.actions = const [],
|
||||
this.callback,
|
||||
});
|
||||
|
||||
final DashboardCardModel item;
|
||||
final bool isSmall;
|
||||
final List<Widget> actions;
|
||||
final VoidCallback? callback;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Hero(
|
||||
tag: item,
|
||||
child: CardWidget(
|
||||
callback: callback,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
item.icon,
|
||||
size: 20,
|
||||
color: Colors.green,
|
||||
),
|
||||
const Spacer(),
|
||||
// Uncomment and fix the widget below if needed
|
||||
// RoundedProgressIndicator(
|
||||
// textValue: Text(
|
||||
// '+${item.persentageValue}%',
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .labelSmall
|
||||
// ?.copyWith(fontWeight: FontWeight.w900),
|
||||
// ),
|
||||
// value: (item.persentage) / 100,
|
||||
// backgroundColor: Colors.grey.shade200,
|
||||
// size: 50,
|
||||
// stroke: 6,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SelectableText(
|
||||
item.name,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.labelLarge?.copyWith(
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AnimatedNumber(
|
||||
number: item.value,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.displayMedium?.copyWith(
|
||||
color: Colors.grey.shade900,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
// SelectableText(
|
||||
// item.notes ?? '',
|
||||
// maxLines: 1,
|
||||
// style: Theme.of(
|
||||
// context,
|
||||
// ).textTheme.labelLarge?.copyWith(
|
||||
// color: Colors.grey.shade600,
|
||||
// fontWeight: FontWeight.w500,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions.isNotEmpty
|
||||
? MenuList(
|
||||
actions: actions,
|
||||
offset: const Offset(0, 5),
|
||||
builder: (controller) => IconButton.filledTonal(
|
||||
onPressed: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.more_vert_rounded),
|
||||
),
|
||||
)
|
||||
: IconButton.filledTonal(
|
||||
onPressed: callback,
|
||||
icon: const Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import 'dart:math';
|
||||
|
||||
import '../../../classes/constents.class.dart';
|
||||
|
||||
/// Package import
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Chart import
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
|
||||
/// Renders the spline chart sample with dynamically updated data points.
|
||||
class AnimationSplineChart extends StatefulWidget {
|
||||
/// Renders the spline chart sample with dynamically updated data points.
|
||||
const AnimationSplineChart({super.key});
|
||||
|
||||
@override
|
||||
State<AnimationSplineChart> createState() => _AnimationSplineChartState();
|
||||
}
|
||||
|
||||
class _AnimationSplineChartState extends State<AnimationSplineChart> {
|
||||
List<_ChartData>? _chartData;
|
||||
List<_ChartData>? _chartData2;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_getChartData();
|
||||
|
||||
return _buildAnimationSplineChart();
|
||||
}
|
||||
|
||||
/// get the spline chart sample with dynamically updated data points.
|
||||
SfCartesianChart _buildAnimationSplineChart() {
|
||||
return SfCartesianChart(
|
||||
plotAreaBorderWidth: 0,
|
||||
primaryXAxis:
|
||||
NumericAxis(majorGridLines: const MajorGridLines(width: 0)),
|
||||
primaryYAxis: NumericAxis(
|
||||
majorTickLines: const MajorTickLines(color: Colors.transparent),
|
||||
axisLine: const AxisLine(width: 0),
|
||||
minimum: 0,
|
||||
maximum: 100),
|
||||
series: _getDefaultSplineSeries());
|
||||
}
|
||||
|
||||
/// get the spline series sample with dynamically updated data points.
|
||||
List<SplineSeries<_ChartData, num>> _getDefaultSplineSeries() {
|
||||
return <SplineSeries<_ChartData, num>>[
|
||||
SplineSeries<_ChartData, num>(
|
||||
dataSource: _chartData!,
|
||||
xValueMapper: (_ChartData sales, _) => sales.x,
|
||||
yValueMapper: (_ChartData sales, _) => sales.y,
|
||||
color: primaryColor,
|
||||
width: 4,
|
||||
markerSettings: const MarkerSettings(
|
||||
isVisible: true,
|
||||
borderWidth: 5,
|
||||
),
|
||||
),
|
||||
SplineSeries<_ChartData, num>(
|
||||
dataSource: _chartData2!,
|
||||
xValueMapper: (_ChartData sales, _) => sales.x,
|
||||
yValueMapper: (_ChartData sales, _) => sales.y,
|
||||
color: Colors.purple,
|
||||
width: 4,
|
||||
markerSettings: const MarkerSettings(
|
||||
isVisible: true,
|
||||
borderWidth: 5,
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_chartData!.clear();
|
||||
_chartData2!.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// get the random value
|
||||
int _getRandomInt(int min, int max) {
|
||||
final Random random = Random();
|
||||
return min + random.nextInt(max - min);
|
||||
}
|
||||
|
||||
//Get the random data points
|
||||
void _getChartData() {
|
||||
_chartData = <_ChartData>[];
|
||||
_chartData2 = <_ChartData>[];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
_chartData!.add(_ChartData(i, _getRandomInt(15, 85)));
|
||||
_chartData2!.add(_ChartData(i, _getRandomInt(15, 85)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ChartData {
|
||||
_ChartData(this.x, this.y);
|
||||
final int x;
|
||||
final int y;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/// Package imports
|
||||
import '../../../classes/constents.class.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Chart import
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
|
||||
/// Render the radial bar customization.
|
||||
class AnimationRadialChart extends StatefulWidget {
|
||||
/// Cretaes customised radial bar series.
|
||||
const AnimationRadialChart({super.key});
|
||||
|
||||
@override
|
||||
State<AnimationRadialChart> createState() => _AnimationRadialChartState();
|
||||
}
|
||||
|
||||
class _AnimationRadialChartState extends State<AnimationRadialChart> {
|
||||
TooltipBehavior? _tooltipBehavior;
|
||||
List<_ChartData>? dataSources;
|
||||
List<Color>? colors;
|
||||
@override
|
||||
void initState() {
|
||||
_tooltipBehavior =
|
||||
TooltipBehavior(enable: true, format: 'point.x : point.y%');
|
||||
dataSources = <_ChartData>[
|
||||
_ChartData(
|
||||
x: 'Vehicle',
|
||||
y: 62.70,
|
||||
text: '10%',
|
||||
pointColor: const Color.fromRGBO(69, 186, 161, 1.0)),
|
||||
_ChartData(
|
||||
x: 'Education',
|
||||
y: 29.20,
|
||||
text: '10%',
|
||||
pointColor: const Color.fromRGBO(230, 135, 111, 1.0)),
|
||||
_ChartData(
|
||||
x: 'Home',
|
||||
y: 85.20,
|
||||
text: '100%',
|
||||
pointColor: const Color.fromRGBO(145, 132, 202, 1.0)),
|
||||
_ChartData(
|
||||
x: 'Personal',
|
||||
y: 45.70,
|
||||
text: '100%',
|
||||
pointColor: const Color.fromRGBO(235, 96, 143, 1.0))
|
||||
];
|
||||
|
||||
colors = const <Color>[
|
||||
Color.fromRGBO(69, 186, 161, 1.0),
|
||||
Color.fromRGBO(230, 135, 111, 1.0),
|
||||
Color.fromRGBO(145, 132, 202, 1.0),
|
||||
Color.fromRGBO(235, 96, 143, 1.0)
|
||||
];
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _buildCustomizedRadialBarChart();
|
||||
}
|
||||
|
||||
/// Return the circular chart with radial customization.
|
||||
SfCircularChart _buildCustomizedRadialBarChart() {
|
||||
return SfCircularChart(
|
||||
title: ChartTitle(text: ''),
|
||||
series: _getRadialBarCustomizedSeries(),
|
||||
tooltipBehavior: _tooltipBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns radial bar which need to be customized.
|
||||
List<RadialBarSeries<_ChartData, String>> _getRadialBarCustomizedSeries() {
|
||||
return <RadialBarSeries<_ChartData, String>>[
|
||||
RadialBarSeries<_ChartData, String>(
|
||||
animationDuration: 0,
|
||||
maximumValue: 100,
|
||||
gap: '10%',
|
||||
radius: '100%',
|
||||
dataSource: <_ChartData>[
|
||||
_ChartData(
|
||||
x: 'Vehicle',
|
||||
y: 62.70,
|
||||
text: '100%',
|
||||
pointColor: Colors.redAccent,
|
||||
),
|
||||
_ChartData(
|
||||
x: 'Education',
|
||||
y: 29.20,
|
||||
text: '100%',
|
||||
pointColor: Colors.orange,
|
||||
),
|
||||
_ChartData(
|
||||
x: 'Home',
|
||||
y: 85.20,
|
||||
text: '100%',
|
||||
pointColor: Colors.indigoAccent,
|
||||
),
|
||||
_ChartData(
|
||||
x: 'Personal',
|
||||
y: 45.70,
|
||||
text: '100%',
|
||||
pointColor: primaryColor,
|
||||
)
|
||||
],
|
||||
cornerStyle: CornerStyle.bothCurve,
|
||||
innerRadius: '50%',
|
||||
xValueMapper: (_ChartData data, _) => data.x,
|
||||
yValueMapper: (_ChartData data, _) => data.y,
|
||||
pointRadiusMapper: (_ChartData data, _) => data.text,
|
||||
|
||||
/// Color mapper for each bar in radial bar series,
|
||||
/// which is get from datasource.
|
||||
pointColorMapper: (_ChartData data, _) => data.pointColor,
|
||||
legendIconType: LegendIconType.circle,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
dataSources!.clear();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _ChartData {
|
||||
_ChartData({
|
||||
required this.x,
|
||||
required this.y,
|
||||
required this.text,
|
||||
required this.pointColor,
|
||||
});
|
||||
|
||||
final String x;
|
||||
final double y;
|
||||
final String text;
|
||||
final Color pointColor;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../providers/auth.provider.dart';
|
||||
|
||||
class Error404Screen extends StatelessWidget {
|
||||
const Error404Screen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: Get.find<AuthProvider>().logout,
|
||||
child: const Text('logout'),
|
||||
),
|
||||
),
|
||||
const Text('error NOT found'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
import '../../providers/alert.provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../providers/routes.provider.dart';
|
||||
import '../../widgets/app_widgets/app_breadcamp.widget.dart';
|
||||
import '../../widgets/app_widgets/app_drawer.widget.dart';
|
||||
import '../../widgets/app_widgets/app_navbar.widget.dart';
|
||||
import '../../widgets/notifications_widgets/toast.widget.dart';
|
||||
import '../../widgets/responsive_widgets/responsive.widget.dart';
|
||||
|
||||
class MainScreen extends StatefulWidget {
|
||||
static const route = '/';
|
||||
const MainScreen({super.key});
|
||||
@override
|
||||
State<MainScreen> createState() => _MainScreenState();
|
||||
}
|
||||
|
||||
class _MainScreenState extends State<MainScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveWidget(
|
||||
builder: (context, screen) => GetBuilder<RoutesProvider>(
|
||||
init: RoutesProvider(),
|
||||
builder: (router) {
|
||||
return Scaffold(
|
||||
key: router.mainKey,
|
||||
appBar: const AppNarBar(),
|
||||
drawer: !screen.isBig ? const AppDrawer() : null,
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (screen.isBig) const AppDrawer(),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
GetBuilder<AlertProvider>(
|
||||
builder: (alerter) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal:
|
||||
(Get.width - 304) > maxContainerWidth
|
||||
? (((Get.width - 304) -
|
||||
maxContainerWidth) /
|
||||
2) +
|
||||
2
|
||||
: 20,
|
||||
),
|
||||
child: alerter.alert ?? const SizedBox(),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (showBreadCamp) const BreadCrumbWidget(),
|
||||
Expanded(
|
||||
child: router.currentWidget.call(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Positioned(
|
||||
top: 0,
|
||||
right: 10,
|
||||
width: 400,
|
||||
child: Toasts(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../models/general_search.model.dart';
|
||||
|
||||
class GeneralSerch extends StatelessWidget {
|
||||
static const route = '/search';
|
||||
const GeneralSerch(this.result, {super.key});
|
||||
|
||||
final List<GeneralSearchModel> result;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(result.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import '../providers/auth.provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../providers/routes.provider.dart';
|
||||
import 'menu_list.widget.dart';
|
||||
|
||||
class AccountWidget extends StatelessWidget {
|
||||
const AccountWidget({
|
||||
required this.desktop,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool desktop;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MenuList(
|
||||
actions: [
|
||||
MenuListItem(
|
||||
callback: () => Routes.toNamed('/settings/account'),
|
||||
svgName: 'profile',
|
||||
title: 'Profile',
|
||||
),
|
||||
MenuListItem(
|
||||
callback: Get.find<AuthProvider>().logout,
|
||||
svgName: 'logout',
|
||||
title: 'Logout',
|
||||
color: Colors.redAccent,
|
||||
),
|
||||
],
|
||||
builder: (controller) => GestureDetector(
|
||||
onTap: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: desktop ? 200 : null,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||
color: desktop ? Colors.grey.shade100 : null,
|
||||
),
|
||||
child: GetBuilder<AuthProvider>(builder: (provider) {
|
||||
return desktop
|
||||
? Row(
|
||||
children: [
|
||||
|
||||
const SizedBox(width: 7),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
provider.profile?.username ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
provider.profile!.role?.name ?? '',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(Icons.keyboard_arrow_down_rounded)
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.account_circle_rounded, size: 30),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
provider.profile?.username ?? '',
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedNumber extends StatefulWidget {
|
||||
const AnimatedNumber({
|
||||
Key? key,
|
||||
required this.number,
|
||||
this.separator = 3,
|
||||
this.style,
|
||||
}) : super(key: key);
|
||||
|
||||
final int number;
|
||||
final int separator;
|
||||
final TextStyle? style;
|
||||
|
||||
@override
|
||||
State<AnimatedNumber> createState() => _AnimatedNumberState();
|
||||
}
|
||||
|
||||
class _AnimatedNumberState extends State<AnimatedNumber>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late Animation<int> _animation;
|
||||
late AnimationController _animationController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
vsync: this,
|
||||
);
|
||||
_animation = IntTween(begin: 0, end: widget.number).animate(CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
));
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedTextValue(
|
||||
animation: _animation,
|
||||
separator: widget.separator,
|
||||
style: widget.style,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedTextValue extends AnimatedWidget {
|
||||
final Animation<int> animation;
|
||||
const AnimatedTextValue({
|
||||
Key? key,
|
||||
required this.animation,
|
||||
required this.separator,
|
||||
this.style,
|
||||
}) : super(key: key, listenable: animation);
|
||||
|
||||
final int separator;
|
||||
final TextStyle? style;
|
||||
|
||||
_separatValue(int value) {
|
||||
if (separator > 0 && value.toString().length > separator) {
|
||||
final String valueString = value.toString();
|
||||
final int valueLength = valueString.length;
|
||||
final int separatorIndex = valueLength - separator;
|
||||
final String valueBeforeSeparator =
|
||||
valueString.substring(0, separatorIndex);
|
||||
final String valueAfterSeparator = valueString.substring(separatorIndex);
|
||||
return '$valueBeforeSeparator,$valueAfterSeparator';
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SelectableText(
|
||||
_separatValue(animation.value),
|
||||
maxLines: 1,
|
||||
style: style ??
|
||||
const TextStyle(
|
||||
fontSize: 26,
|
||||
height: 1.4,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../providers/routes.provider.dart';
|
||||
|
||||
class BreadCrumbWidget extends StatelessWidget {
|
||||
const BreadCrumbWidget({this.onBreadTap, super.key});
|
||||
|
||||
final void Function(String)? onBreadTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<RoutesProvider>(
|
||||
builder: (router) {
|
||||
final items = router.current.trim().replaceFirst('/', '').split('/');
|
||||
return Visibility(
|
||||
visible: items.isNotEmpty,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 10,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(10),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BreadCrumb(
|
||||
items: items
|
||||
.map(
|
||||
(e) => BreadCrumbItem(
|
||||
padding: const EdgeInsets.all(5),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(5)),
|
||||
onTap: onBreadTap == null
|
||||
? null
|
||||
: () {
|
||||
final route = items
|
||||
.getRange(0, items.indexOf(e) + 1)
|
||||
.toList()
|
||||
.join('/');
|
||||
onBreadTap?.call('/$route');
|
||||
},
|
||||
content: Text(
|
||||
e.capitalize.toString(),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: onBreadTap == null
|
||||
? Colors.grey.shade600
|
||||
: primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
divider: Text(
|
||||
' / ',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium
|
||||
?.copyWith(color: Colors.grey),
|
||||
),
|
||||
overflow: const WrapOverflow(
|
||||
keepLastDivider: false,
|
||||
direction: Axis.horizontal,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
import '../../classes/functions.class.dart';
|
||||
import '../../providers/routes.provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../classes/enums.class.dart';
|
||||
import '../../classes/routes.class.dart';
|
||||
import '../../models/app_models/drawer_item.model.dart';
|
||||
import 'app_logo.widget.dart';
|
||||
import '../responsive_widgets/responsive.widget.dart';
|
||||
|
||||
class AppDrawer extends StatefulWidget {
|
||||
const AppDrawer({super.key});
|
||||
|
||||
@override
|
||||
State<AppDrawer> createState() => _AppDrawerState();
|
||||
}
|
||||
|
||||
class _AppDrawerState extends State<AppDrawer> with TickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final Animation<double> _animation;
|
||||
|
||||
final provider = Get.find<RoutesProvider>();
|
||||
bool isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 275),
|
||||
vsync: this,
|
||||
);
|
||||
_animation = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
provider.addListener(toggle);
|
||||
|
||||
if (provider.currentMenu.children.isNotEmpty) {
|
||||
_controller.forward();
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final scrollController = ScrollController();
|
||||
|
||||
toggle() {
|
||||
if (provider.currentMenu.children.isNotEmpty) {
|
||||
_controller.forward();
|
||||
}
|
||||
if (provider.currentMenu.children.isEmpty) {
|
||||
_controller.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
provider.removeListener(toggle);
|
||||
// _controller.dispose();
|
||||
// TODO:: find a sloution to dispose _controller
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
List<Widget> getMenu(DrawerItem item) {
|
||||
if (item.type == DrawerItemType.menu) {
|
||||
final children = item.children;
|
||||
return children.map(getItem).toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Widget getItem(DrawerItem item, {String? parent, bool sub = true}) {
|
||||
if (item.type == DrawerItemType.menu) {
|
||||
final children = item.children;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 1,
|
||||
),
|
||||
child: ExpansionTile(
|
||||
key: GlobalKey(),
|
||||
leading: svg(
|
||||
item.icon,
|
||||
size: sub ? 15 : 20,
|
||||
color: item.isOpen ? Colors.blueGrey.shade800 : Colors.blueGrey,
|
||||
),
|
||||
title: Text(
|
||||
item.name,
|
||||
selectionColor: Colors.grey.shade900,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: item.isOpen ? FontWeight.w600 : FontWeight.w500,
|
||||
fontSize: sub ? 14 : null,
|
||||
),
|
||||
),
|
||||
// childrenPadding: const EdgeInsets.only(left: 10),
|
||||
initiallyExpanded: item.isOpen,
|
||||
backgroundColor: Colors.grey.withOpacity(.05),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
collapsedShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
children: children.map((v) => getItem(v, sub: true)).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
final selected = provider.current == item.route;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
|
||||
child: ListTile(
|
||||
key: ValueKey(item),
|
||||
title: Text(item.name),
|
||||
dense: sub,
|
||||
leading: svg(
|
||||
item.icon,
|
||||
size: sub ? 15 : 20,
|
||||
color: selected ? Colors.blueGrey.shade800 : Colors.blueGrey,
|
||||
),
|
||||
selectedColor: Colors.grey.shade900,
|
||||
hoverColor: Colors.grey.withOpacity(.1),
|
||||
selected: selected,
|
||||
selectedTileColor: Colors.grey.withOpacity(.2),
|
||||
shape: ContinuousRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
style: ListTileStyle.drawer,
|
||||
onTap: () {
|
||||
Routes.toNamed(
|
||||
item.route,
|
||||
duplicate: true,
|
||||
);
|
||||
// closeDrawerMenu(item.route);
|
||||
if (provider.isDrawerOpen) {
|
||||
provider.closeDrawer();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveWidget(builder: (context, screen) {
|
||||
return GetBuilder<RoutesProvider>(builder: (provider) {
|
||||
return Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 92,
|
||||
child: NavigationRail(
|
||||
onDestinationSelected: (value) {
|
||||
final item = drawerMenu[value];
|
||||
if (item.type == DrawerItemType.link) {
|
||||
Routes.toNamed(
|
||||
item.route,
|
||||
duplicate: true,
|
||||
);
|
||||
if (provider.isDrawerOpen) {
|
||||
provider.closeDrawer();
|
||||
}
|
||||
}
|
||||
provider.currentIndex = value;
|
||||
provider.update();
|
||||
// change(value);
|
||||
},
|
||||
useIndicator: true,
|
||||
indicatorColor: Colors.white,
|
||||
backgroundColor: primaryColor,
|
||||
labelType: NavigationRailLabelType.all,
|
||||
unselectedLabelTextStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium
|
||||
?.copyWith(color: Colors.white70),
|
||||
selectedLabelTextStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium
|
||||
?.copyWith(color: Colors.white),
|
||||
indicatorShape: ContinuousRectangleBorder(
|
||||
side:
|
||||
BorderSide(color: Colors.grey.withOpacity(.2), width: 2),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
leading: !screen.isBig
|
||||
? GestureDetector(
|
||||
onTap: provider.closeDrawer,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Logo(color: Colors.white),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
destinations: [
|
||||
...drawerMenu
|
||||
.map<NavigationRailDestination>(
|
||||
(item) => NavigationRailDestination(
|
||||
padding: const EdgeInsets.all(10),
|
||||
icon: svg(
|
||||
item.icon,
|
||||
size: 20,
|
||||
color: Colors.white70,
|
||||
),
|
||||
selectedIcon: svg(
|
||||
item.icon,
|
||||
size: 20,
|
||||
),
|
||||
label: Text(item.name),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
selectedIndex: provider.currentIndex,
|
||||
),
|
||||
),
|
||||
SizeTransition(
|
||||
sizeFactor: _animation,
|
||||
axis: Axis.horizontal,
|
||||
axisAlignment: -1,
|
||||
child: Drawer(
|
||||
backgroundColor: drawerColor,
|
||||
shadowColor: Colors.black38,
|
||||
elevation: 5,
|
||||
shape: const ContinuousRectangleBorder(),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: screen.isMobile ? 90 : 10),
|
||||
child: GetBuilder<RoutesProvider>(
|
||||
builder: (provider) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Scrollbar(
|
||||
controller: scrollController,
|
||||
child: ListView(
|
||||
controller: scrollController,
|
||||
children: getMenu(provider.currentMenu),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../classes/functions.class.dart';
|
||||
|
||||
class Logo extends StatelessWidget {
|
||||
const Logo({this.color, super.key});
|
||||
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return svg('logo', size: 35, color: color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import '../../widgets/responsive_widgets/responsive.widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../classes/functions.class.dart';
|
||||
import '../account.widget.dart';
|
||||
import '../navbar_buttom.widget.dart';
|
||||
import 'app_logo.widget.dart';
|
||||
import 'app_search.widget.dart';
|
||||
|
||||
class AppNarBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
const AppNarBar({
|
||||
this.title = '',
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size.fromHeight(80);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveWidget(builder: (context, screen) {
|
||||
return AppBar(
|
||||
elevation: 5,
|
||||
shadowColor: Colors.black26,
|
||||
scrolledUnderElevation: 5,
|
||||
leadingWidth: 92,
|
||||
toolbarHeight: preferredSize.height,
|
||||
backgroundColor: navbarColor,
|
||||
centerTitle: true,
|
||||
leading: !screen.isBig
|
||||
? Container(
|
||||
width: 92,
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: IconButton.filledTonal(
|
||||
icon: const Icon(Icons.menu),
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: primaryColor,
|
||||
shadowColor: Colors.black26,
|
||||
elevation: 5,
|
||||
shape: const CircleBorder(),
|
||||
),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
},
|
||||
tooltip:
|
||||
MaterialLocalizations.of(context).openAppDrawerTooltip,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
color: primaryColor,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(15),
|
||||
child: Logo(color: Colors.white),
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
const Expanded(
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 900,
|
||||
child: AppSearch(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (!screen.isMobile)
|
||||
SizedBox(
|
||||
height: 50,
|
||||
child: NavBarButtonWidget(
|
||||
desktop: screen.isBig,
|
||||
text: 'Show Model',
|
||||
icon: Icons.add_rounded,
|
||||
onTap: () => showModel('test'),
|
||||
),
|
||||
),
|
||||
const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [],
|
||||
),
|
||||
SizedBox(
|
||||
height: 50,
|
||||
child: AccountWidget(
|
||||
desktop: screen.isBig,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import '../../providers/search.provider.dart';
|
||||
import '../../widgets/simple_loader.widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../classes/shortcuts.class.dart';
|
||||
|
||||
class AppSearch extends StatefulWidget {
|
||||
const AppSearch({super.key});
|
||||
|
||||
@override
|
||||
State<AppSearch> createState() => _AppSearchState();
|
||||
}
|
||||
|
||||
class _AppSearchState extends State<AppSearch> {
|
||||
final provider = Get.find<SearchProvider>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CallbackShortcuts(
|
||||
bindings: {
|
||||
escape: provider.clear,
|
||||
controlF: provider.focus,
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 5),
|
||||
child: TextField(
|
||||
controller: provider.searchController,
|
||||
focusNode: provider.node,
|
||||
onTapOutside: (e) => provider.unfocus(),
|
||||
onSubmitted: provider.search,
|
||||
onChanged: (value) => setState(() {}),
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.search,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search',
|
||||
fillColor: Colors.white,
|
||||
prefixIcon: Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: GetBuilder<SearchProvider>(
|
||||
builder: (provider) {
|
||||
return provider.isLoading
|
||||
? const SimpleLoader(
|
||||
size: 20,
|
||||
strokeWidth: 2,
|
||||
)
|
||||
: const Icon(Icons.search_rounded);
|
||||
},
|
||||
),
|
||||
),
|
||||
suffixIcon: GetBuilder<SearchProvider>(
|
||||
builder: (provider) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Visibility(
|
||||
visible: provider.searchController.text.isEmpty,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0x77EFEFEF),
|
||||
borderRadius: BorderRadius.all(Radius.circular(5)),
|
||||
),
|
||||
child: Text(
|
||||
'Ctrl + F',
|
||||
style:
|
||||
Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF616162),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Visibility(
|
||||
visible: provider.searchController.text.isNotEmpty,
|
||||
child: InkWell(
|
||||
onTap: provider.clear,
|
||||
child: const Icon(
|
||||
Icons.cancel_rounded,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: provider.searchController.text.isNotEmpty,
|
||||
child: const SizedBox(width: 15),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
filled: true,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: primaryColor.withOpacity(.1),
|
||||
width: 2,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: primaryColor,
|
||||
width: 2,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../classes/constents.class.dart';
|
||||
|
||||
class CardWidget extends StatelessWidget {
|
||||
const CardWidget({
|
||||
required this.child,
|
||||
this.callback,
|
||||
this.width = 300,
|
||||
this.height,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final VoidCallback? callback;
|
||||
final double width;
|
||||
final double? height;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: callback,
|
||||
child: Container(
|
||||
width: width,
|
||||
height: height,
|
||||
margin: const EdgeInsets.all(10),
|
||||
padding: const EdgeInsets.all(30),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
boxShadow: cardShadow,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef WidgetChildBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
Widget child,
|
||||
);
|
||||
|
||||
class ChildBuilder extends StatelessWidget {
|
||||
/// Creates a widget that delegates its build to a callback.
|
||||
///
|
||||
/// The [builder] argument must not be null.
|
||||
const ChildBuilder({
|
||||
super.key,
|
||||
required this.builder,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
/// Called to obtain the child widget.
|
||||
///
|
||||
/// This function is called whenever this widget is included in its parent's
|
||||
/// build and the old widget (if any) that it synchronizes with has a distinct
|
||||
/// object identity. Typically the parent's build method will construct
|
||||
/// a new tree of widgets and so a new Builder child will not be [identical]
|
||||
/// to the corresponding old one.
|
||||
final WidgetChildBuilder builder;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => builder(context, child);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../widgets/responsive_widgets/responsive.widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:syncfusion_flutter_core/theme.dart';
|
||||
|
||||
import '../../classes/functions.class.dart';
|
||||
import '../../models/data_table_models/grid_column.model.dart';
|
||||
|
||||
class DataTableWidget extends StatelessWidget {
|
||||
const DataTableWidget(this.columnNames, {required this.dataRows, super.key});
|
||||
final List<GridColumnModel> columnNames;
|
||||
final List<DataGridRow> dataRows;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveWidget(builder: (context, screen) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.blueGrey.withOpacity(.1),
|
||||
),
|
||||
),
|
||||
child: SfDataGridTheme(
|
||||
data: SfDataGridThemeData(
|
||||
rowHoverColor: primaryColor.withOpacity(.1),
|
||||
headerHoverColor: primaryColor.withOpacity(.1),
|
||||
sortIcon: const Icon(
|
||||
Icons.arrow_downward_rounded,
|
||||
size: 20,
|
||||
color: Colors.blueGrey,
|
||||
),
|
||||
filterIcon: const Icon(
|
||||
Icons.filter_alt_rounded,
|
||||
size: 20,
|
||||
color: Colors.blueGrey,
|
||||
),
|
||||
),
|
||||
child: SfDataGrid(
|
||||
rowsPerPage: 30,
|
||||
allowFiltering: true,
|
||||
allowSorting: true,
|
||||
columnWidthMode: ColumnWidthMode.fill,
|
||||
rowHeight: 70,
|
||||
gridLinesVisibility: GridLinesVisibility.horizontal,
|
||||
headerGridLinesVisibility: GridLinesVisibility.both,
|
||||
source: TableDataList(
|
||||
context,
|
||||
data: dataRows,
|
||||
isSmallScreen: screen.isSmall,
|
||||
),
|
||||
// tableSummaryRows: [
|
||||
// GridTableSummaryRow(
|
||||
// showSummaryInRow: true,
|
||||
// title: 'Total: {Count} User',
|
||||
// columns: [
|
||||
// const GridSummaryColumn(
|
||||
// name: 'Count',
|
||||
// columnName: 'ID',
|
||||
// summaryType: GridSummaryType.count,
|
||||
// )
|
||||
// ],
|
||||
// position: GridTableSummaryRowPosition.bottom,
|
||||
// )
|
||||
// ],
|
||||
columns: columnNames
|
||||
.map(
|
||||
(e) => GridColumn(
|
||||
columnName: e.name,
|
||||
width: e.width,
|
||||
allowFiltering: e.allowFiltering,
|
||||
allowSorting: e.allowSorting,
|
||||
columnWidthMode: e.columnWidthMode,
|
||||
filterIconPadding: EdgeInsets.zero,
|
||||
label: Container(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
alignment: Alignment.center,
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
e.name,
|
||||
style:
|
||||
Theme.of(context).textTheme.labelMedium?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.blueGrey,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class TableDataList<T> extends DataGridSource {
|
||||
TableDataList(
|
||||
BuildContext cnx, {
|
||||
required List<DataGridRow> data,
|
||||
bool isSmallScreen = false,
|
||||
}) {
|
||||
context = cnx;
|
||||
_usersList = data;
|
||||
}
|
||||
|
||||
List<DataGridRow> _usersList = [];
|
||||
late BuildContext context;
|
||||
|
||||
@override
|
||||
List<DataGridRow> get rows => _usersList;
|
||||
|
||||
@override
|
||||
Widget? buildTableSummaryCellWidget(
|
||||
GridTableSummaryRow summaryRow,
|
||||
GridSummaryColumn? summaryColumn,
|
||||
RowColumnIndex rowColumnIndex,
|
||||
String summaryValue) {
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: SelectableText(
|
||||
summaryValue,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.blueGrey,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
DataGridRowAdapter? buildRow(DataGridRow row) {
|
||||
return DataGridRowAdapter(
|
||||
cells: row.getCells().map<Widget>((dataGridCell) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: dataGridCell.value is Widget
|
||||
? dataGridCell.value
|
||||
: dataGridCell.value is DateTime
|
||||
? SelectableText(
|
||||
formatDate(dataGridCell.value as DateTime),
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
)
|
||||
: SelectableText(
|
||||
dataGridCell.value.toString(),
|
||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
||||
color:
|
||||
dataGridCell.value is int ? Colors.blueGrey : null),
|
||||
),
|
||||
);
|
||||
}).toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../classes/functions.class.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
|
||||
|
||||
import '../../../demo.data.dart';
|
||||
import '../../classes/enums.class.dart';
|
||||
import '../../models/data_table_models/grid_column.model.dart';
|
||||
import '../card.widget.dart';
|
||||
import '../dialogs/add_user.dialog.dart';
|
||||
import '../menu_list.widget.dart';
|
||||
import '../simple_loader.widget.dart';
|
||||
import 'data_table.widget.dart';
|
||||
import 'data_table_search.widget.dart';
|
||||
|
||||
class DataTableCard extends StatefulWidget {
|
||||
const DataTableCard(this.isBig, this.is4K, this.isMobile, {super.key});
|
||||
|
||||
final bool isBig;
|
||||
final bool is4K;
|
||||
final bool isMobile;
|
||||
|
||||
@override
|
||||
State<DataTableCard> createState() => _DataTableCardState();
|
||||
}
|
||||
|
||||
class _DataTableCardState extends State<DataTableCard> {
|
||||
bool isLoadingTable = true;
|
||||
|
||||
List<UsersModel> users = [];
|
||||
List<UsersModel> filteredUsers = [];
|
||||
|
||||
final searchController = TextEditingController();
|
||||
|
||||
void set(VoidCallback func) => mounted ? setState(func) : null;
|
||||
|
||||
void onTableSearch(String value) {
|
||||
if (value.isEmpty) {
|
||||
filteredUsers = users.toList();
|
||||
} else {
|
||||
filteredUsers = users.where((e) {
|
||||
return e.id == value ||
|
||||
e.name.toLowerCase().contains(value.toLowerCase()) ||
|
||||
e.email.toLowerCase().contains(value.toLowerCase()) ||
|
||||
e.phone.toLowerCase().contains(value.toLowerCase());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
set(() {});
|
||||
}
|
||||
|
||||
Future<void> getTableData() async {
|
||||
set(() => isLoadingTable = true);
|
||||
await Future.delayed(const Duration(seconds: 1), () {
|
||||
users = demoUsersList;
|
||||
filteredUsers = demoUsersList;
|
||||
set(() => isLoadingTable = false);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
getTableData();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CardWidget(
|
||||
height: 700,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: SelectableText(
|
||||
'Users',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if (!widget.isMobile)
|
||||
DataTableSearchWidget(
|
||||
onChange: onTableSearch,
|
||||
controller: searchController,
|
||||
hint: 'Search Name, Email, Phone',
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
IconButton.filledTonal(
|
||||
onPressed: () {},
|
||||
tooltip: 'Export CSV',
|
||||
splashRadius: 25,
|
||||
icon: const Icon(Icons.ios_share_rounded, size: 20),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
IconButton.filledTonal(
|
||||
onPressed: getTableData,
|
||||
tooltip: 'Refresh',
|
||||
splashRadius: 25,
|
||||
icon: isLoadingTable
|
||||
? const SimpleLoader(
|
||||
size: 20,
|
||||
strokeWidth: 2,
|
||||
)
|
||||
: const Icon(Icons.refresh_rounded, size: 20),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
const IconButton.filledTonal(
|
||||
onPressed: showAddUserDialog,
|
||||
tooltip: 'Add User',
|
||||
splashRadius: 25,
|
||||
icon: Icon(Icons.add_rounded, size: 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: isLoadingTable
|
||||
? const Center(child: SimpleLoader())
|
||||
: DataTableWidget(
|
||||
[
|
||||
GridColumnModel(
|
||||
name: '#',
|
||||
width: 70,
|
||||
allowFiltering: false,
|
||||
allowSorting: false,
|
||||
),
|
||||
GridColumnModel(name: 'Name'),
|
||||
if (widget.isBig) GridColumnModel(name: 'Date of birth'),
|
||||
if (widget.isBig) GridColumnModel(name: 'Gender'),
|
||||
GridColumnModel(name: 'Email'),
|
||||
GridColumnModel(name: 'Phone'),
|
||||
GridColumnModel(
|
||||
name: 'Status',
|
||||
allowFiltering: false,
|
||||
allowSorting: false,
|
||||
width: 120,
|
||||
),
|
||||
GridColumnModel(
|
||||
name: 'Actions',
|
||||
allowFiltering: false,
|
||||
allowSorting: false,
|
||||
columnWidthMode: ColumnWidthMode.lastColumnFill,
|
||||
),
|
||||
],
|
||||
dataRows: filteredUsers
|
||||
.map<DataGridRow>(
|
||||
(user) => DataGridRow(
|
||||
cells: [
|
||||
DataGridCell<Widget>(
|
||||
columnName: '#',
|
||||
value: CircleAvatar(
|
||||
radius: 35,
|
||||
backgroundImage: NetworkImage(user.avatar),
|
||||
),
|
||||
),
|
||||
DataGridCell<String>(
|
||||
columnName: 'Name', value: user.name),
|
||||
if (widget.isBig)
|
||||
DataGridCell<DateTime>(
|
||||
columnName: 'Date of birth',
|
||||
value: user.dateOfBirth,
|
||||
),
|
||||
if (widget.isBig)
|
||||
DataGridCell<String>(
|
||||
columnName: 'Gender', value: user.gender),
|
||||
DataGridCell<String>(
|
||||
columnName: 'Email', value: user.email),
|
||||
DataGridCell<String>(
|
||||
columnName: 'Phone', value: user.phone),
|
||||
DataGridCell<Widget>(
|
||||
columnName: 'Status',
|
||||
value: SizedBox(
|
||||
height: 30,
|
||||
child: FittedBox(
|
||||
child: CupertinoSwitch(
|
||||
value: user.status,
|
||||
activeColor: primaryColor,
|
||||
onChanged: (value) {
|
||||
user.status = value;
|
||||
|
||||
set(() {});
|
||||
toast(
|
||||
'User "${user.name}" has been ${value ? 'Activated' : 'Deactivated'}',
|
||||
type: value
|
||||
? ToastType.success
|
||||
: ToastType.warning,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataGridCell<Widget>(
|
||||
columnName: 'Actions',
|
||||
value: widget.is4K
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FittedBox(
|
||||
child: IconButton(
|
||||
onPressed: () {},
|
||||
tooltip: 'Details',
|
||||
splashRadius: 25,
|
||||
icon: const Icon(
|
||||
Icons.person_outline,
|
||||
color: Colors.blueGrey,
|
||||
size: 20),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
FittedBox(
|
||||
child: IconButton(
|
||||
onPressed: () {},
|
||||
tooltip: 'Edit',
|
||||
splashRadius: 25,
|
||||
icon: const Icon(
|
||||
Icons.edit_outlined,
|
||||
color: Colors.blueGrey,
|
||||
size: 20),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
FittedBox(
|
||||
child: IconButton(
|
||||
onPressed: () {},
|
||||
tooltip: 'Delete Account',
|
||||
splashRadius: 25,
|
||||
icon: const Icon(
|
||||
Icons.delete_outline_rounded,
|
||||
color: Colors.red,
|
||||
size: 20),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: MenuList(
|
||||
actions: [
|
||||
MenuListItem(
|
||||
callback: () => print(user.id),
|
||||
svgName: 'profile',
|
||||
title: 'User Details',
|
||||
),
|
||||
MenuListItem(
|
||||
callback: () => print(user.name),
|
||||
svgName: 'menu',
|
||||
title: 'Details',
|
||||
),
|
||||
MenuListItem(
|
||||
callback: () => print(user.id),
|
||||
title: 'Remove',
|
||||
svgName: 'logout',
|
||||
color: Colors.redAccent,
|
||||
),
|
||||
],
|
||||
offset: const Offset(0, 5),
|
||||
builder: (controller) => IconButton(
|
||||
onPressed: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.more_vert_rounded),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../classes/shortcuts.class.dart';
|
||||
|
||||
class DataTableSearchWidget extends StatelessWidget {
|
||||
const DataTableSearchWidget(
|
||||
{required this.controller, required this.onChange, this.hint, super.key});
|
||||
|
||||
final TextEditingController controller;
|
||||
final void Function(String) onChange;
|
||||
final String? hint;
|
||||
|
||||
clear() {
|
||||
controller.clear();
|
||||
onChange('');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: CallbackShortcuts(
|
||||
bindings: {escape: clear},
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
onSubmitted: onChange,
|
||||
onChanged: onChange,
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.search,
|
||||
decoration: InputDecoration(
|
||||
hintText: hint ?? 'Search',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium
|
||||
?.copyWith(color: Colors.blueGrey),
|
||||
fillColor: Colors.white,
|
||||
prefixIcon: const Padding(
|
||||
padding: EdgeInsets.all(15),
|
||||
child: Icon(Icons.search_rounded),
|
||||
),
|
||||
suffixIcon: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Visibility(
|
||||
visible: controller.text.isNotEmpty,
|
||||
child: InkWell(
|
||||
onTap: clear,
|
||||
child: const Icon(
|
||||
Icons.cancel_rounded,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: controller.text.isNotEmpty,
|
||||
child: const SizedBox(width: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
filled: true,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: primaryColor.withOpacity(.1),
|
||||
width: 2,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: primaryColor,
|
||||
width: 2,
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../classes/enums.class.dart';
|
||||
import '../../classes/functions.class.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
Future<bool> showAddUserDialog() async {
|
||||
bool ifSuccess() {
|
||||
Get.back();
|
||||
alert(
|
||||
'User has been added !!',
|
||||
type: AlertType.success,
|
||||
autoDismissAfter: const Duration(seconds: 4),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ifFiled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await Get.defaultDialog<bool>(
|
||||
title: 'Add User',
|
||||
backgroundColor: Colors.white,
|
||||
buttonColor: primaryColor,
|
||||
content: const AddUserDialogWidget(),
|
||||
onConfirm: ifSuccess,
|
||||
onCancel: ifFiled,
|
||||
).then((value) {
|
||||
return value ?? false ? ifSuccess() : ifFiled();
|
||||
});
|
||||
}
|
||||
|
||||
class AddUserDialogWidget extends StatelessWidget {
|
||||
const AddUserDialogWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 1000,
|
||||
height: Get.height - 300,
|
||||
color: Colors.white,
|
||||
child: const SingleChildScrollView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../classes/functions.class.dart';
|
||||
|
||||
class MenuList extends StatelessWidget {
|
||||
const MenuList({
|
||||
required this.builder,
|
||||
required this.actions,
|
||||
this.offset,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget Function(MenuController) builder;
|
||||
final List<Widget> actions;
|
||||
final Offset? offset;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MenuAnchor(
|
||||
alignmentOffset: offset ?? const Offset(0, 15),
|
||||
menuChildren: actions,
|
||||
builder: (_, controller, __) => builder(controller),
|
||||
style: MenuStyle(
|
||||
shape: MaterialStateProperty.all(
|
||||
const ContinuousRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.bottomLeft,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MenuListItem extends StatelessWidget {
|
||||
const MenuListItem({
|
||||
required this.title,
|
||||
required this.svgName,
|
||||
this.width = 200,
|
||||
this.callback,
|
||||
this.color,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final double width;
|
||||
final VoidCallback? callback;
|
||||
final String title;
|
||||
final String svgName;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
child: MenuItemButton(
|
||||
style: MenuItemButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
requestFocusOnHover: false,
|
||||
onPressed: callback,
|
||||
trailingIcon: svg(svgName, size: 16, color: color ?? Colors.blueGrey),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(color: color ?? Colors.blueGrey.shade900),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import '../classes/constents.class.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NavBarButtonWidget extends StatelessWidget {
|
||||
const NavBarButtonWidget({
|
||||
required this.desktop,
|
||||
required this.onTap,
|
||||
required this.icon,
|
||||
required this.text,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool desktop;
|
||||
final VoidCallback onTap;
|
||||
final IconData icon;
|
||||
final String text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: desktop
|
||||
? ElevatedButton.icon(
|
||||
onPressed: onTap,
|
||||
icon: Icon(icon),
|
||||
label: Text(text),
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: 45,
|
||||
child: Tooltip(
|
||||
message: text,
|
||||
child: IconButton.filled(
|
||||
onPressed: onTap,
|
||||
icon: Icon(icon),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: primaryColor,
|
||||
shadowColor: Colors.black26,
|
||||
elevation: 5,
|
||||
shape: const CircleBorder(),
|
||||
// shape: const ContinuousRectangleBorder(
|
||||
// borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
// ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../providers/alert.provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../classes/enums.class.dart';
|
||||
|
||||
class Alert extends StatefulWidget {
|
||||
const Alert(
|
||||
this.message, {
|
||||
this.type = AlertType.primary,
|
||||
this.autoDismissAfter,
|
||||
this.dismissible = false,
|
||||
this.onDismiss,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String message;
|
||||
final AlertType type;
|
||||
final bool dismissible;
|
||||
final Duration? autoDismissAfter;
|
||||
final VoidCallback? onDismiss;
|
||||
|
||||
@override
|
||||
State<Alert> createState() => _AlertState();
|
||||
}
|
||||
|
||||
class _AlertState extends State<Alert> with TickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final Animation<double> _animation;
|
||||
|
||||
final provider = Get.find<AlertProvider>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 275),
|
||||
vsync: this,
|
||||
);
|
||||
_animation = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
provider.addListener(toggle);
|
||||
|
||||
if (provider.alert is Widget) {
|
||||
_controller.forward();
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (provider.alert is Widget) {
|
||||
_controller.forward();
|
||||
}
|
||||
if (provider.alert == null) {
|
||||
_controller.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
provider.removeListener(toggle);
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Color getColor() {
|
||||
switch (widget.type) {
|
||||
case AlertType.success:
|
||||
return Colors.green;
|
||||
case AlertType.danger:
|
||||
return Colors.red;
|
||||
case AlertType.warning:
|
||||
return Colors.amber;
|
||||
case AlertType.info:
|
||||
return Colors.blue;
|
||||
default:
|
||||
return primaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
IconData getIcon() {
|
||||
switch (widget.type) {
|
||||
case AlertType.success:
|
||||
return Icons.check_circle_rounded;
|
||||
case AlertType.danger:
|
||||
return Icons.cancel_rounded;
|
||||
case AlertType.warning:
|
||||
return Icons.warning_rounded;
|
||||
case AlertType.info:
|
||||
return Icons.info_rounded;
|
||||
default:
|
||||
return Icons.notifications_rounded;
|
||||
}
|
||||
}
|
||||
|
||||
void dissmiss() {
|
||||
if (mounted) {
|
||||
_controller.reverse().then((value) {
|
||||
if (widget.onDismiss is VoidCallback) widget.onDismiss?.call();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.autoDismissAfter is Duration &&
|
||||
widget.autoDismissAfter!.inSeconds > 0) {
|
||||
Future.delayed(widget.autoDismissAfter as Duration, dissmiss);
|
||||
}
|
||||
final color = getColor();
|
||||
return SizeTransition(
|
||||
sizeFactor: _animation,
|
||||
axis: Axis.vertical,
|
||||
axisAlignment: -1,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 5),
|
||||
Icon(getIcon(), color: color),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: SelectableText(
|
||||
widget.message,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(color: color),
|
||||
),
|
||||
),
|
||||
if (widget.dismissible) ...[
|
||||
const SizedBox(width: 10),
|
||||
InkWell(
|
||||
onTap: dissmiss,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
radius: 25,
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
),
|
||||
child: Text(
|
||||
'clear',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall
|
||||
?.copyWith(color: color, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import '../../providers/toast.provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../classes/constents.class.dart';
|
||||
import '../../classes/enums.class.dart';
|
||||
|
||||
class ToastModel {
|
||||
final String? message;
|
||||
final ToastType type;
|
||||
final VoidCallback? callback;
|
||||
|
||||
ToastModel({
|
||||
this.message,
|
||||
this.callback,
|
||||
this.type = ToastType.primary,
|
||||
});
|
||||
|
||||
Color get color => getColor();
|
||||
IconData get icon => getIcon();
|
||||
|
||||
Color getColor() {
|
||||
switch (type) {
|
||||
case ToastType.success:
|
||||
return Colors.green;
|
||||
case ToastType.danger:
|
||||
return Colors.red;
|
||||
case ToastType.warning:
|
||||
return Colors.amber;
|
||||
case ToastType.info:
|
||||
return Colors.blue;
|
||||
default:
|
||||
return primaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
IconData getIcon() {
|
||||
switch (type) {
|
||||
case ToastType.success:
|
||||
return Icons.check_circle_rounded;
|
||||
case ToastType.danger:
|
||||
return Icons.cancel_rounded;
|
||||
case ToastType.warning:
|
||||
return Icons.warning_rounded;
|
||||
case ToastType.info:
|
||||
return Icons.info_rounded;
|
||||
default:
|
||||
return Icons.notifications_rounded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Toasts extends StatefulWidget {
|
||||
const Toasts({super.key});
|
||||
|
||||
@override
|
||||
State<Toasts> createState() => _ToastsState();
|
||||
}
|
||||
|
||||
class _ToastsState extends State<Toasts> {
|
||||
final listKey = GlobalKey<AnimatedListState>();
|
||||
final provider = Get.find<ToastProvider>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
provider.setKey(listKey);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final slideInTween =
|
||||
Tween<Offset>(begin: const Offset(3, 0), end: Offset.zero);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedList(
|
||||
shrinkWrap: true,
|
||||
key: listKey,
|
||||
itemBuilder: (cnx, i, animation) => provider.buildItem(provider.toasts[i], animation),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Toast extends StatelessWidget {
|
||||
const Toast(this.item, this.dismiss, {super.key});
|
||||
final ToastModel item;
|
||||
final void Function(ToastModel, DissmissType) dismiss;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = item.color;
|
||||
if (toastAutoDissmiss) {
|
||||
Future.delayed(
|
||||
toastDissmissDuration, () => dismiss(item, DissmissType.auto))
|
||||
.catchError((e) {});
|
||||
}
|
||||
return Dismissible(
|
||||
key: ValueKey(item.hashCode),
|
||||
onDismissed: (_) => dismiss(item, DissmissType.swipe),
|
||||
confirmDismiss: (direction) async {
|
||||
return direction == DismissDirection.startToEnd;
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: GestureDetector(
|
||||
onTap: item.callback,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
color: Colors.white,
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(color: color.withOpacity(.5), width: 2)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
item.icon,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(item.message ?? ''),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
InkWell(
|
||||
onTap: () => dismiss(item, DissmissType.click),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
splashColor: color.withOpacity(.1),
|
||||
child: Icon(
|
||||
Icons.close_rounded,
|
||||
size: 20,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
|
||||
import '../child_builder.widget.dart';
|
||||
|
||||
class ResponsiveGridItem extends StatelessWidget {
|
||||
const ResponsiveGridItem({
|
||||
required this.child,
|
||||
this.crossAxisCellCount = 3,
|
||||
this.animationIndex = 0,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final int crossAxisCellCount;
|
||||
final int animationIndex;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChildBuilder(
|
||||
child: AnimationConfiguration.staggeredGrid(
|
||||
position: animationIndex,
|
||||
duration: const Duration(milliseconds: 375),
|
||||
columnCount: 6,
|
||||
child: ScaleAnimation(
|
||||
child: FadeInAnimation(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
builder: (context, child) {
|
||||
return StaggeredGridTile.fit(
|
||||
crossAxisCellCount: crossAxisCellCount,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../classes/enums.class.dart';
|
||||
|
||||
typedef ResponsiveBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
ScreenSize screen,
|
||||
);
|
||||
|
||||
class ScreenSize {
|
||||
final double width;
|
||||
ScreenSize(this.width);
|
||||
|
||||
bool get is4K => width > 1600;
|
||||
bool get isDesktop => width > 1024 && !is4K;
|
||||
bool get isTablet => width > 768 && !isDesktop;
|
||||
bool get isMobile => !isTablet && !isDesktop && !is4K;
|
||||
bool get isSmall => isMobile || isTablet;
|
||||
bool get isBig => isDesktop || is4K;
|
||||
ScreenSizeType get screenType => calculateScreenType();
|
||||
|
||||
ScreenSizeType calculateScreenType() {
|
||||
if (isDesktop) return ScreenSizeType.desktop;
|
||||
if (isTablet) return ScreenSizeType.tablet;
|
||||
return ScreenSizeType.mobile;
|
||||
}
|
||||
|
||||
int gridAxis() {
|
||||
if (is4K) return 24;
|
||||
if (isDesktop) return 18;
|
||||
if (isTablet) return 12;
|
||||
return 6;
|
||||
}
|
||||
|
||||
int setAxis(int? xl, int? lg, int? md, int? sm) {
|
||||
xl ??= 24;
|
||||
if (is4K) return xl;
|
||||
if (isDesktop) return lg ?? xl;
|
||||
if (isTablet) return md ?? lg ?? xl;
|
||||
return sm ?? md ?? lg ?? xl;
|
||||
}
|
||||
}
|
||||
|
||||
class ResponsiveWidget extends StatelessWidget {
|
||||
const ResponsiveWidget({
|
||||
required this.builder,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final ResponsiveBuilder builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
ScreenSize size = ScreenSize(constraints.maxWidth);
|
||||
return builder(context, size);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../classes/constents.class.dart';
|
||||
import 'responsive.widget.dart';
|
||||
|
||||
class ResponsiveGrid extends StatefulWidget {
|
||||
const ResponsiveGrid({required this.childrenBuilder, super.key});
|
||||
|
||||
final List<Widget> Function(
|
||||
BuildContext,
|
||||
ScreenSize,
|
||||
) childrenBuilder;
|
||||
|
||||
@override
|
||||
State<ResponsiveGrid> createState() => _ResponsiveGridState();
|
||||
}
|
||||
|
||||
class _ResponsiveGridState extends State<ResponsiveGrid> {
|
||||
late final ScrollController controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = ScrollController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveWidget(
|
||||
builder: (cnx, screen) {
|
||||
return Scrollbar(
|
||||
thickness: 10,
|
||||
thumbVisibility: true,
|
||||
controller: controller,
|
||||
child: SingleChildScrollView(
|
||||
controller: controller,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: (Get.width - 304) > maxContainerWidth
|
||||
? (((Get.width - 304) - maxContainerWidth) / 2)
|
||||
: 20,
|
||||
),
|
||||
child: AnimationLimiter(
|
||||
child: StaggeredGrid.count(
|
||||
crossAxisCount: screen.gridAxis(),
|
||||
children: widget.childrenBuilder(cnx, screen),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import '../classes/constents.class.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:vector_math/vector_math.dart' as math;
|
||||
|
||||
class RoundedProgressIndicator extends StatefulWidget {
|
||||
const RoundedProgressIndicator({
|
||||
this.textValue,
|
||||
this.color = primaryColor,
|
||||
this.backgroundColor = Colors.white,
|
||||
this.value = 0.5,
|
||||
this.size = 40,
|
||||
this.stroke = 4,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget? textValue;
|
||||
final Color color;
|
||||
final Color backgroundColor;
|
||||
final double value;
|
||||
final double size;
|
||||
final double stroke;
|
||||
|
||||
@override
|
||||
State<RoundedProgressIndicator> createState() =>
|
||||
_RoundedProgressIndicatorState();
|
||||
}
|
||||
|
||||
class _RoundedProgressIndicatorState extends State<RoundedProgressIndicator>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late Animation<double> _animation;
|
||||
late AnimationController _animationController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
vsync: this,
|
||||
);
|
||||
_animation =
|
||||
Tween<double>(begin: 0, end: widget.value).animate(CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
));
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: widget.size,
|
||||
width: widget.size,
|
||||
child: widget.textValue is Widget
|
||||
? Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: AnimatedRoundedProgressIndicator(
|
||||
backgroundColor: widget.backgroundColor,
|
||||
color: widget.color,
|
||||
stroke: widget.stroke,
|
||||
animation: _animation,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Center(child: widget.textValue as Widget),
|
||||
),
|
||||
],
|
||||
)
|
||||
: AnimatedRoundedProgressIndicator(
|
||||
backgroundColor: widget.backgroundColor,
|
||||
color: widget.color,
|
||||
stroke: widget.stroke,
|
||||
animation: _animation,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedRoundedProgressIndicator extends AnimatedWidget {
|
||||
const AnimatedRoundedProgressIndicator({
|
||||
Key? key,
|
||||
this.color = primaryColor,
|
||||
this.backgroundColor = Colors.white,
|
||||
this.stroke = 4,
|
||||
required this.animation,
|
||||
}) : super(key: key, listenable: animation);
|
||||
|
||||
final Animation<double> animation;
|
||||
final Color color;
|
||||
final Color backgroundColor;
|
||||
final double stroke;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(
|
||||
painter: CartCircularprogress(
|
||||
backColor: backgroundColor,
|
||||
frontColor: color,
|
||||
strokeWidth: stroke,
|
||||
value: animation.value,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CartCircularprogress extends CustomPainter {
|
||||
const CartCircularprogress({
|
||||
required this.backColor,
|
||||
required this.frontColor,
|
||||
required this.strokeWidth,
|
||||
required this.value,
|
||||
});
|
||||
final Color backColor, frontColor;
|
||||
final double strokeWidth, value;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final w = size.width, h = size.height;
|
||||
final paint1 = Paint()
|
||||
..strokeWidth = strokeWidth
|
||||
..color = backColor
|
||||
..style = PaintingStyle.stroke;
|
||||
final paint2 = Paint()
|
||||
..strokeWidth = strokeWidth
|
||||
..color = frontColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
final r =
|
||||
Rect.fromCenter(center: Offset(w / 2, h / 2), width: w, height: h);
|
||||
|
||||
canvas.drawArc(r, math.radians(0), math.radians(360), false, paint1);
|
||||
canvas.drawArc(
|
||||
r, math.radians(270), math.radians(360 * value), false, paint2);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SimpleLoader extends StatelessWidget {
|
||||
const SimpleLoader({
|
||||
this.color,
|
||||
this.strokeWidth = 4,
|
||||
this.size = 40,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Color? color;
|
||||
final double strokeWidth;
|
||||
final double size;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: size,
|
||||
width: size,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation(color),
|
||||
strokeWidth: strokeWidth,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user