diff --git a/assets/icons/call.svg b/assets/icons/call.svg new file mode 100644 index 0000000..e7d1c11 --- /dev/null +++ b/assets/icons/call.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/key.svg b/assets/icons/key.svg new file mode 100644 index 0000000..fe524b4 --- /dev/null +++ b/assets/icons/key.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/user.svg b/assets/icons/user.svg new file mode 100644 index 0000000..fbe07ed --- /dev/null +++ b/assets/icons/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/vec/call.svg.vec b/assets/vec/call.svg.vec new file mode 100644 index 0000000..d9b7763 Binary files /dev/null and b/assets/vec/call.svg.vec differ diff --git a/assets/vec/key.svg.vec b/assets/vec/key.svg.vec new file mode 100644 index 0000000..3252fd9 Binary files /dev/null and b/assets/vec/key.svg.vec differ diff --git a/lib/data/data_provider/local_storage/hive/hive_provider.dart b/lib/data/data_provider/local_storage/hive/hive_provider.dart new file mode 100644 index 0000000..c97f027 --- /dev/null +++ b/lib/data/data_provider/local_storage/hive/hive_provider.dart @@ -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 init() async { + await Hive.initFlutter(); + } +} diff --git a/lib/data/data_provider/local_storage/hive/hive_types.dart b/lib/data/data_provider/local_storage/hive/hive_types.dart new file mode 100644 index 0000000..eff0bf2 --- /dev/null +++ b/lib/data/data_provider/local_storage/hive/hive_types.dart @@ -0,0 +1 @@ +const int userTypeId = 0; diff --git a/lib/data/data_provider/local_storage/i_local_storage_provider.dart b/lib/data/data_provider/local_storage/i_local_storage_provider.dart new file mode 100644 index 0000000..2ccacf8 --- /dev/null +++ b/lib/data/data_provider/local_storage/i_local_storage_provider.dart @@ -0,0 +1,3 @@ +abstract class ILocalStorageProvider { + Future init(); +} diff --git a/lib/data/data_source/local_storage/user/user_local_storage.dart b/lib/data/data_source/local_storage/user/user_local_storage.dart new file mode 100644 index 0000000..0648a2d --- /dev/null +++ b/lib/data/data_source/local_storage/user/user_local_storage.dart @@ -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 userAuthed(); +} + +class UserLocalStorage extends IUserLocalStorage { + final user = Hive.box(HiveBoxNames.user.name); + + @override + Future userAuthed() async { + if (user.isNotEmpty ) { + return true; + } else { + return false; + } + } +} diff --git a/lib/data/model/user/user_model.dart b/lib/data/model/user/user_model.dart new file mode 100644 index 0000000..37f2b3b --- /dev/null +++ b/lib/data/model/user/user_model.dart @@ -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}); +} diff --git a/lib/data/model/user/user_model.g.dart b/lib/data/model/user/user_model.g.dart new file mode 100644 index 0000000..e786785 --- /dev/null +++ b/lib/data/model/user/user_model.g.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class UserModelAdapter extends TypeAdapter { + @override + final int typeId = 0; + + @override + UserModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + 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; +} diff --git a/lib/domain/entity/user/user_entity.dart b/lib/domain/entity/user/user_entity.dart new file mode 100644 index 0000000..1988ce3 --- /dev/null +++ b/lib/domain/entity/user/user_entity.dart @@ -0,0 +1,6 @@ +class UserEntity { + String? token; + String? refreshToken; + + UserEntity({this.token, this.refreshToken}); +} diff --git a/lib/domain/repository/user/user_repository.dart b/lib/domain/repository/user/user_repository.dart new file mode 100644 index 0000000..599c891 --- /dev/null +++ b/lib/domain/repository/user/user_repository.dart @@ -0,0 +1,25 @@ +import 'package:rasadyar_app/data/data_source/local_storage/user/user_local_storage.dart'; + +abstract class IUserRepository { + Future userAuthed(); + /*Future setUserAuthed(bool value); + Future setUserName(String name); + Future getUserName(); + Future setUserPhone(String phone); + Future getUserPhone(); + Future setUserEmail(String email); + Future getUserEmail(); + Future setUserPassword(String password); + Future getUserPassword();*/ +} + +class UserRepository implements IUserRepository { + final IUserLocalStorage _userLocalStorage; + + UserRepository(this._userLocalStorage); + + @override + Future userAuthed() async { + return await _userLocalStorage.userAuthed(); + } +} diff --git a/lib/domain/service/user/user_service.dart b/lib/domain/service/user/user_service.dart new file mode 100644 index 0000000..db8005c --- /dev/null +++ b/lib/domain/service/user/user_service.dart @@ -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(); + } + + Future isUserAuthed() async { + return await _userLocalStorage.userAuthed(); + } +} diff --git a/lib/infrastructure/di/di.dart b/lib/infrastructure/di/di.dart new file mode 100644 index 0000000..7b43cc5 --- /dev/null +++ b/lib/infrastructure/di/di.dart @@ -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()); +} + +Future setupAllProvider() async { + await _setupLocalStorage(); + await di.allReady(); +} + +Future _setupLocalStorage() async { + final hiveProvider = di.get(instanceName: 'HiveProvider'); + await hiveProvider.init(); + Hive.registerAdapter(UserModelAdapter()); + + await Hive.openBox(HiveBoxNames.user.name); + + //user + di.registerLazySingleton(() => UserLocalStorage()); + di.registerLazySingleton( + () => UserRepository(di.get()), + ); + + // +} diff --git a/lib/main.dart b/lib/main.dart index 787cc7e..c62ccab 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 createState() => _HomeState(); -} - -class _HomeState extends State { - List _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'), ); } } diff --git a/lib/presentation/common/assets.dart b/lib/presentation/common/assets.dart index 185d647..41892cf 100644 --- a/lib/presentation/common/assets.dart +++ b/lib/presentation/common/assets.dart @@ -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'; diff --git a/lib/presentation/pages/auth/auth_with_otp/logic.dart b/lib/presentation/pages/auth/auth_with_otp/logic.dart new file mode 100644 index 0000000..5412998 --- /dev/null +++ b/lib/presentation/pages/auth/auth_with_otp/logic.dart @@ -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(); + } +} diff --git a/lib/presentation/pages/auth/auth_with_otp/view.dart b/lib/presentation/pages/auth/auth_with_otp/view.dart new file mode 100644 index 0000000..6a566f4 --- /dev/null +++ b/lib/presentation/pages/auth/auth_with_otp/view.dart @@ -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(); + } +} diff --git a/lib/presentation/pages/auth/auth_with_use_and_pass/logic.dart b/lib/presentation/pages/auth/auth_with_use_and_pass/logic.dart new file mode 100644 index 0000000..6de4422 --- /dev/null +++ b/lib/presentation/pages/auth/auth_with_use_and_pass/logic.dart @@ -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> formKey = GlobalKey().obs; + Rx phoneNumberController = TextEditingController().obs; + Rx 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(); + } +} diff --git a/lib/presentation/pages/auth/auth_with_use_and_pass/view.dart b/lib/presentation/pages/auth/auth_with_use_and_pass/view.dart new file mode 100644 index 0000000..022543e --- /dev/null +++ b/lib/presentation/pages/auth/auth_with_use_and_pass/view.dart @@ -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 { + 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().t(data.value.currentState?.validate()); + di.get().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), + ); + } +} diff --git a/lib/presentation/pages/splash/logic.dart b/lib/presentation/pages/splash/logic.dart new file mode 100644 index 0000000..d34910b --- /dev/null +++ b/lib/presentation/pages/splash/logic.dart @@ -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> scaleAnimation = Rxn(); + Rxn> 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( + begin: 0.8, + end: 1.2, + ).animate(scaleController); + + rotationAnimation.value = Tween( + 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(); + } +} diff --git a/lib/presentation/pages/splash/view.dart b/lib/presentation/pages/splash/view.dart new file mode 100644 index 0000000..75f37c3 --- /dev/null +++ b/lib/presentation/pages/splash/view.dart @@ -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 { + 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), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/system_design/system_design.dart b/lib/presentation/pages/system_design/system_design.dart new file mode 100644 index 0000000..51ebace --- /dev/null +++ b/lib/presentation/pages/system_design/system_design.dart @@ -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 createState() => _SystemDesignPageState(); +} + +class _SystemDesignPageState extends State { + List _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), + ], + ), + ); + } +} diff --git a/lib/presentation/routes/app_pages.dart b/lib/presentation/routes/app_pages.dart new file mode 100644 index 0000000..9faa5bf --- /dev/null +++ b/lib/presentation/routes/app_pages.dart @@ -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 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()), + ), + ]; +} diff --git a/lib/presentation/routes/app_paths.dart b/lib/presentation/routes/app_paths.dart new file mode 100644 index 0000000..dd68834 --- /dev/null +++ b/lib/presentation/routes/app_paths.dart @@ -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'; +} diff --git a/lib/presentation/widget/buttons/elevated.dart b/lib/presentation/widget/buttons/elevated.dart index c03bc2e..f97ecfc 100644 --- a/lib/presentation/widget/buttons/elevated.dart +++ b/lib/presentation/widget/buttons/elevated.dart @@ -36,9 +36,7 @@ class _RElevatedState extends State { @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, diff --git a/lib/presentation/widget/captcha/captcha_widget.dart b/lib/presentation/widget/captcha/captcha_widget.dart new file mode 100644 index 0000000..fb25167 --- /dev/null +++ b/lib/presentation/widget/captcha/captcha_widget.dart @@ -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 formKey = GlobalKey(); + + 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 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 { + late List points; + late List points1; + late List 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 generateRandomLine() { + final random = Random(); + int pointCount = random.nextInt(10) + 5; + List 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), + ); + } +} diff --git a/lib/presentation/widget/vec_widget.dart b/lib/presentation/widget/vec_widget.dart index 8f76b40..3fb78f1 100644 --- a/lib/presentation/widget/vec_widget.dart +++ b/lib/presentation/widget/vec_widget.dart @@ -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, diff --git a/pubspec.lock b/pubspec.lock index 454e494..f6fe18f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index 52bb4ec..e3a7258 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,7 +54,6 @@ dev_dependencies: - flutter: uses-material-design: true diff --git a/runner.sh b/runner.sh new file mode 100644 index 0000000..e0b9992 --- /dev/null +++ b/runner.sh @@ -0,0 +1 @@ +dart run build_runner build --delete-conflicting-outputs \ No newline at end of file