fix : splash animation

feat : auth with password
chore : app Architecture
This commit is contained in:
2025-04-07 16:49:15 +03:30
parent 50cc84461e
commit e83388670c
32 changed files with 1192 additions and 276 deletions

8
assets/icons/call.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="vuesax/bold/call">
<g id="call">
<path id="Vector" d="M11.05 14.95L9.2 16.8C8.81 17.19 8.19 17.19 7.79 16.81C7.68 16.7 7.57 16.6 7.46 16.49C6.43 15.45 5.5 14.36 4.67 13.22C3.85 12.08 3.19 10.94 2.71 9.81C2.24 8.67 2 7.58 2 6.54C2 5.86 2.12 5.21 2.36 4.61C2.6 4 2.98 3.44 3.51 2.94C4.15 2.31 4.85 2 5.59 2C5.87 2 6.15 2.06 6.4 2.18C6.66 2.3 6.89 2.48 7.07 2.74L9.39 6.01C9.57 6.26 9.7 6.49 9.79 6.71C9.88 6.92 9.93 7.13 9.93 7.32C9.93 7.56 9.86 7.8 9.72 8.03C9.59 8.26 9.4 8.5 9.16 8.74L8.4 9.53C8.29 9.64 8.24 9.77 8.24 9.93C8.24 10.01 8.25 10.08 8.27 10.16C8.3 10.24 8.33 10.3 8.35 10.36C8.53 10.69 8.84 11.12 9.28 11.64C9.73 12.16 10.21 12.69 10.73 13.22C10.83 13.32 10.94 13.42 11.04 13.52C11.44 13.91 11.45 14.55 11.05 14.95Z" fill="#B0B0B0"/>
<path id="Vector_2" d="M21.97 18.33C21.97 18.61 21.92 18.9 21.82 19.18C21.79 19.26 21.76 19.34 21.72 19.42C21.55 19.78 21.33 20.12 21.04 20.44C20.55 20.98 20.01 21.37 19.4 21.62C19.39 21.62 19.38 21.63 19.37 21.63C18.78 21.87 18.14 22 17.45 22C16.43 22 15.34 21.76 14.19 21.27C13.04 20.78 11.89 20.12 10.75 19.29C10.36 19 9.96998 18.71 9.59998 18.4L12.87 15.13C13.15 15.34 13.4 15.5 13.61 15.61C13.66 15.63 13.72 15.66 13.79 15.69C13.87 15.72 13.95 15.73 14.04 15.73C14.21 15.73 14.34 15.67 14.45 15.56L15.21 14.81C15.46 14.56 15.7 14.37 15.93 14.25C16.16 14.11 16.39 14.04 16.64 14.04C16.83 14.04 17.03 14.08 17.25 14.17C17.47 14.26 17.7 14.39 17.95 14.56L21.26 16.91C21.52 17.09 21.7 17.3 21.81 17.55C21.91 17.8 21.97 18.05 21.97 18.33Z" fill="#B0B0B0"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

3
assets/icons/key.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="24" height="13" viewBox="0 0 24 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="key" d="M6.26087 8.32828C5.68696 8.32828 5.19565 8.12441 4.78696 7.71667C4.37826 7.30893 4.17391 6.81878 4.17391 6.24621C4.17391 5.67364 4.37826 5.18348 4.78696 4.77575C5.19565 4.36801 5.68696 4.16414 6.26087 4.16414C6.83478 4.16414 7.32609 4.36801 7.73478 4.77575C8.14348 5.18348 8.34783 5.67364 8.34783 6.24621C8.34783 6.81878 8.14348 7.30893 7.73478 7.71667C7.32609 8.12441 6.83478 8.32828 6.26087 8.32828ZM6.26087 12.4924C4.52174 12.4924 3.04348 11.8851 1.82609 10.6706C0.608696 9.45606 0 7.98126 0 6.24621C0 4.51115 0.608696 3.03635 1.82609 1.82181C3.04348 0.60727 4.52174 0 6.26087 0C7.42609 0 8.48261 0.286284 9.43044 0.858853C10.3783 1.43142 11.1304 2.18617 11.687 3.1231H20.8696L24 6.24621L19.3043 10.9309L17.2174 9.36931L15.1304 10.9309L12.913 9.36931H11.687C11.1304 10.3062 10.3783 11.061 9.43044 11.6336C8.48261 12.2061 7.42609 12.4924 6.26087 12.4924ZM6.26087 10.4103C7.23478 10.4103 8.0913 10.1154 8.83044 9.52547C9.56957 8.93555 10.0609 8.18947 10.3043 7.28724H13.5652L15.0783 8.3543L17.2174 6.76672L19.0696 8.19815L21.0261 6.24621L19.9826 5.20517H10.3043C10.0609 4.30294 9.56957 3.55687 8.83044 2.96695C8.0913 2.37703 7.23478 2.08207 6.26087 2.08207C5.11304 2.08207 4.13043 2.48981 3.31304 3.30528C2.49565 4.12076 2.08696 5.10107 2.08696 6.24621C2.08696 7.39134 2.49565 8.37165 3.31304 9.18713C4.13043 10.0026 5.11304 10.4103 6.26087 10.4103Z" fill="#B8B8B8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

1
assets/icons/user.svg Normal file
View File

@@ -0,0 +1 @@
<svg id="Layer_1" enable-background="new 0 0 100 100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-.051" x2="91.965" y1="-4.851" y2="95.99"><stop offset="0" stop-color="#97e0ff"/><stop offset="1" stop-color="#1075ff"/></linearGradient><path d="m50 2.5000012c-26.2000008 0-47.5 21.2999973-47.5 47.5s21.2999992 47.4999988 47.5 47.4999988 47.5-21.3000031 47.5-47.5-21.3000031-47.4999988-47.5-47.4999988zm0 28.1999996c8.2000008 0 14.9000015 6.7000008 14.9000015 14.8999977s-6.7000007 14.9000015-14.9000015 14.9000015-14.9000015-6.7000008-14.9000015-14.9000015 6.7000007-14.8999977 14.9000015-14.8999977zm-26 48.1000023v-2.1999969c0-6.3000031 5.1000004-11.5 11.5-11.5h29c6.3000031 0 11.5 5.0999985 11.5 11.5v2.1999969c-6.9000015 6.1999969-16 10-26 10s-19.1000004-3.8000031-26-10z" fill="url(#SVGID_1_)"/></svg>

After

Width:  |  Height:  |  Size: 926 B

BIN
assets/vec/call.svg.vec Normal file

Binary file not shown.

BIN
assets/vec/key.svg.vec Normal file

Binary file not shown.

View File

@@ -0,0 +1,12 @@
import 'package:hive_ce_flutter/hive_flutter.dart';
import 'package:rasadyar_app/data/data_provider/local_storage/i_local_storage_provider.dart';
enum HiveBoxNames { user, settings, auth }
class HiveProvider extends ILocalStorageProvider {
@override
Future<void> init() async {
await Hive.initFlutter();
}
}

View File

@@ -0,0 +1 @@
const int userTypeId = 0;

View File

@@ -0,0 +1,3 @@
abstract class ILocalStorageProvider {
Future<void> init();
}

View File

@@ -0,0 +1,19 @@
import 'package:hive_ce_flutter/hive_flutter.dart';
import 'package:rasadyar_app/data/data_provider/local_storage/hive/hive_provider.dart';
abstract class IUserLocalStorage {
Future<bool> userAuthed();
}
class UserLocalStorage extends IUserLocalStorage {
final user = Hive.box(HiveBoxNames.user.name);
@override
Future<bool> userAuthed() async {
if (user.isNotEmpty ) {
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,17 @@
import 'package:hive_ce/hive.dart';
import '../../data_provider/local_storage/hive/hive_types.dart';
part 'user_model.g.dart';
@HiveType(typeId: userTypeId)
class UserModel extends HiveObject{
@HiveField(0)
String? token;
@HiveField(1)
String? refreshToken;
UserModel({this.token, this.refreshToken});
}

View File

@@ -0,0 +1,44 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class UserModelAdapter extends TypeAdapter<UserModel> {
@override
final int typeId = 0;
@override
UserModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return UserModel(
token: fields[0] as String?,
refreshToken: fields[1] as String?,
);
}
@override
void write(BinaryWriter writer, UserModel obj) {
writer
..writeByte(2)
..writeByte(0)
..write(obj.token)
..writeByte(1)
..write(obj.refreshToken);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,6 @@
class UserEntity {
String? token;
String? refreshToken;
UserEntity({this.token, this.refreshToken});
}

View File

@@ -0,0 +1,25 @@
import 'package:rasadyar_app/data/data_source/local_storage/user/user_local_storage.dart';
abstract class IUserRepository {
Future<bool> userAuthed();
/*Future<void> setUserAuthed(bool value);
Future<void> setUserName(String name);
Future<String?> getUserName();
Future<void> setUserPhone(String phone);
Future<String?> getUserPhone();
Future<void> setUserEmail(String email);
Future<String?> getUserEmail();
Future<void> setUserPassword(String password);
Future<String?> getUserPassword();*/
}
class UserRepository implements IUserRepository {
final IUserLocalStorage _userLocalStorage;
UserRepository(this._userLocalStorage);
@override
Future<bool> userAuthed() async {
return await _userLocalStorage.userAuthed();
}
}

View File

@@ -0,0 +1,17 @@
import 'package:get/get.dart';
import 'package:rasadyar_app/domain/repository/user/user_repository.dart';
import 'package:rasadyar_app/infrastructure/di/di.dart';
class UserService extends GetxService {
late IUserRepository _userLocalStorage;
@override
void onInit() {
return super.onInit();
_userLocalStorage = di.get<UserRepository>();
}
Future<bool> isUserAuthed() async {
return await _userLocalStorage.userAuthed();
}
}

View File

@@ -0,0 +1,35 @@
import 'package:get_it/get_it.dart';
import 'package:hive_ce_flutter/hive_flutter.dart';
import 'package:logger/logger.dart';
import 'package:rasadyar_app/data/data_provider/local_storage/hive/hive_provider.dart';
import 'package:rasadyar_app/data/data_source/local_storage/user/user_local_storage.dart';
import 'package:rasadyar_app/data/model/user/user_model.dart';
import 'package:rasadyar_app/domain/repository/user/user_repository.dart';
final di = GetIt.instance;
void setupInjection() {
di.registerLazySingleton(() => HiveProvider(), instanceName: 'HiveProvider');
di.registerSingleton<Logger>( Logger());
}
Future<void> setupAllProvider() async {
await _setupLocalStorage();
await di.allReady();
}
Future<void> _setupLocalStorage() async {
final hiveProvider = di.get<HiveProvider>(instanceName: 'HiveProvider');
await hiveProvider.init();
Hive.registerAdapter(UserModelAdapter());
await Hive.openBox<UserModel>(HiveBoxNames.user.name);
//user
di.registerLazySingleton<IUserLocalStorage>(() => UserLocalStorage());
di.registerLazySingleton<IUserRepository>(
() => UserRepository(di.get<IUserLocalStorage>()),
);
//
}

View File

@@ -1,19 +1,16 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rasadyar_app/domain/service/user/user_service.dart';
import 'package:rasadyar_app/infrastructure/di/di.dart';
import 'package:rasadyar_app/presentation/common/app_color.dart';
import 'package:rasadyar_app/presentation/common/app_fonts.dart';
import 'package:rasadyar_app/presentation/utils/color_utils.dart';
import 'package:rasadyar_app/presentation/widget/buttons/elevated.dart';
import 'package:rasadyar_app/presentation/widget/buttons/fab_outlined.dart';
import 'package:rasadyar_app/presentation/widget/buttons/outline_elevated.dart';
import 'package:rasadyar_app/presentation/widget/buttons/text_button.dart';
import 'package:rasadyar_app/presentation/widget/inputs/r_input.dart';
import 'package:rasadyar_app/presentation/widget/pagination/pagination_from_until.dart';
import 'package:rasadyar_app/presentation/widget/pagination/show_more.dart';
import 'package:rasadyar_app/presentation/widget/tabs/tab.dart';
import 'package:rasadyar_app/presentation/routes/app_pages.dart';
import 'presentation/widget/buttons/fab.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
setupInjection();
await setupAllProvider();
void main() {
runApp(MyApp());
}
@@ -22,264 +19,15 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
return GetMaterialApp(
title: 'رصدیار',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: Home(),
);
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<bool> _isOpen = [false, false, false, false, false, false];
void _handleAdd() {
print("Add FAB pressed");
}
void _handleEdit() {
print("Edit FAB pressed");
}
void _handleDelete() {
print("Delete FAB pressed");
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("System design"), centerTitle: true),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ExpansionPanelList(
expansionCallback: (panelIndex, isExpanded) {
setState(() {
_isOpen[panelIndex] = isExpanded;
});
},
children: [
buttonWidget(),
fabWidget(),
outlinedFabWidget(),
paginationWidget(),
tabWidget(),
inputsWidget(),
],
),
),
),
);
}
ExpansionPanel inputsWidget() {
return ExpansionPanel(
isExpanded: _isOpen[5],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"inputs",
style: AppFonts.yekan20Regular.copyWith(color: Colors.red),
),
);
},
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
spacing: 14,
children: [
RTextField(
hintText: 'حجم کشتار را در روز به قطعه وارد کنید',
hintStyle: AppFonts.yekan13Regular,
),
RTextField(
label: 'تلفن مرغداری',
labelStyle: AppFonts.yekan10Regular,
),
],
),
),
);
}
ExpansionPanel tabWidget() {
return ExpansionPanel(
isExpanded: _isOpen[4],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"tab",
style: AppFonts.yekan20Regular.copyWith(color: Colors.red),
),
);
},
body: Column(
spacing: 14,
children: [
CupertinoSegmentedControlDemo(),
CupertinoSegmentedControlDemo2(),
],
),
);
}
ExpansionPanel paginationWidget() {
return ExpansionPanel(
isExpanded: _isOpen[3],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"پیجینیشن",
style: AppFonts.yekan20Regular.copyWith(color: Colors.red),
),
);
},
body: Column(spacing: 14, children: [RShowMore(), PaginationFromUntil()]),
);
}
ExpansionPanel outlinedFabWidget() {
return ExpansionPanel(
isExpanded: _isOpen[2],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"Outlined Fab ",
style: AppFonts.yekan20Regular.copyWith(color: Colors.green),
),
);
},
body: Column(
spacing: 14,
children: [
Row(),
RFabOutlined.smallAdd(onPressed: () {}),
RFabOutlined.smallAdd(onPressed: null),
RFabOutlined.smallAddNoBorder(onPressed: () {}),
RFabOutlined.smallAddNoBorder(onPressed: null),
RFabOutlined.add(onPressed: () {}),
RFabOutlined.add(onPressed: null),
RFabOutlined.addNoBorder(onPressed: () {}),
RFabOutlined.addNoBorder(onPressed: null),
],
),
);
}
ExpansionPanel fabWidget() {
return ExpansionPanel(
isExpanded: _isOpen[1],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"Fab",
style: AppFonts.yekan20Regular.copyWith(color: Colors.green),
),
);
},
body: Column(
spacing: 14,
children: [
Row(),
RFab.smallAdd(onPressed: () {}),
RFab.smallAdd(onPressed: null),
RFab.add(onPressed: () {}),
RFab.add(onPressed: null),
RFab.smallEdit(onPressed: null),
RFab.smallEdit(onPressed: () {}),
RFab.edit(onPressed: () {}),
RFab.edit(onPressed: null),
RFab.smallDelete(onPressed: () {}),
RFab.smallDelete(onPressed: null),
RFab.delete(onPressed: () {}),
RFab.delete(onPressed: null),
RFab.smallAction(onPressed: () {}),
RFab.smallAction(onPressed: null),
RFab.action(onPressed: () {}),
RFab.action(onPressed: null),
RFab.smallFilter(onPressed: () {}),
RFab.smallFilter(onPressed: null),
RFab.filter(onPressed: () {}),
RFab.filter(onPressed: null),
RFab.smallDownload(onPressed: () {}),
RFab.smallDownload(onPressed: null),
RFab.download(onPressed: () {}),
RFab.download(onPressed: null),
RFab.smallExcel(onPressed: () {}),
RFab.smallExcel(onPressed: null),
RFab.excel(onPressed: () {}),
RFab.excel(onPressed: null),
RFab.smallBack(onPressed: () {}),
RFab.smallBack(onPressed: null),
RFab.back(onPressed: () {}),
RFab.back(onPressed: null),
],
),
);
}
ExpansionPanel buttonWidget() {
return ExpansionPanel(
isExpanded: _isOpen[0],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"دکمه ها",
style: AppFonts.yekan20Regular.copyWith(color: Colors.green),
),
);
},
body: Column(
spacing: 14,
children: [
Row(),
RElevated(text: 'ثبت', onPressed: () {}),
RElevated(text: 'ثبت', onPressed: null),
ROutlinedElevated(text: 'ثبت', onPressed: () {}),
ROutlinedElevated(
text: 'ثبتwwww',
onPressed: () {},
backgroundColor: AppColor.blueNormal.disabledColor,
pressedBackgroundColor: AppColor.blueNormal,
),
ROutlinedElevated(text: 'ثبت', onPressed: null),
RTextButton(text: 'ثبت', onPressed: () {}),
RTextButton(text: 'ثبت', onPressed: null),
],
colorScheme: ColorScheme.fromSeed(seedColor: AppColor.blueNormal),
),
initialRoute: AppPages.initRoutes,
initialBinding: BindingsBuilder.put(() => UserService()),
getPages: AppPages.pages,
locale: Locale('fa'),
);
}
}

View File

@@ -5,19 +5,24 @@ class Assets {
static const String iconsAdd = 'assets/icons/add.svg';
static const String iconsArrowLeft = 'assets/icons/arrow_left.svg';
static const String iconsArrowRight = 'assets/icons/arrow_right.svg';
static const String iconsCall = 'assets/icons/call.svg';
static const String iconsDownload = 'assets/icons/download.svg';
static const String iconsEdit = 'assets/icons/edit.svg';
static const String iconsFilter = 'assets/icons/filter.svg';
static const String iconsKey = 'assets/icons/key.svg';
static const String iconsScan = 'assets/icons/scan.svg';
static const String iconsTrash = 'assets/icons/trash.svg';
static const String iconsUser = 'assets/icons/user.svg';
static const String imagesInnerSplash = 'assets/images/inner_splash.webp';
static const String imagesOutterSplash = 'assets/images/outter_splash.webp';
static const String vecAddSvg = 'assets/vec/add.svg.vec';
static const String vecArrowLeftSvg = 'assets/vec/arrow_left.svg.vec';
static const String vecArrowRightSvg = 'assets/vec/arrow_right.svg.vec';
static const String vecCallSvg = 'assets/vec/call.svg.vec';
static const String vecDownloadSvg = 'assets/vec/download.svg.vec';
static const String vecEditSvg = 'assets/vec/edit.svg.vec';
static const String vecFilterSvg = 'assets/vec/filter.svg.vec';
static const String vecKeySvg = 'assets/vec/key.svg.vec';
static const String vecScanSvg = 'assets/vec/scan.svg.vec';
static const String vecTrashSvg = 'assets/vec/trash.svg.vec';

View File

@@ -0,0 +1,15 @@
import 'package:get/get.dart';
class AuthWithOtpLogic extends GetxController {
@override
void onReady() {
// TODO: implement onReady
super.onReady();
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'logic.dart';
class AuthWithOtpPage extends StatelessWidget {
const AuthWithOtpPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final AuthWithOtpLogic logic = Get.put(AuthWithOtpLogic());
return Container();
}
}

View File

@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rasadyar_app/presentation/widget/captcha/captcha_widget.dart';
class AuthWithUseAndPassLogic extends GetxController {
Rx<GlobalKey<FormState>> formKey = GlobalKey<FormState>().obs;
Rx<TextEditingController> phoneNumberController = TextEditingController().obs;
Rx<TextEditingController> passwordController = TextEditingController().obs;
CaptchaController captchaController = CaptchaController();
RxnString phoneNumber = RxnString(null);
RxnString password = RxnString(null);
RxBool isOnError = false.obs;
RxBool hidePassword = true.obs;
@override
void onReady() {
// TODO: implement onReady
super.onReady();
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
}

View File

@@ -0,0 +1,210 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:rasadyar_app/infrastructure/di/di.dart';
import 'package:rasadyar_app/presentation/common/app_color.dart';
import 'package:rasadyar_app/presentation/common/app_fonts.dart';
import 'package:rasadyar_app/presentation/common/assets.dart';
import 'package:rasadyar_app/presentation/widget/buttons/elevated.dart';
import 'package:rasadyar_app/presentation/widget/captcha/captcha_widget.dart';
import 'package:rasadyar_app/presentation/widget/vec_widget.dart';
import 'logic.dart';
class AuthWithUseAndPassPage extends GetView<AuthWithUseAndPassLogic> {
AuthWithUseAndPassPage({super.key});
@override
Widget build(BuildContext context) {
final AuthWithUseAndPassLogic logic = Get.put(AuthWithUseAndPassLogic());
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [SizedBox(height: 80), logoWidget(), loginForm()],
),
),
);
}
Widget loginForm() {
return ObxValue((data) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 50),
child: Form(
key: data.value,
child: Column(
children: [
ObxValue((phoneController) {
return TextFormField(
controller: controller.phoneNumberController.value,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
gapPadding: 11,
),
labelText: 'شماره موبایل',
labelStyle: AppFonts.yekan13Regular,
errorStyle: AppFonts.yekan13Regular.copyWith(
color: AppColor.redNormal,
),
prefixIconConstraints: BoxConstraints(
maxHeight: 40,
minHeight: 40,
maxWidth: 40,
minWidth: 40,
),
prefixIcon: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 6, 8),
child: vecWidget(Assets.vecCallSvg),
),
suffix:
phoneController.value.text.trim().isNotEmpty
? clearButton(() {
phoneController.value.clear();
phoneController.refresh();
})
: null,
counterText: '',
),
keyboardType: TextInputType.numberWithOptions(
decimal: false,
signed: false,
),
maxLines: 1,
maxLength: 11,
onChanged: (value) {
if (controller.isOnError.value) {
controller.isOnError.value = !controller.isOnError.value;
data.value.currentState?.reset();
data.refresh();
phoneController.value.text = value;
}
phoneController.refresh();
},
textInputAction: TextInputAction.next,
validator: (value) {
if (value == null) {
return '⚠️ شماره موبایل را وارد کنید';
} else if (value.length < 11) {
return '⚠️ شماره موبایل باید 11 رقم باشد';
}
return null;
},
style: AppFonts.yekan13Regular,
);
}, controller.phoneNumberController),
SizedBox(height: 26),
ObxValue((passwordController) {
return TextFormField(
controller: passwordController.value,
obscureText: controller.hidePassword.value,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
gapPadding: 11,
),
labelText: 'رمز عبور',
labelStyle: AppFonts.yekan13Regular,
errorStyle: AppFonts.yekan13Regular.copyWith(
color: AppColor.redNormal,
),
prefixIconConstraints: BoxConstraints(
maxHeight: 34,
minHeight: 34,
maxWidth: 34,
minWidth: 34,
),
prefixIcon: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 8, 8),
child: vecWidget(Assets.vecKeySvg),
),
suffix:
passwordController.value.text.trim().isNotEmpty
? GestureDetector(
onTap: () {
controller.hidePassword.value =
!controller.hidePassword.value;
},
child: Icon(
controller.hidePassword.value
? CupertinoIcons.eye
: CupertinoIcons.eye_slash,
),
)
: null,
counterText: '',
),
textInputAction: TextInputAction.done,
keyboardType: TextInputType.visiblePassword,
maxLines: 1,
onChanged: (value) {
if (controller.isOnError.value) {
controller.isOnError.value = !controller.isOnError.value;
data.value.currentState?.reset();
passwordController.value.text = value;
}
passwordController.refresh();
},
validator: (value) {
if (value == null || value.isEmpty) {
return '⚠️ رمز عبور را وارد کنید'; // "Please enter the password"
}
return null;
},
style: AppFonts.yekan13Regular,
);
}, controller.passwordController),
SizedBox(height: 26),
CaptchaWidget(controller: controller.captchaController),
SizedBox(height: 23),
RElevated(
text: 'ورود',
onPressed: () {
di.get<Logger>().t(data.value.currentState?.validate());
di.get<Logger>().t(controller.captchaController.validate());
if (data.value.currentState?.validate() == true &&
controller.captchaController.validate()) {
print("==============>ssakldjaskljdklasjd");
}
},
width: Get.width,
height: 48,
),
],
),
),
);
}, controller.formKey);
}
Widget logoWidget() {
return Column(
children: [
Row(),
Image.asset(Assets.imagesInnerSplash, width: 120, height: 120),
Text(
'سامانه رصدیار',
style: AppFonts.yekan16Regular.copyWith(
color: AppColor.darkGreyNormal,
),
),
],
);
}
Widget clearButton(VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Icon(CupertinoIcons.multiply_circle, size: 24),
);
}
}

View File

@@ -0,0 +1,67 @@
import 'package:flutter/animation.dart';
import 'package:get/get.dart';
import 'package:rasadyar_app/presentation/routes/app_pages.dart';
class SplashLogic extends GetxController with GetTickerProviderStateMixin {
late final AnimationController scaleController;
late final AnimationController rotateController;
Rxn<Animation<double>> scaleAnimation = Rxn();
Rxn<Animation<double>> rotationAnimation = Rxn();
@override
void onInit() {
super.onInit();
scaleController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
);
rotateController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 8000),
);
scaleAnimation.value = Tween<double>(
begin: 0.8,
end: 1.2,
).animate(scaleController);
rotationAnimation.value = Tween<double>(
begin: 0.0,
end: 1,
).animate(rotateController);
rotateController.forward();
rotateController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
rotateController.repeat();
} else if (status == AnimationStatus.dismissed) {
rotateController.forward();
}
});
scaleController.forward();
scaleController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
scaleController.reverse();
} else if (status == AnimationStatus.dismissed) {
scaleController.forward();
}
});
}
@override
void onReady() {
super.onReady();
Future.delayed(const Duration(seconds: 1), () {
Get.offAllNamed(AppPaths.authWithUserAndPass);
});
}
@override
void onClose() {
rotateController.dispose();
scaleController.dispose();
super.onClose();
}
}

View File

@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rasadyar_app/presentation/common/app_color.dart';
import 'package:rasadyar_app/presentation/common/assets.dart';
import 'logic.dart';
class SplashPage extends GetView<SplashLogic> {
const SplashPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.blueDarker,
body: Center(
child: Stack(
alignment: Alignment.center,
children: [
ObxValue((data) {
return ScaleTransition(
scale: data.value!,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 1),
child: Image.asset(
Assets.imagesInnerSplash,
width: 190,
height: 190,
),
),
);
}, controller.scaleAnimation),
ObxValue((data) {
return RotationTransition(
turns: data.value!,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 1),
child: Image.asset(Assets.imagesOutterSplash),
),
);
}, controller.rotationAnimation),
],
),
),
);
}
}

View File

@@ -0,0 +1,265 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_app/presentation/common/app_color.dart';
import 'package:rasadyar_app/presentation/common/app_fonts.dart';
import 'package:rasadyar_app/presentation/utils/color_utils.dart';
import 'package:rasadyar_app/presentation/widget/buttons/elevated.dart';
import 'package:rasadyar_app/presentation/widget/buttons/fab.dart';
import 'package:rasadyar_app/presentation/widget/buttons/fab_outlined.dart';
import 'package:rasadyar_app/presentation/widget/buttons/outline_elevated.dart';
import 'package:rasadyar_app/presentation/widget/buttons/text_button.dart';
import 'package:rasadyar_app/presentation/widget/inputs/r_input.dart';
import 'package:rasadyar_app/presentation/widget/pagination/pagination_from_until.dart';
import 'package:rasadyar_app/presentation/widget/pagination/show_more.dart';
import 'package:rasadyar_app/presentation/widget/tabs/tab.dart';
class SystemDesignPage extends StatefulWidget {
const SystemDesignPage({super.key});
@override
State<SystemDesignPage> createState() => _SystemDesignPageState();
}
class _SystemDesignPageState extends State<SystemDesignPage> {
List<bool> _isOpen = [false, false, false, false, false, false];
void _handleAdd() {
print("Add FAB pressed");
}
void _handleEdit() {
print("Edit FAB pressed");
}
void _handleDelete() {
print("Delete FAB pressed");
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("System design"), centerTitle: true),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ExpansionPanelList(
expansionCallback: (panelIndex, isExpanded) {
setState(() {
_isOpen[panelIndex] = isExpanded;
});
},
children: [
buttonWidget(),
fabWidget(),
outlinedFabWidget(),
paginationWidget(),
tabWidget(),
inputsWidget(),
],
),
),
),
);
}
ExpansionPanel inputsWidget() {
return ExpansionPanel(
isExpanded: _isOpen[5],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"inputs",
style: AppFonts.yekan20Regular.copyWith(color: Colors.red),
),
);
},
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
spacing: 14,
children: [
RTextField(
hintText: 'حجم کشتار را در روز به قطعه وارد کنید',
hintStyle: AppFonts.yekan13Regular,
),
RTextField(
label: 'تلفن مرغداری',
labelStyle: AppFonts.yekan10Regular,
),
],
),
),
);
}
ExpansionPanel tabWidget() {
return ExpansionPanel(
isExpanded: _isOpen[4],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"tab",
style: AppFonts.yekan20Regular.copyWith(color: Colors.red),
),
);
},
body: Column(
spacing: 14,
children: [
CupertinoSegmentedControlDemo(),
CupertinoSegmentedControlDemo2(),
],
),
);
}
ExpansionPanel paginationWidget() {
return ExpansionPanel(
isExpanded: _isOpen[3],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"پیجینیشن",
style: AppFonts.yekan20Regular.copyWith(color: Colors.red),
),
);
},
body: Column(spacing: 14, children: [RShowMore(), PaginationFromUntil()]),
);
}
ExpansionPanel outlinedFabWidget() {
return ExpansionPanel(
isExpanded: _isOpen[2],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"Outlined Fab ",
style: AppFonts.yekan20Regular.copyWith(color: Colors.green),
),
);
},
body: Column(
spacing: 14,
children: [
Row(),
RFabOutlined.smallAdd(onPressed: () {}),
RFabOutlined.smallAdd(onPressed: null),
RFabOutlined.smallAddNoBorder(onPressed: () {}),
RFabOutlined.smallAddNoBorder(onPressed: null),
RFabOutlined.add(onPressed: () {}),
RFabOutlined.add(onPressed: null),
RFabOutlined.addNoBorder(onPressed: () {}),
RFabOutlined.addNoBorder(onPressed: null),
],
),
);
}
ExpansionPanel fabWidget() {
return ExpansionPanel(
isExpanded: _isOpen[1],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"Fab",
style: AppFonts.yekan20Regular.copyWith(color: Colors.green),
),
);
},
body: Column(
spacing: 14,
children: [
Row(),
RFab.smallAdd(onPressed: () {}),
RFab.smallAdd(onPressed: null),
RFab.add(onPressed: () {}),
RFab.add(onPressed: null),
RFab.smallEdit(onPressed: null),
RFab.smallEdit(onPressed: () {}),
RFab.edit(onPressed: () {}),
RFab.edit(onPressed: null),
RFab.smallDelete(onPressed: () {}),
RFab.smallDelete(onPressed: null),
RFab.delete(onPressed: () {}),
RFab.delete(onPressed: null),
RFab.smallAction(onPressed: () {}),
RFab.smallAction(onPressed: null),
RFab.action(onPressed: () {}),
RFab.action(onPressed: null),
RFab.smallFilter(onPressed: () {}),
RFab.smallFilter(onPressed: null),
RFab.filter(onPressed: () {}),
RFab.filter(onPressed: null),
RFab.smallDownload(onPressed: () {}),
RFab.smallDownload(onPressed: null),
RFab.download(onPressed: () {}),
RFab.download(onPressed: null),
RFab.smallExcel(onPressed: () {}),
RFab.smallExcel(onPressed: null),
RFab.excel(onPressed: () {}),
RFab.excel(onPressed: null),
RFab.smallBack(onPressed: () {}),
RFab.smallBack(onPressed: null),
RFab.back(onPressed: () {}),
RFab.back(onPressed: null),
],
),
);
}
ExpansionPanel buttonWidget() {
return ExpansionPanel(
isExpanded: _isOpen[0],
headerBuilder: (context, isExpanded) {
return ListTile(
title: Text(
"دکمه ها",
style: AppFonts.yekan20Regular.copyWith(color: Colors.green),
),
);
},
body: Column(
spacing: 14,
children: [
Row(),
RElevated(text: 'ثبت', onPressed: () {}),
RElevated(text: 'ثبت', onPressed: null),
ROutlinedElevated(text: 'ثبت', onPressed: () {}),
ROutlinedElevated(
text: 'ثبتwwww',
onPressed: () {},
backgroundColor: AppColor.blueNormal.disabledColor,
pressedBackgroundColor: AppColor.blueNormal,
),
ROutlinedElevated(text: 'ثبت', onPressed: null),
RTextButton(text: 'ثبت', onPressed: () {}),
RTextButton(text: 'ثبت', onPressed: null),
],
),
);
}
}

View File

@@ -0,0 +1,33 @@
import 'package:get/get.dart';
import 'package:rasadyar_app/presentation/pages/auth/auth_with_otp/logic.dart';
import 'package:rasadyar_app/presentation/pages/auth/auth_with_otp/view.dart';
import 'package:rasadyar_app/presentation/pages/auth/auth_with_use_and_pass/logic.dart';
import 'package:rasadyar_app/presentation/pages/auth/auth_with_use_and_pass/view.dart';
import 'package:rasadyar_app/presentation/pages/splash/logic.dart';
import 'package:rasadyar_app/presentation/pages/splash/view.dart';
part 'app_paths.dart';
sealed class AppPages {
AppPages._();
static const String initRoutes = AppPaths.splash;
static List<GetPage> pages = [
GetPage(
name: AppPaths.splash,
page: () => SplashPage(),
binding: BindingsBuilder.put(() => SplashLogic()),
),
GetPage(
name: AppPaths.authWithOtp,
page: () => AuthWithOtpPage(),
binding: BindingsBuilder.put(() => AuthWithOtpLogic()),
),
GetPage(
name: AppPaths.authWithUserAndPass,
page: () => AuthWithUseAndPassPage(),
binding: BindingsBuilder.put(() => AuthWithUseAndPassLogic()),
),
];
}

View File

@@ -0,0 +1,9 @@
part of 'app_pages.dart';
sealed class AppPaths {
AppPaths._();
static const String splash = '/splash';
static const String authWithUserAndPass = '/authWithUserAndPass';
static const String authWithOtp = '/authWithOtp';
}

View File

@@ -36,9 +36,7 @@ class _RElevatedState extends State<RElevated> {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
setState(() {});
},
onPressed: widget.onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: widget.backgroundColor ?? AppColor.blueNormal,
foregroundColor: widget.foregroundColor ?? Colors.white,

View File

@@ -0,0 +1,266 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_app/presentation/common/app_color.dart';
import 'package:rasadyar_app/presentation/common/app_fonts.dart';
class CaptchaController {
int? captchaCode;
TextEditingController textController = TextEditingController();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
late Function() refreshCaptcha;
bool validate() {
if (formKey.currentState?.validate() == true) {
return true;
}
return false;
}
String get enteredText => textController.text;
bool isCorrect() {
return textController.text == captchaCode.toString();
}
void clear() {
textController.clear();
}
}
class RandomLinePainter extends CustomPainter {
final Random random = Random();
final Paint linePaint;
final List<Offset> points;
RandomLinePainter({
required this.points,
required Color lineColor,
double strokeWidth = 2.0,
}) : linePaint =
Paint()
..color = lineColor
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
@override
void paint(Canvas canvas, Size size) {
final path = Path();
if (points.isNotEmpty) {
path.moveTo(points[0].dx, points[0].dy);
for (int i = 1; i < points.length; i++) {
path.lineTo(points[i].dx, points[i].dy);
}
canvas.drawPath(path, linePaint);
}
}
@override
bool shouldRepaint(RandomLinePainter oldDelegate) => true;
}
class CaptchaWidget extends StatefulWidget {
final CaptchaController controller;
final bool autoValidateMode;
const CaptchaWidget({
required this.controller,
this.autoValidateMode = false,
super.key,
});
@override
_CaptchaWidgetState createState() => _CaptchaWidgetState();
}
class _CaptchaWidgetState extends State<CaptchaWidget> {
late List<Offset> points;
late List<Offset> points1;
late List<Offset> points2;
bool isOnError = false;
@override
void initState() {
super.initState();
generateLines();
getRandomSixDigitNumber();
// Set the refresh function in the controller
widget.controller.refreshCaptcha = () {
getRandomSixDigitNumber();
generateLines();
setState(() {});
};
}
void generateLines() {
points = generateRandomLine();
points1 = generateRandomLine();
points2 = generateRandomLine();
setState(() {});
}
List<Offset> generateRandomLine() {
final random = Random();
int pointCount = random.nextInt(10) + 5;
List<Offset> points = [];
double previousY = 0;
for (int i = 0; i < pointCount; i++) {
double x = (i / (pointCount - 1)) * 135;
if (i == 0) {
previousY = 24;
} else {
double change = (random.nextDouble() * 20) - 10;
previousY = max(5, min(43, previousY + change));
}
points.add(Offset(x, previousY));
}
return points;
}
void getRandomSixDigitNumber() {
final random = Random();
widget.controller.captchaCode = random.nextInt(900000) + 100000;
setState(() {});
}
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 135,
height: 48,
decoration: BoxDecoration(
color: AppColor.whiteNormalHover,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Stack(
alignment: AlignmentDirectional.center,
children: [
CustomPaint(
painter: RandomLinePainter(
points: points,
lineColor: Colors.blue,
strokeWidth: 1.0,
),
size: const Size(double.infinity, double.infinity),
),
CustomPaint(
painter: RandomLinePainter(
points: points1,
lineColor: Colors.green,
strokeWidth: 1.0,
),
size: const Size(double.infinity, double.infinity),
),
CustomPaint(
painter: RandomLinePainter(
points: points2,
lineColor: Colors.red,
strokeWidth: 1.0,
),
size: const Size(double.infinity, double.infinity),
),
Text(
widget.controller.captchaCode.toString(),
style: AppFonts.yekan24Regular,
),
],
),
),
const SizedBox(height: 20),
IconButton(
padding: EdgeInsets.zero,
onPressed: widget.controller.refreshCaptcha,
icon: Icon(CupertinoIcons.refresh, size: 16),
),
Expanded(
child: Form(
key: widget.controller.formKey,
autovalidateMode:
widget.autoValidateMode
? AutovalidateMode.onUserInteraction
: AutovalidateMode.disabled,
child: TextFormField(
controller: widget.controller.textController,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
gapPadding: 11,
),
labelText: 'کد امنیتی',
labelStyle: AppFonts.yekan13Regular,
errorStyle: AppFonts.yekan10Regular.copyWith(
color: AppColor.redNormal,
fontSize: 8,
),
suffixIconConstraints: BoxConstraints(
maxHeight: 24,
minHeight: 24,
maxWidth: 24,
minWidth: 24,
),
suffix:
widget.controller.textController.text.trim().isNotEmpty
? clearButton(() {
widget.controller.textController.clear();
setState(() {});
})
: null,
counterText: '',
),
keyboardType: TextInputType.numberWithOptions(
decimal: false,
signed: false,
),
maxLines: 1,
maxLength: 6,
onChanged: (value) {
if (isOnError) {
isOnError = !isOnError;
widget.controller.formKey.currentState?.reset();
widget.controller.textController.text = value;
}
setState(() {});
},
validator: (value) {
if (value == null || value.isEmpty) {
isOnError = true;
return 'کد امنیتی را وارد کنید';
}
if (value != widget.controller.captchaCode.toString()) {
isOnError = true;
return '⚠️کد امنیتی وارد شده اشتباه است';
}
return null;
},
style: AppFonts.yekan13Regular,
),
),
),
],
);
}
Widget clearButton(VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Icon(CupertinoIcons.multiply_circle, size: 18),
);
}
}

View File

@@ -18,6 +18,25 @@ SvgPicture vecWidget(
color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null,
);
}
SvgPicture svgWidget(
String assets, {
double? width,
double? height,
BoxFit? fit,
Color? color,
}) {
return SvgPicture.asset(
assets,
width: width,
height: height,
fit: fit ?? BoxFit.contain,
colorFilter:
color != null ? ColorFilter.mode(color, BlendMode.srcIn) : null,
);
}
Widget vecWidget2(
String assets, {
double? width,

View File

@@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.6.0"
version: "2.7.0"
async:
dependency: transitive
description:
@@ -247,10 +247,10 @@ packages:
dependency: "direct dev"
description:
name: freezed
sha256: a6274c34d61b3d68082f2b0e9a641a3ec197e525d269f35b82f62d5b2c6d9f75
sha256: "6022db4c7bfa626841b2a10f34dd1e1b68e8f8f9650db6112dcdeeca45ca793c"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.6"
freezed_annotation:
dependency: "direct main"
description:

View File

@@ -54,7 +54,6 @@ dev_dependencies:
flutter:
uses-material-design: true

1
runner.sh Normal file
View File

@@ -0,0 +1 @@
dart run build_runner build --delete-conflicting-outputs