Merge pull request 'go router added' (#9) from router_add into main

Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/9
This commit is contained in:
a.ghabeli
2025-08-05 08:23:55 +03:30
14 changed files with 887 additions and 108 deletions
+1
View File
@@ -22,6 +22,7 @@ class AppColors {
static const Color warningColor = Color(0xFFFFC107);
static const Color grey = Color(0xFF555555);
static const Color grey50 = Color(0xFF9E9E9E);
static const Color grey60 = Color(0xFF777777);
static const Color grey70 = Color(0xFF444444);
static const Color grey80 = Color(0xFF222222);
+136
View File
@@ -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();
}
}
+136
View File
@@ -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);
}
+101
View File
@@ -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),
),
),
],
),
),
),
);
}
}
+70
View File
@@ -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);
}
}
+205
View File
@@ -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
View File
@@ -2,13 +2,21 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'core/routes/app_routes.dart';
import 'core/utils/logger.dart';
import 'core/utils/size_config.dart';
import 'data/repositories/auth_repository.dart';
import 'data/services/auth_service.dart';
import 'view_models/auth_view_model.dart';
import 'views/login/login_screen.dart';
void main() {
// Initialize the logger
appLogger.init();
// Log app start
appLogger.info('🚀 Starting TM App...');
// appLogger.logNavigation('Home', 'Login');
runApp(
MultiProvider(
providers: [
@@ -34,12 +42,12 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
SizeConfig.init(context);
return MaterialApp(
return MaterialApp.router(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: LoginScreen(),
routerConfig: appRouter,
);
}
}
+23 -14
View File
@@ -1,25 +1,26 @@
import 'package:flutter/material.dart';
import '../../../core/constants/strings.dart';
import '../../common/widgets/bottom_navigation.dart';
import '../../core/constants/colors.dart';
import '../../core/utils/size_config.dart';
import 'widgets.dart';
class Homescreen extends StatelessWidget {
const Homescreen({super.key});
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
return SafeArea(
child: Scaffold(
backgroundColor: Colors.white,
body: _body(),
bottomNavigationBar: TenderBottomNavigation(),
body: _body(context),
appBar: _appbar(),
),
);
}
SafeArea _body() {
return SafeArea(
child: ListView(
Widget _body(BuildContext context) {
return ListView(
children: [
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
@@ -31,8 +32,8 @@ class Homescreen extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_appbar(),
SizedBox(height: 32.0.h()),
// _appbar(),
// SizedBox(height: 32.0.h()),
_progressBarsRow(),
SizedBox(height: 32.0.h()),
_firstStatisticsRow(),
@@ -46,12 +47,18 @@ class Homescreen extends StatelessWidget {
),
),
],
),
);
}
Widget _appbar() {
return Row(
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(
@@ -69,6 +76,8 @@ class Homescreen extends StatelessWidget {
fit: BoxFit.cover,
),
],
),
),
);
}
+1 -1
View File
@@ -30,7 +30,7 @@ class _ProgressBarColumnState extends State<ProgressBarColumn>
// Initialize the animation controller
_controller = AnimationController(
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
+25
View File
@@ -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),
),
),
);
}
}
+4 -49
View File
@@ -1,11 +1,8 @@
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/utils/size_config.dart';
import 'models/tender_model.dart';
import 'widgets/main_tenders_slider.dart';
import 'widgets/widgets.dart';
class TendersScreen extends StatefulWidget {
@@ -39,59 +36,17 @@ class _TendersScreenState extends State<TendersScreen>
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
bottomNavigationBar: TenderBottomNavigation(),
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(
// horizontal: 24.0.w(),
vertical: 24.0.h(),
),
padding: EdgeInsets.symmetric(vertical: 24.0.h()),
child: Column(
children: [
TenderAppBar(),
SizedBox(height: 24.0.h()),
MainTabBar(controller: controller),
// MainTabBar(controller: controller),
SizedBox(height: 24.0.h()),
Stack(
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),
],
),
),
],
),
MainTendersSlider(),
],
),
),
@@ -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 '../../../core/constants/assets.dart';
import '../../../core/constants/colors.dart';
import '../../../core/utils/size_config.dart';
class TenderActionButtons extends StatelessWidget {
@@ -30,7 +31,13 @@ class TenderActionButtons extends StatelessWidget {
// Dislike button
GestureDetector(
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()),
@@ -44,7 +51,13 @@ class TenderActionButtons extends StatelessWidget {
// Like button
GestureDetector(
onTap: onLike,
child: SvgPicture.asset(AssetsManager.like),
child: SvgPicture.asset(
AssetsManager.like,
colorFilter: ColorFilter.mode(
isLiked ? AppColors.successColor : AppColors.grey50,
BlendMode.srcATop,
),
),
),
],
);
+4
View File
@@ -16,12 +16,16 @@ dependencies:
freezed: ^3.1.0
freezed_annotation: ^3.1.0
flutter_svg: ^2.2.0
go_router: ^16.0.0
logger: ^2.6.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
build_runner: ^2.5.4
go_router_builder: ^3.0.0
flutter: