feat: 增加loading库

This commit is contained in:
liyi 2025-09-02 11:45:38 +08:00
parent 1adeb429cc
commit 981eef1145
21 changed files with 390 additions and 198 deletions

View File

@ -1,3 +1,5 @@
class ApiPath {
static const String sendValidationCode = "/v1/common/sendValidationCode";
static const String validationCodeLogin = "/v1/user/codeLogin";
static const String passwordLogin = "/v1/user/pwdLogin";
}

View File

@ -13,20 +13,7 @@ class BaseApiService {
dio.options.connectTimeout = const Duration(seconds: 30);
dio.options.receiveTimeout = const Duration(seconds: 30);
// 🔥
if (kDebugMode) {
dio.interceptors.add(dioAlias.LogInterceptor(
request: true,
requestHeader: true,
requestBody: true,
responseHeader: true,
responseBody: true,
error: true,
logPrint: (obj) {
debugPrint('[DIO] $obj');
},
));
}
}
///

View File

@ -1,12 +1,13 @@
//
// 使 sendValidationCodeAuth channelcodeType
import 'package:starwork_flutter/common/constant/validation_code_type.dart';
import 'package:starwork_flutter/common/constant/verification_code_channel.dart';
import 'package:starwork_flutter/common/constant/verification_code_type.dart';
class SendValidationCodeRequest {
final String countryCode;
final String account;
final String channel; //1 2
final ValidationCodeType codeType;
final VerificationCodeChannel channel; //1 2
final VerificationCodeType codeType;
SendValidationCodeRequest({
required this.countryCode,
@ -19,7 +20,7 @@ class SendValidationCodeRequest {
return {
'countryCode': countryCode,
'account': account,
'channel': channel,
'channel': channel.value,
'codeType': codeType.value,
};
}

View File

@ -0,0 +1,9 @@
class LoginResponse {
final String token;
LoginResponse({required this.token});
factory LoginResponse.fromJson(Map<String, dynamic> json) {
return LoginResponse(token: json['token']);
}
}

View File

@ -1,24 +0,0 @@
class UserModel {
final int id;
final String name;
final String email;
UserModel({required this.id, required this.name, required this.email});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
};
}
}

View File

@ -2,8 +2,6 @@ import 'package:starwork_flutter/api/api_path.dart';
import 'package:starwork_flutter/api/api_response.dart';
import 'package:starwork_flutter/api/base_api_service.dart';
import 'package:starwork_flutter/api/model/common/request/send_validation_code_request.dart';
import 'package:starwork_flutter/api/model/string_response.dart';
import 'package:starwork_flutter/api/model/user/user_model.dart';
import 'package:starwork_flutter/common/constant/http_constant.dart';
class CommonApiService {

View File

@ -1,20 +1,26 @@
import 'package:get/get.dart';
import 'package:starwork_flutter/api/api_path.dart';
import 'package:starwork_flutter/api/api_response.dart';
import 'package:starwork_flutter/api/base_api_service.dart';
import 'package:starwork_flutter/api/model/user/user_model.dart';
import 'package:starwork_flutter/api/model/user/request/validation_code_login.dart';
import 'package:starwork_flutter/api/model/user/response/token_response.dart';
import 'package:starwork_flutter/common/constant/http_constant.dart';
class UserApiService {
final BaseApiService _api;
UserApiService(this._api); //
Future<ApiResponse<UserModel>> login(Map<String, dynamic> userData) {
//
Future<ApiResponse<LoginResponse>> validationCodeLogin({
required ValidationCodeLoginRequest request,
}) {
return _api.makeRequest(
//
path: '/login',
method: 'POST',
data: userData,
fromJson: (data) => UserModel.fromJson(data),
path: ApiPath.validationCodeLogin,
method: HttpConstant.post,
data: request.toJson(),
fromJson: (data) => LoginResponse.fromJson(data),
);
}
}

View File

@ -1,7 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:starwork_flutter/common/constant/cache_keys.dart';
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
import 'package:starwork_flutter/flavors.dart';
import 'package:starwork_flutter/i18n/app_i18n.dart';
import 'package:starwork_flutter/routes/app_pages.dart';
@ -19,11 +22,15 @@ class App extends StatelessWidget {
builder: (_, child) {
return GetMaterialApp(
theme: ThemeData(
textSelectionTheme: TextSelectionThemeData(
cursorColor: Colors.blue.shade200,
selectionColor: Colors.blue.shade100,
selectionHandleColor: Colors.blue.shade300,
)),
scaffoldBackgroundColor: Colors.white,
brightness: Brightness.light,
textSelectionTheme: TextSelectionThemeData(
cursorColor: Colors.blue.shade200,
selectionColor: Colors.blue.shade100,
selectionHandleColor: Colors.blue.shade300,
),
dialogBackgroundColor: Colors.white,
),
//
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate, // Material组件本地化字符串
@ -41,10 +48,20 @@ class App extends StatelessWidget {
fallbackLocale: const Locale('zh', 'CN'),
// fallback 使
getPages: AppPages.pages,
initialRoute: AppRoutes.login,
initialRoute: _checkIsLogin(),
debugShowCheckedModeBanner: false,
builder: EasyLoading.init(),
);
},
);
}
String _checkIsLogin() {
var token = SharedPreferencesUtils.getString(CacheKeys.token);
if (token != null && token.isNotEmpty) {
return AppRoutes.main;
} else {
return AppRoutes.login;
}
}
}

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:starwork_flutter/api/base_api_service.dart';
@ -9,6 +10,8 @@ import 'package:starwork_flutter/api/service/common_api_service.dart';
import 'package:starwork_flutter/api/service/user_api_service.dart';
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
import 'package:starwork_flutter/i18n/app_i18n.dart';
import 'package:starwork_flutter/views/login/login_controller.dart';
import 'package:starwork_flutter/views/main/main_controller.dart';
class AppInitialization {
static Future<void> initializeApp() async {
@ -16,18 +19,18 @@ class AppInitialization {
WidgetsFlutterBinding.ensureInitialized();
setSystemStatusBar();
await SharedPreferencesUtils.init();
initEasyLoading();
// 便
print('✅ SharedPreferences initialized');
Get.lazyPut(() => BaseApiService());
Get.lazyPut(() => CommonApiService(Get.find<BaseApiService>()));
Get.put(BaseApiService());
Get.put(CommonApiService(Get.find<BaseApiService>()));
Get.put(UserApiService(Get.find<BaseApiService>()));
Get.put(LoginController());
Get.put(MainController());
print('✅ API services registered');
} catch (e, stack) {
print('❌ Initialization failed: $e');
print(stack);
// Sentry
rethrow;
}
}
@ -43,4 +46,20 @@ class AppInitialization {
);
}
}
static void initEasyLoading() {
EasyLoading.instance
..displayDuration = const Duration(milliseconds: 1000)
..indicatorType = EasyLoadingIndicatorType.wanderingCubes
..loadingStyle = EasyLoadingStyle.dark
..indicatorSize = 45.0
..radius = 10.0
..progressColor = Colors.yellow
..backgroundColor = Colors.green
..indicatorColor = Colors.yellow
..textColor = Colors.yellow
..maskColor = Colors.blue.withOpacity(0.5)
..userInteractions = false
..dismissOnTap = false;
}
}

View File

@ -1,17 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
class BaseController extends GetxController {
void showToast(String message) {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.black54,
textColor: Colors.white,
fontSize: 14.0.sp,
);
EasyLoading.showToast(message);
}
void showLoading() {
EasyLoading.show(status: 'loading...');
}
void hideLoading() {
EasyLoading.dismiss();
}
void showSuccess({String message = '操作成功'}) {
EasyLoading.showSuccess(message.tr);
}
void showError({String message = '操作失败'}) {
EasyLoading.showError(message.tr);
}
@override
void onClose() {
if (EasyLoading.isShow) {
EasyLoading.dismiss();
}
super.onClose();
}
}

View File

@ -1,3 +1,4 @@
class CacheKeys {
static const String isSendValidationCode = 'isSendValidationCode';
static const String token = 'token';
}

View File

@ -0,0 +1,36 @@
class PlatformType {
static const web = PlatformType('1', 'web');
static const app = PlatformType('2', 'app');
static const smallProgram = PlatformType('3', '小程序');
static const pc = PlatformType('4', 'pc');
final String value;
final String label;
const PlatformType(this.value, this.label);
//
static PlatformType? fromValue(String? value) {
return {
'1': web,
'2': app,
'3': smallProgram,
'4': pc,
}[value];
}
// toString() value
@override
String toString() => value;
// ==
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PlatformType &&
runtimeType == other.runtimeType &&
value == other.value;
@override
int get hashCode => value.hashCode;
}

View File

@ -1,46 +0,0 @@
class ValidationCodeType {
static const login = ValidationCodeType('1', '登录');
static const reset = ValidationCodeType('2', '重置密码');
static const bindPhone = ValidationCodeType('3', '绑定手机号');
static const unbindPhone = ValidationCodeType('4', '解绑手机号');
static const deleteAccount = ValidationCodeType('5', '删除账号');
static const bindEmail = ValidationCodeType('6', '绑定邮箱');
static const unbindEmail = ValidationCodeType('7', '解绑邮箱');
static const deleteLock = ValidationCodeType('8', '删除门锁');
static const updatePassword = ValidationCodeType('9', '修改密码');
final String value;
final String label;
const ValidationCodeType(this.value, this.label);
//
static ValidationCodeType? fromValue(String? value) {
return {
'1': login,
'2': reset,
'3': bindPhone,
'4': unbindPhone,
'5': deleteAccount,
'6': bindEmail,
'7': unbindEmail,
'8': deleteLock,
'9': updatePassword,
}[value];
}
// toString() value
@override
String toString() => value;
// ==
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ValidationCodeType &&
runtimeType == other.runtimeType &&
value == other.value;
@override
int get hashCode => value.hashCode;
}

View File

@ -1,15 +1,15 @@
// Description:
class ValidationCodeChannel {
static const sms = ValidationCodeChannel('1', '短信');
static const email = ValidationCodeChannel('2', '邮箱');
class VerificationCodeChannel {
static const sms = VerificationCodeChannel('1', '短信');
static const email = VerificationCodeChannel('2', '邮箱');
final String value;
final String label;
const ValidationCodeChannel(this.value, this.label);
const VerificationCodeChannel(this.value, this.label);
//
static ValidationCodeChannel? fromValue(String? value) {
static VerificationCodeChannel? fromValue(String? value) {
return {
'1': sms,
'2': email,
@ -24,7 +24,7 @@ class ValidationCodeChannel {
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ValidationCodeChannel &&
other is VerificationCodeChannel &&
runtimeType == other.runtimeType &&
value == other.value;

View File

@ -0,0 +1,46 @@
class VerificationCodeType {
static const login = VerificationCodeType('1', '登录');
static const reset = VerificationCodeType('2', '重置密码');
static const bindPhone = VerificationCodeType('3', '绑定手机号');
static const unbindPhone = VerificationCodeType('4', '解绑手机号');
static const deleteAccount = VerificationCodeType('5', '删除账号');
static const bindEmail = VerificationCodeType('6', '绑定邮箱');
static const unbindEmail = VerificationCodeType('7', '解绑邮箱');
static const deleteLock = VerificationCodeType('8', '删除门锁');
static const updatePassword = VerificationCodeType('9', '修改密码');
final String value;
final String label;
const VerificationCodeType(this.value, this.label);
//
static VerificationCodeType? fromValue(String? value) {
return {
'1': login,
'2': reset,
'3': bindPhone,
'4': unbindPhone,
'5': deleteAccount,
'6': bindEmail,
'7': unbindEmail,
'8': deleteLock,
'9': updatePassword,
}[value];
}
// toString() value
@override
String toString() => value;
// ==
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is VerificationCodeType &&
runtimeType == other.runtimeType &&
value == other.value;
@override
int get hashCode => value.hashCode;
}

View File

@ -1,4 +1,5 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
class SharedPreferencesUtils {
static SharedPreferences? _prefs;
@ -8,7 +9,63 @@ class SharedPreferencesUtils {
_prefs = await SharedPreferences.getInstance();
}
// 使
///
/// [key]
/// [value]
/// [expiry] Duration(hours: 1)
static Future<bool> setStringWithExpiry(
String key,
String value,
Duration expiry,
) async {
final prefs = _prefs;
if (prefs == null) return false;
final expiryKey = '_expiry_$key'; // _expiry_
final expireAt = DateTime.now().add(expiry).millisecondsSinceEpoch;
// 使 Transaction
return await prefs.setString(key, value) &&
await prefs.setInt(expiryKey, expireAt);
}
///
/// null
static String? getStringWithExpiry(String key) {
final prefs = _prefs;
if (prefs == null) return null;
final expiryKey = '_expiry_$key';
final expireAt = prefs.getInt(expiryKey);
//
if (expireAt == null) {
return prefs.getString(key);
}
//
final now = DateTime.now().millisecondsSinceEpoch;
if (now > expireAt) {
//
removeKeyWithExpiry(key);
return null;
}
return prefs.getString(key);
}
/// value expiry
static Future<void> removeKeyWithExpiry(String key) async {
final prefs = _prefs;
if (prefs == null) return;
final expiryKey = '_expiry_$key';
await prefs.remove(key);
await prefs.remove(expiryKey);
}
static Future<bool> setString(String key, String value) async {
return _prefs?.setString(key, value) ?? Future.value(false);
}
@ -16,4 +73,4 @@ class SharedPreferencesUtils {
static String? getString(String key) {
return _prefs?.getString(key);
}
}
}

View File

@ -1,10 +1,24 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:starwork_flutter/api/api_response.dart';
import 'package:starwork_flutter/api/model/common/request/send_validation_code_request.dart';
import 'package:starwork_flutter/api/model/user/request/validation_code_login.dart';
import 'package:starwork_flutter/api/service/common_api_service.dart';
import 'package:starwork_flutter/api/service/user_api_service.dart';
import 'package:starwork_flutter/base/base_controller.dart';
import 'package:starwork_flutter/common/constant/cache_keys.dart';
import 'package:starwork_flutter/common/constant/platform_type.dart';
import 'package:starwork_flutter/common/constant/verification_code_channel.dart';
import 'package:starwork_flutter/common/constant/verification_code_type.dart';
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
import 'package:starwork_flutter/routes/app_routes.dart';
class InputVerificationCodeController extends BaseController {
final userApi = Get.find<UserApiService>();
final commonApi = Get.find<CommonApiService>();
var rawPhone = ''.obs;
var phone = ''.obs;
var previousRoute = ''.obs;
@ -15,7 +29,7 @@ class InputVerificationCodeController extends BaseController {
var countdownValue = 60.obs;
@override
void onInit() {
void onInit() async {
super.onInit();
final params = Get.parameters;
@ -24,56 +38,92 @@ class InputVerificationCodeController extends BaseController {
previousRoute.value = Get.previousRoute; // "/login"
if (phoneParam != null && phoneParam.isNotEmpty) {
rawPhone.value = phoneParam;
phone.value = maskMiddleFive(phoneParam);
// 60
startCountdown();
if (previousRoute.value.contains(AppRoutes.login)) {
seedVerificationCode(codeType: VerificationCodeType.login);
} else if (previousRoute.value.contains(AppRoutes.forgotPassword)) {
seedVerificationCode(codeType: VerificationCodeType.reset);
}
}
}
@override
void onClose() {
super.onClose();
String key = '${CacheKeys.isSendValidationCode}_${rawPhone.value}';
String value = '${rawPhone.value}_${countdownValue.value}';
//
SharedPreferencesUtils.setStringWithExpiry(
key,
value,
Duration(seconds: countdownValue.value),
);
}
String maskMiddleFive(String phone) {
if (phone.length < 8) return phone; // 8/
// 8 3 5 **** 3
// 13812345678 138****678
String firstPart = phone.substring(0, 3); // 3138
String middleReplaced = '*****'; // 5 ****
String lastPart = phone.substring(8); // 3678
return '$firstPart$middleReplaced$lastPart'; // 138****678
}
/// 60
void startCountdown() {
if (isCountingDown.value) return; //
isCountingDown.value = true;
countdownValue.value = 60;
// 使 Timer
Timer.periodic(const Duration(seconds: 1), (timer) {
if (countdownValue.value > 1) {
countdownValue.value--;
} else {
timer.cancel(); //
isCountingDown.value = false;
countdownValue.value = 0;
}
});
}
void requestPhoneCodeLogin(String code) {
//
void checkVerificationCode(String pin) {
if (previousRoute.value.contains(AppRoutes.login)) {
_handleLogin();
Get.offAllNamed(AppRoutes.main);
} else if (previousRoute.value.contains(AppRoutes.forgotPassword)) {
_handleForgotPassword();
Get.toNamed(AppRoutes.setNewPassword);
}
}
void _handleLogin() {
Get.offAndToNamed(AppRoutes.main);
void _handleSeedVerificationCode({
required VerificationCodeType codeType,
}) async {}
//
void seedVerificationCode({
required VerificationCodeType codeType,
}) async {
showLoading();
var sendValidationCodeResult = await commonApi.sendValidationCode(
request: SendValidationCodeRequest(
countryCode: '+86',
account: rawPhone.value.trim(),
channel: VerificationCodeChannel.sms,
codeType: codeType,
),
);
if (sendValidationCodeResult.isSuccess) {
if (isCountingDown.value) return; //
isCountingDown.value = true;
countdownValue.value = 60;
// 使 Timer
Timer.periodic(const Duration(seconds: 1), (timer) {
if (countdownValue.value > 1) {
countdownValue.value--;
} else {
timer.cancel(); //
isCountingDown.value = false;
countdownValue.value = 0;
}
});
showSuccess(message: '验证码已发送,请注意查收'.tr);
} else {
showError(message: sendValidationCodeResult.errorMsg!);
}
hideLoading();
}
void _handleForgotPassword() {
Get.toNamed(AppRoutes.setNewPassword);
//
void _toPage() async {
if (previousRoute.value.contains(AppRoutes.login)) {
Get.offAllNamed(AppRoutes.main);
} else if (previousRoute.value.contains(AppRoutes.forgotPassword)) {
Get.toNamed(AppRoutes.setNewPassword);
}
}
}

View File

@ -3,6 +3,8 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:pinput/pinput.dart';
import 'package:starwork_flutter/common/constant/verification_code_type.dart';
import 'package:starwork_flutter/routes/app_routes.dart';
import 'package:starwork_flutter/views/login/inputVerificationCode/input_verification_code_controller.dart';
class InputVerificationCodeView
@ -47,6 +49,7 @@ class InputVerificationCodeView
Pinput(
length: 6,
obscureText: false,
autofocus: true,
defaultPinTheme: PinTheme(
width: 50.w,
height: 50.w,
@ -63,7 +66,7 @@ class InputVerificationCodeView
return null;
},
onCompleted: (pin) {
controller.requestPhoneCodeLogin(pin);
controller.checkVerificationCode(pin);
},
),
],
@ -106,7 +109,15 @@ class InputVerificationCodeView
} else {
return GestureDetector(
onTap: () {
controller.startCountdown();
if (controller.previousRoute.value.contains(AppRoutes.login)) {
controller.seedVerificationCode(
codeType: VerificationCodeType.login,
);
} else if (controller.previousRoute.value.contains(AppRoutes.forgotPassword)) {
controller.seedVerificationCode(
codeType: VerificationCodeType.reset,
);
}
},
child: Text(
'重新获取验证码'.tr,

View File

@ -2,21 +2,16 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:starwork_flutter/api/model/common/request/send_validation_code_request.dart';
import 'package:starwork_flutter/api/service/common_api_service.dart';
import 'package:starwork_flutter/base/base_controller.dart';
import 'package:starwork_flutter/common/constant/login_type.dart';
import 'package:starwork_flutter/common/constant/validation_code_channel.dart';
import 'package:starwork_flutter/common/constant/validation_code_type.dart';
import 'package:starwork_flutter/routes/app_pages.dart';
import 'package:get/get.dart';
import 'package:starwork_flutter/base/base_controller.dart';
import 'package:starwork_flutter/common/constant/cache_keys.dart';
import 'package:starwork_flutter/common/constant/login_type.dart';
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
import 'package:starwork_flutter/routes/app_routes.dart';
class LoginController extends BaseController {
final commonApi = Get.find<CommonApiService>();
int phoneNumberSize = 11;
TextEditingController phoneController = TextEditingController();
TextEditingController passwordController = TextEditingController();
@ -44,7 +39,6 @@ class LoginController extends BaseController {
void _validateForm() {
isFormValid.value = phoneController.text.length == phoneNumberSize;
debugPrint('isFormValid: ${isFormValid.value}');
}
//
@ -55,23 +49,15 @@ class LoginController extends BaseController {
content: '1',
onConfirm: () async {
isPrivacyAgreementValid.value = 1;
Get.toNamed(
AppRoutes.inputVerificationCode,
parameters: {
'phone': phoneController.text.trim(),
},
);
},
);
}
_handleRequestPhoneCode();
}
void _handleRequestPhoneCode() async {
var sendValidationCodeResult = await commonApi.sendValidationCode(
request: SendValidationCodeRequest(
countryCode: '+86',
account: phoneController.text.trim(),
channel: ValidationCodeChannel.sms.value,
codeType: ValidationCodeType.login,
),
);
if (sendValidationCodeResult.isSuccess) {
} else {
Get.toNamed(
AppRoutes.inputVerificationCode,
parameters: {
@ -88,10 +74,10 @@ class LoginController extends BaseController {
}) {
Get.dialog(
Dialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14.r), //
),
backgroundColor: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [

View File

@ -126,6 +126,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_easyloading:
dependency: "direct main"
description:
name: flutter_easyloading
sha256: ba21a3c883544e582f9cc455a4a0907556714e1e9cf0eababfcb600da191d17c
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.5"
flutter_flavorizr:
dependency: "direct dev"
description:
@ -155,6 +163,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.9.3"
flutter_spinkit:
dependency: "direct main"
description:
name: flutter_spinkit
sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.2.1"
flutter_test:
dependency: "direct dev"
description: flutter
@ -165,14 +181,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc"
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.2.8"
get:
dependency: "direct main"
description:
@ -461,6 +469,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.1"
skeletonizer:
dependency: "direct main"
description:
name: skeletonizer
sha256: "0dcacc51c144af4edaf37672072156f49e47036becbc394d7c51850c5c1e884b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.3"
sky_engine:
dependency: transitive
description: flutter

View File

@ -25,12 +25,15 @@ dependencies:
shared_preferences: ^2.2.3
# 屏幕适配
flutter_screenutil: ^5.9.3
# 提示
fluttertoast: ^8.2.8
# 验证码输入框
pinput: ^5.0.1
# 网络请求库
dio: ^5.9.0
# loading 动画库 需要指定flutter_spinkit为固定版本
flutter_easyloading: ^3.0.0
flutter_spinkit: 5.2.1
# 骨架屏
skeletonizer: ^1.4.3
dev_dependencies: