go router added
This commit is contained in:
@@ -22,6 +22,7 @@ class AppColors {
|
|||||||
static const Color warningColor = Color(0xFFFFC107);
|
static const Color warningColor = Color(0xFFFFC107);
|
||||||
|
|
||||||
static const Color grey = Color(0xFF555555);
|
static const Color grey = Color(0xFF555555);
|
||||||
|
static const Color grey50 = Color(0xFF9E9E9E);
|
||||||
static const Color grey60 = Color(0xFF777777);
|
static const Color grey60 = Color(0xFF777777);
|
||||||
static const Color grey70 = Color(0xFF444444);
|
static const Color grey70 = Color(0xFF444444);
|
||||||
static const Color grey80 = Color(0xFF222222);
|
static const Color grey80 = Color(0xFF222222);
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../views/home/home_screen.dart';
|
||||||
|
import '../../views/login/login_screen.dart';
|
||||||
|
import '../../views/profile/profile_screen.dart';
|
||||||
|
import '../../views/tenders/tenders_screen.dart';
|
||||||
|
import 'app_shell_screen.dart';
|
||||||
|
|
||||||
|
part 'app_routes.g.dart';
|
||||||
|
|
||||||
|
// Global navigator keys
|
||||||
|
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
final GlobalKey<NavigatorState> homeNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
final GlobalKey<NavigatorState> tendersNavigatorKey =
|
||||||
|
GlobalKey<NavigatorState>();
|
||||||
|
final GlobalKey<NavigatorState> profileNavigatorKey =
|
||||||
|
GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
// Main app router
|
||||||
|
final GoRouter appRouter = GoRouter(
|
||||||
|
routes: $appRoutes,
|
||||||
|
initialLocation: '/login',
|
||||||
|
navigatorKey: rootNavigatorKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Login route - outside the shell
|
||||||
|
@TypedGoRoute<LoginScreenRoute>(path: '/login')
|
||||||
|
class LoginScreenRoute extends GoRouteData with _$LoginScreenRoute {
|
||||||
|
const LoginScreenRoute();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, GoRouterState state) {
|
||||||
|
return const LoginScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shell route with bottom navigation using StatefulShellRoute.indexedStack
|
||||||
|
@TypedStatefulShellRoute<AppShellRouteData>(
|
||||||
|
branches: <TypedStatefulShellBranch<StatefulShellBranchData>>[
|
||||||
|
TypedStatefulShellBranch<HomeBranch>(
|
||||||
|
routes: <TypedRoute<RouteData>>[
|
||||||
|
TypedGoRoute<HomeRouteData>(path: '/home'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
TypedStatefulShellBranch<TendersBranch>(
|
||||||
|
routes: <TypedRoute<RouteData>>[
|
||||||
|
TypedGoRoute<TendersRouteData>(path: '/tenders'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
TypedStatefulShellBranch<ProfileBranch>(
|
||||||
|
routes: <TypedRoute<RouteData>>[
|
||||||
|
TypedGoRoute<ProfileRouteData>(path: '/profile'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
class AppShellRouteData extends StatefulShellRouteData {
|
||||||
|
const AppShellRouteData();
|
||||||
|
|
||||||
|
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget builder(
|
||||||
|
BuildContext context,
|
||||||
|
GoRouterState state,
|
||||||
|
StatefulNavigationShell navigationShell,
|
||||||
|
) {
|
||||||
|
return AppShellScreen(navigationShell: navigationShell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Branch data classes
|
||||||
|
class HomeBranch extends StatefulShellBranchData {
|
||||||
|
const HomeBranch();
|
||||||
|
static final GlobalKey<NavigatorState> $navigatorKey = homeNavigatorKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TendersBranch extends StatefulShellBranchData {
|
||||||
|
const TendersBranch();
|
||||||
|
static final GlobalKey<NavigatorState> $navigatorKey = tendersNavigatorKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProfileBranch extends StatefulShellBranchData {
|
||||||
|
const ProfileBranch();
|
||||||
|
static final GlobalKey<NavigatorState> $navigatorKey = profileNavigatorKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route data classes
|
||||||
|
class HomeRouteData extends GoRouteData with _$HomeRouteData {
|
||||||
|
const HomeRouteData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, GoRouterState state) {
|
||||||
|
return const HomeScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||||
|
// return CustomTransitionPage<void>(
|
||||||
|
// key: state.pageKey,
|
||||||
|
// child: const HomeScreen(),
|
||||||
|
// transitionDuration: const Duration(milliseconds: 500),
|
||||||
|
// transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
|
// const begin = Offset(1.0, 0.0); // From right to left
|
||||||
|
// const end = Offset.zero;
|
||||||
|
// const curve = Curves.linearToEaseOut;
|
||||||
|
|
||||||
|
// var tween = Tween(
|
||||||
|
// begin: begin,
|
||||||
|
// end: end,
|
||||||
|
// ).chain(CurveTween(curve: curve));
|
||||||
|
// var offsetAnimation = animation.drive(tween);
|
||||||
|
// return SlideTransition(position: offsetAnimation, child: child);
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
class TendersRouteData extends GoRouteData with _$TendersRouteData {
|
||||||
|
const TendersRouteData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, GoRouterState state) {
|
||||||
|
return const TendersScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProfileRouteData extends GoRouteData with _$ProfileRouteData {
|
||||||
|
const ProfileRouteData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, GoRouterState state) {
|
||||||
|
return const ProfileScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'app_routes.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// GoRouterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
List<RouteBase> get $appRoutes => [$loginScreenRoute, $appShellRouteData];
|
||||||
|
|
||||||
|
RouteBase get $loginScreenRoute =>
|
||||||
|
GoRouteData.$route(path: '/login', factory: _$LoginScreenRoute._fromState);
|
||||||
|
|
||||||
|
mixin _$LoginScreenRoute on GoRouteData {
|
||||||
|
static LoginScreenRoute _fromState(GoRouterState state) =>
|
||||||
|
const LoginScreenRoute();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get location => GoRouteData.$location('/login');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void go(BuildContext context) => context.go(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void pushReplacement(BuildContext context) =>
|
||||||
|
context.pushReplacement(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void replace(BuildContext context) => context.replace(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
RouteBase get $appShellRouteData => StatefulShellRouteData.$route(
|
||||||
|
parentNavigatorKey: AppShellRouteData.$parentNavigatorKey,
|
||||||
|
factory: $AppShellRouteDataExtension._fromState,
|
||||||
|
branches: [
|
||||||
|
StatefulShellBranchData.$branch(
|
||||||
|
navigatorKey: HomeBranch.$navigatorKey,
|
||||||
|
|
||||||
|
routes: [
|
||||||
|
GoRouteData.$route(path: '/home', factory: _$HomeRouteData._fromState),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
StatefulShellBranchData.$branch(
|
||||||
|
navigatorKey: TendersBranch.$navigatorKey,
|
||||||
|
|
||||||
|
routes: [
|
||||||
|
GoRouteData.$route(
|
||||||
|
path: '/tenders',
|
||||||
|
|
||||||
|
factory: _$TendersRouteData._fromState,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
StatefulShellBranchData.$branch(
|
||||||
|
navigatorKey: ProfileBranch.$navigatorKey,
|
||||||
|
|
||||||
|
routes: [
|
||||||
|
GoRouteData.$route(
|
||||||
|
path: '/profile',
|
||||||
|
|
||||||
|
factory: _$ProfileRouteData._fromState,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
extension $AppShellRouteDataExtension on AppShellRouteData {
|
||||||
|
static AppShellRouteData _fromState(GoRouterState state) =>
|
||||||
|
const AppShellRouteData();
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin _$HomeRouteData on GoRouteData {
|
||||||
|
static HomeRouteData _fromState(GoRouterState state) => const HomeRouteData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get location => GoRouteData.$location('/home');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void go(BuildContext context) => context.go(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void pushReplacement(BuildContext context) =>
|
||||||
|
context.pushReplacement(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void replace(BuildContext context) => context.replace(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin _$TendersRouteData on GoRouteData {
|
||||||
|
static TendersRouteData _fromState(GoRouterState state) =>
|
||||||
|
const TendersRouteData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get location => GoRouteData.$location('/tenders');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void go(BuildContext context) => context.go(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void pushReplacement(BuildContext context) =>
|
||||||
|
context.pushReplacement(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void replace(BuildContext context) => context.replace(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin _$ProfileRouteData on GoRouteData {
|
||||||
|
static ProfileRouteData _fromState(GoRouterState state) =>
|
||||||
|
const ProfileRouteData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get location => GoRouteData.$location('/profile');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void go(BuildContext context) => context.go(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void pushReplacement(BuildContext context) =>
|
||||||
|
context.pushReplacement(location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void replace(BuildContext context) => context.replace(location);
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// Shell screen with bottom navigation
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../utils/size_config.dart';
|
||||||
|
|
||||||
|
class AppShellScreen extends StatelessWidget {
|
||||||
|
const AppShellScreen({required this.navigationShell, super.key});
|
||||||
|
|
||||||
|
final StatefulNavigationShell navigationShell;
|
||||||
|
|
||||||
|
void _gotoBranch(int index) {
|
||||||
|
navigationShell.goBranch(
|
||||||
|
index,
|
||||||
|
initialLocation: index == navigationShell.currentIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: navigationShell,
|
||||||
|
bottomNavigationBar: Container(
|
||||||
|
height: 72.0.h(),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFE3E3E3),
|
||||||
|
border: Border.fromBorderSide(
|
||||||
|
BorderSide(color: const Color(0xFFE3E3E3)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
_bottomNavigationItem(
|
||||||
|
context: context,
|
||||||
|
text: 'Home',
|
||||||
|
isActive: navigationShell.currentIndex == 0,
|
||||||
|
onTap: () => _gotoBranch(0),
|
||||||
|
iconPath: 'assets/icons/home.svg',
|
||||||
|
activeIconPath: 'assets/icons/homeActive.svg',
|
||||||
|
),
|
||||||
|
_bottomNavigationItem(
|
||||||
|
context: context,
|
||||||
|
text: 'Tenders',
|
||||||
|
isActive: navigationShell.currentIndex == 1,
|
||||||
|
onTap: () => _gotoBranch(1),
|
||||||
|
iconPath: 'assets/icons/task_square.svg',
|
||||||
|
activeIconPath: 'assets/icons/task-square_active.svg',
|
||||||
|
),
|
||||||
|
_bottomNavigationItem(
|
||||||
|
context: context,
|
||||||
|
text: 'Profile',
|
||||||
|
isActive: navigationShell.currentIndex == 2,
|
||||||
|
onTap: () => _gotoBranch(2),
|
||||||
|
iconPath: 'assets/icons/profile-circle.svg',
|
||||||
|
activeIconPath: 'assets/icons/profile-circle_active.svg',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _bottomNavigationItem({
|
||||||
|
required BuildContext context,
|
||||||
|
required String text,
|
||||||
|
required bool isActive,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
required String iconPath,
|
||||||
|
required String activeIconPath,
|
||||||
|
}) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 64.0.w(),
|
||||||
|
height: 72.0.h(),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: 12.0.h(), bottom: 14.0.h()),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(isActive ? activeIconPath : iconPath),
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.0.sp(),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color:
|
||||||
|
isActive
|
||||||
|
? const Color(0xFF0164FF)
|
||||||
|
: const Color(0xFF777777),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
typedef OnError<E> = void Function(E error);
|
||||||
|
|
||||||
|
abstract class Either<E, S> {
|
||||||
|
const Either();
|
||||||
|
|
||||||
|
E? getError();
|
||||||
|
|
||||||
|
S? getSuccess();
|
||||||
|
|
||||||
|
bool isError();
|
||||||
|
|
||||||
|
bool isSuccess();
|
||||||
|
|
||||||
|
void when({
|
||||||
|
required OnError<E> error,
|
||||||
|
required void Function(S success) success,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Success<E, S> extends Either<E, S> {
|
||||||
|
final S _success;
|
||||||
|
|
||||||
|
const Success(this._success);
|
||||||
|
|
||||||
|
@override
|
||||||
|
E? getError() => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
S? getSuccess() => _success;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isError() => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isSuccess() => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void when({
|
||||||
|
required OnError<E> error,
|
||||||
|
required void Function(S success) success,
|
||||||
|
}) {
|
||||||
|
success(_success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Error<E, S> extends Either<E, S> {
|
||||||
|
final E _error;
|
||||||
|
|
||||||
|
const Error(this._error);
|
||||||
|
|
||||||
|
@override
|
||||||
|
E? getError() => _error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
S? getSuccess() => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isError() => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isSuccess() => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void when({
|
||||||
|
required OnError<E> error,
|
||||||
|
required void Function(S success) success,
|
||||||
|
}) {
|
||||||
|
error(_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
|
/// AppLogger is a singleton class that provides centralized logging functionality
|
||||||
|
/// throughout the application using the logger package.
|
||||||
|
class AppLogger {
|
||||||
|
// Private constructor
|
||||||
|
AppLogger._internal();
|
||||||
|
|
||||||
|
// Singleton instance
|
||||||
|
static final AppLogger _instance = AppLogger._internal();
|
||||||
|
|
||||||
|
// Factory constructor to return the singleton instance
|
||||||
|
factory AppLogger() => _instance;
|
||||||
|
|
||||||
|
// Logger instance
|
||||||
|
late final Logger _logger;
|
||||||
|
|
||||||
|
// Initialize the logger with custom configuration
|
||||||
|
void init({Level? logLevel, bool? stackTraceEnabled}) {
|
||||||
|
_logger = Logger(
|
||||||
|
level: logLevel ?? (kDebugMode ? Level.debug : Level.info),
|
||||||
|
printer: PrettyPrinter(
|
||||||
|
methodCount: stackTraceEnabled ?? false ? 2 : 0,
|
||||||
|
errorMethodCount: 8,
|
||||||
|
lineLength: 120,
|
||||||
|
colors: true,
|
||||||
|
printEmojis: true,
|
||||||
|
dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,
|
||||||
|
),
|
||||||
|
output: _CustomConsoleOutput(),
|
||||||
|
filter: _CustomLogFilter(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the logger instance (for advanced usage)
|
||||||
|
Logger get logger => _logger;
|
||||||
|
|
||||||
|
// Convenient logging methods
|
||||||
|
void debug(dynamic message, {dynamic error, StackTrace? stackTrace}) {
|
||||||
|
_logger.d(message, error: error, stackTrace: stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void info(dynamic message, {dynamic error, StackTrace? stackTrace}) {
|
||||||
|
_logger.i(message, error: error, stackTrace: stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void warning(dynamic message, {dynamic error, StackTrace? stackTrace}) {
|
||||||
|
_logger.w(message, error: error, stackTrace: stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void error(dynamic message, {dynamic error, StackTrace? stackTrace}) {
|
||||||
|
_logger.e(message, error: error, stackTrace: stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fatal(dynamic message, {dynamic error, StackTrace? stackTrace}) {
|
||||||
|
_logger.f(message, error: error, stackTrace: stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network logging
|
||||||
|
void logNetwork({
|
||||||
|
required String method,
|
||||||
|
required String url,
|
||||||
|
int? statusCode,
|
||||||
|
dynamic requestBody,
|
||||||
|
dynamic responseBody,
|
||||||
|
dynamic error,
|
||||||
|
}) {
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
buffer.writeln('🌐 Network Request:');
|
||||||
|
buffer.writeln(' Method: $method');
|
||||||
|
buffer.writeln(' URL: $url');
|
||||||
|
|
||||||
|
if (statusCode != null) {
|
||||||
|
buffer.writeln(' Status: $statusCode');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestBody != null) {
|
||||||
|
buffer.writeln(' Request Body: $requestBody');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseBody != null) {
|
||||||
|
buffer.writeln(' Response: $responseBody');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
buffer.writeln(' Error: $error');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
_logger.e(buffer.toString());
|
||||||
|
} else if (statusCode != null && statusCode >= 400) {
|
||||||
|
_logger.w(buffer.toString());
|
||||||
|
} else {
|
||||||
|
_logger.i(buffer.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performance logging
|
||||||
|
void logPerformance(String operation, Duration duration) {
|
||||||
|
final milliseconds = duration.inMilliseconds;
|
||||||
|
final message = '⏱️ Performance: $operation took ${milliseconds}ms';
|
||||||
|
|
||||||
|
if (milliseconds > 1000) {
|
||||||
|
_logger.w(message);
|
||||||
|
} else {
|
||||||
|
_logger.d(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation logging
|
||||||
|
void logNavigation(String from, String to) {
|
||||||
|
_logger.i('🧭 Navigation: $from → $to');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom log with tag
|
||||||
|
void logWithTag(String tag, dynamic message, {Level? level}) {
|
||||||
|
final taggedMessage = '[$tag] $message';
|
||||||
|
|
||||||
|
switch (level ?? Level.info) {
|
||||||
|
case Level.debug:
|
||||||
|
debug(taggedMessage);
|
||||||
|
break;
|
||||||
|
case Level.info:
|
||||||
|
info(taggedMessage);
|
||||||
|
break;
|
||||||
|
case Level.warning:
|
||||||
|
warning(taggedMessage);
|
||||||
|
break;
|
||||||
|
case Level.error:
|
||||||
|
error(taggedMessage);
|
||||||
|
break;
|
||||||
|
case Level.fatal:
|
||||||
|
fatal(taggedMessage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info(taggedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the logger (call this in app disposal if needed)
|
||||||
|
void close() {
|
||||||
|
_logger.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Custom log filter to control which logs are shown
|
||||||
|
class _CustomLogFilter extends LogFilter {
|
||||||
|
@override
|
||||||
|
bool shouldLog(LogEvent event) {
|
||||||
|
// In debug mode, show all logs
|
||||||
|
if (kDebugMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In release mode, only show warnings and above
|
||||||
|
return event.level.value >= Level.warning.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Custom console output for better control over log output
|
||||||
|
class _CustomConsoleOutput extends ConsoleOutput {
|
||||||
|
@override
|
||||||
|
void output(OutputEvent event) {
|
||||||
|
// You can customize how logs are output here
|
||||||
|
// For example, you could write to a file in addition to console
|
||||||
|
super.output(event);
|
||||||
|
|
||||||
|
// Example: Write errors to a file (uncomment if needed)
|
||||||
|
// if (event.level == Level.error || event.level == Level.fatal) {
|
||||||
|
// _writeToFile(event.lines.join('\n'));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example method for writing to file (implement if needed)
|
||||||
|
// Future<void> _writeToFile(String log) async {
|
||||||
|
// // Implementation for file writing
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global logger instance for easy access
|
||||||
|
final appLogger = AppLogger();
|
||||||
|
|
||||||
|
// Extension for convenient logging on any object
|
||||||
|
extension LoggerExtension on Object {
|
||||||
|
void logDebug([String? message]) {
|
||||||
|
appLogger.debug(message ?? toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void logInfo([String? message]) {
|
||||||
|
appLogger.info(message ?? toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void logWarning([String? message]) {
|
||||||
|
appLogger.warning(message ?? toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void logError([String? message, dynamic error, StackTrace? stackTrace]) {
|
||||||
|
appLogger.error(
|
||||||
|
message ?? toString(),
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-3
@@ -2,13 +2,21 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'core/routes/app_routes.dart';
|
||||||
|
import 'core/utils/logger.dart';
|
||||||
import 'core/utils/size_config.dart';
|
import 'core/utils/size_config.dart';
|
||||||
import 'data/repositories/auth_repository.dart';
|
import 'data/repositories/auth_repository.dart';
|
||||||
import 'data/services/auth_service.dart';
|
import 'data/services/auth_service.dart';
|
||||||
import 'view_models/auth_view_model.dart';
|
import 'view_models/auth_view_model.dart';
|
||||||
import 'views/login/login_screen.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
// Initialize the logger
|
||||||
|
appLogger.init();
|
||||||
|
|
||||||
|
// Log app start
|
||||||
|
appLogger.info('🚀 Starting TM App...');
|
||||||
|
// appLogger.logNavigation('Home', 'Login');
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
@@ -34,12 +42,12 @@ class MyApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
SizeConfig.init(context);
|
SizeConfig.init(context);
|
||||||
return MaterialApp(
|
return MaterialApp.router(
|
||||||
title: 'Flutter Demo',
|
title: 'Flutter Demo',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||||
),
|
),
|
||||||
home: LoginScreen(),
|
routerConfig: appRouter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +1,86 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../../core/constants/strings.dart';
|
import '../../../core/constants/strings.dart';
|
||||||
import '../../common/widgets/bottom_navigation.dart';
|
import '../../core/constants/colors.dart';
|
||||||
import '../../core/utils/size_config.dart';
|
import '../../core/utils/size_config.dart';
|
||||||
import 'widgets.dart';
|
import 'widgets.dart';
|
||||||
|
|
||||||
class Homescreen extends StatelessWidget {
|
class HomeScreen extends StatelessWidget {
|
||||||
const Homescreen({super.key});
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
body: _body(),
|
|
||||||
bottomNavigationBar: TenderBottomNavigation(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
SafeArea _body() {
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: ListView(
|
child: Scaffold(
|
||||||
children: [
|
backgroundColor: Colors.white,
|
||||||
Padding(
|
body: _body(context),
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
appBar: _appbar(),
|
||||||
24.0.w(),
|
|
||||||
26.0.h(),
|
|
||||||
24.0.w(),
|
|
||||||
24.0.h(),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_appbar(),
|
|
||||||
SizedBox(height: 32.0.h()),
|
|
||||||
_progressBarsRow(),
|
|
||||||
SizedBox(height: 32.0.h()),
|
|
||||||
_firstStatisticsRow(),
|
|
||||||
SizedBox(height: 8.0.h()),
|
|
||||||
_secondStatisticsRow(),
|
|
||||||
SizedBox(height: 32.0.h()),
|
|
||||||
_yourTenderText(),
|
|
||||||
SizedBox(height: 28.0.h()),
|
|
||||||
_bottomListView(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _appbar() {
|
Widget _body(BuildContext context) {
|
||||||
return Row(
|
return ListView(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Padding(
|
||||||
AppStrings.home,
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
style: TextStyle(
|
24.0.w(),
|
||||||
fontSize: 20,
|
26.0.h(),
|
||||||
fontWeight: FontWeight.bold,
|
24.0.w(),
|
||||||
color: Color(0xFF222222),
|
24.0.h(),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// _appbar(),
|
||||||
|
// SizedBox(height: 32.0.h()),
|
||||||
|
_progressBarsRow(),
|
||||||
|
SizedBox(height: 32.0.h()),
|
||||||
|
_firstStatisticsRow(),
|
||||||
|
SizedBox(height: 8.0.h()),
|
||||||
|
_secondStatisticsRow(),
|
||||||
|
SizedBox(height: 32.0.h()),
|
||||||
|
_yourTenderText(),
|
||||||
|
SizedBox(height: 28.0.h()),
|
||||||
|
_bottomListView(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Image.asset(
|
|
||||||
'assets/icons/tenderLogo.png',
|
|
||||||
width: 59.0.w(),
|
|
||||||
height: 28.0.h(),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreferredSize _appbar() {
|
||||||
|
return PreferredSize(
|
||||||
|
preferredSize: Size.fromHeight(56.0.h()),
|
||||||
|
child: Container(
|
||||||
|
color: AppColors.backgroundColor,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
|
||||||
|
height: 56.0.h(),
|
||||||
|
width: double.infinity,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppStrings.home,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Color(0xFF222222),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Image.asset(
|
||||||
|
'assets/icons/tenderLogo.png',
|
||||||
|
width: 59.0.w(),
|
||||||
|
height: 28.0.h(),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _progressBarsRow() {
|
Widget _progressBarsRow() {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class _ProgressBarColumnState extends State<ProgressBarColumn>
|
|||||||
// Initialize the animation controller
|
// Initialize the animation controller
|
||||||
_controller = AnimationController(
|
_controller = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
duration: Duration(milliseconds: 1500), // duration of the animation
|
duration: Duration(milliseconds: 2000), // duration of the animation
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a Tween animation from 0.0 to 0.75
|
// Create a Tween animation from 0.0 to 0.75
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../core/constants/colors.dart';
|
||||||
|
|
||||||
|
class ProfileScreen extends StatelessWidget {
|
||||||
|
const ProfileScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppColors.backgroundColor,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Profile'),
|
||||||
|
backgroundColor: AppColors.grey10,
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
body: const Center(
|
||||||
|
child: Text(
|
||||||
|
'Profile Screen',
|
||||||
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
|
|
||||||
import '../../common/widgets/bottom_navigation.dart';
|
|
||||||
import '../../core/constants/assets.dart';
|
|
||||||
import '../../core/constants/colors.dart';
|
import '../../core/constants/colors.dart';
|
||||||
import '../../core/utils/size_config.dart';
|
import '../../core/utils/size_config.dart';
|
||||||
import 'models/tender_model.dart';
|
import 'widgets/main_tenders_slider.dart';
|
||||||
import 'widgets/widgets.dart';
|
import 'widgets/widgets.dart';
|
||||||
|
|
||||||
class TendersScreen extends StatefulWidget {
|
class TendersScreen extends StatefulWidget {
|
||||||
@@ -39,59 +36,17 @@ class _TendersScreenState extends State<TendersScreen>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.backgroundColor,
|
backgroundColor: AppColors.backgroundColor,
|
||||||
bottomNavigationBar: TenderBottomNavigation(),
|
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(vertical: 24.0.h()),
|
||||||
// horizontal: 24.0.w(),
|
|
||||||
vertical: 24.0.h(),
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
TenderAppBar(),
|
TenderAppBar(),
|
||||||
SizedBox(height: 24.0.h()),
|
SizedBox(height: 24.0.h()),
|
||||||
|
// MainTabBar(controller: controller),
|
||||||
MainTabBar(controller: controller),
|
|
||||||
SizedBox(height: 24.0.h()),
|
SizedBox(height: 24.0.h()),
|
||||||
Stack(
|
MainTendersSlider(),
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 762.0.h(),
|
|
||||||
child: PageView.builder(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
itemCount: sampleTenders.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final tender = sampleTenders[index];
|
|
||||||
return TenderCard(
|
|
||||||
date: tender.date,
|
|
||||||
deadline: tender.deadline,
|
|
||||||
title: tender.title,
|
|
||||||
description: tender.description,
|
|
||||||
tenderId: tender.tenderId,
|
|
||||||
location: tender.location,
|
|
||||||
country: tender.country,
|
|
||||||
matchPercentage: tender.matchPercentage,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: 237.0.h(),
|
|
||||||
bottom: 366.0.h(),
|
|
||||||
left: 9.0.w(),
|
|
||||||
right: 9.0.w(),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
SvgPicture.asset(AssetsManager.arrowCircleLeft),
|
|
||||||
SvgPicture.asset(AssetsManager.arrowCircleRight),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
|
import '../../../core/constants/assets.dart';
|
||||||
|
import '../../../core/utils/size_config.dart';
|
||||||
|
import '../models/tender_model.dart';
|
||||||
|
import 'tender_card.dart';
|
||||||
|
|
||||||
|
class MainTendersSlider extends StatefulWidget {
|
||||||
|
const MainTendersSlider({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MainTendersSlider> createState() => _MainTendersSliderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MainTendersSliderState extends State<MainTendersSlider> {
|
||||||
|
final PageController pageController = PageController();
|
||||||
|
|
||||||
|
List<Tender> likedTenders = [];
|
||||||
|
List<Tender> dislikedTenders = [];
|
||||||
|
|
||||||
|
void _onDislike(Tender tender) {
|
||||||
|
if (likedTenders.contains(tender)) {
|
||||||
|
likedTenders.remove(tender);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dislikedTenders.contains(tender)) {
|
||||||
|
dislikedTenders.add(tender);
|
||||||
|
} else {
|
||||||
|
dislikedTenders.remove(tender);
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onLike(Tender tender) {
|
||||||
|
if (dislikedTenders.contains(tender)) {
|
||||||
|
dislikedTenders.remove(tender);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!likedTenders.contains(tender)) {
|
||||||
|
likedTenders.add(tender);
|
||||||
|
} else {
|
||||||
|
likedTenders.remove(tender);
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToPreviousPage() {
|
||||||
|
pageController.animateToPage(
|
||||||
|
pageController.page!.toInt() - 1,
|
||||||
|
duration: Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOutCubic,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToNextPage() {
|
||||||
|
pageController.animateToPage(
|
||||||
|
pageController.page!.toInt() + 1,
|
||||||
|
duration: Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOutCubic,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 762.0.h(),
|
||||||
|
child: PageView.builder(
|
||||||
|
controller: pageController,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: sampleTenders.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final tender = sampleTenders[index];
|
||||||
|
return TenderCard(
|
||||||
|
date: tender.date,
|
||||||
|
deadline: tender.deadline,
|
||||||
|
title: tender.title,
|
||||||
|
description: tender.description,
|
||||||
|
tenderId: tender.tenderId,
|
||||||
|
location: tender.location,
|
||||||
|
country: tender.country,
|
||||||
|
matchPercentage: tender.matchPercentage,
|
||||||
|
isLiked: likedTenders.contains(tender),
|
||||||
|
isDisliked: dislikedTenders.contains(tender),
|
||||||
|
onDislike: () => _onDislike(tender),
|
||||||
|
onLike: () => _onLike(tender),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 237.0.h(),
|
||||||
|
bottom: 366.0.h(),
|
||||||
|
left: 9.0.w(),
|
||||||
|
right: 9.0.w(),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _goToPreviousPage,
|
||||||
|
child: SvgPicture.asset(AssetsManager.arrowCircleLeft),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _goToNextPage,
|
||||||
|
child: SvgPicture.asset(AssetsManager.arrowCircleRight),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
import '../../../core/constants/assets.dart';
|
import '../../../core/constants/assets.dart';
|
||||||
|
import '../../../core/constants/colors.dart';
|
||||||
import '../../../core/utils/size_config.dart';
|
import '../../../core/utils/size_config.dart';
|
||||||
|
|
||||||
class TenderActionButtons extends StatelessWidget {
|
class TenderActionButtons extends StatelessWidget {
|
||||||
@@ -30,7 +31,13 @@ class TenderActionButtons extends StatelessWidget {
|
|||||||
// Dislike button
|
// Dislike button
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: onDislike,
|
onTap: onDislike,
|
||||||
child: SvgPicture.asset(AssetsManager.dislike),
|
child: SvgPicture.asset(
|
||||||
|
AssetsManager.dislike,
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
isDisliked ? AppColors.errorColor : AppColors.grey50,
|
||||||
|
BlendMode.srcATop,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(width: 16.0.w()),
|
SizedBox(width: 16.0.w()),
|
||||||
@@ -44,7 +51,13 @@ class TenderActionButtons extends StatelessWidget {
|
|||||||
// Like button
|
// Like button
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: onLike,
|
onTap: onLike,
|
||||||
child: SvgPicture.asset(AssetsManager.like),
|
child: SvgPicture.asset(
|
||||||
|
AssetsManager.like,
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
isLiked ? AppColors.successColor : AppColors.grey50,
|
||||||
|
BlendMode.srcATop,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,12 +16,16 @@ dependencies:
|
|||||||
freezed: ^3.1.0
|
freezed: ^3.1.0
|
||||||
freezed_annotation: ^3.1.0
|
freezed_annotation: ^3.1.0
|
||||||
flutter_svg: ^2.2.0
|
flutter_svg: ^2.2.0
|
||||||
|
go_router: ^16.0.0
|
||||||
|
logger: ^2.6.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
build_runner: ^2.5.4
|
build_runner: ^2.5.4
|
||||||
|
go_router_builder: ^3.0.0
|
||||||
|
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
Reference in New Issue
Block a user