Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ios/Flutter/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
1 change: 1 addition & 0 deletions ios/Flutter/Release.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
41 changes: 41 additions & 0 deletions ios/Podfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}

def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end

File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
use_frameworks!
use_modular_headers!

flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
24 changes: 24 additions & 0 deletions lib/core/error/custom_toast.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:flutter/material.dart';

class CustomToast {
static toast(
BuildContext context,
String message, {
Duration? duration,
}) {
showToast(message,
context: context,
animation: StyledToastAnimation.slideFromTop,
reverseAnimation: StyledToastAnimation.slideToTop,
position: StyledToastPosition.top,
startOffset: const Offset(0.0, -3.0),
reverseEndOffset: const Offset(0.0, -3.0),
duration: duration ?? const Duration(seconds: 4),
animDuration: const Duration(seconds: 1),
curve: Curves.elasticOut,
reverseCurve: Curves.fastOutSlowIn);
}

CustomToast._();
}
14 changes: 14 additions & 0 deletions lib/core/error/failures.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:equatable/equatable.dart';

abstract class Failure extends Equatable {
final String message;

const Failure(this.message);

@override
List<Object> get props => [message];
}

class DatabaseFailure extends Failure {
const DatabaseFailure(String message) : super(message);
}
29 changes: 29 additions & 0 deletions lib/core/helper/helper_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
extension FormValidation on String? {
bool get isValidEmail {
if (this == null) return false;
final emailRegExp = RegExp(
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$');
return emailRegExp.hasMatch(this!);
}

bool get isNotNull => this != null && this!.trim().isNotEmpty;

bool get isValidPhone {
if (this == null) return false;
final phoneRegExp = RegExp(r"^\+?0[0-9]{10}$");
return phoneRegExp.hasMatch(this!);
}

bool get isValidDate {
if (this == null) return false;
final dateRegExp =
RegExp(r"^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$");
return dateRegExp.hasMatch(this!);
}

bool get isValidBank {
if (this == null) return false;
final dateRegExp = RegExp(r"^([0-9]{11})|([0-9]{2}-[0-9]{3}-[0-9]{6})$");
return dateRegExp.hasMatch(this!.replaceAll('-', ''));
}
}
78 changes: 78 additions & 0 deletions lib/core/helper/input_formatters.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'package:flutter/services.dart';

class DateInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final newTextLength = newValue.text.length;
var selectionIndex = newValue.selection.end;
var usedSubstringIndex = 0;
final newText = StringBuffer();

var a = 3;
if (newTextLength >= a) {
newText
.write(newValue.text.substring(0, usedSubstringIndex = a - 1) + '/');
if (newValue.selection.end >= a - 1) selectionIndex++;
}

var b = 5;
if (newTextLength >= b) {
newText
.write(newValue.text.substring(2, usedSubstringIndex = b - 1) + '/');
if (newValue.selection.end >= b - 1) selectionIndex++;
}

if (newTextLength >= usedSubstringIndex) {
newText.write(newValue.text.substring(usedSubstringIndex));
}
return TextEditingValue(
text: newText.toString(),
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}

class CardInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final newTextLength = newValue.text.length;
var selectionIndex = newValue.selection.end;
var usedSubstringIndex = 0;
final newText = StringBuffer();

var a = 5;
if (newTextLength >= a) {
newText
.write(newValue.text.substring(0, usedSubstringIndex = a - 1) + '-');
if (newValue.selection.end >= a - 1) selectionIndex++;
}

var b = 9;
if (newTextLength >= b) {
newText
.write(newValue.text.substring(4, usedSubstringIndex = b - 1) + '-');
if (newValue.selection.end >= b - 1) selectionIndex++;
}

var c = 13;
if (newTextLength >= c) {
newText
.write(newValue.text.substring(8, usedSubstringIndex = c - 1) + '-');
if (newValue.selection.end >= c - 1) selectionIndex++;
}

if (newTextLength >= usedSubstringIndex) {
newText.write(newValue.text.substring(usedSubstringIndex));
}
return TextEditingValue(
text: newText.toString(),
selection: TextSelection.collapsed(offset: selectionIndex),
);
}
}
59 changes: 59 additions & 0 deletions lib/core/services/service_locator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'package:get_it/get_it.dart';
import 'package:mc_crud_test/features/customer/data/repositories/customer_repository_impl.dart';
import '../../features/customer/data/datasources/customer_local_data_source.dart';
import '../../features/customer/domain/repositories/customer_repository.dart';
import '../../features/customer/domain/usecases/add_customer_usecase.dart';
import '../../features/customer/domain/usecases/delete_customer_usecase.dart';
import '../../features/customer/domain/usecases/edit_customer_usecase.dart';
import '../../features/customer/domain/usecases/get_customer_by_id_usecase.dart';
import '../../features/customer/domain/usecases/get_customers_usecase.dart';
import '../../features/customer/presentation/bloc/customer_bloc.dart';

final _getIt = GetIt.instance;

T inject<T extends Object>() => _getIt.call();

class _$AppModule extends _AppModule {}

Future<void> setup() async {
var app = _$AppModule();
_getIt.registerSingleton(app._customerLocalDataSource);
_getIt.registerFactory(() => app._customerRepository);
_getIt.registerFactory(() => app._addCustomer);
_getIt.registerFactory(() => app._deleteCustomer);
_getIt.registerFactory(() => app._editCustomer);
_getIt.registerFactory(() => app._getCustomerById);
_getIt.registerFactory(() => app._getCustomers);
_getIt.registerFactory(() => app._customerBloc);
}

abstract class _AppModule {
CustomerLocalDataSource get _customerLocalDataSource =>
CustomerLocalDataSourceImpl();

CustomerRepository get _customerRepository =>
CustomerRepositoryImpl(localDataSource: inject());

AddCustomerUseCase get _addCustomer =>
AddCustomerUseCase(repository: inject());

DeleteCustomerUseCase get _deleteCustomer =>
DeleteCustomerUseCase(repository: inject());

EditCustomerUseCase get _editCustomer =>
EditCustomerUseCase(repository: inject());

GetCustomerByIdUseCase get _getCustomerById =>
GetCustomerByIdUseCase(repository: inject());

GetCustomersUseCase get _getCustomers =>
GetCustomersUseCase(repository: inject());

CustomerBloc get _customerBloc => CustomerBloc(
addCustomerUseCase: inject(),
deleteCustomerUseCase: inject(),
editCustomerUseCase: inject(),
getCustomerByIdUseCase: inject(),
getCustomersUseCase: inject(),
);
}
6 changes: 6 additions & 0 deletions lib/core/usecases/usecase.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:dartz/dartz.dart';
import 'package:mc_crud_test/core/error/failures.dart';

abstract class UseCase<Response, Params> {
Future<Either<Failure, Response>> call({required Params params});
}
20 changes: 20 additions & 0 deletions lib/custom_navigator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';

class CustomNavigator {
static final GlobalKey<NavigatorState>? navigatorKey =
GlobalKey<NavigatorState>();

static Future<dynamic> pushNamed(
String routeName, {
Object? arguments,
}) async {
return await navigatorKey?.currentState?.pushNamed(
routeName,
arguments: arguments,
);
}

static void pop({dynamic value}) => navigatorKey?.currentState?.pop(value);

CustomNavigator._();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:hive_flutter/hive_flutter.dart';
import '../../domain/entities/customer.dart';
import 'package:mockito/annotations.dart';

@GenerateNiceMocks([
MockSpec<CustomerLocalDataSource>(onMissingStub: OnMissingStub.returnDefault)
])
abstract class CustomerLocalDataSource {
Future<List<Customer>> getCustomers();

Future<Customer?> getCustomerById(String id);

Future<Customer> addCustomer(Customer customer);

Future<Customer> editCustomer(Customer customer);

Future<void> deleteCustomer(String email);
}

class CustomerLocalDataSourceImpl implements CustomerLocalDataSource {
Box<Customer>? _box;
final String _key = "customer";

Future<void> _init() async {
if (_box == null) {
Hive.registerAdapter(CustomerAdapter());
_box = await Hive.openBox<Customer>(_key);
}
}

@override
Future<Customer> addCustomer(Customer customer) async {
await _init();
await _box?.put(customer.email, customer);
return customer;
}

@override
Future<void> deleteCustomer(String email) async {
await _init();
await _box?.delete(email);
}

@override
Future<Customer> editCustomer(Customer customer) async {
await _init();
await _box?.put(customer.email, customer);
return customer;
}

@override
Future<Customer?> getCustomerById(String id) async {
await _init();
return _box?.get(id);
}

@override
Future<List<Customer>> getCustomers() async {
await _init();
return _box!.values.toList();
}
}
Loading