feat: add arch

This commit is contained in:
Yashar Panahi
2025-08-02 12:31:42 +03:30
parent 59c3091b2f
commit 64c4ebf0f8
12 changed files with 231 additions and 110 deletions
View File
View File
View File
View File
+21
View File
@@ -0,0 +1,21 @@
class AppException implements Exception {
final String message;
final String prefix;
AppException([this.message = 'An unknown error occurred.', this.prefix = '']);
@override
String toString() {
return '$prefix$message';
}
}
class FetchDataException extends AppException {
FetchDataException([String? message])
: super(message ?? 'Error During Communication: ', 'Network Error');
}
class AuthenticationException extends AppException {
AuthenticationException([String? message])
: super(message ?? 'Authentication Failed.', 'Auth Error');
}
+31
View File
@@ -0,0 +1,31 @@
sealed class Result<T> {
const Result();
/// Creates a successful [Result], completed with the specified [value].
const factory Result.ok(T value) = Ok._;
/// Creates an error [Result], completed with the specified [error].
const factory Result.error(Exception error) = Error._;
}
/// Subclass of Result for values
final class Ok<T> extends Result<T> {
const Ok._(this.value);
/// Returned value in result
final T value;
@override
String toString() => 'Result<$T>.ok($value)';
}
/// Subclass of Result for errors
final class Error<T> extends Result<T> {
const Error._(this.error);
/// Returned error in result
final Exception error;
@override
String toString() => 'Result<$T>.error($error)';
}
+6
View File
@@ -0,0 +1,6 @@
class UserModel {
final String token;
final String name;
UserModel({required this.token, required this.name});
}
@@ -0,0 +1,24 @@
import 'package:tm_app/core/utils/result.dart';
import '../../core/utils/app_exceptions.dart';
import '../models/user_model.dart';
import '../services/auth_service.dart';
class AuthRepository {
final AuthService _authService;
AuthRepository(this._authService);
Future<Result<UserModel>> login(String email, String password) async {
try {
final response = await _authService.login(email, password);
final user = UserModel(token: response['token'], name: response['name']);
return Result.ok(user); // در حالت موفقیت، یک Ok برمی‌گرداند
} catch (e) {
// در حالت خطا، یک Error برمی‌گرداند
return Result.error(
AuthenticationException('Email or password is not correct.'),
);
}
}
}
+12
View File
@@ -0,0 +1,12 @@
class AuthService {
Future<Map<String, dynamic>> login(String email, String password) async {
// شبیه‌سازی API Call
await Future.delayed(const Duration(seconds: 2));
if (email == 'test@example.com' && password == 'password123') {
return {'token': 'xyz123', 'name': 'John Doe'};
} else {
throw Exception('Invalid credentials');
}
}
}
+25 -110
View File
@@ -1,122 +1,37 @@
// main.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'data/repositories/auth_repository.dart';
import 'data/services/auth_service.dart';
import 'view_models/auth_view_model.dart';
import 'views/login_screen.dart';
void main() { void main() {
runApp(const MyApp()); runApp(
MultiProvider(
providers: [
Provider(create: (_) => AuthService()),
ProxyProvider<AuthService, AuthRepository>(
update: (_, authService, __) => AuthRepository(authService),
),
ChangeNotifierProvider(
create:
(context) => AuthViewModel(
Provider.of<AuthRepository>(context, listen: false),
),
),
],
child: const MyApp(),
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
// This widget is the root of your application.
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return const MaterialApp(home: LoginScreen());
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a purple toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
} }
} }
+41
View File
@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import '../core/utils/result.dart';
import '../data/models/user_model.dart';
import '../data/repositories/auth_repository.dart';
class AuthViewModel with ChangeNotifier {
final AuthRepository _authRepository;
AuthViewModel(this._authRepository);
bool _isLoading = false;
String? _errorMessage;
UserModel? _loggedInUser;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
UserModel? get loggedInUser => _loggedInUser;
Future<void> login(String email, String password) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
final result = await _authRepository.login(email, password);
switch (result) {
case Ok<UserModel>():
_loggedInUser = result.value;
// موفقیت‌آمیز
break;
case Error<UserModel>():
_errorMessage = result.error.toString();
// خطا
break;
}
_isLoading = false;
notifyListeners();
}
}
+71
View File
@@ -0,0 +1,71 @@
// lib/views/login_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../view_models/auth_view_model.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
final authViewModel = Provider.of<AuthViewModel>(context);
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
TextField(
controller: _passwordController,
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
),
const SizedBox(height: 20),
if (authViewModel.isLoading)
const CircularProgressIndicator()
else
ElevatedButton(
onPressed: () {
authViewModel.login(
_emailController.text,
_passwordController.text,
);
},
child: const Text('Login'),
),
if (authViewModel.errorMessage != null)
Padding(
padding: const EdgeInsets.only(top: 20),
child: Text(
authViewModel.errorMessage!,
style: const TextStyle(color: Colors.red),
),
),
if (authViewModel.loggedInUser != null)
Padding(
padding: const EdgeInsets.only(top: 20),
child: Text(
'Welcome, ${authViewModel.loggedInUser!.name}!',
style: const TextStyle(color: Colors.green),
),
),
],
),
),
);
}
}