feat : update gradle
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
{"flutter_ddd_module":"D:/fl_project/rasadyar_app/flutter_ddd_module"}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,19 +10,16 @@ android {
|
|||||||
ndkVersion = "27.0.12077973"
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
jvmTarget = "21"
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId = "com.hoshomandsazan.rasadyar_app"
|
applicationId = "com.hoshomandsazan.rasadyar_app"
|
||||||
// You can update the following values to match your application needs.
|
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
@@ -32,8 +28,6 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// TODO: Add your own signing config for the release build.
|
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pluginManagement {
|
|||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.7.0" apply false
|
id("com.android.application") version "8.7.0" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
id("org.jetbrains.kotlin.android") version "1.9.25" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|||||||
@@ -2,15 +2,7 @@ import 'package:rasadyar_app/data/data_source/local_storage/user/user_local_stor
|
|||||||
|
|
||||||
abstract class IUserRepository {
|
abstract class IUserRepository {
|
||||||
Future<bool> userAuthed();
|
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 {
|
class UserRepository implements IUserRepository {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
extension ColorUtils on Color {
|
|
||||||
Color _darken([double amount = 0.1]) {
|
|
||||||
assert(amount >= 0 && amount <= 1, 'مقدار تیرگی باید بین 0 و 1 باشد');
|
|
||||||
final hslColor = HSLColor.fromColor(this);
|
|
||||||
final newLightness = (hslColor.lightness - amount).clamp(0.0, 1.0);
|
|
||||||
final hslDarkerColor = hslColor.withLightness(newLightness);
|
|
||||||
return hslDarkerColor.toColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
get disabledColor{
|
|
||||||
return withAlpha(38);
|
|
||||||
}
|
|
||||||
|
|
||||||
get hoverColor{
|
|
||||||
return _darken(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
get pressedColor{
|
|
||||||
return _darken(0.10);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rasadyar_core/core.dart';
|
||||||
|
|
||||||
|
enum AuthType { useAndPass, otp }
|
||||||
|
|
||||||
|
enum AuthStatus { init }
|
||||||
|
|
||||||
|
enum OtpStatus { init, sent, verified, reSend }
|
||||||
|
|
||||||
|
class AuthWithUseAndPassLogic extends GetxController {
|
||||||
|
Rx<GlobalKey<FormState>> formKey = GlobalKey<FormState>().obs;
|
||||||
|
Rx<GlobalKey<FormState>> formKeyOtp = GlobalKey<FormState>().obs;
|
||||||
|
Rx<GlobalKey<FormState>> formKeySentOtp = GlobalKey<FormState>().obs;
|
||||||
|
Rx<TextEditingController> phoneNumberController = TextEditingController().obs;
|
||||||
|
Rx<TextEditingController> passwordController = TextEditingController().obs;
|
||||||
|
Rx<TextEditingController> phoneOtpNumberController =
|
||||||
|
TextEditingController().obs;
|
||||||
|
Rx<TextEditingController> otpCodeController = TextEditingController().obs;
|
||||||
|
CaptchaController captchaController = CaptchaController();
|
||||||
|
CaptchaController captchaOtpController = CaptchaController();
|
||||||
|
|
||||||
|
RxnString phoneNumber = RxnString(null);
|
||||||
|
RxnString password = RxnString(null);
|
||||||
|
RxBool isOnError = false.obs;
|
||||||
|
RxBool hidePassword = true.obs;
|
||||||
|
Rx<AuthType> authType = AuthType.useAndPass.obs;
|
||||||
|
Rx<AuthStatus> authStatus = AuthStatus.init.obs;
|
||||||
|
Rx<OtpStatus> otpStatus = OtpStatus.init.obs;
|
||||||
|
|
||||||
|
RxInt secondsRemaining = 120.obs;
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
void startTimer() {
|
||||||
|
_timer?.cancel();
|
||||||
|
secondsRemaining.value = 120;
|
||||||
|
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
if (secondsRemaining.value > 0) {
|
||||||
|
secondsRemaining.value--;
|
||||||
|
} else {
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopTimer() {
|
||||||
|
_timer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
String get timeFormatted {
|
||||||
|
final minutes = secondsRemaining.value ~/ 60;
|
||||||
|
final seconds = secondsRemaining.value % 60;
|
||||||
|
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onReady() {
|
||||||
|
// TODO: implement onReady
|
||||||
|
super.onReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,522 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rasadyar_core/core.dart';
|
||||||
|
|
||||||
|
import 'logic.dart';
|
||||||
|
|
||||||
|
class AuthWithUseAndPassPage extends GetView<AuthWithUseAndPassLogic> {
|
||||||
|
const AuthWithUseAndPassPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 80),
|
||||||
|
logoWidget(),
|
||||||
|
ObxValue((types) {
|
||||||
|
switch (types.value) {
|
||||||
|
case AuthType.otp:
|
||||||
|
return otpForm();
|
||||||
|
case AuthType.useAndPass:
|
||||||
|
return useAndPassFrom();
|
||||||
|
}
|
||||||
|
}, controller.authType),
|
||||||
|
|
||||||
|
SizedBox(height: 50),
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'مطالعه بیانیه ',
|
||||||
|
style: AppFonts.yekan14.copyWith(
|
||||||
|
color: AppColor.darkGreyDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
recognizer: TapGestureRecognizer()..onTap = () {},
|
||||||
|
text: 'حریم خصوصی',
|
||||||
|
style: AppFonts.yekan14.copyWith(
|
||||||
|
color: AppColor.blueNormal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 18),
|
||||||
|
|
||||||
|
ObxValue((types) {
|
||||||
|
return RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
recognizer:
|
||||||
|
TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
if (controller.authType.value == AuthType.otp) {
|
||||||
|
controller.authType.value = AuthType.useAndPass;
|
||||||
|
if (controller.otpStatus.value !=
|
||||||
|
OtpStatus.init) {
|
||||||
|
controller.otpStatus.value = OtpStatus.init;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
controller.authType.value = AuthType.otp;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text:
|
||||||
|
controller.authType.value == AuthType.otp
|
||||||
|
? 'ورود با رمز ثابت'
|
||||||
|
: 'ورود با رمز یکبار مصرف',
|
||||||
|
|
||||||
|
style: AppFonts.yekan14.copyWith(
|
||||||
|
color: AppColor.blueNormal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, controller.authType),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget useAndPassFrom() {
|
||||||
|
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.yekan13,
|
||||||
|
errorStyle: AppFonts.yekan13.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.yekan13,
|
||||||
|
);
|
||||||
|
}, 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.yekan13,
|
||||||
|
errorStyle: AppFonts.yekan13.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.yekan13,
|
||||||
|
);
|
||||||
|
}, controller.passwordController),
|
||||||
|
SizedBox(height: 26),
|
||||||
|
|
||||||
|
CaptchaWidget(controller: controller.captchaController),
|
||||||
|
|
||||||
|
SizedBox(height: 23),
|
||||||
|
RElevated(
|
||||||
|
text: 'ورود',
|
||||||
|
onPressed: () async {
|
||||||
|
Jalali? picked = await showPersianDatePicker(
|
||||||
|
context: Get.context!,
|
||||||
|
|
||||||
|
initialDate: Jalali.now(),
|
||||||
|
firstDate: Jalali(1385, 8),
|
||||||
|
lastDate: Jalali(1450, 9),
|
||||||
|
initialEntryMode: PersianDatePickerEntryMode.calendarOnly,
|
||||||
|
initialDatePickerMode: PersianDatePickerMode.year,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.value.currentState?.validate() == true &&
|
||||||
|
controller.captchaController.validate()) {}
|
||||||
|
},
|
||||||
|
width: Get.width,
|
||||||
|
height: 48,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, controller.formKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget otpForm() {
|
||||||
|
return ObxValue((status) {
|
||||||
|
switch (status.value) {
|
||||||
|
case OtpStatus.init:
|
||||||
|
return sendCodeForm();
|
||||||
|
case OtpStatus.sent:
|
||||||
|
case OtpStatus.verified:
|
||||||
|
case OtpStatus.reSend:
|
||||||
|
return confirmCodeForm();
|
||||||
|
}
|
||||||
|
}, controller.otpStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget sendCodeForm() {
|
||||||
|
return ObxValue((data) {
|
||||||
|
return Form(
|
||||||
|
key: data.value,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 50),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 26),
|
||||||
|
ObxValue((phoneController) {
|
||||||
|
return TextFormField(
|
||||||
|
controller: phoneController.value,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
gapPadding: 11,
|
||||||
|
),
|
||||||
|
labelText: 'شماره موبایل',
|
||||||
|
labelStyle: AppFonts.yekan13,
|
||||||
|
errorStyle: AppFonts.yekan13.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.yekan13,
|
||||||
|
);
|
||||||
|
}, controller.phoneOtpNumberController),
|
||||||
|
|
||||||
|
SizedBox(height: 26),
|
||||||
|
|
||||||
|
CaptchaWidget(controller: controller.captchaOtpController),
|
||||||
|
|
||||||
|
SizedBox(height: 23),
|
||||||
|
RElevated(
|
||||||
|
text: 'ارسال رمز یکبار مصرف',
|
||||||
|
onPressed: () {
|
||||||
|
if (data.value.currentState?.validate() == true &&
|
||||||
|
controller.captchaOtpController.validate()) {
|
||||||
|
controller.otpStatus.value = OtpStatus.sent;
|
||||||
|
controller.startTimer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
width: Get.width,
|
||||||
|
height: 48,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, controller.formKeyOtp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget confirmCodeForm() {
|
||||||
|
return ObxValue((data) {
|
||||||
|
return Form(
|
||||||
|
key: data.value,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 50),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
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.yekan13,
|
||||||
|
errorStyle: AppFonts.yekan13.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.yekan13,
|
||||||
|
);
|
||||||
|
}, controller.passwordController),
|
||||||
|
|
||||||
|
SizedBox(height: 23),
|
||||||
|
|
||||||
|
ObxValue((timer) {
|
||||||
|
if (timer.value == 0) {
|
||||||
|
return TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
controller.otpStatus.value = OtpStatus.reSend;
|
||||||
|
controller.startTimer();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
style: AppFonts.yekan13.copyWith(
|
||||||
|
color: AppColor.blueNormal,
|
||||||
|
),
|
||||||
|
'ارسال مجدد کد یکبار مصرف',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Text(
|
||||||
|
'اعتبار رمز ارسال شده ${controller.timeFormatted}',
|
||||||
|
style: AppFonts.yekan13,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, controller.secondsRemaining),
|
||||||
|
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: ' کد ارسال شده به شماره ',
|
||||||
|
style: AppFonts.yekan14.copyWith(
|
||||||
|
color: AppColor.darkGreyDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: controller.phoneOtpNumberController.value.text,
|
||||||
|
style: AppFonts.yekan13Bold.copyWith(
|
||||||
|
color: AppColor.darkGreyDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
recognizer:
|
||||||
|
TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
controller.otpStatus.value = OtpStatus.init;
|
||||||
|
controller.captchaOtpController.clear();
|
||||||
|
},
|
||||||
|
text: ' ویرایش',
|
||||||
|
style: AppFonts.yekan14.copyWith(
|
||||||
|
color: AppColor.blueNormal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 23),
|
||||||
|
RElevated(
|
||||||
|
text: 'ورود',
|
||||||
|
onPressed: () {
|
||||||
|
if (controller.formKeyOtp.value.currentState?.validate() ==
|
||||||
|
true &&
|
||||||
|
controller.captchaOtpController.validate()) {}
|
||||||
|
},
|
||||||
|
width: Get.width,
|
||||||
|
height: 48,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, controller.formKeySentOtp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget logoWidget() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Row(),
|
||||||
|
Image.asset(Assets.imagesInnerSplash, width: 120, height: 120),
|
||||||
|
Text(
|
||||||
|
'سامانه رصدیار',
|
||||||
|
style: AppFonts.yekan16.copyWith(color: AppColor.darkGreyNormal),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget clearButton(VoidCallback onTap) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Icon(CupertinoIcons.multiply_circle, size: 24),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
packages/auth/lib/src/presentation/pages/splash/logic.dart
Normal file
68
packages/auth/lib/src/presentation/pages/splash/logic.dart
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:flutter/animation.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:rasadyar_app/presentation/routes/app_pages.dart';
|
||||||
|
import 'package:inspection/inspection.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(InspectionRoutes.inspection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
rotateController.dispose();
|
||||||
|
scaleController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
47
packages/auth/lib/src/presentation/pages/splash/view.dart
Normal file
47
packages/auth/lib/src/presentation/pages/splash/view.dart
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rasadyar_core/core.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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rasadyar_core/core.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.yekan20.copyWith(color: Colors.red),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
spacing: 14,
|
||||||
|
children: [
|
||||||
|
RTextField(
|
||||||
|
hintText: 'حجم کشتار را در روز به قطعه وارد کنید',
|
||||||
|
hintStyle: AppFonts.yekan13,
|
||||||
|
),
|
||||||
|
RTextField(
|
||||||
|
label: 'تلفن مرغداری',
|
||||||
|
labelStyle: AppFonts.yekan10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpansionPanel tabWidget() {
|
||||||
|
return ExpansionPanel(
|
||||||
|
isExpanded: _isOpen[4],
|
||||||
|
headerBuilder: (context, isExpanded) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
"tab",
|
||||||
|
style: AppFonts.yekan20.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.yekan20.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.yekan20.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.yekan20.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.yekan20.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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
packages/auth/lib/src/presentation/routes/app_pages.dart
Normal file
20
packages/auth/lib/src/presentation/routes/app_pages.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:inspection/presentation/routes/app_pages.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';
|
||||||
|
import 'package:rasadyar_app/presentation/pages/system_design/system_design.dart';
|
||||||
|
|
||||||
|
part 'app_paths.dart';
|
||||||
|
|
||||||
|
sealed class AppPages {
|
||||||
|
AppPages._();
|
||||||
|
|
||||||
|
static const String initRoutes = AppPaths.splash;
|
||||||
|
static const String initDesignSystem = AppPaths.systemDesignPage;
|
||||||
|
|
||||||
|
static List<GetPage> pages = [
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
10
packages/auth/lib/src/presentation/routes/app_paths.dart
Normal file
10
packages/auth/lib/src/presentation/routes/app_paths.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
part of 'app_pages.dart';
|
||||||
|
|
||||||
|
sealed class AppPaths {
|
||||||
|
AppPaths._();
|
||||||
|
|
||||||
|
static const String splash = '/splash';
|
||||||
|
static const String authWithUserAndPass = '/authWithUserAndPass';
|
||||||
|
static const String authWithOtp = '/authWithOtp';
|
||||||
|
static const String systemDesignPage = '/systemDesignPage';
|
||||||
|
}
|
||||||
2
tools/package_builder.sh
Normal file
2
tools/package_builder.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
dart create --template=package ../packages/auth
|
||||||
Reference in New Issue
Block a user