Initial commit

This commit is contained in:
jahongireshonqulov
2025-10-18 09:40:06 +05:00
commit 1bf3e41abe
352 changed files with 16315 additions and 0 deletions

43
lib/app.dart Normal file
View File

@@ -0,0 +1,43 @@
import 'package:cargocalculaterapp/router/app_routes.dart';
import 'package:cargocalculaterapp/router/name_routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'core/app_bloc/app_bloc.dart';
import 'core/theme/theme_data.dart';
import 'generated/l10n.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<AppBloc, AppState>(
builder: (context, state) {
return KeyboardDismissOnTap(
child: MaterialApp(
navigatorKey: rootNavigatorKey,
initialRoute: Routes.initial,
onGenerateRoute: AppRoutes.onGenerateRoute,
title: 'CargoApp',
debugShowCheckedModeBanner: false,
themeMode: ThemeMode.values.byName(
state.themeMode?.name ?? ThemeMode.light.name,
),
theme: state.themeMode == ThemeMode.light ? lightTheme : darkTheme,
darkTheme: darkTheme,
locale: Locale(state.appLocale),
supportedLocales: AppLocalization.delegate.supportedLocales,
localizationsDelegates: const [
AppLocalization.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
),
);
},
);
}
}

View File

@@ -0,0 +1,130 @@
import 'package:dio/dio.dart';
import '../core/entity/name/name_entity.dart';
class Constants {
static String baseUrl = "https://ctrans-api.felix-its.uz";
}
class Validations {
static const someThingWentWrong = 'Something went wrong!';
}
class DioConstants {
static final options = Options(
contentType: 'application/json',
sendTimeout: const Duration(seconds: 30),
);
}
class AppKeys {
static const locale = 'locale';
static const accessToken = 'access_token';
static const firstName = 'firstname';
static const lastName = 'lastname';
static const email = 'email';
static const isFirst = "isFirst";
static const gender = "gender";
static const userId = "userId";
static const dateOfBirth = "dateOfBirth";
static const refreshToken = "refreshToken";
static const phone = "phone";
static const hasProfile = "hasProfile";
static const isProd = 'isProd';
static const page = 'page';
static const limit = 'limit';
static const status = 'status';
static const sort = 'sort';
static const themeMode = 'themeMode';
static const ucode = 'UCode';
}
class AppConst {
static const limit = 20;
static const reviewLimit = 10;
static const all = "all";
static const status = "status";
static const waiting = "WAITING";
static const partReceived = "PART_RECEIVED";
static const fullReceived = "FULL_RECEIVED";
static const shipped = "SHIPPED";
static const leftChine = "LEFT_CHINA";
static const enteredUzbekistan = "ENTERED_UZBEKISTAN";
static const tashkentWarehouse = "TASHKENT_WAREHOUSE";
static const customsClearance = "CUSTOMS_CLEARANCE";
static const delivered = "DELIVERED";
static const clickAction = "page_link";
static const Map<String, NameEntity> statusList = {
all: NameEntity(uz: "Barchasi", ru: "Все", zh: "全部"),
waiting: NameEntity(uz: "Kutilmoqda", ru: "Ожидание", zh: "等待中"),
partReceived: NameEntity(
uz: "Yarim keldi",
ru: "Частичный прибыл",
zh: "部分接收",
),
fullReceived: NameEntity(
uz: "To'liq keldi",
ru: "Полный прибл",
zh: "已全部接收",
),
shipped: NameEntity(uz: "Yuborildi", ru: "Отправлено", zh: "已发货"),
leftChine: NameEntity(
uz: "Xitoydan chiqdi",
ru: "Покинул Китай",
zh: "离开中国",
),
enteredUzbekistan: NameEntity(
uz: "O'zbekistonga kirdi",
ru: "Прибыл в Узбекистан",
zh: "进入乌兹别克斯坦",
),
tashkentWarehouse: NameEntity(
uz: "Toshkent omborida",
ru: "На складе в Ташкенте",
zh: "塔什干仓库",
),
customsClearance: NameEntity(
uz: "Bojxona rasmiylashtiruvi",
ru: "Таможенное оформление",
zh: "清关中",
),
delivered: NameEntity(uz: "Yetkazib berildi", ru: "Доставлено", zh: "已送达"),
};
static const warehouseOptions = {
"1": NameEntity(uz: "Yiwu", ru: "Иу", zh: "义乌"),
"2": NameEntity(uz: "Foshan", ru: "Фошань", zh: "佛山"),
"3": NameEntity(uz: "Qashqar", ru: "Кашгар", zh: "喀什"),
"4": NameEntity(uz: "Hargos", ru: "Харгос", zh: "哈戈斯"),
};
}
class Urls {
static const login = '/api/v1/auth/login/user';
static const signUp = '/api/v1/auth/register';
static const getProfile = '/api/v1/mobile/me';
static const updateProfile = '/api/v1/mobile/me/{id}';
static const verify = '/api/v1/auth/verify-sms';
static const orderList = '/api/v1/mobile/orders';
static const calculatePrice = '/api/v1/leads/calculate';
static const lead = '/api/v1/leads';
static const addFcm = '/api/v1/user/update-sfm-token';
static const comment = '/api/v1/testimonials';
static const notification = '/api/v1/notifications';
static const notificationRead = '/api/v1/notifications/mark-as-read';
static const banners = '/api/v1/slider';
static const delete = '/api/v1/mobile/me';
}
class CacheKeys {
static const profile = "profile.cache";
}
class Warnings {
Warnings._();
static const someThingWentWrong = 'Something went wrong!';
}
class RegExConst {
static final phoneRegex = RegExp(r'^\+?\d{12}$');
static final emailRegex = RegExp(r'^[a-zA-Z0-9._%+-]+@gmail\.com$');
}

View File

@@ -0,0 +1,46 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '../../injector_container.dart';
import '../local_source/local_source.dart';
import '../theme/app_theme.dart';
part 'app_event.dart';
part 'app_state.dart';
class AppBloc extends Bloc<AppEvent, AppState> {
AppBloc()
: super(
AppState(
lightTheme: lightThemes.values.first,
darkTheme: darkThemes.values.first,
themeMode: ThemeMode.values.byName(sl<LocalSource>().getThemeMode()),
appLocale: sl<LocalSource>().getLocale(),
),
) {
on<AppThemeSwitchLight>(_switchToLightHandler);
on<AppThemeSwitchDark>(_switchToDarkHandler);
on<AppChangeLocale>(_changeLocale);
}
void _changeLocale(AppChangeLocale event, Emitter<AppState> emit) {
sl<LocalSource>().setLocale(event.appLocale);
emit(state.copyWith(appLocale: event.appLocale));
}
void _switchToLightHandler(
AppThemeSwitchLight event,
Emitter<AppState> emit,
) {
sl<LocalSource>().setThemeMode(ThemeMode.light.name);
emit(
state.copyWith(lightTheme: event.lightTheme, themeMode: ThemeMode.light),
);
}
void _switchToDarkHandler(AppThemeSwitchDark event, Emitter<AppState> emit) {
sl<LocalSource>().setThemeMode(ThemeMode.dark.name);
emit(state.copyWith(darkTheme: event.darkTheme, themeMode: ThemeMode.dark));
}
}

View File

@@ -0,0 +1,32 @@
part of 'app_bloc.dart';
sealed class AppEvent extends Equatable {
const AppEvent();
}
final class AppThemeSwitchLight extends AppEvent {
final ThemeData lightTheme;
const AppThemeSwitchLight({required this.lightTheme});
@override
List<Object?> get props => [lightTheme];
}
final class AppThemeSwitchDark extends AppEvent {
final ThemeData darkTheme;
const AppThemeSwitchDark({required this.darkTheme});
@override
List<Object?> get props => [darkTheme];
}
final class AppChangeLocale extends AppEvent {
final String appLocale;
const AppChangeLocale(this.appLocale);
@override
List<Object?> get props => [appLocale];
}

View File

@@ -0,0 +1,37 @@
part of 'app_bloc.dart';
class AppState extends Equatable {
final ThemeData? lightTheme;
final ThemeData? darkTheme;
final ThemeMode? themeMode;
final String appLocale;
const AppState({
required this.appLocale,
this.lightTheme,
this.darkTheme,
this.themeMode,
});
AppState copyWith({
ThemeData? lightTheme,
ThemeData? darkTheme,
ThemeMode? themeMode,
String? appLocale,
}) {
return AppState(
lightTheme: lightTheme ?? this.lightTheme,
darkTheme: darkTheme ?? this.darkTheme,
themeMode: themeMode ?? this.themeMode,
appLocale: appLocale ?? this.appLocale,
);
}
@override
List<Object?> get props => [
lightTheme,
darkTheme,
themeMode,
appLocale,
];
}

View File

@@ -0,0 +1,12 @@
import 'package:equatable/equatable.dart';
class NameEntity extends Equatable {
final String? uz;
final String? ru;
final String? zh;
const NameEntity({this.uz, this.ru, this.zh});
@override
List<Object?> get props => [uz, ru, zh];
}

View File

@@ -0,0 +1,26 @@
class ServerException implements Exception {
final String message;
ServerException({required this.message});
factory ServerException.fromJson(Map<String, dynamic> json) {
return ServerException(message: json['message']);
}
}
class NoInternetException implements Exception {}
class CacheException implements Exception {
final String message;
CacheException({required this.message});
@override
String toString() {
return message;
}
}
class MultipleOrdersException implements Exception {
MultipleOrdersException();
}

View File

@@ -0,0 +1,33 @@
import 'package:equatable/equatable.dart';
abstract class Failure extends Equatable {}
class ServerFailure extends Failure {
final String message;
ServerFailure({required this.message});
@override
List<Object?> get props => [message];
}
class NoInternetFailure extends Failure {
@override
List<Object?> get props => [];
}
class CacheFailure extends Failure {
final String message;
CacheFailure({required this.message});
@override
List<Object?> get props => [message];
}
class MultipleOrderFailure extends Failure {
MultipleOrderFailure();
@override
List<Object?> get props => [];
}

View File

@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
import '../theme/colors/app_colors.dart';
import '../theme/theme_text_style.dart';
extension BuildContextExt on BuildContext {
ThemeTextStyles get text => Theme.of(this).extension<ThemeTextStyles>()!;
AppThemeColors get color => Theme.of(this).extension<AppThemeColors>()!;
bool get isDarkMode => Theme.of(this).brightness == Brightness.dark;
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
extension HexColor on Color {
/// String is in the format "aabbcc" or "ffaabbcc" with an optional leading "#".
static Color fromHex(String hexString) {
final buffer = StringBuffer();
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
buffer.write(hexString.replaceFirst('#', ''));
return Color(int.parse(buffer.toString(), radix: 16));
}
}

View File

@@ -0,0 +1,13 @@
extension SliverCountExtension on int {
int get doubleTheListCount => (this * 2) - 1;
int get exactIndex => (this ~/ 2);
int get lastIndex => (this * 2) - 2;
}
extension VersionParsing on String {
int toVersion() {
return int.tryParse(replaceAll(".", "")) ?? 0;
}
}

View File

@@ -0,0 +1,12 @@
import 'package:flutter/cupertino.dart';
import '../../router/app_routes.dart';
extension ScreenSize on double {
double get w {
return this / 375 * MediaQuery.sizeOf(rootNavigatorKey.currentContext!).width;
}
double get h {
return this / 812 * MediaQuery.sizeOf(rootNavigatorKey.currentContext!).height;
}
}

View File

@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import '../../router/app_routes.dart';
import '../entity/name/name_entity.dart';
import '../models/name.dart';
class Functions {
static String getTranslatedItem(NameEntity? name, BuildContext context) {
var local = Localizations.localeOf(context);
switch (local.languageCode) {
case 'uz':
{
return name?.uz ?? " ";
}
case "ru":
{
return name?.ru ?? " ";
}
case "zh":
{
return name?.zh ?? " ";
}
default:
{
return name?.uz ?? " ";
}
}
}
static String getTranslatedNameModel(Name? name, BuildContext context) {
var local = Localizations.localeOf(context);
switch (local.languageCode) {
case 'uz':
{
return name?.uz ?? " ";
}
case "ru":
{
return name?.ru ?? " ";
}
case "zh":
{
return name?.zh ?? " ";
}
default:
{
return name?.uz ?? " ";
}
}
}
static void showErrorSnackBar(String message) {
final snackBar = SnackBar(
backgroundColor: Colors.red,
content: Row(
children: [
const Icon(Icons.error, color: Colors.white),
const SizedBox(width: 8),
Expanded(
child: Text(
message,
style: const TextStyle(color: Colors.white),
),
),
],
),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 3),
);
ScaffoldMessenger.of(
rootNavigatorKey.currentContext!,
).showSnackBar(snackBar);
}
}

View File

@@ -0,0 +1,13 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
class Debouncer {
static Timer? _timer;
static run(VoidCallback action) {
if (null != _timer) {
_timer?.cancel();
}
_timer = Timer(const Duration(milliseconds: 600), action);
}
}

View File

@@ -0,0 +1,37 @@
import 'package:flutter/services.dart';
class CustomTextInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
var re = RegExp(('[^0-9]'));
if (newValue.selection.baseOffset == 0 && newValue.text.isNotEmpty) {
return newValue;
}
if (newValue.text.isEmpty) {
return newValue.copyWith(text: '');
} else if (newValue.text.compareTo(oldValue.text) != 0) {
final selectionIndexFromTheRight =
newValue.text.length - newValue.selection.extentOffset;
final reversedText =
String.fromCharCodes(newValue.text.runes.toList().reversed);
final chars = reversedText.replaceAll(re, '').split('');
var reversedNewString = '';
for (var i = 0; i < chars.length; i++) {
if (i % 3 == 0 && i != 0) reversedNewString += ' ';
reversedNewString += chars[i];
}
final newString =
String.fromCharCodes(reversedNewString.runes.toList().reversed);
return TextEditingValue(
text: newString,
selection: TextSelection.collapsed(
offset: newString.length - selectionIndexFromTheRight,
),
);
} else {
return newValue;
}
}
}

View File

@@ -0,0 +1,64 @@
import 'package:flutter/services.dart';
class MaskedTextInputFormatter extends TextInputFormatter {
final String mask;
final String separator;
MaskedTextInputFormatter({
required this.mask,
required this.separator,
});
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
String text = newValue.text;
String newText = newValue.toJSON()['text'].toString();
if (text.isNotEmpty) {
if (text.length > oldValue.text.length) {
if (text.length > mask.length) return oldValue;
if (text.length < mask.length && mask[text.length - 1] == separator) {
return TextEditingValue(
text:
'${oldValue.text}$separator${text.substring(text.length - 1)}',
selection: TextSelection.collapsed(
offset: newValue.selection.end + 1,
),
);
}
if (text.length == mask.replaceAll(separator, "").length &&
oldValue.text.isEmpty) {
String newText = '';
int t = 0;
for (int i = 0; i < text.length; i++) {
if (mask[i + t] == separator) {
newText += separator;
t++;
}
newText += text[i];
}
return TextEditingValue(
text: newText,
selection: TextSelection.collapsed(offset: newText.length),
);
}
} else {
if (newText.substring(newText.length - 1) == separator) {
return TextEditingValue(
text: newValue.text.substring(0, newValue.text.length - 1),
selection: TextSelection.collapsed(
offset: newValue.selection.end - 1,
),
);
}
}
return TextEditingValue(
text: newText,
selection: TextSelection.collapsed(
offset: newValue.selection.end,
),
);
}
return newValue;
}
}

View File

@@ -0,0 +1,134 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import '../../constants/constants.dart';
class LocalSource {
final Box<dynamic> box;
LocalSource(this.box);
Future<void> clear() async {
await box.clear();
}
Future<void> setLocale(String locale) async {
await box.put(AppKeys.locale, locale);
}
String getLocale() {
return box.get(AppKeys.locale, defaultValue: "uz") ?? "uz";
}
void setAccessToken(String accessToken) {
box.put(AppKeys.accessToken, accessToken);
}
String? getAccessToken() {
return box.get(AppKeys.accessToken);
}
void setFirstName(String firstName) {
box.put(AppKeys.firstName, firstName);
}
String? getFirstName() {
return box.get(AppKeys.firstName);
}
void setLastName(String lastName) {
box.put(AppKeys.lastName, lastName);
}
String? getLastName() {
return box.get(AppKeys.lastName);
}
void setEmail(String email) {
box.put(AppKeys.email, email);
}
String? getEmail() {
return box.get(AppKeys.email);
}
void setIsFirstEnter(bool isFirst) {
box.put(AppKeys.isFirst, isFirst);
}
bool getIsFirstEnter() {
return box.get(AppKeys.isFirst) ?? true;
}
void setUserId(String id) {
box.put(AppKeys.userId, id);
}
String getUserId() {
return box.get(AppKeys.userId) ?? "";
}
void setDateOfBirth(String id) {
box.put(AppKeys.dateOfBirth, id);
}
String getDateOfBirth() {
return box.get(AppKeys.dateOfBirth) ?? "";
}
void setRefreshToken(String id) {
box.put(AppKeys.refreshToken, id);
}
String getRefreshToken() {
return box.get(AppKeys.refreshToken) ?? "";
}
void setPhone(String id) {
box.put(AppKeys.phone, id);
}
String getPhone() {
return box.get(AppKeys.phone) ?? "";
}
void setHasProfile(bool id) {
box.put(AppKeys.hasProfile, id);
}
bool getHasProfile() {
return box.get(AppKeys.hasProfile) ?? false;
}
void setGender(String id) {
box.put(AppKeys.gender, id);
}
String getGender() {
return box.get(AppKeys.gender) ?? "";
}
void setIsProd(bool isProd) {
box.put(AppKeys.isProd, isProd);
}
bool getIsProd() {
return box.get(AppKeys.isProd) ?? true;
}
void setThemeMode(String mode) {
box.put(AppKeys.themeMode, mode);
}
String getThemeMode() {
return box.get(AppKeys.themeMode) ?? ThemeMode.light.name;
}
void setUCode(String mode) {
box.put(AppKeys.ucode, mode);
}
String getUCode() {
return box.get(AppKeys.ucode) ?? "";
}
}

24
lib/core/models/name.dart Normal file
View File

@@ -0,0 +1,24 @@
import 'package:equatable/equatable.dart';
class Name extends Equatable {
const Name({this.uz, this.ru, this.zh});
factory Name.fromJson(dynamic json) {
return Name(uz: json['uz'], ru: json['ru'], zh: json['zh']);
}
final String? uz;
final String? ru;
final String? zh;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['uz'] = uz;
map['ru'] = ru;
map['zh'] = zh;
return map;
}
@override
List<Object?> get props => [uz, ru, zh];
}

View File

@@ -0,0 +1,14 @@
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
abstract class NetworkInfo {
Future<bool> get isConnected;
}
class NetworkInfoImpl implements NetworkInfo{
final InternetConnection internetConnectionCheckerPlus;
NetworkInfoImpl(this.internetConnectionCheckerPlus);
@override
Future<bool> get isConnected => internetConnectionCheckerPlus.hasInternetAccess;
}

View File

@@ -0,0 +1,121 @@
import 'package:flutter/material.dart';
import 'colors/app_colors.dart';
class AppTextStyles {
AppTextStyles._();
static const profileCategory = TextStyle(
color: LightThemeColors.textColor,
fontSize: 16,
fontWeight: FontWeight.w500,
);
static const titleBig = TextStyle(
color: LightThemeColors.textColor,
fontSize: 24,
fontWeight: FontWeight.w700,
);
static const authTitle = TextStyle(
color: LightThemeColors.textColor,
fontSize: 20,
fontWeight: FontWeight.w700,
);
static const timer = TextStyle(
color: ThemeColors.timerRed,
fontSize: 12,
fontWeight: FontWeight.w400,
);
static const categoryName = TextStyle(
color: LightThemeColors.textColor,
fontSize: 14,
fontWeight: FontWeight.w500,
height: 1,
letterSpacing: 0,
);
static const bigTitle = TextStyle(
color: LightThemeColors.textColor,
fontSize: 20,
fontWeight: FontWeight.w600,
);
static const orderTimer = TextStyle(
color: ThemeColors.timerRed,
fontSize: 16,
fontWeight: FontWeight.w400,
);
static const timerStyle = TextStyle(
color: ThemeColors.timerRed,
fontSize: 16,
fontWeight: FontWeight.w400,
);
static const saleRed = TextStyle(
color: ThemeColors.timerRed,
fontSize: 16,
fontWeight: FontWeight.w700,
);
static const timerDesc = TextStyle(
color: ThemeColors.timerRed,
fontSize: 16,
fontWeight: FontWeight.w600,
);
static const statusText = TextStyle(
color: LightThemeColors.buttonColorText,
fontSize: 13,
fontWeight: FontWeight.w600,
);
static const language = TextStyle(
color: LightThemeColors.textColor,
fontSize: 14,
fontWeight: FontWeight.w400,
);
static const languageBig = TextStyle(
color: LightThemeColors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
letterSpacing: 0,
);
static const authDesc = TextStyle(
color: LightThemeColors.secondaryText,
fontSize: 14,
fontWeight: FontWeight.w400,
);
static const timerBlue = TextStyle(
color: LightThemeColors.primaryColor,
fontSize: 14,
fontWeight: FontWeight.w500,
);
static const orderListTitle = TextStyle(
color: LightThemeColors.black40Transparent,
fontSize: 10,
fontWeight: FontWeight.w500,
height: 1,
);
static const status = TextStyle(
color: ThemeColors.green173text,
fontSize: 14,
fontWeight: FontWeight.w600,
height: 1,
letterSpacing: 0,
);
static const orderTitle = TextStyle(
color: LightThemeColors.textColor,
fontSize: 16,
fontWeight: FontWeight.w700,
);
static const statusNumber = TextStyle(
color: LightThemeColors.statusNumber,
fontSize: 12,
fontWeight: FontWeight.w400,
);
static const statusDesc = TextStyle(
color: LightThemeColors.textColor,
fontSize: 12,
fontWeight: FontWeight.w600,
);
static const secondaryText14 = TextStyle(
color: LightThemeColors.lightSecondary,
fontSize: 14,
fontWeight: FontWeight.w500,
);
}

View File

@@ -0,0 +1,14 @@
import 'package:cargocalculaterapp/core/theme/theme_data.dart';
import 'package:flutter/material.dart';
enum LightThemes { violetTheme }
enum DarkThemes { violetTheme }
Map<LightThemes, ThemeData> lightThemes = {
LightThemes.violetTheme: lightTheme,
};
Map<DarkThemes, ThemeData> darkThemes = {
DarkThemes.violetTheme: darkTheme,
};

View File

@@ -0,0 +1,273 @@
import 'package:flutter/material.dart';
class ThemeColors {
static const timerRed = Color(0xFFCF0000);
static const successInputSMS = Color(0xFF36B82C);
static const warningSurface = Color(0xFFFFEFE4);
static const green173Light = Color(0xFFC7FCF6);
static const green191Light = Color(0xFFE9FBFF);
static const warning = Color(0xFFEE5800);
static const green173text = Color(0xFF02685E);
static const green191text = Color(0xFF00668F);
}
class LightThemeColors extends ThemeColors {
static const dividerColor = Color(0x0dFFFFFF);
static const accentColor = Color(0xFF4275ba); // Neon Yellow
static const darkBackground = Color(0xFF171717); // Dark Grey
static const grey = Color(0xFF242424); // Grey
static const midGrey = Color(0xFF474747); // Mid Grey
static const lightGrey = Color(0xFF747474); // Light Grey
static const white = Color(0xFFffffff);
static const white60Transparent = Color(0x99FFFFFF);
static const white30Transparent = Color(0x4dFFFFFF);
static const white10Transparent = Color(0x1aFFFFFF);
static const white20Transparent = Color(0x33FFFFFF);
static const white40Transparent = Color(0x66FFFFFF);
static const errorColor = Color(0xFFCF0000);
///shimmer
static const backgroundGray = Color(0x80FFFFFF);
static const shimmerHighlight = Color(0xffffffff);
static const statusNumber = Color(0xff495057);
///new colors
static const primaryColor = Color(0xff0074B0);
static const textColor = Color(0xFF0C0C0C);
static const secondaryText = Color(0xFF85938E);
static const scaffoldBackgroundColor = Color(0xFFffffff);
static const borderColor = Color(0xFF707479);
static const edittextBackground = Color(0xFFF8FBFC);
static const buttonColorText = Color(0xffffffff);
static const disabledColor = Color(0x331783B2);
static const iconBackground = Color(0xffE3F3F6);
static const lightSecondary = Color(0xff858D93);
static const grayBackground = Color(0xffF8FBFC);
static const statusBackground = Color(0xffffffff);
static const lightBorder = Color(0xffE3E3E3);
static const black40Transparent = Color(0x66000000);
static const statusIconBackground = Color(0xffF1F3F5);
}
class DarkThemeColors {
static const dividerColor = Color(0x0dFFFFFF);
static const accentColor = Color(0xFF4275ba); // Neon Yellow
static const darkBackground = Color(0xFF171717); // Dark Grey
static const grey = Color(0xFF242424); // Grey
static const midGrey = Color(0xFF474747); // Mid Grey
static const lightGrey = Color(0xFF747474); // Light Grey
static const white = Colors.white;
static const white60Transparent = Color(0x99FFFFFF);
static const white30Transparent = Color(0x4dFFFFFF);
static const white10Transparent = Color(0x1aFFFFFF);
static const errorColor = Color(0xFFCF0000);
///new colors
static const primaryColor = Color(0xff1783B2);
static const textColor = Color(0xFFFFFFFF);
static const secondaryText = Color(0xFFC7C7C7);
static const scaffoldBackgroundColor = Color(0xFF171717);
static const borderColor = Color(0xFF707479);
static const edittextBackground = Color(0xFF000000);
static const buttonColorText = Color(0xffffffff);
static const disabledColor = Color(0x331783B2);
static const iconBackground = Color(0xff1A272D);
static const lightSecondary = Color(0xffc7c7c7);
static const grayBackground = Color(0xff000000);
static const statusBackground = Color(0xff1A272D);
static const lightBorder = Color(0xff252525);
static const statusIconBackground = Color(0xff000000);
}
@immutable
class AppThemeColors extends ThemeExtension<AppThemeColors> {
final Color scaffoldBackgroundColor;
final Color textColor;
final Color dividerColor;
final Color accentColor;
final Color darkBackground;
final Color grey;
final Color midGrey;
final Color lightGrey;
final Color white;
final Color white60Transparent;
final Color white30Transparent;
final Color white10Transparent;
final Color buttonColorText;
///newcolors
final Color primaryColor;
final Color secondaryText;
final Color borderColor;
final Color edittextBackground;
final Color iconBackground;
final Color lightSecondary;
final Color grayBackground;
final Color statusBackground;
final Color lightBorder;
final Color statusIconBackground;
const AppThemeColors({
required this.scaffoldBackgroundColor,
required this.textColor,
required this.dividerColor,
required this.accentColor,
required this.darkBackground,
required this.grey,
required this.midGrey,
required this.lightGrey,
required this.white,
required this.white60Transparent,
required this.white30Transparent,
required this.white10Transparent,
required this.buttonColorText,
required this.primaryColor,
required this.secondaryText,
required this.borderColor,
required this.edittextBackground,
required this.iconBackground,
required this.lightSecondary,
required this.grayBackground,
required this.statusBackground,
required this.lightBorder,
required this.statusIconBackground,
});
@override
ThemeExtension<AppThemeColors> copyWith({
Color? scaffoldBackgroundColor,
Color? textColor,
Color? dividerColor,
Color? accentColor,
Color? darkBackground,
Color? grey,
Color? midGrey,
Color? lightGrey,
Color? white,
Color? white60Transparent,
Color? white30Transparent,
Color? white10Transparent,
Color? buttonColorText,
Color? primaryColor,
Color? secondaryText,
Color? borderColor,
Color? edittextBackground,
Color? iconBackground,
Color? lightSecondary,
Color? grayBackground,
Color? statusBackground,
Color? lightBorder,
Color? statusIconBackground,
}) {
return AppThemeColors(
scaffoldBackgroundColor:
scaffoldBackgroundColor ?? this.scaffoldBackgroundColor,
textColor: textColor ?? this.textColor,
dividerColor: dividerColor ?? this.dividerColor,
accentColor: accentColor ?? this.accentColor,
darkBackground: darkBackground ?? this.darkBackground,
grey: grey ?? this.grey,
midGrey: midGrey ?? this.midGrey,
lightGrey: lightGrey ?? this.lightGrey,
white: white ?? this.white,
white60Transparent: white60Transparent ?? this.white60Transparent,
white30Transparent: white30Transparent ?? this.white30Transparent,
white10Transparent: white10Transparent ?? this.white10Transparent,
buttonColorText: buttonColorText ?? this.buttonColorText,
primaryColor: primaryColor ?? this.primaryColor,
secondaryText: secondaryText ?? this.secondaryText,
borderColor: borderColor ?? this.borderColor,
edittextBackground: edittextBackground ?? this.edittextBackground,
iconBackground: iconBackground ?? this.iconBackground,
lightSecondary: lightSecondary ?? this.lightSecondary,
grayBackground: grayBackground ?? this.grayBackground,
statusBackground: statusBackground ?? this.statusBackground,
lightBorder: lightBorder ?? this.lightBorder,
statusIconBackground: statusIconBackground ?? this.statusIconBackground,
);
}
@override
AppThemeColors lerp(ThemeExtension<AppThemeColors>? other, double t) {
if (other is! AppThemeColors) return this;
return AppThemeColors(
scaffoldBackgroundColor: Color.lerp(scaffoldBackgroundColor, other.scaffoldBackgroundColor, t)!,
textColor: Color.lerp(textColor, other.textColor, t)!,
dividerColor: Color.lerp(dividerColor, other.dividerColor, t)!,
accentColor: Color.lerp(accentColor, other.accentColor, t)!,
darkBackground: Color.lerp(darkBackground, other.darkBackground, t)!,
grey: Color.lerp(grey, other.grey, t)!,
midGrey: Color.lerp(midGrey, other.midGrey, t)!,
lightGrey: Color.lerp(lightGrey, other.lightGrey, t)!,
white: Color.lerp(white, other.white, t)!,
white60Transparent: Color.lerp(white60Transparent, other.white60Transparent, t)!,
white30Transparent: Color.lerp(white30Transparent, other.white30Transparent, t)!,
white10Transparent: Color.lerp(white10Transparent, other.white10Transparent, t)!,
buttonColorText: Color.lerp(buttonColorText, other.buttonColorText, t)!,
primaryColor: Color.lerp(primaryColor, other.primaryColor, t)!,
secondaryText: Color.lerp(secondaryText, other.secondaryText, t)!,
borderColor: Color.lerp(borderColor, other.borderColor, t)!,
edittextBackground: Color.lerp(edittextBackground, other.edittextBackground, t)!,
iconBackground: Color.lerp(iconBackground, other.iconBackground, t)!,
lightSecondary: Color.lerp(lightSecondary, other.lightSecondary, t)!,
grayBackground: Color.lerp(grayBackground, other.grayBackground, t)!,
statusBackground: Color.lerp(statusBackground, other.statusBackground, t)!,
lightBorder: Color.lerp(lightBorder, other.lightBorder, t)!,
statusIconBackground: Color.lerp(statusIconBackground, other.statusIconBackground, t)!,
);
}
static get light => const AppThemeColors(
scaffoldBackgroundColor: LightThemeColors.scaffoldBackgroundColor,
textColor: LightThemeColors.textColor,
dividerColor: LightThemeColors.dividerColor,
accentColor: LightThemeColors.accentColor,
darkBackground: LightThemeColors.darkBackground,
grey: LightThemeColors.grey,
midGrey: LightThemeColors.midGrey,
lightGrey: LightThemeColors.lightGrey,
white: LightThemeColors.white,
white60Transparent: LightThemeColors.white60Transparent,
white30Transparent: LightThemeColors.white30Transparent,
white10Transparent: LightThemeColors.white10Transparent,
buttonColorText: LightThemeColors.buttonColorText,
primaryColor: LightThemeColors.primaryColor,
secondaryText: LightThemeColors.secondaryText,
borderColor: LightThemeColors.borderColor,
edittextBackground: LightThemeColors.edittextBackground,
iconBackground: LightThemeColors.iconBackground,
lightSecondary: LightThemeColors.lightSecondary,
grayBackground: LightThemeColors.grayBackground,
statusBackground: LightThemeColors.statusBackground,
lightBorder: LightThemeColors.lightBorder,
statusIconBackground: LightThemeColors.statusIconBackground,
);
static get dark => const AppThemeColors(
scaffoldBackgroundColor: DarkThemeColors.scaffoldBackgroundColor,
textColor: DarkThemeColors.textColor,
dividerColor: DarkThemeColors.dividerColor,
accentColor: DarkThemeColors.accentColor,
darkBackground: DarkThemeColors.darkBackground,
grey: DarkThemeColors.grey,
midGrey: DarkThemeColors.midGrey,
lightGrey: DarkThemeColors.lightGrey,
white: DarkThemeColors.white,
white60Transparent: DarkThemeColors.white60Transparent,
white30Transparent: DarkThemeColors.white30Transparent,
white10Transparent: DarkThemeColors.white10Transparent,
buttonColorText: DarkThemeColors.buttonColorText,
primaryColor: DarkThemeColors.primaryColor,
secondaryText: DarkThemeColors.secondaryText,
borderColor: DarkThemeColors.borderColor,
edittextBackground: DarkThemeColors.edittextBackground,
iconBackground: DarkThemeColors.iconBackground,
lightSecondary: DarkThemeColors.lightSecondary,
grayBackground: DarkThemeColors.grayBackground,
statusBackground: DarkThemeColors.statusBackground,
lightBorder: DarkThemeColors.lightBorder,
statusIconBackground: DarkThemeColors.statusIconBackground,
);
}

View File

@@ -0,0 +1,434 @@
import 'package:cargocalculaterapp/core/theme/theme_text_style.dart';
import 'package:flutter/material.dart';
import 'colors/app_colors.dart';
final appTheme = ThemeData(
fontFamily: "Inter",
extensions: <ThemeExtension<dynamic>>[
AppThemeColors.light,
AppThemeColors.dark,
ThemeTextStyles.light,
ThemeTextStyles.dark,
],
useMaterial3: true,
applyElevationOverlayColor: true,
// disabledColor: ThemeColors.disabledColor,
// splashColor: ThemeColors.primary1Transparent,
// focusColor: ThemeColors.primaryColor,
// colorSchemeSeed: ThemeColors.primaryColor,
visualDensity: VisualDensity.standard,
materialTapTargetSize: MaterialTapTargetSize.padded,
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
},
),
// textButtonTheme: TextButtonThemeData(
// style: ButtonStyle(
// padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
// EdgeInsets.zero,
// ),
// ),
// ),
dividerTheme: const DividerThemeData(thickness: 1),
);
final lightTheme = appTheme.copyWith(
extensions: <ThemeExtension<dynamic>>[
AppThemeColors.light,
ThemeTextStyles.light,
],
scaffoldBackgroundColor: LightThemeColors.scaffoldBackgroundColor,
brightness: Brightness.light,
dividerTheme: appTheme.dividerTheme.copyWith(
color: LightThemeColors.dividerColor,
),
colorScheme: const ColorScheme.light(
secondary: LightThemeColors.white,
primary: LightThemeColors.accentColor,
),
// listTileTheme: const ListTileThemeData(
// minVerticalPadding: 14,
// minLeadingWidth: 16,
// horizontalTitleGap: 12,
// tileColor: LightThemeColors.textFieldBackGround,
// selectedColor: LightThemeColors.backgroundColor,
// selectedTileColor: LightThemeColors.backgroundColor,
// shape: RoundedRectangleBorder(
// borderRadius: AppUtils.kBorderRadius4,
// ),
// ),
tabBarTheme: TabBarThemeData(
labelColor: LightThemeColors.accentColor,
unselectedLabelColor: LightThemeColors.grey,
labelPadding: EdgeInsets.zero,
labelStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w400),
unselectedLabelStyle: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
),
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(color: LightThemeColors.accentColor, width: 2.0),
),
overlayColor: WidgetStateProperty.resolveWith<Color>((states) {
return LightThemeColors.lightGrey;
}),
),
appBarTheme: const AppBarTheme(
elevation: 0,
centerTitle: true,
scrolledUnderElevation: 0,
foregroundColor: LightThemeColors.scaffoldBackgroundColor,
backgroundColor: LightThemeColors.white,
surfaceTintColor: LightThemeColors.scaffoldBackgroundColor,
shadowColor: Colors.black,
titleTextStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: LightThemeColors.textColor,
),
toolbarHeight: 56,
iconTheme: IconThemeData(color: LightThemeColors.primaryColor),
),
inputDecorationTheme: const InputDecorationTheme(
contentPadding: EdgeInsets.all(16),
alignLabelWithHint: true,
labelStyle: TextStyle(
color: LightThemeColors.white,
fontSize: 16,
fontWeight: FontWeight.w400,
),
hintStyle: TextStyle(
color: LightThemeColors.secondaryText,
fontSize: 16,
fontWeight: FontWeight.w500,
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: LightThemeColors.errorColor),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: LightThemeColors.borderColor),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: LightThemeColors.borderColor),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: LightThemeColors.primaryColor),
),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: LightThemeColors.white10Transparent),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: LightThemeColors.errorColor),
),
filled: true,
isDense: true,
fillColor: LightThemeColors.edittextBackground,
floatingLabelAlignment: FloatingLabelAlignment.start,
floatingLabelBehavior: FloatingLabelBehavior.always,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
overlayColor: WidgetStateProperty.resolveWith<Color?>((
Set<WidgetState> states,
) {
if (states.contains(WidgetState.hovered)) {
return LightThemeColors.errorColor;
}
return null;
}),
elevation: WidgetStateProperty.all<double>(0),
textStyle: WidgetStateProperty.all<TextStyle>(
const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: LightThemeColors.textColor,
),
),
foregroundColor: WidgetStateProperty.resolveWith<Color>((states) {
if (states.contains(WidgetState.disabled)) {
return LightThemeColors.white;
} else {
return LightThemeColors.white;
}
}),
backgroundColor: WidgetStateProperty.resolveWith<Color>((states) {
if (states.contains(WidgetState.disabled)) {
return LightThemeColors.disabledColor;
} else {
return LightThemeColors.primaryColor;
}
}),
maximumSize: WidgetStateProperty.all<Size>(
const Size(double.infinity, 56),
),
minimumSize: WidgetStateProperty.all<Size>(
const Size(double.infinity, 56),
),
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(24)),
),
),
),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: LightThemeColors.scaffoldBackgroundColor,
type: BottomNavigationBarType.fixed,
showSelectedLabels: true,
selectedLabelStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
unselectedLabelStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
unselectedItemColor: LightThemeColors.lightSecondary,
selectedItemColor: LightThemeColors.primaryColor,
selectedIconTheme: IconThemeData(size: 25),
unselectedIconTheme: IconThemeData(size: 25),
elevation: 2,
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
elevation: WidgetStateProperty.all<double>(2),
foregroundColor: WidgetStateProperty.resolveWith<Color>((states) {
return LightThemeColors.scaffoldBackgroundColor;
}),
backgroundColor: WidgetStateProperty.resolveWith<Color>((states) {
if (states.contains(WidgetState.disabled)) {
return Colors.transparent;
} else {
return Colors.transparent;
}
}),
maximumSize: WidgetStateProperty.all<Size>(
const Size(double.infinity, double.infinity),
),
minimumSize: WidgetStateProperty.all<Size>(
const Size(double.minPositive, double.minPositive),
),
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
),
),
textStyle: WidgetStateProperty.all<TextStyle>(
const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: LightThemeColors.accentColor,
),
),
// overlayColor: WidgetStateProperty.resolveWith<Color>((states) {
// return LightThemeColors.lightGrey;
// }),
),
),
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: Colors.transparent,
// surfaceTintColor: Colors.transparent,
modalBackgroundColor: Colors.transparent,
modalElevation: 0,
elevation: 0,
),
);
final darkTheme = appTheme.copyWith(
extensions: <ThemeExtension<dynamic>>[
AppThemeColors.dark,
ThemeTextStyles.dark,
],
scaffoldBackgroundColor: DarkThemeColors.scaffoldBackgroundColor,
brightness: Brightness.dark,
dividerTheme: appTheme.dividerTheme.copyWith(
color: DarkThemeColors.dividerColor,
),
colorScheme: const ColorScheme.dark(secondary: Colors.white),
tabBarTheme: TabBarThemeData(
labelColor: DarkThemeColors.accentColor,
unselectedLabelColor: DarkThemeColors.grey,
labelPadding: EdgeInsets.zero,
labelStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w400),
unselectedLabelStyle: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
),
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(color: DarkThemeColors.accentColor, width: 2.0),
),
overlayColor: WidgetStateProperty.resolveWith<Color>((states) {
return DarkThemeColors.lightGrey;
}),
),
appBarTheme: const AppBarTheme(
elevation: 0,
centerTitle: true,
scrolledUnderElevation: 0,
foregroundColor: DarkThemeColors.scaffoldBackgroundColor,
backgroundColor: DarkThemeColors.scaffoldBackgroundColor,
surfaceTintColor: DarkThemeColors.scaffoldBackgroundColor,
shadowColor: Colors.black,
titleTextStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: DarkThemeColors.textColor,
),
toolbarHeight: 56,
iconTheme: IconThemeData(color: DarkThemeColors.primaryColor),
),
inputDecorationTheme: const InputDecorationTheme(
alignLabelWithHint: true,
contentPadding: EdgeInsets.all(16),
labelStyle: TextStyle(
color: DarkThemeColors.textColor,
fontSize: 16,
fontWeight: FontWeight.w400,
),
hintStyle: TextStyle(
color: DarkThemeColors.secondaryText,
fontSize: 16,
fontWeight: FontWeight.w400,
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: DarkThemeColors.errorColor),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: DarkThemeColors.borderColor),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: DarkThemeColors.borderColor),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: DarkThemeColors.primaryColor),
),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: DarkThemeColors.white10Transparent),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
borderSide: BorderSide(color: DarkThemeColors.errorColor),
),
filled: true,
isDense: true,
fillColor: DarkThemeColors.edittextBackground,
floatingLabelAlignment: FloatingLabelAlignment.start,
floatingLabelBehavior: FloatingLabelBehavior.always,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
overlayColor: WidgetStateProperty.resolveWith<Color?>((
Set<WidgetState> states,
) {
if (states.contains(WidgetState.hovered)) {
return DarkThemeColors.errorColor;
}
return null;
}),
elevation: WidgetStateProperty.all<double>(0),
textStyle: WidgetStateProperty.all<TextStyle>(
const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: DarkThemeColors.buttonColorText,
),
),
foregroundColor: WidgetStateProperty.resolveWith<Color>((states) {
if (states.contains(WidgetState.disabled)) {
return DarkThemeColors.white60Transparent;
} else {
return DarkThemeColors.buttonColorText;
}
}),
backgroundColor: WidgetStateProperty.resolveWith<Color>((states) {
if (states.contains(WidgetState.disabled)) {
return DarkThemeColors.disabledColor;
} else {
return DarkThemeColors.primaryColor;
}
}),
maximumSize: WidgetStateProperty.all<Size>(
const Size(double.infinity, 56),
),
minimumSize: WidgetStateProperty.all<Size>(
const Size(double.infinity, 56),
),
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(24)),
),
),
),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: DarkThemeColors.scaffoldBackgroundColor,
type: BottomNavigationBarType.fixed,
showSelectedLabels: true,
selectedLabelStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
unselectedLabelStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
unselectedItemColor: DarkThemeColors.lightSecondary,
selectedItemColor: DarkThemeColors.primaryColor,
selectedIconTheme: IconThemeData(size: 25),
unselectedIconTheme: IconThemeData(size: 25),
elevation: 2,
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
elevation: WidgetStateProperty.all<double>(2),
foregroundColor: WidgetStateProperty.resolveWith<Color>((states) {
return DarkThemeColors.scaffoldBackgroundColor;
}),
backgroundColor: WidgetStateProperty.resolveWith<Color>((states) {
if (states.contains(WidgetState.disabled)) {
return Colors.transparent;
} else {
return Colors.transparent;
}
}),
maximumSize: WidgetStateProperty.all<Size>(
const Size(double.infinity, double.infinity),
),
minimumSize: WidgetStateProperty.all<Size>(
const Size(double.minPositive, double.minPositive),
),
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
),
),
textStyle: WidgetStateProperty.all<TextStyle>(
const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: DarkThemeColors.accentColor,
),
),
// overlayColor: WidgetStateProperty.resolveWith<Color>((states) {
// return DarkThemeColors.lightGrey;
// }),
),
),
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: Colors.transparent,
// surfaceTintColor: Colors.transparent,
modalBackgroundColor: Colors.transparent,
modalElevation: 0,
elevation: 0,
),
);

View File

@@ -0,0 +1,118 @@
import 'package:cargocalculaterapp/core/theme/colors/app_colors.dart';
import 'package:flutter/material.dart';
import 'app_text_styles.dart';
@immutable
class ThemeTextStyles extends ThemeExtension<ThemeTextStyles> {
final TextStyle profileCategory;
final TextStyle titleBig;
final TextStyle authTitle;
final TextStyle categoryName;
final TextStyle bigTitle;
final TextStyle statusText;
final TextStyle authDesc;
final TextStyle orderListTitle;
final TextStyle orderTitle;
final TextStyle statusNumber;
final TextStyle statusDesc;
final TextStyle secondaryText14;
@override
ThemeExtension<ThemeTextStyles> copyWith({
TextStyle? profileCategory,
TextStyle? titleBig,
TextStyle? authTitle,
TextStyle? categoryName,
TextStyle? bigTitle,
TextStyle? statusText,
TextStyle? authDesc,
TextStyle? orderListTitle,
TextStyle? orderTitle,
TextStyle? statusNumber,
TextStyle? statusDesc,
TextStyle? secondaryText14,
}) {
return ThemeTextStyles(
profileCategory: profileCategory ?? this.profileCategory,
titleBig: titleBig ?? this.titleBig,
authTitle: authTitle ?? this.authTitle,
categoryName: categoryName ?? this.categoryName,
bigTitle: bigTitle ?? this.bigTitle,
statusText: statusText ?? this.statusText,
authDesc: authDesc ?? this.authDesc,
orderListTitle: orderListTitle ?? this.orderListTitle,
orderTitle: orderTitle ?? this.orderTitle,
statusNumber: statusNumber ?? this.statusNumber,
statusDesc: statusDesc ?? this.statusDesc,
secondaryText14: secondaryText14 ?? this.secondaryText14,
);
}
const ThemeTextStyles({
required this.profileCategory,
required this.titleBig,
required this.authTitle,
required this.categoryName,
required this.bigTitle,
required this.statusText,
required this.authDesc,
required this.orderListTitle,
required this.orderTitle,
required this.statusNumber,
required this.statusDesc,
required this.secondaryText14,
});
static get light => const ThemeTextStyles(
profileCategory: AppTextStyles.profileCategory,
titleBig: AppTextStyles.titleBig,
authTitle: AppTextStyles.authTitle,
categoryName: AppTextStyles.categoryName,
bigTitle: AppTextStyles.bigTitle,
statusText: AppTextStyles.statusText,
authDesc: AppTextStyles.authDesc,
orderListTitle: AppTextStyles.orderListTitle,
orderTitle: AppTextStyles.orderTitle,
statusNumber: AppTextStyles.statusNumber,
statusDesc: AppTextStyles.statusDesc,
secondaryText14: AppTextStyles.secondaryText14,
);
static get dark => ThemeTextStyles(
profileCategory: AppTextStyles.profileCategory.copyWith(
color: DarkThemeColors.textColor,
),
titleBig: AppTextStyles.titleBig.copyWith(color: DarkThemeColors.textColor),
authTitle: AppTextStyles.authTitle.copyWith(color: DarkThemeColors.textColor),
categoryName: AppTextStyles.categoryName.copyWith(color: DarkThemeColors.textColor),
bigTitle: AppTextStyles.bigTitle.copyWith(color: DarkThemeColors.textColor),
statusText: AppTextStyles.statusText.copyWith(color: DarkThemeColors.buttonColorText),
authDesc: AppTextStyles.authDesc.copyWith(color: DarkThemeColors.secondaryText),
orderListTitle: AppTextStyles.orderListTitle.copyWith(color: DarkThemeColors.secondaryText),
orderTitle: AppTextStyles.orderTitle.copyWith(color: DarkThemeColors.textColor),
statusNumber: AppTextStyles.statusNumber.copyWith(color: DarkThemeColors.secondaryText),
statusDesc: AppTextStyles.statusDesc.copyWith(color: DarkThemeColors.textColor),
secondaryText14: AppTextStyles.secondaryText14.copyWith(color: DarkThemeColors.lightSecondary),
);
@override
ThemeTextStyles lerp(ThemeExtension<ThemeTextStyles>? other, double t) {
if (other is! ThemeTextStyles) return this;
return ThemeTextStyles(
profileCategory: TextStyle.lerp(profileCategory, other.profileCategory, t)!,
titleBig: TextStyle.lerp(titleBig, other.titleBig, t)!,
authTitle: TextStyle.lerp(authTitle, other.authTitle, t)!,
categoryName: TextStyle.lerp(categoryName, other.categoryName, t)!,
bigTitle: TextStyle.lerp(bigTitle, other.bigTitle, t)!,
statusText: TextStyle.lerp(statusText, other.statusText, t)!,
authDesc: TextStyle.lerp(authDesc, other.authDesc, t)!,
orderListTitle: TextStyle.lerp(orderListTitle, other.orderListTitle, t)!,
orderTitle: TextStyle.lerp(orderTitle, other.orderTitle, t)!,
statusNumber: TextStyle.lerp(statusNumber, other.statusNumber, t)!,
statusDesc: TextStyle.lerp(statusDesc, other.statusDesc, t)!,
secondaryText14: TextStyle.lerp(secondaryText14, other.secondaryText14, t)!,
);
}
}

View File

@@ -0,0 +1,14 @@
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import '../error/failure.dart';
abstract class UseCase<Type, Params> {
Future<Either<Failure, Type>> call(Params params);
}
class NoParams extends Equatable {
const NoParams();
@override
List<Object?> get props => [];
}

View File

@@ -0,0 +1,484 @@
import 'package:flutter/material.dart';
import '../theme/colors/app_colors.dart';
class AppUtils {
AppUtils._();
static const kDividerR = Divider(
height: 1,
thickness: 1,
color: LightThemeColors.dividerColor,
);
static const kGap12 = SliverToBoxAdapter(child: kBoxHeight12);
static const kGap16 = SliverToBoxAdapter(child: kBoxHeight16);
static const kGap8 = SliverToBoxAdapter(child: kBoxHeight8);
static const kGap4 = SliverToBoxAdapter(child: kBoxHeight4);
static const kGap10 = SliverToBoxAdapter(child: kBoxHeight10);
static const kGap22 = SliverToBoxAdapter(child: kBoxHeight22);
static const kGap24 = SliverToBoxAdapter(child: kBoxHeight24);
static const kGapBox = SliverToBoxAdapter(child: SizedBox());
static const kBoxWith4 = SizedBox(width: 4);
static const kBoxWith8 = SizedBox(width: 8);
static const kBoxWith12 = SizedBox(width: 12);
/// divider
static const kDivider = Divider(
height: 2,
thickness: 2,
color: LightThemeColors.dividerColor,
);
static const kPad16Divider = Divider(
height: 2,
thickness: 2,
indent: 16,
color: LightThemeColors.dividerColor,
);
static const kGap16Divider = SliverToBoxAdapter(child: kPadHor16Divider);
static const kPadHor16Divider = Divider(
height: 1,
thickness: 1,
indent: 16,
endIndent: 16,
);
static const kPad60Divider = Padding(
padding: EdgeInsets.only(left: 60),
child: Divider(height: 1, thickness: 1),
);
/// spacer
static const kSpacer = Spacer();
/// Sizedbox
static const kBox = SizedBox.shrink();
static const kBoxHeight2 = SizedBox(height: 2);
static const kBoxHeight3 = SizedBox(height: 3);
static const kBoxHeight4 = SizedBox(height: 4);
static const kBoxHeight6 = SizedBox(height: 6);
static const kBoxHeight8 = SizedBox(height: 8);
static const kBoxHeight10 = SizedBox(height: 10);
static const kBoxHeight12 = SizedBox(height: 12);
static const kBoxHeight14 = SizedBox(height: 14);
static const kBoxHeight16 = SizedBox(height: 16);
static const kBoxHeight18 = SizedBox(height: 18);
static const kBoxHeight20 = SizedBox(height: 20);
static const kBoxHeight22 = SizedBox(height: 22);
static const kBoxHeight24 = SizedBox(height: 24);
static const kBoxHeight26 = SizedBox(height: 26);
static const kBoxHeight28 = SizedBox(height: 28);
static const kBoxHeight40 = SizedBox(height: 40);
static const kBoxHeight42 = SizedBox(height: 42);
static const kBoxHeight38 = SizedBox(height: 48);
static const kBoxHeight45 = SizedBox(height: 45);
static const kBoxHeight48 = SizedBox(height: 48);
static const kBoxHeight58 = SizedBox(height: 58);
static const kBoxHeight30 = SizedBox(height: 30);
static const kBoxHeight32 = SizedBox(height: 32);
static const kBoxHeight34 = SizedBox(height: 34);
static const kBoxHeight36 = SizedBox(height: 36);
static const kBoxHeight64 = SizedBox(height: 64);
static const kBoxHeight84 = SizedBox(height: 84);
static const kBoxHeight124 = SizedBox(height: 124);
static const kBoxHeight204 = SizedBox(height: 204);
static const kBoxWidth2 = SizedBox(width: 2);
static const kBoxWidth8 = SizedBox(width: 8);
static const kBoxWidth6 = SizedBox(width: 6);
static const kBoxWidth4 = SizedBox(width: 4);
static const kBoxWidth3 = SizedBox(width: 3);
static const kBoxWidth10 = SizedBox(width: 10);
static const kBoxWidth12 = SizedBox(width: 12);
static const kBoxWidth14 = SizedBox(width: 14);
static const kBoxWidth20 = SizedBox(width: 20);
static const kBoxWidth16 = SizedBox(width: 16);
static const kBoxWidth22 = SizedBox(width: 22);
static const kBoxWidth24 = SizedBox(width: 24);
static const kBoxWidth30 = SizedBox(width: 30);
static const kBoxWidth40 = SizedBox(width: 40);
static const kBoxWidth56 = SizedBox(width: 56);
static const kBoxWidth95 = SizedBox(width: 95);
/// padding
static const kPaddingAll4 = EdgeInsets.all(4);
static const kPaddingAll3 = EdgeInsets.all(3);
static const kPaddingAll2 = EdgeInsets.all(2);
static const kPaddingAll1 = EdgeInsets.all(1);
static const kPaddingHorizontal12Vertical8 = EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8,
);
static const kPaddingHorizontal12Vertical10 = EdgeInsets.symmetric(
horizontal: 12,
);
static const kPaddingVer10 = EdgeInsets.symmetric(vertical: 10);
static const kPaddingAll6 = EdgeInsets.all(6);
static const kPaddingHorizontal16Vertical8 = EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8,
);
static const kPaddingAll8 = EdgeInsets.all(8);
static const kPaddingAll12 = EdgeInsets.all(12);
static const kPaddingAll10 = EdgeInsets.all(10);
static const kPaddingAll16 = EdgeInsets.all(16);
static const kPaddingAll18 = EdgeInsets.all(18);
static const kPaddingAll20 = EdgeInsets.all(20);
static const kPaddingAllB16 = EdgeInsets.fromLTRB(16, 16, 16, 0);
static const kPaddingAll24 = EdgeInsets.all(24);
static const kPaddingHorizontal16 = EdgeInsets.symmetric(horizontal: 16);
static const kPaddingHorizontal16T0B8 = EdgeInsets.only(
left: 16,
right: 16,
bottom: 8,
top: 0,
);
static const kPaddingHorizontal16T0B16 = EdgeInsets.only(
left: 16,
right: 16,
bottom: 16,
top: 0,
);
static const kPaddingHorizontal32T32B20 = EdgeInsets.only(
left: 32,
right: 32,
bottom: 20,
top: 32,
);
static const kPaddingLTRB8 = EdgeInsets.only(
left: 16,
right: 16,
top: 16,
bottom: 8,
);
static const kPaddingL8T12R16B12 = EdgeInsets.only(
left: 12,
right: 16,
top: 12,
bottom: 8,
);
static const kPaddingLTR = EdgeInsets.only(left: 16, right: 16, top: 16);
static const kPaddingL37 = EdgeInsets.only(left: 37);
static const kPaddingT4R8B4 = EdgeInsets.only(right: 8, bottom: 4, top: 4);
static const kPaddingT24 = EdgeInsets.only(top: 24);
static const kPaddingR4 = EdgeInsets.only(right: 4);
static const kPaddingR10 = EdgeInsets.only(right: 10);
static const kPaddingR16 = EdgeInsets.only(right: 16);
static const kPaddingLT8RB = EdgeInsets.only(
left: 16,
right: 16,
top: 8,
bottom: 16,
);
static const kPaddingLT0RB = EdgeInsets.only(
left: 16,
right: 16,
top: 0,
bottom: 16,
);
static const kPadding12LTRB0 = EdgeInsets.only(
left: 12,
right: 12,
top: 12,
bottom: 0,
);
static const kPaddingTop10 = EdgeInsets.only(top: 10);
static const kPaddingTop15 = EdgeInsets.only(top: 15);
static const kPaddingTop21 = EdgeInsets.only(top: 21);
static const kPaddingTop24 = EdgeInsets.only(top: 24);
static const kPaddingTop28 = EdgeInsets.only(top: 28);
static const kPaddingTop20 = EdgeInsets.only(top: 40);
static const kPaddingL16R12 = EdgeInsets.only(left: 16, right: 12, bottom: 8);
static const kPaddingL16R12TB8 = EdgeInsets.only(
left: 16,
right: 12,
bottom: 8,
top: 8,
);
static const kPaddingL16R16T0B24 = EdgeInsets.only(
left: 16,
right: 16,
bottom: 24,
top: 0,
);
static const kPaddingL16R16T24B16 = EdgeInsets.only(
left: 16,
right: 16,
top: 24,
bottom: 16,
);
static const kPaddingL16R16T24B0 = EdgeInsets.only(
left: 16,
right: 16,
top: 24,
);
static const kPaddingL16R16T16B24 = EdgeInsets.only(
left: 16,
right: 16,
top: 16,
bottom: 24,
);
static const kPaddingL8T8 = EdgeInsets.only(left: 8, top: 8);
static const kPaddingL60 = EdgeInsets.only(left: 60);
static const kPaddingT20B24R16 = EdgeInsets.only(
top: 20,
bottom: 24,
right: 16,
);
static const kPaddingL16T20B24 = EdgeInsets.only(
left: 16,
top: 20,
bottom: 24,
);
static const kPaddingR20T16B16 = EdgeInsets.only(
right: 20,
top: 16,
bottom: 16,
);
static const kPaddingHor32Ver20 = EdgeInsets.symmetric(
horizontal: 32,
vertical: 20,
);
static const kPaddingHor16 = EdgeInsets.symmetric(horizontal: 16);
static const kPaddingHor4 = EdgeInsets.symmetric(horizontal: 4);
static const kPaddingHor6 = EdgeInsets.symmetric(horizontal: 6);
static const kPaddingVer12 = EdgeInsets.symmetric(vertical: 12);
static const kPaddingVer16 = EdgeInsets.symmetric(vertical: 16);
static const kPaddingVer24 = EdgeInsets.symmetric(vertical: 24);
static const kPaddingVer44 = EdgeInsets.symmetric(vertical: 44);
static const kPaddingHor18 = EdgeInsets.symmetric(horizontal: 18);
static const kPaddingHor20 = EdgeInsets.symmetric(horizontal: 20);
static const kPaddingHor24 = EdgeInsets.symmetric(horizontal: 24);
static const kPaddingHor28 = EdgeInsets.symmetric(horizontal: 28);
static const kPaddingHor12 = EdgeInsets.symmetric(horizontal: 12);
static const kPaddingHor10 = EdgeInsets.symmetric(horizontal: 10);
static const kPaddingHor32 = EdgeInsets.symmetric(horizontal: 32);
static const kPaddingHor34 = EdgeInsets.symmetric(horizontal: 34);
static const kPaddingHor36 = EdgeInsets.symmetric(horizontal: 36);
static const kPaddingHor38 = EdgeInsets.symmetric(horizontal: 38);
static const kPaddingHor44 = EdgeInsets.symmetric(horizontal: 44);
static const kPaddingVer8 = EdgeInsets.symmetric(vertical: 8);
static const kPaddingHor16Ver12 = EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
);
static const kPaddingHor12Ver16 = EdgeInsets.symmetric(
horizontal: 12,
vertical: 16,
);
static const kPaddingHor32Ver16 = EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
);
static const kPaddingHor16Ver8 = EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
);
static const kPaddingHor8Ver4 = EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
);
static const kPaddingHor8Ver5 = EdgeInsets.symmetric(
horizontal: 8,
vertical: 5,
);
static const kPaddingHor6Ver4 = EdgeInsets.symmetric(
horizontal: 6,
vertical: 4,
);
static const kPaddingHor12Ver8 = EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
);
static const kPaddingVer10Hor16 = EdgeInsets.symmetric(
vertical: 10,
horizontal: 16,
);
static const kPaddingVer12Hor18 = EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
);
static const kPaddingVer8Hor12 = EdgeInsets.symmetric(
vertical: 8,
horizontal: 12,
);
static const kPaddingVer10Hor12 = EdgeInsets.symmetric(
vertical: 10,
horizontal: 12,
);
static const kPaddingVer2Hor4 = EdgeInsets.symmetric(
vertical: 2,
horizontal: 4,
);
static const kPaddingVer2Hor8 = EdgeInsets.symmetric(
vertical: 2,
horizontal: 8,
);
static const kPaddingVer3Hor10 = EdgeInsets.symmetric(
vertical: 3,
horizontal: 10,
);
static const kPaddingVer2Hor10 = EdgeInsets.symmetric(
vertical: 2,
horizontal: 10,
);
static const kPaddingVer2Hor6 = EdgeInsets.symmetric(
vertical: 2,
horizontal: 6,
);
static const kPaddingVer5Hor2 = EdgeInsets.symmetric(
vertical: 5,
horizontal: 2,
);
static const kPaddingVer18Hor16 = EdgeInsets.symmetric(
vertical: 18,
horizontal: 16,
);
static const kPaddingVer24Hor16 = EdgeInsets.symmetric(
vertical: 24,
horizontal: 16,
);
static const kPaddingVer24Hor8 = EdgeInsets.symmetric(
vertical: 24,
horizontal: 8,
);
static const kPaddingVer14Hor12 = EdgeInsets.symmetric(
vertical: 14,
horizontal: 12,
);
static const kPaddingVer14Hor16 = EdgeInsets.symmetric(
vertical: 14,
horizontal: 16,
);
static const kPaddingVer16Hor24 = EdgeInsets.symmetric(
vertical: 16,
horizontal: 24,
);
static const kPaddingVer4Hor12 = EdgeInsets.symmetric(
vertical: 4,
horizontal: 12,
);
static const kPaddingVer4Hor16 = EdgeInsets.symmetric(
vertical: 4,
horizontal: 16,
);
static const kPaddingVer6Hor12 = EdgeInsets.symmetric(
vertical: 6,
horizontal: 12,
);
static const kPaddingVer12Hor14 = EdgeInsets.symmetric(
vertical: 12,
horizontal: 14,
);
static const kPaddingVer28Hor28 = EdgeInsets.symmetric(
vertical: 28,
horizontal: 28,
);
/// border radius
static const kRadius = Radius.zero;
static const kRadius8 = Radius.circular(8);
static const kRadius6 = Radius.circular(6);
static const kRadius12 = Radius.circular(12);
static const kBorderRadius = BorderRadius.all(Radius.circular(0));
static const kBorderRadius2 = BorderRadius.all(Radius.circular(2));
static const kBorderRadius4 = BorderRadius.all(Radius.circular(4));
static const kBorderRadius5 = BorderRadius.all(Radius.circular(5));
static const kBorderRadius6 = BorderRadius.all(Radius.circular(6));
static const kBorderRadius8 = BorderRadius.all(Radius.circular(8));
static const kBorderRadiusTop8 = BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
);
static const kBorderRadiusTop10 = BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
);
static const kBorderRadiusTop12 = BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
);
static const kBorderRadiusTop16 = BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
);
static const kBorderRadiusBottom16 = BorderRadius.only(
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
);
static const kBorderRadiusTop24 = BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
);
static const kBorderRadiusBottom8 = BorderRadius.only(
bottomLeft: Radius.circular(8),
bottomRight: Radius.circular(8),
);
static const kBorderRadiusBottom10 = BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
);
static const kBorderRadiusTopBot12 = BorderRadius.only(
topRight: AppUtils.kRadius12,
bottomRight: AppUtils.kRadius12,
);
static const kBorderRadiusLeftTopBob12 = BorderRadius.only(
topLeft: AppUtils.kRadius12,
bottomLeft: AppUtils.kRadius12,
);
static const kBorderRadius12 = BorderRadius.all(Radius.circular(12));
static const kBorderRadius10 = BorderRadius.all(Radius.circular(10));
static const kBorderRadius16 = BorderRadius.all(Radius.circular(16));
static const kBorderRadius14 = BorderRadius.all(Radius.circular(14));
static const kBorderRadius24 = BorderRadius.all(Radius.circular(24));
static const kBorderRadius20 = BorderRadius.all(Radius.circular(20));
static const kBorderRadius32 = BorderRadius.all(Radius.circular(32));
static const kBorderRadius48 = BorderRadius.all(Radius.circular(48));
static const kBorderRadius64 = BorderRadius.all(Radius.circular(64));
// static const kBoxDecoration = BoxDecoration(
// borderRadius: AppUtils.kBorderRadius24,
// color: LightThemeColors.whiteBackground,
// );
// static const kBoxDecorationGray = BoxDecoration(
// borderRadius: AppUtils.kBorderRadius24,
// color: LightThemeColors.backgroundGray,
// );
//
// static const kBoxDecoration12 = BoxDecoration(
// borderRadius: AppUtils.kBorderRadius12,
// color: LightThemeColors.whiteBackground,
// );
// static const kBoxDecoration12BorderColor = BoxDecoration(
// borderRadius: AppUtils.kBorderRadius12,
// color: LightThemeColors.badgeGray,
// );
// static const kBoxDecoration12Gray = BoxDecoration(
// borderRadius: AppUtils.kBorderRadius12,
// color: LightThemeColors.scaffoldBackgroundColor,
// );
// static BoxDecoration kBoxDecoration12WhiteBorderRed = BoxDecoration(
// borderRadius: AppUtils.kBorderRadius12,
// color: LightThemeColors.whiteBackground,
// border: Border.all(color: LightThemeColors.red),
// );
// static const kBoxDecorationTop24 = BoxDecoration(
// color: LightThemeColors.white,
// borderRadius: AppUtils.kBorderRadiusTop24,
// );
// static const kBoxDecorationTop = BoxDecoration(
// borderRadius: BorderRadius.only(
// topLeft: Radius.circular(24),
// topRight: Radius.circular(24),
// ),
// color: LightThemeColors.whiteBackground,
// );
// static const kBoxDecorationBottom = BoxDecoration(
// borderRadius: BorderRadius.only(
// bottomLeft: Radius.circular(24),
// bottomRight: Radius.circular(24),
// ),
// color: LightThemeColors.whiteBackground,
// );
}

View File

@@ -0,0 +1,99 @@
import 'package:cargocalculaterapp/constants/constants.dart';
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:cargocalculaterapp/core/functions/base_finctions.dart';
import 'package:cargocalculaterapp/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import '../../theme/app_text_styles.dart';
import '../../utils/app_utils.dart';
class CustomDropDown extends StatelessWidget {
const CustomDropDown({
super.key,
this.name,
required this.isRequired,
this.errorText,
this.isError,
this.style,
required this.onChange,
this.value,
});
final String? name;
final bool isRequired;
final String? errorText;
final bool? isError;
final TextStyle? style;
final String? value;
final Function(String?) onChange;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (name?.isNotEmpty ?? false)
Text(name ?? '', style: style ?? context.text.categoryName),
AppUtils.kBoxWidth4,
if (isRequired) SvgPicture.asset("assets/svg/ic_required.svg"),
],
),
AppUtils.kBoxHeight8,
InputDecorator(
decoration: InputDecoration(
fillColor: context.color.white10Transparent,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: context.color.borderColor,
),
borderRadius: AppUtils.kBorderRadius12,
),
contentPadding: AppUtils.kPaddingAll16,
),
child: DropdownButtonHideUnderline(
child: DropdownButton(
dropdownColor: context.color.grayBackground,
hint: Text(AppLocalization.current.warehouse,style: TextStyle(
color: context.color.secondaryText,
fontSize: 16,
fontWeight: FontWeight.w400,
),),
icon: Icon(
Icons.keyboard_arrow_down_outlined,
size: 24,
color: context.color.textColor,
),
isDense: true,
iconSize: 0,
value: value,
items:
AppConst.warehouseOptions.keys.map((entry) {
return DropdownMenuItem(
value: entry,
child: Text(
Functions.getTranslatedItem(
AppConst.warehouseOptions[entry],
context,
),
style: context.text.profileCategory,
),
);
}).toList(),
onChanged: onChange,
),
),
),
if (isError ?? false) AppUtils.kBoxHeight4,
if (isError ?? false)
Text(
errorText ?? "",
style: AppTextStyles.timer.copyWith(fontSize: 12),
),
],
);
}
}

View File

@@ -0,0 +1,24 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../theme/colors/app_colors.dart';
class CustomLoadingWidget extends StatelessWidget {
final Color? color;
const CustomLoadingWidget({super.key, this.color});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Platform.isAndroid
? CircularProgressIndicator(
color: color ?? LightThemeColors.lightGrey,
)
: CupertinoActivityIndicator(color: color),
],
);
}
}

View File

@@ -0,0 +1,90 @@
import "dart:io";
import "package:cargocalculaterapp/core/theme/colors/app_colors.dart";
import "package:flutter/cupertino.dart";
import "package:flutter/material.dart";
import "dart:ui";
///
/// Wrap around any widget that makes an async call to show a modal progress
/// indicator while the async call is in progress.
///
/// HUD=Heads Up Display
///
class ModalProgressHUD extends StatelessWidget {
/// A required [bool] to toggle the modal overlay.
final bool inAsyncCall;
/// A [double] specifying the opacity of the modal overlay, defaults to 0.3
final double opacity;
/// A [Color] object which is assigned to the loading barrier, defaults to grey
final Color color;
/// A [Widget] which is shown at the center of the modal overlay,
/// defaults to the standard android spinner animation.
/// An [Offset] object which is applied to the [progressIndicator] when specified.
final Offset? offset;
/// A [bool] which controls whether the modal overlay can be dismissible when interated.
final bool dismissible;
/// A [Widget] over which the modal overlay is activated.
final Widget child;
/// A [double] value specifying the amount of background blur when progress hud is active.
final double blur;
const ModalProgressHUD({
super.key,
required this.inAsyncCall,
this.opacity = 0.3,
this.color = Colors.grey,
this.offset,
this.dismissible = false,
required this.child,
this.blur = 0.0,
});
@override
Widget build(BuildContext context) {
Widget layOutProgressIndicator;
if (offset == null) {
layOutProgressIndicator = Center(
child:
Platform.isIOS
? const CupertinoActivityIndicator()
: const CircularProgressIndicator(
color: LightThemeColors.accentColor,
),
);
} else {
layOutProgressIndicator = Positioned(
left: offset!.dx,
top: offset!.dy,
child:
Platform.isIOS
? const CupertinoActivityIndicator()
: const CircularProgressIndicator(
color: LightThemeColors.accentColor,
),
);
}
return Stack(
children: [
child,
if (inAsyncCall) ...[
BackdropFilter(
filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
child: Opacity(
opacity: opacity,
child: ModalBarrier(dismissible: dismissible, color: color),
),
),
layOutProgressIndicator,
],
],
);
}
}

View File

@@ -0,0 +1,95 @@
import 'package:cargocalculaterapp/core/extension/extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../utils/app_utils.dart';
typedef RatingChangeCallback = void Function(double rating)?;
const starPoints = 5;
class RatingBarWidget extends StatelessWidget {
final double rating;
final double iconsSize;
final Color activeRatingColor;
final Color inactiveRatingColor;
final RatingChangeCallback onRatingChanged;
final bool isAnimate;
final Widget separator;
final bool isSharp;
const RatingBarWidget({
super.key,
required this.rating,
this.iconsSize = 18,
this.activeRatingColor = const Color(0xffFFA047),
this.inactiveRatingColor = const Color(0xffCED5DF),
this.onRatingChanged,
this.isAnimate = false,
this.separator = AppUtils.kBoxWidth3,
this.isSharp = false,
});
@override
Widget build(BuildContext context) {
if (isAnimate) {
return TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOut,
tween: Tween<double>(begin: 0, end: rating),
builder: (_, value, __) {
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
10,
(index) => index.isEven
? buildStar(index.exactIndex, rating, isSharp)
: separator,
),
);
},
);
}
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
10,
(index) => index.isEven
? buildStar(index.exactIndex, rating, isSharp)
: separator,
),
);
}
Widget buildStar(int index, double rating, isSharp) {
Widget icon;
double present = rating - index;
icon = ShaderMask(
blendMode: BlendMode.srcIn,
shaderCallback: (bounds) {
return LinearGradient(
tileMode: TileMode.clamp,
colors: [activeRatingColor, inactiveRatingColor],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: [present, present],
).createShader(bounds);
},
child: SvgPicture.asset(
isSharp
? "assets/svg/ic_star_selected.svg"
: "assets/svg/ic_star_selected.svg",
height: iconsSize,
width: iconsSize,
),
);
return InkResponse(
onTap: onRatingChanged == null
? null
: () => onRatingChanged!(index + 1.0),
child: icon,
);
}
}

View File

@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import '../../theme/colors/app_colors.dart';
import '../../utils/app_utils.dart';
class ShimmerWidget extends StatelessWidget {
const ShimmerWidget({
super.key,
required this.width,
required this.height,
this.borderRadius = AppUtils.kBorderRadius6,
this.margin,
});
final double width;
final double height;
final BorderRadiusGeometry? borderRadius;
final EdgeInsetsGeometry? margin;
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
enabled: true,
baseColor:
Theme.of(context).brightness == Brightness.dark
? const Color(0x80FFFFFF)
: const Color(0xffF8F8F9),
highlightColor: LightThemeColors.shimmerHighlight,
child: Container(
width: width,
height: height,
margin: margin,
decoration: BoxDecoration(
color: LightThemeColors.white,
borderRadius: borderRadius,
),
),
);
}
}

View File

@@ -0,0 +1,108 @@
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import '../../theme/app_text_styles.dart';
import '../../theme/colors/app_colors.dart';
import '../../utils/app_utils.dart';
class CustomTextFieldName extends StatelessWidget {
const CustomTextFieldName({
super.key,
this.name,
required this.hint,
this.readOnly = false,
this.onTap,
this.prefixWidget,
this.controller,
this.format,
this.inputType,
this.onchange,
this.style,
this.suffix,
this.errorText,
this.isError,
this.isRequired = false,
this.maxLines,
this.minLines,
this.inputStyle,
this.textCapitalization = TextCapitalization.sentences,
this.fillColor,
this.obscureText = false,
});
final String? name;
final String hint;
final bool readOnly;
final Function()? onTap;
final Widget? prefixWidget;
final TextEditingController? controller;
final List<TextInputFormatter>? format;
final TextInputType? inputType;
final bool isRequired;
final Function(String)? onchange;
final TextStyle? style;
final Widget? suffix;
final String? errorText;
final bool? isError;
final int? maxLines;
final int? minLines;
final TextStyle? inputStyle;
final TextCapitalization textCapitalization;
final Color? fillColor;
final bool obscureText;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (name?.isNotEmpty ?? false)
Text(name ?? '', style: style ?? context.text.categoryName),
AppUtils.kBoxWidth4,
if (isRequired) SvgPicture.asset("assets/svg/ic_required.svg"),
],
),
AppUtils.kBoxHeight8,
TextField(
style: inputStyle ?? TextStyle(color: context.color.textColor),
keyboardType: inputType,
controller: controller,
onTap: onTap,
readOnly: readOnly,
onChanged: onchange,
inputFormatters: format,
maxLines: maxLines,
minLines: minLines,
obscureText: obscureText,
textCapitalization: textCapitalization,
decoration: InputDecoration(
fillColor: fillColor,
hintText: hint,
prefixIcon: prefixWidget,
suffixIcon: suffix,
focusedBorder: OutlineInputBorder(
borderRadius: AppUtils.kBorderRadius16,
borderSide: BorderSide(
color:
!(isError ?? false)
? context.color.accentColor
: ThemeColors.timerRed,
),
),
),
),
if (isError ?? false) AppUtils.kBoxHeight4,
if (isError ?? false)
Text(
errorText ?? "",
style: AppTextStyles.timer.copyWith(fontSize: 12),
),
],
);
}
}

View File

@@ -0,0 +1,18 @@
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart';
abstract class AuthRemoteDataSource {
Future<LoginResponse> login(LoginRequest request);
Future<SignUpResponse> signUp(SignUpRequest request);
Future<AuthVerificationResponse> verify(AuthVerificationRequest request);
Future<FcmAddResponse> addFcm(FcmAddRequest request);
}

View File

@@ -0,0 +1,105 @@
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart';
import 'package:dio/dio.dart';
import '../../../../../constants/constants.dart';
import '../../../../../core/error/exceptions.dart';
import '../../../../../core/local_source/local_source.dart';
import '../../../../../injector_container.dart';
import 'auth_remote_data_source.dart';
class AuthRemoteDataSourceImpl extends AuthRemoteDataSource {
final Dio dio;
AuthRemoteDataSourceImpl(this.dio);
@override
Future<LoginResponse> login(LoginRequest request) async {
try {
final Response response = await dio.post(
Constants.baseUrl + Urls.login,
options: DioConstants.options,
data: request,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return LoginResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
@override
Future<SignUpResponse> signUp(SignUpRequest request) async {
try {
final Response response = await dio.post(
Constants.baseUrl + Urls.signUp,
options: DioConstants.options,
data: request,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return SignUpResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
@override
Future<AuthVerificationResponse> verify(
AuthVerificationRequest request,
) async {
try {
final Response response = await dio.post(
Constants.baseUrl + Urls.verify,
options: DioConstants.options
..headers = {
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
},
data: request,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return AuthVerificationResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
@override
Future<FcmAddResponse> addFcm(FcmAddRequest request) async {
try {
final Response response = await dio.put(
Constants.baseUrl + Urls.addFcm,
options: DioConstants.options
..headers = {
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
},
data: request,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return FcmAddResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
}

View File

@@ -0,0 +1,21 @@
class AuthVerificationRequest {
AuthVerificationRequest({this.phoneNumber, this.smsCode, this.email});
AuthVerificationRequest.fromJson(dynamic json) {
phoneNumber = json['phone_number'];
smsCode = json['smsCode'];
email = json['email'];
}
String? phoneNumber;
String? smsCode;
String? email;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['phone_number'] = phoneNumber;
map['smsCode'] = smsCode;
map['email'] = email;
return map;
}
}

View File

@@ -0,0 +1,16 @@
class AuthVerificationResponse {
AuthVerificationResponse({
this.accessToken,});
AuthVerificationResponse.fromJson(dynamic json) {
accessToken = json['access_token'];
}
String? accessToken;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['access_token'] = accessToken;
return map;
}
}

View File

@@ -0,0 +1,16 @@
class FcmAddRequest {
FcmAddRequest({
this.sfmToken,});
FcmAddRequest.fromJson(dynamic json) {
sfmToken = json['sfm_token'];
}
String? sfmToken;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['sfm_token'] = sfmToken;
return map;
}
}

View File

@@ -0,0 +1,51 @@
class FcmAddResponse {
FcmAddResponse({
this.message,
this.user,});
FcmAddResponse.fromJson(dynamic json) {
message = json['message'];
user = json['user'] != null ? User.fromJson(json['user']) : null;
}
String? message;
User? user;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['message'] = message;
if (user != null) {
map['user'] = user?.toJson();
}
return map;
}
}
class User {
User({
this.fullname,
this.phoneNumber,
this.email,
this.sfmToken,});
User.fromJson(dynamic json) {
fullname = json['fullname'];
phoneNumber = json['phone_number'];
email = json['email'];
sfmToken = json['sfm_token'];
}
String? fullname;
String? phoneNumber;
String? email;
String? sfmToken;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['fullname'] = fullname;
map['phone_number'] = phoneNumber;
map['email'] = email;
map['sfm_token'] = sfmToken;
return map;
}
}

View File

@@ -0,0 +1,24 @@
class LoginRequest {
LoginRequest({
this.email,
this.phone,
this.ucode,});
LoginRequest.fromJson(dynamic json) {
email = json['email'];
phone = json['phone'];
ucode = json['ucode'];
}
String? email;
String? phone;
String? ucode;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['email'] = email;
map['phone'] = phone;
map['ucode'] = ucode;
return map;
}
}

View File

@@ -0,0 +1,20 @@
class LoginResponse {
LoginResponse({
this.token,
this.refreshToken,});
LoginResponse.fromJson(dynamic json) {
token = json['token'];
refreshToken = json['refreshToken'];
}
String? token;
String? refreshToken;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['token'] = token;
map['refreshToken'] = refreshToken;
return map;
}
}

View File

@@ -0,0 +1,25 @@
class SignUpRequest {
SignUpRequest({
this.phoneNumber,
this.email,
this.fullname,});
SignUpRequest.fromJson(dynamic json) {
phoneNumber = json['phone_number'];
email = json['email'];
fullname = json['fullname'];
}
String? phoneNumber;
String? email;
String? fullname;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['phone_number'] = phoneNumber;
map['email'] = email;
map['fullname'] = fullname;
return map;
}
}

View File

@@ -0,0 +1,16 @@
class SignUpResponse {
SignUpResponse({
this.token,});
SignUpResponse.fromJson(dynamic json) {
token = json['token'];
}
String? token;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['token'] = token;
return map;
}
}

View File

@@ -0,0 +1,73 @@
import 'package:cargocalculaterapp/core/error/failure.dart';
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart';
import 'package:dartz/dartz.dart';
import '../../../../core/error/exceptions.dart';
import '../../domain/repository/auth_repository.dart';
import '../data_source/remote/auth_remote_data_source.dart';
class AuthRepositoryImpl extends AuthRepository {
final AuthRemoteDataSource remoteDataSource;
AuthRepositoryImpl(this.remoteDataSource);
@override
Future<Either<Failure, LoginResponse>> login(LoginRequest request) async {
try {
final response = await remoteDataSource.login(request);
return Right(response);
} catch (e) {
if (e is ServerException) {
return Left(ServerFailure(message: e.message));
}
return Left(ServerFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, SignUpResponse>> signUp(SignUpRequest request) async {
try {
final response = await remoteDataSource.signUp(request);
return Right(response);
} catch (e) {
if (e is ServerException) {
return Left(ServerFailure(message: e.message));
}
return Left(ServerFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, AuthVerificationResponse>> verify(
AuthVerificationRequest request,
) async {
try {
final response = await remoteDataSource.verify(request);
return Right(response);
} catch (e) {
if (e is ServerException) {
return Left(ServerFailure(message: e.message));
}
return Left(ServerFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, FcmAddResponse>> fcmAdd(FcmAddRequest request) async {
try {
final response = await remoteDataSource.addFcm(request);
return Right(response);
} catch (e) {
if (e is ServerException) {
return Left(ServerFailure(message: e.message));
}
return Left(ServerFailure(message: e.toString()));
}
}
}

View File

@@ -0,0 +1,23 @@
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart';
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart';
import 'package:dartz/dartz.dart';
import '../../../../core/error/failure.dart';
abstract class AuthRepository {
Future<Either<Failure, LoginResponse>> login(LoginRequest request);
Future<Either<Failure, SignUpResponse>> signUp(SignUpRequest request);
Future<Either<Failure, AuthVerificationResponse>> verify(
AuthVerificationRequest request,
);
Future<Either<Failure, FcmAddResponse>> fcmAdd(FcmAddRequest request);
}

View File

@@ -0,0 +1,18 @@
import 'package:cargocalculaterapp/core/error/failure.dart';
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/fcm_add_response.dart';
import 'package:dartz/dartz.dart';
import '../repository/auth_repository.dart';
class FcmAddUseCase extends UseCase<FcmAddResponse, FcmAddRequest> {
final AuthRepository repository;
FcmAddUseCase(this.repository);
@override
Future<Either<Failure, FcmAddResponse>> call(FcmAddRequest params) async {
return await repository.fcmAdd(params);
}
}

View File

@@ -0,0 +1,19 @@
import 'package:cargocalculaterapp/core/error/failure.dart';
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
import 'package:cargocalculaterapp/features/auth/data/model/login_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/login_response.dart';
import 'package:dartz/dartz.dart';
import '../repository/auth_repository.dart';
class LoginUseCase extends UseCase<LoginResponse, LoginRequest> {
final AuthRepository repository;
LoginUseCase(this.repository);
@override
Future<Either<Failure, LoginResponse>> call(LoginRequest params) async {
final response = await repository.login(params);
return response;
}
}

View File

@@ -0,0 +1,19 @@
import 'package:cargocalculaterapp/core/error/failure.dart';
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/sign_up_response.dart';
import 'package:dartz/dartz.dart';
import '../repository/auth_repository.dart';
class SignUpUseCase extends UseCase<SignUpResponse, SignUpRequest> {
final AuthRepository repository;
SignUpUseCase(this.repository);
@override
Future<Either<Failure, SignUpResponse>> call(SignUpRequest params) async {
final response = await repository.signUp(params);
return response;
}
}

View File

@@ -0,0 +1,22 @@
import 'package:cargocalculaterapp/core/error/failure.dart';
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_request.dart';
import 'package:cargocalculaterapp/features/auth/data/model/auth_verification_response.dart';
import 'package:dartz/dartz.dart';
import '../repository/auth_repository.dart';
class VerifyPhoneUseCase
extends UseCase<AuthVerificationResponse, AuthVerificationRequest> {
final AuthRepository repository;
VerifyPhoneUseCase(this.repository);
@override
Future<Either<Failure, AuthVerificationResponse>> call(
AuthVerificationRequest params,
) async {
final response = await repository.verify(params);
return response;
}
}

View File

@@ -0,0 +1,116 @@
import 'package:cargocalculaterapp/core/local_source/local_source.dart';
import 'package:cargocalculaterapp/generated/l10n.dart';
import 'package:cargocalculaterapp/router/app_routes.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../constants/constants.dart';
import '../../../../../injector_container.dart';
import '../../../../../router/name_routes.dart';
import '../../../data/model/login_request.dart';
import '../../../domain/usecases/login_usecase.dart';
import '../../pages/auth_confirm/argument/auth_confirm_argument.dart';
part 'auth_event.dart';
part 'auth_state.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc(this.loginUseCase)
: super(
const AuthState(
passwordHidden: true,
isLoading: false,
isButtonEnabled: false,
),
) {
on<PasswordVisibilityEvent>(_showHidePassword);
on<OnInputEnterEvent>(_onInputEnter);
on<SubmitEvent>(_onSubmit);
}
final LoginUseCase loginUseCase;
void _showHidePassword(
PasswordVisibilityEvent event,
Emitter<AuthState> emit,
) {
emit(state.copyWith(passwordHidden: !state.passwordHidden));
}
void _onInputEnter(OnInputEnterEvent event, Emitter<AuthState> emit) {
emit(
state.copyWith(
isButtonEnabled:
(isEmail(event.login) || isPhoneNumber(event.login)) &&
event.password.isNotEmpty,
),
);
}
Future<void> _onSubmit(SubmitEvent event, Emitter<AuthState> emit) async {
emit(state.copyWith(isLoading: true));
final response = await loginUseCase(
LoginRequest(
email: isEmail(event.login) ? event.login : "",
phone: isPhoneNumber(event.login)
? event.login.replaceAll("+", "")
: "",
ucode: event.password,
),
);
response.fold(
(l) {
emit(state.copyWith(isLoading: false));
showErrorSnackBar();
},
(r) {
emit(state.copyWith(isLoading: false));
sl<LocalSource>().setUCode(event.password);
Navigator.pushNamed(
rootNavigatorKey.currentContext!,
Routes.authConfirm,
arguments: AuthConfirmArgument(
fullName: "",
mail: isEmail(event.login) ? event.login : "",
phoneNumber: isPhoneNumber(event.login) ? event.login : "",
fromLoginPage: true,
password: event.password,
),
);
},
);
}
bool isEmail(String input) {
return RegExConst.emailRegex.hasMatch(input);
}
bool isPhoneNumber(String input) {
return RegExConst.phoneRegex.hasMatch(input);
}
void showErrorSnackBar() {
final snackBar = SnackBar(
backgroundColor: Colors.red,
content: Row(
children: [
const Icon(Icons.error, color: Colors.white),
const SizedBox(width: 8),
Expanded(
child: Text(
AppLocalization.current.login_error,
style: const TextStyle(color: Colors.white),
),
),
],
),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 3),
);
ScaffoldMessenger.of(
rootNavigatorKey.currentContext!,
).showSnackBar(snackBar);
}
}

View File

@@ -0,0 +1,32 @@
part of 'auth_bloc.dart';
sealed class AuthEvent extends Equatable {
const AuthEvent();
}
final class PasswordVisibilityEvent extends AuthEvent {
const PasswordVisibilityEvent();
@override
List<Object?> get props => [];
}
final class OnInputEnterEvent extends AuthEvent {
final String login;
final String password;
const OnInputEnterEvent({required this.login, required this.password});
@override
List<Object?> get props => [login, password];
}
final class SubmitEvent extends AuthEvent {
final String login;
final String password;
const SubmitEvent({required this.login, required this.password});
@override
List<Object?> get props => [login, password];
}

View File

@@ -0,0 +1,28 @@
part of 'auth_bloc.dart';
class AuthState extends Equatable {
const AuthState({
required this.passwordHidden,
required this.isLoading,
required this.isButtonEnabled,
});
final bool passwordHidden;
final bool isLoading;
final bool isButtonEnabled;
AuthState copyWith({
bool? passwordHidden,
bool? isLoading,
bool? isButtonEnabled,
}) {
return AuthState(
isLoading: isLoading ?? this.isLoading,
passwordHidden: passwordHidden ?? this.passwordHidden,
isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled,
);
}
@override
List<Object?> get props => [passwordHidden, isLoading, isButtonEnabled];
}

View File

@@ -0,0 +1,112 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../core/error/failure.dart';
import '../../../../../core/functions/base_finctions.dart';
import '../../../../../core/local_source/local_source.dart';
import '../../../../../injector_container.dart';
import '../../../../../service/notification_service.dart';
import '../../../data/model/auth_verification_request.dart';
import '../../../data/model/fcm_add_request.dart';
import '../../../data/model/login_request.dart';
import '../../../data/model/sign_up_request.dart';
import '../../../domain/usecases/fcm_add_usecase.dart';
import '../../../domain/usecases/login_usecase.dart';
import '../../../domain/usecases/sign_up_usecase.dart';
import '../../../domain/usecases/verify_phone_usecase.dart';
import '../../pages/auth_confirm/argument/auth_confirm_argument.dart';
part 'auth_confirm_event.dart';
part 'auth_confirm_state.dart';
class AuthConfirmBloc extends Bloc<AuthConfirmEvent, AuthConfirmState> {
AuthConfirmBloc(
this.signUpUseCase,
this.verifyPhoneUseCase,
this.loginUseCase,
this.fcmAddUseCase,
) : super(const InitialState(true)) {
on<TimerChangedEvent>(_timerChanged);
on<SmsCodeEnterEvent>(_codeChanged);
on<ResendCodeEvent>(_resendCode);
on<OnSubmitEvent>(_onSubmitCode);
}
final SignUpUseCase signUpUseCase;
final VerifyPhoneUseCase verifyPhoneUseCase;
final LoginUseCase loginUseCase;
final FcmAddUseCase fcmAddUseCase;
Future<void> _timerChanged(
TimerChangedEvent event,
Emitter<AuthConfirmState> emit,
) async {
emit(InitialState(event.isVisible));
}
void _codeChanged(SmsCodeEnterEvent event, Emitter<AuthConfirmState> emit) {
if (event.code.length == 4) {
emit(ButtonEnableState(state.isTimerVisible, event.code));
} else {
emit(InitialState(state.isTimerVisible));
}
}
Future<void> _resendCode(
ResendCodeEvent event,
Emitter<AuthConfirmState> emit,
) async {
add(const TimerChangedEvent(isVisible: true));
if (event.argument.fromLoginPage) {
await loginUseCase(
LoginRequest(
email: event.argument.mail,
phone: event.argument.phoneNumber?.replaceAll("+", ""),
ucode: event.argument.password,
),
);
} else {
await signUpUseCase(
SignUpRequest(
fullname: event.argument.fullName,
email: event.argument.mail,
phoneNumber: event.argument.phoneNumber?.replaceAll("+", ""),
),
);
}
}
Future<void> _onSubmitCode(
OnSubmitEvent event,
Emitter<AuthConfirmState> emit,
) async {
emit(LoadingState(state.isTimerVisible));
final response = await verifyPhoneUseCase(
AuthVerificationRequest(
phoneNumber: event.argument?.phoneNumber?.replaceAll("+", ""),
smsCode: event.code,
email: event.argument?.mail ?? "",
),
);
await response.fold(
(l) {
if (l is ServerFailure) {
Functions.showErrorSnackBar(l.message);
}
emit(InitialState(state.isTimerVisible));
},
(r) async {
sl<LocalSource>().setHasProfile(true);
sl<LocalSource>().setAccessToken(r.accessToken ?? "");
await _addFcmToken();
emit(SuccessSate(state.isTimerVisible));
},
);
}
Future<void> _addFcmToken() async {
final String fcmToken = await NotificationService.getFcmToken();
await fcmAddUseCase(FcmAddRequest(sfmToken: fcmToken));
}
}

View File

@@ -0,0 +1,42 @@
part of 'auth_confirm_bloc.dart';
sealed class AuthConfirmEvent extends Equatable {
const AuthConfirmEvent();
}
final class SmsCodeEnterEvent extends AuthConfirmEvent {
final String code;
const SmsCodeEnterEvent(this.code);
@override
List<Object?> get props => [code];
}
final class TimerChangedEvent extends AuthConfirmEvent {
final bool isVisible;
const TimerChangedEvent({required this.isVisible});
@override
List<Object?> get props => [isVisible];
}
final class OnSubmitEvent extends AuthConfirmEvent {
final String code;
final AuthConfirmArgument? argument;
const OnSubmitEvent({required this.code, required this.argument});
@override
List<Object?> get props => [code, argument];
}
final class ResendCodeEvent extends AuthConfirmEvent {
final AuthConfirmArgument argument;
const ResendCodeEvent({required this.argument});
@override
List<Object?> get props => [argument];
}

View File

@@ -0,0 +1,44 @@
part of 'auth_confirm_bloc.dart';
sealed class AuthConfirmState extends Equatable {
const AuthConfirmState(this.isTimerVisible);
final bool isTimerVisible;
}
class InitialState extends AuthConfirmState {
const InitialState(super.isTimerVisible);
@override
List<Object?> get props => [super.isTimerVisible];
}
class LoadingState extends AuthConfirmState {
const LoadingState(super.isTimerVisible);
@override
List<Object?> get props => [super.isTimerVisible];
}
class SuccessSate extends AuthConfirmState {
const SuccessSate(super.isTimerVisible);
@override
List<Object?> get props => [super.isTimerVisible];
}
class ErrorState extends AuthConfirmState {
const ErrorState(super.isTimerVisible);
@override
List<Object?> get props => [super.isTimerVisible];
}
class ButtonEnableState extends AuthConfirmState {
const ButtonEnableState(super.isTimerVisible, this.code);
final String code;
@override
List<Object?> get props => [super.isTimerVisible, code];
}

View File

@@ -0,0 +1,95 @@
import 'package:cargocalculaterapp/core/local_source/local_source.dart';
import 'package:cargocalculaterapp/generated/l10n.dart';
import 'package:cargocalculaterapp/router/app_routes.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../constants/constants.dart';
import '../../../../../core/error/failure.dart';
import '../../../../../core/functions/base_finctions.dart';
import '../../../../../injector_container.dart';
import '../../../../../router/name_routes.dart';
import '../../../data/model/sign_up_request.dart';
import '../../../domain/usecases/sign_up_usecase.dart';
import '../../pages/auth_confirm/argument/auth_confirm_argument.dart';
part 'sign_up_event.dart';
part 'sign_up_state.dart';
class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
SignUpBloc(this.signUpUseCase)
: super(
const SignUpState(
isLoading: false,
isButtonEnabled: false,
passwordHidden: true,
),
) {
on<PasswordVisibilityEvent>(_passwordVisibility);
on<OnInputEnterEvent>(_onDataEnter);
on<SubmitEvent>(_signUp);
}
final SignUpUseCase signUpUseCase;
void _passwordVisibility(
PasswordVisibilityEvent event,
Emitter<SignUpState> emit,
) {
emit(state.copyWith(isButtonEnabled: !state.isButtonEnabled));
}
void _onDataEnter(OnInputEnterEvent event, Emitter<SignUpState> emit) {
emit(
state.copyWith(
isButtonEnabled:
(isEmail(event.login) || isPhoneNumber(event.login)) &&
event.fullName.isNotEmpty,
),
);
}
bool isEmail(String input) {
return RegExConst.emailRegex.hasMatch(input);
}
bool isPhoneNumber(String input) {
return RegExConst.phoneRegex.hasMatch(input);
}
Future<void> _signUp(SubmitEvent event, Emitter<SignUpState> emit) async {
emit(state.copyWith(isLoading: true));
final response = await signUpUseCase(
SignUpRequest(
fullname: event.fullName,
email: isEmail(event.login) ? event.login : "",
phoneNumber: isPhoneNumber(event.login)
? event.login.replaceAll("+", "")
: "",
),
);
response.fold(
(l) {
emit(state.copyWith(isLoading: false));
if (l is ServerFailure) {
Functions.showErrorSnackBar(AppLocalization.current.phone_registered);
}
},
(r) {
sl<LocalSource>().setAccessToken(r.token ?? "");
emit(state.copyWith(isLoading: false));
Navigator.pushNamed(
rootNavigatorKey.currentContext!,
Routes.authConfirm,
arguments: AuthConfirmArgument(
fullName: event.fullName,
mail: isEmail(event.login) ? event.login : "",
phoneNumber: isPhoneNumber(event.login) ? event.login : "",
fromLoginPage: false,
),
);
},
);
}
}

View File

@@ -0,0 +1,32 @@
part of 'sign_up_bloc.dart';
sealed class SignUpEvent extends Equatable {
const SignUpEvent();
}
final class PasswordVisibilityEvent extends SignUpEvent {
const PasswordVisibilityEvent();
@override
List<Object?> get props => [];
}
final class OnInputEnterEvent extends SignUpEvent {
final String login;
final String fullName;
const OnInputEnterEvent({required this.login, required this.fullName});
@override
List<Object?> get props => [login, fullName];
}
final class SubmitEvent extends SignUpEvent {
final String login;
final String fullName;
const SubmitEvent({required this.login, required this.fullName});
@override
List<Object?> get props => [login, fullName];
}

View File

@@ -0,0 +1,28 @@
part of 'sign_up_bloc.dart';
class SignUpState extends Equatable {
const SignUpState({
required this.passwordHidden,
required this.isLoading,
required this.isButtonEnabled,
});
final bool passwordHidden;
final bool isLoading;
final bool isButtonEnabled;
SignUpState copyWith({
bool? passwordHidden,
bool? isLoading,
bool? isButtonEnabled,
}) {
return SignUpState(
isLoading: isLoading ?? this.isLoading,
passwordHidden: passwordHidden ?? this.passwordHidden,
isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled,
);
}
@override
List<Object?> get props => [passwordHidden, isLoading, isButtonEnabled];
}

View File

@@ -0,0 +1,185 @@
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:cargocalculaterapp/core/utils/app_utils.dart';
import 'package:cargocalculaterapp/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../../../core/widgets/loading/custom_loading.dart';
import '../../../../../core/widgets/text_filds/custom_text_field_name.dart';
import '../../../../../router/name_routes.dart';
import '../../bloc/auth/auth_bloc.dart';
import '../mixin/auth_mixin.dart';
class AuthPage extends StatefulWidget {
const AuthPage({super.key});
@override
State<AuthPage> createState() => _AuthPageState();
}
class _AuthPageState extends State<AuthPage> with AuthMixin {
@override
void initState() {
initControllers();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(),
body: ListView(
padding: AppUtils.kPaddingHor16,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 60),
child: SvgPicture.asset("assets/svg/ic_logo_auth.svg"),
),
AppUtils.kBoxHeight48,
Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
borderRadius: AppUtils.kBorderRadius12,
color: context.color.iconBackground,
),
padding: AppUtils.kPaddingAll6,
child: SvgPicture.asset("assets/svg/ic_user.svg"),
),
AppUtils.kBoxWidth12,
Text(
AppLocalization.current.auth_login,
style: context.text.authTitle,
),
],
),
AppUtils.kBoxHeight32,
CustomTextFieldName(
hint: AppLocalization.current.enter_phone_or_mail,
inputType: TextInputType.text,
name: AppLocalization.current.phone,
controller: loginController,
onchange: (value) {
context.read<AuthBloc>().add(
OnInputEnterEvent(
login: value,
password: passwordController.text,
),
);
},
),
AppUtils.kBoxHeight16,
CustomTextFieldName(
hint: "********",
prefixWidget: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"CT-",
style: context.text.profileCategory.copyWith(
fontWeight: FontWeight.w400,
),
),
],
),
inputType: TextInputType.number,
name: AppLocalization.current.password,
obscureText: state.passwordHidden,
maxLines: 1,
controller: passwordController,
suffix: IconButton(
onPressed: () {
context.read<AuthBloc>().add(
const PasswordVisibilityEvent(),
);
},
icon: Icon(
state.passwordHidden
? Icons.visibility_off_rounded
: Icons.visibility_rounded,
),
),
onchange: (value) {
context.read<AuthBloc>().add(
OnInputEnterEvent(
login: loginController.text,
password: value,
),
);
},
),
AppUtils.kBoxHeight48,
Padding(
padding: AppUtils.kPaddingHor34,
child: ElevatedButton(
onPressed: state.isButtonEnabled && !state.isLoading
? () {
context.read<AuthBloc>().add(
SubmitEvent(
login: loginController.text,
password: "CT-${passwordController.text}",
),
);
}
: null,
child: state.isLoading
? const CustomLoadingWidget()
: Text(AppLocalization.current.auth),
),
),
AppUtils.kBoxHeight16,
Text(
AppLocalization.current.no_account,
style: context.text.authDesc,
textAlign: TextAlign.center,
),
AppUtils.kBoxHeight16,
Padding(
padding: AppUtils.kPaddingHor34,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.pushNamed(context, Routes.signUp);
},
borderRadius: AppUtils.kBorderRadius24,
child: Ink(
decoration: BoxDecoration(
color: context.color.scaffoldBackgroundColor,
borderRadius: AppUtils.kBorderRadius24,
border: Border.all(color: context.color.primaryColor),
),
height: 56,
padding: AppUtils.kPaddingHor16,
child: Center(
child: Text(
AppLocalization.current.sign_up,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: context.color.textColor,
),
),
),
),
),
),
),
],
),
);
},
);
}
@override
void dispose() {
disposeControllers();
super.dispose();
}
}

View File

@@ -0,0 +1,15 @@
class AuthConfirmArgument {
final String? fullName;
final String? mail;
final String? phoneNumber;
final bool fromLoginPage;
final String? password;
AuthConfirmArgument({
this.fullName,
required this.mail,
required this.phoneNumber,
required this.fromLoginPage,
this.password,
});
}

View File

@@ -0,0 +1,233 @@
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:cargocalculaterapp/core/utils/app_utils.dart';
import 'package:cargocalculaterapp/core/widgets/loading/custom_loading.dart';
import 'package:cargocalculaterapp/features/auth/presentation/pages/auth_confirm/timer_widget.dart';
import 'package:cargocalculaterapp/generated/l10n.dart';
import 'package:cargocalculaterapp/router/name_routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:pin_code_fields/pin_code_fields.dart';
import '../../../../../core/theme/app_text_styles.dart';
import '../../../../../core/theme/colors/app_colors.dart';
import '../../bloc/auth_confirm/auth_confirm_bloc.dart';
import '../mixin/auth_confirm_mixin.dart';
import 'argument/auth_confirm_argument.dart';
class AuthConfirmPage extends StatefulWidget {
const AuthConfirmPage({super.key, required this.argument});
final AuthConfirmArgument? argument;
@override
State<AuthConfirmPage> createState() => _AuthConfirmPageState();
}
class _AuthConfirmPageState extends State<AuthConfirmPage>
with AuthConfirmMixin {
@override
void initState() {
initControllers();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocConsumer<AuthConfirmBloc, AuthConfirmState>(
listener: (context, state) {
if (state is SuccessSate) {
Navigator.pushNamedAndRemoveUntil(
context,
Routes.main,
(route) => route.isFirst,
);
}
},
builder: (context, state) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: AppUtils.kPaddingAll16,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
borderRadius: AppUtils.kBorderRadius16,
color: context.color.iconBackground,
),
padding: AppUtils.kPaddingAll16,
child: SvgPicture.asset(
"assets/svg/ic_mail.svg",
width: 32,
height: 32,
),
),
AppUtils.kBoxHeight16,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
(widget.argument?.mail?.isNotEmpty ?? false)
? AppLocalization.current.confirm_email
: AppLocalization.current.confirm_phone_text,
style: context.text.authTitle,
textAlign: TextAlign.center,
),
),
AppUtils.kBoxHeight16,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
(widget.argument?.mail?.isNotEmpty ?? false)
? AppLocalization.current.enter_code_mail(
widget.argument?.mail ?? "",
)
: AppLocalization.current.enter_code_phone(
widget.argument?.phoneNumber ?? "",
),
style: context.text.authDesc,
textAlign: TextAlign.center,
),
),
AppUtils.kBoxHeight48,
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 296,
child: PinCodeTextField(
controller: confirmController,
appContext: context,
autoDisposeControllers: false,
length: 4,
animationType: AnimationType.scale,
enabled: true,
enablePinAutofill: true,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
onChanged: (code) {
context.read<AuthConfirmBloc>().add(
SmsCodeEnterEvent(code),
);
},
autoFocus: true,
keyboardType: const TextInputType.numberWithOptions(),
showCursor: true,
textStyle: context.text.bigTitle,
cursorColor: context.color.accentColor,
cursorHeight: 20,
enableActiveFill: true,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
pinTheme: PinTheme(
activeColor:
state is ErrorState
? ThemeColors.timerRed
: state is SuccessSate
? ThemeColors.successInputSMS
: context.color.borderColor,
selectedFillColor:
context.color.scaffoldBackgroundColor,
activeFillColor:
context.color.scaffoldBackgroundColor,
shape: PinCodeFieldShape.box,
inactiveFillColor:
context.color.scaffoldBackgroundColor,
borderRadius: const BorderRadius.all(
Radius.circular(12),
),
borderWidth: 1,
inactiveColor:
state is ErrorState
? ThemeColors.timerRed
: state is SuccessSate
? ThemeColors.successInputSMS
: context.color.borderColor,
fieldWidth: 56,
fieldHeight: 64,
selectedColor:
state is ErrorState
? ThemeColors.timerRed
: state is SuccessSate
? ThemeColors.successInputSMS
: context.color.primaryColor,
),
),
),
],
),
if (state is ErrorState) AppUtils.kBoxHeight16,
if (state is ErrorState)
Center(
child: Text(
AppLocalization.current.incorrect_code,
style: AppTextStyles.timerBlue,
),
),
AppUtils.kBoxHeight24,
if (state.isTimerVisible)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
AppLocalization.current.send_sms,
style: AppTextStyles.timerBlue,
),
const TimerWidget(),
],
),
if (!state.isTimerVisible)
Center(
child: GestureDetector(
onTap: () {
if (widget.argument != null) {
context.read<AuthConfirmBloc>().add(
ResendCodeEvent(argument: widget.argument!),
);
}
},
child: Text(
AppLocalization.current.send_sms,
style: AppTextStyles.timerBlue,
),
),
),
AppUtils.kBoxHeight48,
Padding(
padding: AppUtils.kPaddingHor34,
child: ElevatedButton(
onPressed:
(confirmController.text.length == 4 &&
state is! LoadingState)
? () {
context.read<AuthConfirmBloc>().add(
OnSubmitEvent(
code: confirmController.text,
argument: widget.argument,
),
);
}
: null,
child:
state is LoadingState
? const CustomLoadingWidget()
: Text(AppLocalization.current.confirm),
),
),
],
),
),
);
},
);
}
@override
void dispose() {
disposeController();
super.dispose();
}
}

View File

@@ -0,0 +1,49 @@
import 'dart:async';
import 'package:cargocalculaterapp/core/theme/app_text_styles.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../bloc/auth_confirm/auth_confirm_bloc.dart';
class TimerWidget extends StatefulWidget {
const TimerWidget({super.key});
@override
State<TimerWidget> createState() => _TimerWidgetState();
}
class _TimerWidgetState extends State<TimerWidget> {
Timer? timer;
int time = 60;
String timeText = "60";
@override
void initState() {
if (timer?.isActive ?? false) {
timer?.cancel();
}
timer = Timer.periodic(const Duration(seconds: 1), (Timer timer) async {
time--;
if (time == 0) {
timer.cancel();
context.read<AuthConfirmBloc>().add(
const TimerChangedEvent(isVisible: false),
);
}
setState(() {
timeText = time.toString().padLeft(2, "0");
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Text(" 00:$timeText", style: AppTextStyles.timerStyle);
}
@override
void dispose() {
timer?.cancel();
super.dispose();
}
}

View File

@@ -0,0 +1,13 @@
import 'package:flutter/cupertino.dart';
mixin AuthConfirmMixin {
late TextEditingController confirmController;
void initControllers() {
confirmController = TextEditingController();
}
void disposeController() {
confirmController.dispose();
}
}

View File

@@ -0,0 +1,16 @@
import 'package:flutter/cupertino.dart';
mixin AuthMixin {
late TextEditingController loginController;
late TextEditingController passwordController;
void initControllers() {
loginController = TextEditingController();
passwordController = TextEditingController();
}
void disposeControllers() {
loginController.dispose();
passwordController.dispose();
}
}

View File

@@ -0,0 +1,16 @@
import 'package:flutter/cupertino.dart';
mixin SignUpMixin {
late TextEditingController loginController;
late TextEditingController fullNameController;
void initControllers() {
loginController = TextEditingController();
fullNameController = TextEditingController();
}
void disposeControllers() {
loginController.dispose();
fullNameController.dispose();
}
}

View File

@@ -0,0 +1,158 @@
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:cargocalculaterapp/features/auth/presentation/pages/mixin/sign_up_mixin.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import '../../../../../core/utils/app_utils.dart';
import '../../../../../core/widgets/loading/custom_loading.dart';
import '../../../../../core/widgets/text_filds/custom_text_field_name.dart';
import '../../../../../generated/l10n.dart';
import '../../bloc/sign_up/sign_up_bloc.dart';
class SignUpPage extends StatefulWidget {
const SignUpPage({super.key});
@override
State<SignUpPage> createState() => _SignUpPageState();
}
class _SignUpPageState extends State<SignUpPage> with SignUpMixin {
@override
void initState() {
initControllers();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpBloc, SignUpState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(),
body: ListView(
padding: AppUtils.kPaddingAll16,
children: [
Center(
child: Container(
width: 64,
height: 64,
decoration: BoxDecoration(
borderRadius: AppUtils.kBorderRadius16,
color: context.color.iconBackground,
),
padding: AppUtils.kPaddingAll16,
child: SvgPicture.asset(
"assets/svg/ic_add_user.svg",
width: 32,
height: 32,
),
),
),
AppUtils.kBoxHeight16,
Text(
AppLocalization.current.sign_up,
style: context.text.authTitle,
textAlign: TextAlign.center,
),
AppUtils.kBoxHeight24,
CustomTextFieldName(
hint: AppLocalization.current.full_name_hint,
inputType: TextInputType.name,
name: AppLocalization.current.full_name,
controller: fullNameController,
onchange: (value) {
context.read<SignUpBloc>().add(
OnInputEnterEvent(
login: loginController.text,
fullName: value,
),
);
},
),
AppUtils.kBoxHeight16,
CustomTextFieldName(
hint: AppLocalization.current.enter_phone_or_mail,
inputType: TextInputType.text,
name: AppLocalization.current.phone,
controller: loginController,
onchange: (value) {
context.read<SignUpBloc>().add(
OnInputEnterEvent(
login: value,
fullName: fullNameController.text,
),
);
},
),
AppUtils.kBoxHeight48,
Padding(
padding: AppUtils.kPaddingHor34,
child: ElevatedButton(
onPressed:
state.isButtonEnabled && !state.isLoading
? () {
context.read<SignUpBloc>().add(
SubmitEvent(
login: loginController.text,
fullName: fullNameController.text,
),
);
}
: null,
child:
state.isLoading
? const CustomLoadingWidget()
: Text(AppLocalization.current.sign_up),
),
),
AppUtils.kBoxHeight16,
Text(
AppLocalization.current.has_account,
style: context.text.authDesc,
textAlign: TextAlign.center,
),
AppUtils.kBoxHeight16,
Padding(
padding: AppUtils.kPaddingHor34,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
borderRadius: AppUtils.kBorderRadius24,
child: Ink(
decoration: BoxDecoration(
color: context.color.scaffoldBackgroundColor,
borderRadius: AppUtils.kBorderRadius24,
border: Border.all(color: context.color.primaryColor),
),
height: 56,
padding: AppUtils.kPaddingHor16,
child: Center(
child: Text(
AppLocalization.current.auth,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: context.color.textColor,
),
),
),
),
),
),
),
],
),
);
},
);
}
@override
void dispose() {
disposeControllers();
super.dispose();
}
}

View File

@@ -0,0 +1,9 @@
import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart';
abstract class CalculatorRemoteDataSource {
Future<CalculatePriceResponse> priceCalculate(PriceCalculateRequest request);
Future<LeadCreateResponse> createLead(LeadCreateRequest request);
}

View File

@@ -0,0 +1,64 @@
import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart';
import 'package:dio/dio.dart';
import '../../../../constants/constants.dart';
import '../../../../core/error/exceptions.dart';
import '../../../../core/local_source/local_source.dart';
import '../../../../injector_container.dart';
import '../model/lead_create_response.dart';
import '../model/lead_create_request.dart';
import 'calculator_remote_data_source.dart';
class CalculatorRemoteDataSourceImpl extends CalculatorRemoteDataSource {
final Dio dio;
CalculatorRemoteDataSourceImpl(this.dio);
@override
Future<CalculatePriceResponse> priceCalculate(
PriceCalculateRequest request,
) async {
try {
final Response response = await dio.post(
Constants.baseUrl + Urls.calculatePrice,
options:
DioConstants.options
..headers = {
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
},
data: request,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return CalculatePriceResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
@override
Future<LeadCreateResponse> createLead(LeadCreateRequest request) async {
try {
final Response response = await dio.post(
Constants.baseUrl + Urls.lead,
options:
DioConstants.options
..headers = {
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
},
data: request,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return LeadCreateResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
}

View File

@@ -0,0 +1,15 @@
class CalculatePriceResponse {
CalculatePriceResponse({this.price});
CalculatePriceResponse.fromJson(dynamic json) {
price = double.tryParse(json['price'].toString());
}
double? price;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['price'] = price;
return map;
}
}

View File

@@ -0,0 +1,73 @@
class LeadCreateRequest {
LeadCreateRequest({
this.warehouseCode,
this.wharehouseUz,
this.wharehouseRu,
this.wharehouseZh,
this.nameUz,
this.nameRu,
this.nameZh,
this.userUcode,
this.phoneNumber,
this.gmail,
this.weight,
this.price,
this.status,
this.averageWeightKg,
this.m3,
});
LeadCreateRequest.fromJson(dynamic json) {
warehouseCode = json['warehouse_code'];
wharehouseUz = json['wharehouseUz'];
wharehouseRu = json['wharehouseRu'];
wharehouseZh = json['wharehouseZh'];
nameUz = json['nameUz'];
nameRu = json['nameRu'];
nameZh = json['nameZh'];
userUcode = json['user_ucode'];
phoneNumber = json['phone_number'];
gmail = json['gmail'];
weight = json['weight'];
price = json['price'];
status = json['status'];
averageWeightKg = json['average_weight_kg'];
m3 = json['status'];
}
int? warehouseCode;
String? wharehouseUz;
String? wharehouseRu;
String? wharehouseZh;
String? nameUz;
String? nameRu;
String? nameZh;
String? userUcode;
String? phoneNumber;
String? gmail;
String? weight;
String? price;
String? status;
String? averageWeightKg;
String? m3;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['warehouse_code'] = warehouseCode;
map['wharehouseUz'] = wharehouseUz;
map['wharehouseRu'] = wharehouseRu;
map['wharehouseZh'] = wharehouseZh;
map['nameUz'] = nameUz;
map['nameRu'] = nameRu;
map['nameZh'] = nameZh;
map['user_ucode'] = userUcode;
map['phone_number'] = phoneNumber;
map['gmail'] = gmail;
map['weight'] = weight;
map['price'] = price;
map['status'] = status;
map['average_weight_kg'] = averageWeightKg;
map['m3'] = m3;
return map;
}
}

View File

@@ -0,0 +1,118 @@
class LeadCreateResponse {
LeadCreateResponse({
this.userUcode,
this.phoneNumber,
this.gmail,
this.weight,
this.status,
this.nameUz,
this.nameRu,
this.nameZh,
this.wharehouseUz,
this.wharehouseRu,
this.wharehouseZh,
this.id,
this.createdAt,
this.updatedAt,
});
LeadCreateResponse.fromJson(dynamic json) {
userUcode = json['user_ucode'];
phoneNumber = json['phone_number'];
gmail = json['gmail'];
weight = json['weight'];
status = json['status'] != null ? Status.fromJson(json['status']) : null;
nameUz = json['nameUz'];
nameRu = json['nameRu'];
nameZh = json['nameZh'];
wharehouseUz = json['wharehouseUz'];
wharehouseRu = json['wharehouseRu'];
wharehouseZh = json['wharehouseZh'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
id = json['id'];
}
String? userUcode;
String? phoneNumber;
String? gmail;
String? weight;
Status? status;
String? nameUz;
String? nameRu;
String? nameZh;
String? wharehouseUz;
String? wharehouseRu;
String? wharehouseZh;
String? id;
String? createdAt;
String? updatedAt;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['user_ucode'] = userUcode;
map['phone_number'] = phoneNumber;
map['gmail'] = gmail;
map['weight'] = weight;
if (status != null) {
map['status'] = status?.toJson();
}
map['nameUz'] = nameUz;
map['nameRu'] = nameRu;
map['nameZh'] = nameZh;
map['wharehouseUz'] = wharehouseUz;
map['wharehouseRu'] = wharehouseRu;
map['wharehouseZh'] = wharehouseZh;
map['createdAt'] = createdAt;
map['updatedAt'] = updatedAt;
map['id'] = id;
return map;
}
}
class Status {
Status({this.code, this.translations});
Status.fromJson(dynamic json) {
code = json['code'];
translations =
json['translations'] != null
? Translations.fromJson(json['translations'])
: null;
}
String? code;
Translations? translations;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['code'] = code;
if (translations != null) {
map['translations'] = translations?.toJson();
}
return map;
}
}
class Translations {
Translations({this.uz, this.ru, this.zh});
Translations.fromJson(dynamic json) {
uz = json['uz'];
ru = json['ru'];
zh = json['zh'];
}
String? uz;
String? ru;
String? zh;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['uz'] = uz;
map['ru'] = ru;
map['zh'] = zh;
return map;
}
}

View File

@@ -0,0 +1,20 @@
class PriceCalculateRequest {
PriceCalculateRequest({
this.avg,
this.m3,});
PriceCalculateRequest.fromJson(dynamic json) {
avg = json['avg'];
m3 = json['m3'];
}
double? avg;
double? m3;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['avg'] = avg;
map['m3'] = m3;
return map;
}
}

View File

@@ -0,0 +1,37 @@
import 'package:cargocalculaterapp/core/error/failure.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart';
import 'package:cargocalculaterapp/features/calculator/domain/repository/calculator_repository.dart';
import 'package:dartz/dartz.dart';
import '../data_source/calculator_remote_data_source.dart';
class CalculatorRepositoryImpl extends CalculatorRepository {
final CalculatorRemoteDataSource calculatorRemoteDataSource;
CalculatorRepositoryImpl(this.calculatorRemoteDataSource);
@override
Future<Either<Failure, CalculatePriceResponse>> calculatePrice(
PriceCalculateRequest request,
) async {
try {
final response = await calculatorRemoteDataSource.priceCalculate(request);
return Right(response);
} catch (e) {
return Left(ServerFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, LeadCreateResponse>> createLead(LeadCreateRequest request) async {
try {
final response = await calculatorRemoteDataSource.createLead(request);
return Right(response);
} catch (e) {
return Left(ServerFailure(message: e.toString()));
}
}
}

View File

@@ -0,0 +1,15 @@
import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart';
import 'package:dartz/dartz.dart';
import '../../../../core/error/failure.dart';
abstract class CalculatorRepository {
Future<Either<Failure, CalculatePriceResponse>> calculatePrice(
PriceCalculateRequest request,
);
Future<Either<Failure, LeadCreateResponse>> createLead(
LeadCreateRequest request,
);
}

View File

@@ -0,0 +1,21 @@
import 'package:cargocalculaterapp/core/error/failure.dart';
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/calculate_price_response.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/price_calculate_request.dart';
import 'package:dartz/dartz.dart';
import '../repository/calculator_repository.dart';
class CalculatePriceUseCase
extends UseCase<CalculatePriceResponse, PriceCalculateRequest> {
final CalculatorRepository repository;
CalculatePriceUseCase(this.repository);
@override
Future<Either<Failure, CalculatePriceResponse>> call(
PriceCalculateRequest params,
) async {
return await repository.calculatePrice(params);
}
}

View File

@@ -0,0 +1,21 @@
import 'package:cargocalculaterapp/core/error/failure.dart';
import 'package:cargocalculaterapp/core/usecase/usecase.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_request.dart';
import 'package:cargocalculaterapp/features/calculator/data/model/lead_create_response.dart';
import 'package:dartz/dartz.dart';
import '../repository/calculator_repository.dart';
class CreateLeadUseCase
extends UseCase<LeadCreateResponse, LeadCreateRequest> {
final CalculatorRepository repository;
CreateLeadUseCase(this.repository);
@override
Future<Either<Failure, LeadCreateResponse>> call(
LeadCreateRequest params,
) async {
return await repository.createLead(params);
}
}

View File

@@ -0,0 +1,17 @@
class CalculatorInfoArgument {
final String productName;
final String weraHouse;
final double weight;
final double size;
final double averageWeight;
final double deliveryPrice;
CalculatorInfoArgument({
required this.productName,
required this.weraHouse,
required this.weight,
required this.size,
required this.averageWeight,
required this.deliveryPrice,
});
}

View File

@@ -0,0 +1,76 @@
import 'package:cargocalculaterapp/router/app_routes.dart';
import 'package:cargocalculaterapp/router/name_routes.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../data/model/price_calculate_request.dart';
import '../../domain/usecase/calculate_price_usecase.dart';
import '../arguments/calculator_info_argument.dart';
part 'calculator_event.dart';
part 'calculator_state.dart';
class CalculatorBloc extends Bloc<CalculatorEvent, CalculatorState> {
CalculatorBloc(this.calculatePriceUseCase)
: super(const CalculatorState(isLoading: false)) {
on<WeightSizeEnterEvent>(_weightSizeEntered);
on<WareHouseSelectEvent>(_wareHouseSelect);
on<CalculateEvent>(_calculate);
}
final CalculatePriceUseCase calculatePriceUseCase;
void _weightSizeEntered(
WeightSizeEnterEvent event,
Emitter<CalculatorState> emit,
) {
emit(
state.copyWith(
size: double.tryParse(event.size.replaceAll(",", "")),
weight: double.tryParse(event.weight.replaceAll(",", "")),
),
);
}
void _wareHouseSelect(
WareHouseSelectEvent event,
Emitter<CalculatorState> emit,
) {
emit(state.copyWith(selectedWarehouse: event.wareHouse));
}
Future<void> _calculate(
CalculateEvent event,
Emitter<CalculatorState> emit,
) async {
emit(state.copyWith(isLoading: true));
final response = await calculatePriceUseCase(
PriceCalculateRequest(
m3: state.size,
avg: (state.weight ?? 0) / (state.size ?? 1),
),
);
response.fold(
(l) {
emit(state.copyWith(isLoading: false));
},
(r) {
emit(state.copyWith(isLoading: false));
Navigator.pushNamed(
rootNavigatorKey.currentContext!,
Routes.calculationInfo,
arguments: CalculatorInfoArgument(
productName: event.productName,
deliveryPrice: r.price ?? 0,
size: state.size ?? 0,
averageWeight: (state.weight ?? 0) / (state.size ?? 1),
weight: state.weight ?? 0,
weraHouse: state.selectedWarehouse ?? "",
),
);
},
);
}
}

View File

@@ -0,0 +1,33 @@
part of 'calculator_bloc.dart';
sealed class CalculatorEvent extends Equatable {
const CalculatorEvent();
}
final class WeightSizeEnterEvent extends CalculatorEvent {
final String weight;
final String size;
const WeightSizeEnterEvent({required this.weight, required this.size});
@override
List<Object?> get props => [weight, size];
}
final class WareHouseSelectEvent extends CalculatorEvent {
final String wareHouse;
const WareHouseSelectEvent({required this.wareHouse});
@override
List<Object?> get props => [wareHouse];
}
final class CalculateEvent extends CalculatorEvent {
const CalculateEvent({required this.productName});
final String productName;
@override
List<Object?> get props => [productName];
}

View File

@@ -0,0 +1,120 @@
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../../../constants/constants.dart';
import '../../../../../generated/l10n.dart';
import '../../../../../router/app_routes.dart';
import '../../../../../router/name_routes.dart';
import '../../../data/model/lead_create_request.dart';
import '../../../domain/usecase/create_lead_usecase.dart';
import '../../arguments/calculator_info_argument.dart';
import '../../pages/calculator_info/dialog/order_dialog.dart';
part 'calculator_info_event.dart';
part 'calculator_info_state.dart';
class CalculatorInfoBloc
extends Bloc<CalculatorInfoEvent, CalculatorInfoState> {
CalculatorInfoBloc(this.createLeadUseCase)
: super(const CalculatorInfoState(isLoading: false)) {
on<CreateLeadEvent>(_createLead);
on<ShowSuccessDialogEvent>(_showSuccessDialog);
}
final CreateLeadUseCase createLeadUseCase;
Future<void> _createLead(
CreateLeadEvent event,
Emitter<CalculatorInfoState> emit,
) async {
emit(state.copyWith(isLoading: true));
final local = Localizations.localeOf(
rootNavigatorKey.currentContext!,
).languageCode;
final response = await createLeadUseCase(
LeadCreateRequest(
status: "NOT_CONTACTED",
m3: "${event.argument.size}",
averageWeightKg: "${event.argument.averageWeight}",
weight: "${event.argument.weight}",
nameRu: local == "ru" ? event.argument.productName : "",
nameUz: local == "uz" ? event.argument.productName : "",
nameZh: local == "zh" ? event.argument.productName : "",
price: "${event.argument.deliveryPrice}",
warehouseCode: int.tryParse(event.argument.weraHouse),
wharehouseRu: AppConst.warehouseOptions[event.argument.weraHouse]?.ru,
wharehouseUz: AppConst.warehouseOptions[event.argument.weraHouse]?.uz,
wharehouseZh: AppConst.warehouseOptions[event.argument.weraHouse]?.zh,
),
);
response.fold(
(l) {
emit(state.copyWith(isLoading: false));
},
(r) {
emit(state.copyWith(isLoading: false));
add(ShowSuccessDialogEvent(isCall: event.isCall));
},
);
}
void _showSuccessDialog(
ShowSuccessDialogEvent event,
Emitter<CalculatorInfoState> emit,
) {
if (event.isCall) {
showCupertinoModalPopup(
context: rootNavigatorKey.currentContext!,
builder: (context) => CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
child: Text(
"+998 99-110-22-22",
style: TextStyle(color: context.color.primaryColor),
),
onPressed: () async {
final Uri url = Uri(scheme: 'tel', path: "+998991102222");
if (await canLaunchUrl(url)) {
await launchUrl(url);
}
Navigator.pop(rootNavigatorKey.currentContext!, true);
},
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () {
Navigator.pop(rootNavigatorKey.currentContext!);
},
child: Text(
AppLocalization.current.cancel,
style: TextStyle(color: context.color.textColor),
),
),
),
).then((value) {
if (value is bool) {
Navigator.pushNamedAndRemoveUntil(
rootNavigatorKey.currentContext!,
Routes.main,
(route) => false,
);
}
});
} else {
showDialog(
context: rootNavigatorKey.currentContext!,
builder: (context) => OrderDialog(isCall: event.isCall),
).then((value) {
Navigator.pushNamedAndRemoveUntil(
rootNavigatorKey.currentContext!,
Routes.main,
(route) => false,
);
});
}
}
}

View File

@@ -0,0 +1,24 @@
part of 'calculator_info_bloc.dart';
sealed class CalculatorInfoEvent extends Equatable {
const CalculatorInfoEvent();
}
final class CreateLeadEvent extends CalculatorInfoEvent {
const CreateLeadEvent({required this.argument, required this.isCall});
final bool isCall;
final CalculatorInfoArgument argument;
@override
List<Object?> get props => [argument, isCall];
}
final class ShowSuccessDialogEvent extends CalculatorInfoEvent {
const ShowSuccessDialogEvent({required this.isCall});
final bool isCall;
@override
List<Object?> get props => [isCall];
}

View File

@@ -0,0 +1,14 @@
part of 'calculator_info_bloc.dart';
class CalculatorInfoState extends Equatable {
const CalculatorInfoState({required this.isLoading});
final bool isLoading;
CalculatorInfoState copyWith({bool? isLoading}) {
return CalculatorInfoState(isLoading: isLoading ?? this.isLoading);
}
@override
List<Object?> get props => [isLoading];
}

View File

@@ -0,0 +1,32 @@
part of 'calculator_bloc.dart';
class CalculatorState extends Equatable {
const CalculatorState({
required this.isLoading,
this.weight,
this.size,
this.selectedWarehouse,
});
final bool isLoading;
final double? weight;
final double? size;
final String? selectedWarehouse;
CalculatorState copyWith({
bool? isLoading,
double? weight,
double? size,
String? selectedWarehouse,
}) {
return CalculatorState(
isLoading: isLoading ?? this.isLoading,
weight: weight ?? this.weight,
size: size ?? this.size,
selectedWarehouse: selectedWarehouse ?? this.selectedWarehouse,
);
}
@override
List<Object?> get props => [isLoading, weight, size, selectedWarehouse];
}

View File

@@ -0,0 +1,22 @@
import 'package:flutter/cupertino.dart';
mixin CalculatorMixin{
late TextEditingController weightController;
late TextEditingController sizeController;
late TextEditingController averageWeightController;
late TextEditingController productNameController;
void initControllers(){
weightController=TextEditingController();
sizeController=TextEditingController();
averageWeightController=TextEditingController();
productNameController=TextEditingController();
}
void disposeController(){
weightController.dispose();
sizeController.dispose();
averageWeightController.dispose();
productNameController.dispose();
}
}

View File

@@ -0,0 +1,155 @@
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:cargocalculaterapp/core/functions/base_finctions.dart';
import 'package:cargocalculaterapp/core/utils/app_utils.dart';
import 'package:cargocalculaterapp/core/widgets/loading/progress_hud.dart';
import 'package:cargocalculaterapp/features/calculator/presentation/pages/calculator_info/widget/calculator_info_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../../../constants/constants.dart';
import '../../../../../generated/l10n.dart';
import '../../arguments/calculator_info_argument.dart';
import '../../bloc/calculator_info/calculator_info_bloc.dart';
class CalculatorInfoPage extends StatelessWidget {
const CalculatorInfoPage({super.key, required this.argument});
final CalculatorInfoArgument? argument;
@override
Widget build(BuildContext context) {
return BlocBuilder<CalculatorInfoBloc, CalculatorInfoState>(
builder: (context, state) {
return Scaffold(
backgroundColor: context.color.grayBackground,
appBar: AppBar(title: Text(AppLocalization.current.calculator)),
body: ModalProgressHUD(
inAsyncCall: state.isLoading,
child: ListView(
padding: AppUtils.kPaddingAll16,
children: [
Container(
margin: const EdgeInsets.only(bottom: 32),
padding: AppUtils.kPaddingAll16,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: AppUtils.kBorderRadius16,
color: context.color.statusBackground,
border: Border.all(color: context.color.lightBorder),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CalculatorInfoWidget(
title: AppLocalization.current.product_name,
name: argument?.productName ?? "-",
),
CalculatorInfoWidget(
title: AppLocalization.current.warehouse,
name: Functions.getTranslatedItem(
AppConst.warehouseOptions[argument?.weraHouse],
context,
),
),
CalculatorInfoWidget(
title: AppLocalization.current.weight,
name: "${argument?.weight} kg",
),
CalculatorInfoWidget(
title: AppLocalization.current.size,
name: "${argument?.size}",
),
CalculatorInfoWidget(
title: AppLocalization.current.average_weight,
name: "${argument?.averageWeight}",
),
CalculatorInfoWidget(
title: AppLocalization.current.delivery_price,
name: "${argument?.deliveryPrice}",
),
],
),
),
Row(
children: [
Expanded(
child: InkWell(
onTap: () {
if (argument != null) {
context.read<CalculatorInfoBloc>().add(
CreateLeadEvent(
isCall: true,
argument: argument!,
),
);
}
},
borderRadius: AppUtils.kBorderRadius24,
child: Ink(
decoration: BoxDecoration(
color: context.color.scaffoldBackgroundColor,
borderRadius: AppUtils.kBorderRadius24,
border: Border.all(
color: context.color.primaryColor,
),
),
height: 56,
padding: AppUtils.kPaddingHor16,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
"assets/svg/ic_phone.svg",
colorFilter: ColorFilter.mode(
context.color.textColor,
BlendMode.srcIn,
),
),
AppUtils.kBoxWidth4,
Text(
AppLocalization.current.phone_call,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: context.color.textColor,
),
),
],
),
),
),
),
AppUtils.kBoxWidth16,
Expanded(
child: ElevatedButton(
onPressed: () {
if (argument != null) {
context.read<CalculatorInfoBloc>().add(
CreateLeadEvent(
isCall: false,
argument: argument!,
),
);
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/ic_list_1.svg"),
AppUtils.kBoxWidth4,
Text(AppLocalization.current.send),
],
),
),
),
],
),
],
),
),
);
},
);
}
}

View File

@@ -0,0 +1,45 @@
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:cargocalculaterapp/core/utils/app_utils.dart';
import 'package:cargocalculaterapp/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class OrderDialog extends StatelessWidget {
const OrderDialog({super.key, required this.isCall});
final bool isCall;
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: context.color.scaffoldBackgroundColor,
insetPadding: AppUtils.kPaddingAll16,
child: Padding(
padding: AppUtils.kPaddingVer24Hor16,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset("assets/svg/ic_done.svg"),
AppUtils.kBoxHeight20,
Text(
isCall
? AppLocalization.current.done_phone
: AppLocalization.current.done_order,
style: context.text.authDesc.copyWith(
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
AppUtils.kBoxHeight20,
ElevatedButton(
onPressed: () {
Navigator.pop(context, true);
},
child: Text(AppLocalization.current.done_ready),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,32 @@
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:flutter/material.dart';
import '../../../../../../core/utils/app_utils.dart';
class CalculatorInfoWidget extends StatelessWidget {
const CalculatorInfoWidget({super.key, required this.title, required this.name});
final String title;
final String name;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: Text(title, style: context.text.orderListTitle)),
AppUtils.kBoxWidth8,
Expanded(
child: Text(
name,
style: context.text.profileCategory,
textAlign: TextAlign.end,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,146 @@
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:cargocalculaterapp/core/utils/app_utils.dart';
import 'package:cargocalculaterapp/core/widgets/loading/progress_hud.dart';
import 'package:cargocalculaterapp/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../../core/widgets/drop_down/custom_drop_down.dart';
import '../../../../core/widgets/text_filds/custom_text_field_name.dart';
import '../bloc/calculator_bloc.dart';
import '../mixin/calculator_mixin.dart';
class CalculatorPage extends StatefulWidget {
const CalculatorPage({super.key});
@override
State<CalculatorPage> createState() => _CalculatorPageState();
}
class _CalculatorPageState extends State<CalculatorPage> with CalculatorMixin {
@override
void initState() {
initControllers();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocConsumer<CalculatorBloc, CalculatorState>(
listener: (context, state) {
if ((state.weight ?? 0) > 0 && (state.size ?? 0) > 0) {
averageWeightController.text =
"${(state.weight ?? 0) / (state.size ?? 1)}";
}
},
builder: (context, state) {
return Scaffold(
backgroundColor: context.color.grayBackground,
appBar: AppBar(title: Text(AppLocalization.current.calculator)),
body: ModalProgressHUD(
inAsyncCall: state.isLoading,
child: ListView(
padding: AppUtils.kPaddingAll16,
children: [
CustomTextFieldName(
hint: AppLocalization.current.weight,
name: AppLocalization.current.weight_in_kg,
isRequired: true,
format: [
FilteringTextInputFormatter.allow(
RegExp(r'^\d*[.,]?\d{0,}$'), // accepts both "." and ","
),
],
controller: weightController,
inputType: const TextInputType.numberWithOptions(
decimal: true,
),
onchange: (value) {
context.read<CalculatorBloc>().add(
WeightSizeEnterEvent(
weight: value,
size: sizeController.text,
),
);
},
),
AppUtils.kBoxHeight16,
CustomTextFieldName(
hint: AppLocalization.current.size,
name: AppLocalization.current.size_in_m3,
isRequired: true,
controller: sizeController,
format: [
FilteringTextInputFormatter.allow(
RegExp(r'^\d*[.,]?\d{0,}$'), // accepts both "." and ","
),
],
inputType: const TextInputType.numberWithOptions(
decimal: true,
),
onchange: (value) {
context.read<CalculatorBloc>().add(
WeightSizeEnterEvent(
weight: weightController.text,
size: value,
),
);
},
),
AppUtils.kBoxHeight16,
CustomTextFieldName(
hint: AppLocalization.current.average_weight,
name: AppLocalization.current.average_weight_in,
controller: averageWeightController,
readOnly: true,
),
AppUtils.kBoxHeight16,
CustomTextFieldName(
hint: AppLocalization.current.product_name,
isRequired: true,
name: AppLocalization.current.product_name,
controller: productNameController,
inputType: TextInputType.name,
),
AppUtils.kBoxHeight16,
CustomDropDown(
isRequired: false,
value: state.selectedWarehouse,
onChange: (value) {
context.read<CalculatorBloc>().add(
WareHouseSelectEvent(wareHouse: value ?? ""),
);
},
name: AppLocalization.current.select_warehouse,
),
AppUtils.kBoxHeight16,
ElevatedButton(
onPressed: ((state.weight ?? 0) > 0 && (state.size ?? 0) > 0)
? () {
context.read<CalculatorBloc>().add(
CalculateEvent(
productName: productNameController.text,
),
);
}
: null,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/svg/ic_calculator_1.svg"),
AppUtils.kBoxWith8,
Text(AppLocalization.current.calculate),
],
),
),
],
),
),
);
},
);
}
}

View File

@@ -0,0 +1,36 @@
import 'package:cargocalculaterapp/core/extension/build_context_extension.dart';
import 'package:flutter/material.dart';
import '../../../../../core/utils/app_utils.dart';
class OrderDataWidget extends StatelessWidget {
const OrderDataWidget({super.key, required this.title, required this.name});
final String title;
final String name;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: Text(title, style: context.text.profileCategory)),
AppUtils.kBoxWidth8,
Expanded(
child: Text(
name,
style: context.text.profileCategory,
textAlign: TextAlign.end,
),
),
],
),
const Padding(padding: AppUtils.kPaddingVer8, child: AppUtils.kDivider),
],
);
}
}

View File

@@ -0,0 +1,25 @@
import 'package:cargocalculaterapp/features/home/data/models/comment_request.dart';
import 'package:cargocalculaterapp/features/home/data/models/comment_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/notification_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/order_single_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/orders_list_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/read_notification_request.dart';
import 'package:cargocalculaterapp/features/home/data/models/read_notification_response.dart';
import '../../models/banner_response.dart';
abstract class HomeRemoteDataSource {
Future<OrdersListResponse> ordersList(Map<String, dynamic> request);
Future<OrderSingleResponse> orderSingle(String orderId);
Future<CommentResponse> comment(CommentRequest request);
Future<NotificationResponse> notification(Map<String, dynamic> request);
Future<ReadNotificationResponse> notificationRead(
ReadNotificationRequest request,
);
Future<List<BannerResponse>> bannersResponse();
}

View File

@@ -0,0 +1,157 @@
import 'package:cargocalculaterapp/features/home/data/models/banner_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/comment_request.dart';
import 'package:cargocalculaterapp/features/home/data/models/comment_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/notification_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/order_single_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/orders_list_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/read_notification_request.dart';
import 'package:cargocalculaterapp/features/home/data/models/read_notification_response.dart';
import 'package:dio/dio.dart';
import '../../../../../constants/constants.dart';
import '../../../../../core/error/exceptions.dart';
import '../../../../../core/local_source/local_source.dart';
import '../../../../../injector_container.dart';
import 'home_remote_data_source.dart';
class HomeRemoteDataSourceImpl extends HomeRemoteDataSource {
final Dio dio;
HomeRemoteDataSourceImpl(this.dio);
@override
Future<OrdersListResponse> ordersList(Map<String, dynamic> request) async {
try {
final Response response = await dio.get(
Constants.baseUrl + Urls.orderList,
options: DioConstants.options
..headers = {
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
},
queryParameters: request,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return OrdersListResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
@override
Future<OrderSingleResponse> orderSingle(String orderId) async {
try {
final Response response = await dio.get(
"${Constants.baseUrl}${Urls.orderList}/$orderId",
options: DioConstants.options
..headers = {
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
},
);
if (response.statusCode == 200 || response.statusCode == 201) {
return OrderSingleResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
@override
Future<CommentResponse> comment(CommentRequest request) async {
try {
final Response response = await dio.post(
"${Constants.baseUrl}${Urls.comment}",
options: DioConstants.options
..headers = {
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
},
data: request,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return CommentResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
@override
Future<NotificationResponse> notification(
Map<String, dynamic> request,
) async {
try {
final Response response = await dio.get(
"${Constants.baseUrl}${Urls.notification}",
options: DioConstants.options
..headers = {
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
},
queryParameters: request,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return NotificationResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
@override
Future<ReadNotificationResponse> notificationRead(
ReadNotificationRequest request,
) async {
try {
final Response response = await dio.put(
"${Constants.baseUrl}${Urls.notificationRead}",
options: DioConstants.options
..headers = {
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
},
data: request,
);
if (response.statusCode == 200 || response.statusCode == 201) {
return ReadNotificationResponse.fromJson(response.data);
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
@override
Future<List<BannerResponse>> bannersResponse() async {
try {
final Response response = await dio.get(
"${Constants.baseUrl}${Urls.banners}",
options: DioConstants.options
..headers = {
"Authorization": "Bearer ${sl<LocalSource>().getAccessToken()}",
},
);
if (response.statusCode == 200 || response.statusCode == 201) {
final List<BannerResponse> images = (response.data as List)
.map((json) => BannerResponse.fromJson(json))
.toList();
return images;
}
throw ServerException.fromJson(response.data);
} on DioException catch (e) {
throw ServerException.fromJson(e.response?.data);
} on FormatException {
throw ServerException(message: Validations.someThingWentWrong);
}
}
}

View File

@@ -0,0 +1,27 @@
class BannerResponse {
BannerResponse({this.id, this.image, this.createdAt, this.updatedAt, this.v});
BannerResponse.fromJson(dynamic json) {
id = json['_id'];
image = json['image'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
v = json['__v'];
}
String? id;
String? image;
String? createdAt;
String? updatedAt;
int? v;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['_id'] = id;
map['image'] = image;
map['createdAt'] = createdAt;
map['updatedAt'] = updatedAt;
map['__v'] = v;
return map;
}
}

View File

@@ -0,0 +1,24 @@
class CommentRequest {
CommentRequest({
this.message,
this.rate,
this.orderNumber,});
CommentRequest.fromJson(dynamic json) {
message = json['message'];
rate = json['rate'];
orderNumber = json['order_number'];
}
String? message;
int? rate;
String? orderNumber;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['message'] = message;
map['rate'] = rate;
map['order_number'] = orderNumber;
return map;
}
}

View File

@@ -0,0 +1,44 @@
class CommentResponse {
CommentResponse({
this.message,
this.rate,
this.userUcode,
this.orderNumber,
this.id,
this.createdAt,
this.updatedAt,
this.v,});
CommentResponse.fromJson(dynamic json) {
message = json['message'];
rate = json['rate'];
userUcode = json['user_ucode'];
orderNumber = json['order_number'];
id = json['_id'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
v = json['__v'];
}
String? message;
int? rate;
String? userUcode;
String? orderNumber;
String? id;
String? createdAt;
String? updatedAt;
int? v;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['message'] = message;
map['rate'] = rate;
map['user_ucode'] = userUcode;
map['order_number'] = orderNumber;
map['_id'] = id;
map['createdAt'] = createdAt;
map['updatedAt'] = updatedAt;
map['__v'] = v;
return map;
}
}

View File

@@ -0,0 +1,79 @@
import 'package:cargocalculaterapp/core/models/name.dart';
class NotificationResponse {
NotificationResponse({this.notifications, this.totalCount});
NotificationResponse.fromJson(dynamic json) {
if (json['notifications'] != null) {
notifications = [];
json['notifications'].forEach((v) {
notifications?.add(Notifications.fromJson(v));
});
}
totalCount = json['totalCount'];
}
List<Notifications>? notifications;
int? totalCount;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
if (notifications != null) {
map['notifications'] = notifications?.map((v) => v.toJson()).toList();
}
map['totalCount'] = totalCount;
return map;
}
}
class Notifications {
Notifications({
this.id,
this.userUcode,
this.orderNumber,
this.text,
this.status,
this.isRead,
this.createdAt,
this.updatedAt,
this.v,
});
Notifications.fromJson(dynamic json) {
id = json['_id'];
userUcode = json['user_ucode'];
orderNumber = json['order_number'];
text = json['text'] != null ? Name.fromJson(json['text']) : null;
status = json['status'];
isRead = json['is_read'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
v = json['__v'];
}
String? id;
String? userUcode;
String? orderNumber;
Name? text;
String? status;
bool? isRead;
String? createdAt;
String? updatedAt;
int? v;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['_id'] = id;
map['user_ucode'] = userUcode;
map['order_number'] = orderNumber;
if (text != null) {
map['text'] = text?.toJson();
}
map['status'] = status;
map['is_read'] = isRead;
map['createdAt'] = createdAt;
map['updatedAt'] = updatedAt;
map['__v'] = v;
return map;
}
}

View File

@@ -0,0 +1,145 @@
import 'package:cargocalculaterapp/core/models/name.dart';
class OrderSingleResponse {
OrderSingleResponse({
this.id,
this.userUcode,
this.phoneNumber,
this.gmail,
this.quantity,
this.nameUz,
this.nameRu,
this.nameZh,
this.weight,
this.m3,
this.averageWeightKg,
this.wharehouseUz,
this.wharehouseRu,
this.wharehouseZh,
this.deliveryPrice,
this.shipmentId,
this.leadId,
this.status,
this.partiallyArrived,
this.fullArrived,
this.orderNumber,
this.createdAt,
this.updatedAt,
this.v,
this.arrivalDate,
this.images,
});
OrderSingleResponse.fromJson(dynamic json) {
userUcode = json['user_ucode'];
phoneNumber = json['phone_number'];
gmail = json['gmail'];
quantity = json['quantity'];
nameUz = json['nameUz'];
nameRu = json['nameRu'];
nameZh = json['nameZh'];
weight = json['weight'];
m3 = json['m3'];
averageWeightKg = json['average_weight_kg'];
wharehouseUz = json['wharehouseUz'];
wharehouseRu = json['wharehouseRu'];
wharehouseZh = json['wharehouseZh'];
deliveryPrice = json['delivery_price'];
shipmentId = json['shipment_id'];
leadId = json['lead_id'];
status = json['status'] != null ? Status.fromJson(json['status']) : null;
partiallyArrived = json['partially_arrived'];
fullArrived = json['full_arrived'];
orderNumber = json['order_number'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
v = json['__v'];
id = json['id'];
arrivalDate = json['arrival_date'];
images = json['images'] != null ? json['images'].cast<String>() : [];
}
String? userUcode;
String? phoneNumber;
String? gmail;
int? quantity;
String? nameUz;
String? nameRu;
String? nameZh;
String? weight;
String? m3;
String? averageWeightKg;
String? wharehouseUz;
String? wharehouseRu;
String? wharehouseZh;
String? deliveryPrice;
String? shipmentId;
String? leadId;
Status? status;
bool? partiallyArrived;
bool? fullArrived;
String? orderNumber;
String? createdAt;
String? updatedAt;
int? v;
String? id;
String? arrivalDate;
List<String>? images;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['user_ucode'] = userUcode;
map['phone_number'] = phoneNumber;
map['gmail'] = gmail;
map['quantity'] = quantity;
map['nameUz'] = nameUz;
map['nameRu'] = nameRu;
map['nameZh'] = nameZh;
map['weight'] = weight;
map['m3'] = m3;
map['average_weight_kg'] = averageWeightKg;
map['wharehouseUz'] = wharehouseUz;
map['wharehouseRu'] = wharehouseRu;
map['wharehouseZh'] = wharehouseZh;
map['delivery_price'] = deliveryPrice;
map['shipment_id'] = shipmentId;
map['lead_id'] = leadId;
if (status != null) {
map['status'] = status?.toJson();
}
map['partially_arrived'] = partiallyArrived;
map['full_arrived'] = fullArrived;
map['order_number'] = orderNumber;
map['createdAt'] = createdAt;
map['updatedAt'] = updatedAt;
map['__v'] = v;
map['id'] = id;
map['arrival_date'] = arrivalDate;
map['images'] = images;
return map;
}
}
class Status {
Status({this.code, this.translations});
Status.fromJson(dynamic json) {
code = json['code'];
translations =
json['translations'] != null
? Name.fromJson(json['translations'])
: null;
}
String? code;
Name? translations;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['code'] = code;
if (translations != null) {
map['translations'] = translations?.toJson();
}
return map;
}
}

View File

@@ -0,0 +1,160 @@
import '../../../../core/models/name.dart';
class OrdersListResponse {
OrdersListResponse({this.totalCount, this.data});
OrdersListResponse.fromJson(dynamic json) {
totalCount = json['totalCount'];
if (json['data'] != null) {
data = [];
json['data'].forEach((v) {
data?.add(Data.fromJson(v));
});
}
}
int? totalCount;
List<Data>? data;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['totalCount'] = totalCount;
if (data != null) {
map['data'] = data?.map((v) => v.toJson()).toList();
}
return map;
}
}
class Data {
Data({
this.id,
this.userUcode,
this.phoneNumber,
this.gmail,
this.quantity,
this.nameUz,
this.nameRu,
this.nameZh,
this.size,
this.wharehouseUz,
this.wharehouseRu,
this.wharehouseZh,
this.deliveryPrice,
this.shipmentId,
this.leadId,
this.status,
this.partiallyArrived,
this.fullArrived,
this.orderNumber,
this.createdAt,
this.updatedAt,
this.v,
this.arrivalDate,
});
Data.fromJson(dynamic json) {
userUcode = json['user_ucode'];
phoneNumber = json['phone_number'];
gmail = json['gmail'];
quantity = json['quantity'];
nameUz = json['nameUz'];
nameRu = json['nameRu'];
nameZh = json['nameZh'];
size = json['size'];
wharehouseUz = json['wharehouseUz'];
wharehouseRu = json['wharehouseRu'];
wharehouseZh = json['wharehouseZh'];
deliveryPrice = json['delivery_price'];
shipmentId = json['shipment_id'];
leadId = json['lead_id'];
status = json['status'] != null ? Status.fromJson(json['status']) : null;
partiallyArrived = json['partially_arrived'];
fullArrived = json['full_arrived'];
orderNumber = json['order_number'];
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
v = json['__v'];
id = json['id'];
arrivalDate = json['arrival_date'];
}
String? userUcode;
String? phoneNumber;
String? gmail;
int? quantity;
String? nameUz;
String? nameRu;
String? nameZh;
String? size;
String? wharehouseUz;
String? wharehouseRu;
String? wharehouseZh;
String? deliveryPrice;
String? shipmentId;
String? leadId;
Status? status;
bool? partiallyArrived;
bool? fullArrived;
String? orderNumber;
String? createdAt;
String? updatedAt;
int? v;
String? id;
String? arrivalDate;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['_id'] = id;
map['user_ucode'] = userUcode;
map['phone_number'] = phoneNumber;
map['gmail'] = gmail;
map['quantity'] = quantity;
map['nameUz'] = nameUz;
map['nameRu'] = nameRu;
map['nameZh'] = nameZh;
map['size'] = size;
map['wharehouseUz'] = wharehouseUz;
map['wharehouseRu'] = wharehouseRu;
map['wharehouseZh'] = wharehouseZh;
map['delivery_price'] = deliveryPrice;
map['shipment_id'] = shipmentId;
map['lead_id'] = leadId;
if (status != null) {
map['status'] = status?.toJson();
}
map['partially_arrived'] = partiallyArrived;
map['full_arrived'] = fullArrived;
map['order_number'] = orderNumber;
map['createdAt'] = createdAt;
map['updatedAt'] = updatedAt;
map['__v'] = v;
map['id'] = id;
map['arrival_date'] = arrivalDate;
return map;
}
}
class Status {
Status({this.code, this.translations});
Status.fromJson(dynamic json) {
code = json['code'];
translations =
json['translations'] != null
? Name.fromJson(json['translations'])
: null;
}
String? code;
Name? translations;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['code'] = code;
if (translations != null) {
map['translations'] = translations?.toJson();
}
return map;
}
}

View File

@@ -0,0 +1,16 @@
class ReadNotificationRequest {
ReadNotificationRequest({
this.notificationIds,});
ReadNotificationRequest.fromJson(dynamic json) {
notificationIds = json['notification_ids'] != null ? json['notification_ids'].cast<String>() : [];
}
List<String>? notificationIds;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['notification_ids'] = notificationIds;
return map;
}
}

View File

@@ -0,0 +1,16 @@
class ReadNotificationResponse {
ReadNotificationResponse({
this.message,});
ReadNotificationResponse.fromJson(dynamic json) {
message = json['message'];
}
String? message;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['message'] = message;
return map;
}
}

View File

@@ -0,0 +1,88 @@
import 'package:cargocalculaterapp/core/error/failure.dart';
import 'package:cargocalculaterapp/features/home/data/models/banner_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/comment_request.dart';
import 'package:cargocalculaterapp/features/home/data/models/comment_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/notification_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/order_single_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/orders_list_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/read_notification_request.dart';
import 'package:cargocalculaterapp/features/home/data/models/read_notification_response.dart';
import 'package:dartz/dartz.dart';
import '../../domain/repository/home_repository.dart';
import '../data_source/remote/home_remote_data_source.dart';
class HomeRepositoryImpl extends HomeRepository {
final HomeRemoteDataSource remoteDataSource;
HomeRepositoryImpl(this.remoteDataSource);
@override
Future<Either<Failure, OrdersListResponse>> ordersList(
Map<String, dynamic> request,
) async {
try {
final response = await remoteDataSource.ordersList(request);
return Right(response);
} catch (e) {
return Left(ServerFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, OrderSingleResponse>> orderSingle(
String orderId,
) async {
try {
final response = await remoteDataSource.orderSingle(orderId);
return Right(response);
} catch (e) {
return Left(ServerFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, CommentResponse>> comment(
CommentRequest request,
) async {
try {
final response = await remoteDataSource.comment(request);
return Right(response);
} catch (e) {
return Left(ServerFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, NotificationResponse>> notification(
Map<String, dynamic> request,
) async {
try {
final response = await remoteDataSource.notification(request);
return Right(response);
} catch (e) {
return Left(ServerFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, ReadNotificationResponse>> notificationRead(
ReadNotificationRequest request,
) async {
try {
final response = await remoteDataSource.notificationRead(request);
return Right(response);
} catch (e) {
return Left(ServerFailure(message: e.toString()));
}
}
@override
Future<Either<Failure, List<BannerResponse>>> bannersGet() async {
try {
final response = await remoteDataSource.bannersResponse();
return Right(response);
} catch (e) {
return Left(ServerFailure(message: e.toString()));
}
}
}

View File

@@ -0,0 +1,30 @@
import 'package:cargocalculaterapp/features/home/data/models/comment_request.dart';
import 'package:cargocalculaterapp/features/home/data/models/comment_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/notification_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/order_single_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/orders_list_response.dart';
import 'package:cargocalculaterapp/features/home/data/models/read_notification_request.dart';
import 'package:cargocalculaterapp/features/home/data/models/read_notification_response.dart';
import 'package:dartz/dartz.dart';
import '../../../../core/error/failure.dart';
import '../../data/models/banner_response.dart';
abstract class HomeRepository {
Future<Either<Failure, OrdersListResponse>> ordersList(
Map<String, dynamic> request,
);
Future<Either<Failure, OrderSingleResponse>> orderSingle(String orderId);
Future<Either<Failure, CommentResponse>> comment(CommentRequest request);
Future<Either<Failure, NotificationResponse>> notification(
Map<String, dynamic> request,
);
Future<Either<Failure, ReadNotificationResponse>> notificationRead(
ReadNotificationRequest request,
);
Future<Either<Failure, List<BannerResponse>>> bannersGet();
}

Some files were not shown because too many files have changed in this diff Show More