Browse Source

fate : 监听联网状态

dev
徐振升 2 weeks ago
parent
commit
34447b0826
  1. 3
      lib/app/bindings/initial_binding.dart
  2. 62
      lib/data/providers/connectivity_provider.dart
  3. 6
      lib/modules/auth/bindings/auth_binding.dart
  4. 118
      lib/modules/auth/controllers/auth_controller.dart
  5. 53
      lib/modules/auth/views/login_page.dart
  6. 15
      lib/modules/home/bindings/home_binding.dart
  7. 5
      lib/modules/my/views/my_page.dart
  8. 2
      lib/modules/problem/bindings/problem_binding.dart
  9. 43
      lib/modules/problem/controllers/problem_controller.dart
  10. 52
      lib/modules/problem/views/problem_page.dart
  11. 2
      pubspec.lock
  12. 1
      pubspec.yaml

3
lib/app/bindings/initial_binding.dart

@ -2,6 +2,7 @@ import 'package:get/get.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; // debugPrint
import 'package:get_storage/get_storage.dart';
import 'package:problem_check_system/data/providers/connectivity_provider.dart';
import 'package:problem_check_system/data/providers/local_database.dart';
class InitialBinding implements Bindings {
@ -9,11 +10,11 @@ class InitialBinding implements Bindings {
void dependencies() {
// GetStorage - 使 put
Get.put<GetStorage>(GetStorage(), permanent: true);
// Dio - 使 put lazyPut
Get.put<Dio>(createDioInstance());
//
Get.put<LocalDatabase>(LocalDatabase());
Get.put<ConnectivityProvider>(ConnectivityProvider());
}
// Dio

62
lib/data/providers/connectivity_provider.dart

@ -0,0 +1,62 @@
// lib/data/providers/connectivity_provider.dart
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
class ConnectivityProvider extends GetxService {
final Connectivity _connectivity = Connectivity();
final RxBool isOnline = false.obs;
late StreamSubscription<List<ConnectivityResult>> _connectivitySubscription;
@override
void onInit() {
super.onInit();
_initConnectivityListener();
}
@override
void onClose() {
_connectivitySubscription.cancel();
super.onClose();
}
Future<void> _initConnectivityListener() async {
_connectivitySubscription = _connectivity.onConnectivityChanged.listen((
results,
) {
final isConnected = results.any(
(result) =>
result == ConnectivityResult.mobile ||
result == ConnectivityResult.wifi ||
result == ConnectivityResult.ethernet,
);
isOnline.value = isConnected;
if (isConnected) {
Get.snackbar(
'网络状态',
'已连接到网络',
colorText: Colors.white,
backgroundColor: Colors.green,
snackPosition: SnackPosition.TOP,
);
} else {
Get.snackbar(
'网络状态',
'已断开网络连接',
colorText: Colors.white,
backgroundColor: Colors.red,
snackPosition: SnackPosition.TOP,
);
}
});
final initialResults = await _connectivity.checkConnectivity();
isOnline.value = initialResults.any(
(result) =>
result == ConnectivityResult.mobile ||
result == ConnectivityResult.wifi ||
result == ConnectivityResult.ethernet,
);
}
}

6
lib/modules/auth/bindings/auth_binding.dart

@ -1,5 +1,6 @@
import 'package:get/get.dart';
import 'package:dio/dio.dart';
import 'package:get_storage/get_storage.dart';
import 'package:problem_check_system/modules/auth/controllers/auth_controller.dart';
import 'package:problem_check_system/data/providers/auth_provider.dart';
@ -13,7 +14,10 @@ class AuthBinding implements Bindings {
// 2. (AuthController) AuthProvider
// Get.find()
Get.lazyPut<AuthController>(
() => AuthController(authProvider: Get.find<AuthProvider>()),
() => AuthController(
authProvider: Get.find<AuthProvider>(),
storage: Get.find<GetStorage>(),
),
);
}
}

118
lib/modules/auth/controllers/auth_controller.dart

@ -1,68 +1,99 @@
// lib/modules/auth/controllers/auth_controller.dart
import 'package:flutter/material.dart'; // 使 TextEditingController
import 'package:get/get.dart';
import 'package:dio/dio.dart';
import 'package:get_storage/get_storage.dart';
//
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:problem_check_system/data/providers/auth_provider.dart';
import 'package:problem_check_system/data/models/login_model.dart';
import 'package:problem_check_system/app/routes/app_routes.dart';
class AuthController extends GetxController {
final AuthProvider _authProvider;
AuthController({required AuthProvider authProvider})
: _authProvider = authProvider;
final GetStorage _storage;
// TextEditingController
late final TextEditingController usernameController;
late final TextEditingController passwordController;
final username = ''.obs;
final password = ''.obs;
final isLoading = false.obs;
final rememberPassword = false.obs;
final _box = GetStorage();
static const _usernameKey = 'username';
static const _passwordKey = 'password';
AuthController({
required AuthProvider authProvider,
required GetStorage storage,
}) : _authProvider = authProvider,
_storage = storage;
@override
void onInit() {
super.onInit();
// onInit
usernameController = TextEditingController();
passwordController = TextEditingController();
_loadSavedCredentials();
}
@override
void onClose() {
// onClose
usernameController.dispose();
passwordController.dispose();
super.onClose();
}
void _loadSavedCredentials() {
final savedUsername = _box.read(_usernameKey);
final savedPassword = _box.read(_passwordKey);
final savedUsername = _storage.read(_usernameKey);
final savedPassword = _storage.read(_passwordKey);
if (savedUsername != null && savedPassword != null) {
username.value = savedUsername;
password.value = savedPassword;
//
usernameController.text = savedUsername;
passwordController.text = savedPassword;
rememberPassword.value = true;
}
}
String getToken() {
return '';
return _storage.read('token') ?? '';
}
void updateUsername(String value) {
username.value = value;
}
void updatePassword(String value) {
password.value = value;
}
// updateUsername updatePassword
// TextEditingController
Future<void> login() async {
if (username.value.isEmpty || password.value.isEmpty) {
//
final username = usernameController.text;
final password = passwordController.text;
if (username.isEmpty || password.isEmpty) {
Get.snackbar('输入错误', '用户名和密码不能为空');
return;
}
isLoading.value = true;
var connectivityResult = await (Connectivity().checkConnectivity());
bool isConnected =
connectivityResult.contains(ConnectivityResult.mobile) ||
connectivityResult.contains(ConnectivityResult.wifi) ||
connectivityResult.contains(ConnectivityResult.ethernet);
if (isConnected) {
await _onlineLogin(username, password);
} else {
await _offlineLogin(username, password);
}
isLoading.value = false;
}
Future<void> _onlineLogin(String username, String password) async {
try {
final loginData = LoginModel(
username: username.value,
password: password.value,
);
final loginData = LoginModel(username: username, password: password);
final response = await _authProvider.signIn(loginData);
@ -70,15 +101,17 @@ class AuthController extends GetxController {
final token = response.data['token'];
final refreshToken = response.data['refreshToken'];
_box.write('token', token);
_box.write('refreshToken', refreshToken);
_storage.write('token', token);
_storage.write('refreshToken', refreshToken);
//
if (rememberPassword.value) {
_box.write(_usernameKey, username.value);
_box.write(_passwordKey, password.value);
_storage.write(_usernameKey, username);
_storage.write(_passwordKey, password);
} else {
_box.remove(_usernameKey);
_box.remove(_passwordKey);
//
_storage.remove(_usernameKey);
_storage.remove(_passwordKey);
}
Get.offAllNamed(AppRoutes.home);
@ -94,18 +127,27 @@ class AuthController extends GetxController {
}
} catch (e) {
Get.snackbar('错误', '发生未知错误: ${e.toString()}');
} finally {
isLoading.value = false;
}
}
Future<void> logout() async {
_box.remove('token');
_box.remove('refreshToken');
if (rememberPassword.value == false) {
_box.remove(_usernameKey);
_box.remove(_passwordKey);
Future<void> _offlineLogin(String username, String password) async {
final storedUsername = _storage.read(_usernameKey);
final storedPassword = _storage.read(_passwordKey);
if (storedUsername != null &&
storedPassword != null &&
storedUsername == username &&
storedPassword == password) {
Get.offAllNamed(AppRoutes.home);
Get.snackbar('离线登录成功', '您已离线登录到系统');
} else {
Get.snackbar('离线登录失败', '无网络连接,且无法验证本地凭证');
}
}
Future<void> logout() async {
_storage.remove('token');
_storage.remove('refreshToken');
Get.offAllNamed(AppRoutes.login);
}
}

53
lib/modules/auth/views/login_page.dart

@ -1,43 +1,20 @@
// lib/modules/auth/views/login_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:problem_check_system/modules/auth/controllers/auth_controller.dart';
class LoginPage extends StatelessWidget {
class LoginPage extends GetView<AuthController> {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
final AuthController controller = Get.find<AuthController>();
// build TextEditingController
final TextEditingController usernameController = TextEditingController(
text: controller.username.value,
);
final TextEditingController passwordController = TextEditingController(
text: controller.password.value,
);
// AuthController
// TextEditingController
// 使 once() ever()
// onChanged
usernameController.text = controller.username.value;
passwordController.text = controller.password.value;
return Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(
children: [
_buildBackground(),
_buildLoginCard(controller, usernameController, passwordController),
],
),
body: Stack(children: [_buildBackground(), _buildLoginCard(controller)]),
);
}
Widget _buildBackground() {
// ... ...
return Stack(
children: [
Container(
@ -72,12 +49,8 @@ class LoginPage extends StatelessWidget {
);
}
// _buildLoginCard
Widget _buildLoginCard(
AuthController controller,
TextEditingController usernameController,
TextEditingController passwordController,
) {
// _buildLoginCard TextEditingController
Widget _buildLoginCard(AuthController controller) {
return Positioned(
left: 20.5.w,
top: 220.5.h,
@ -85,7 +58,7 @@ class LoginPage extends StatelessWidget {
width: 334.w,
height: 574.5.h,
decoration: BoxDecoration(
color: const Color(0xFFFFFFFF).withOpacity(0.6), // 使 withOpacity
color: const Color(0xFFFFFFFF).withOpacity(0.6),
borderRadius: BorderRadius.all(Radius.circular(23.5.r)),
),
padding: EdgeInsets.all(24.w),
@ -93,20 +66,18 @@ class LoginPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
// 使 _buildTextFieldSection
// 使 TextEditingController
_buildTextFieldSection(
label: '账号',
hintText: '请输入您的账号',
controller: usernameController,
onChanged: controller.updateUsername, // onChanged
controller: controller.usernameController,
),
const SizedBox(height: 22),
_buildTextFieldSection(
label: '密码',
hintText: '请输入您的密码',
obscureText: true,
controller: passwordController,
onChanged: controller.updatePassword,
controller: controller.passwordController,
),
const SizedBox(height: 9.5),
_buildRememberPasswordRow(controller),
@ -118,13 +89,12 @@ class LoginPage extends StatelessWidget {
);
}
// _buildTextFieldSection TextEditingController
// _buildTextFieldSection onChanged
Widget _buildTextFieldSection({
required String label,
required String hintText,
required TextEditingController controller,
bool obscureText = false,
required Function(String) onChanged,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -136,7 +106,6 @@ class LoginPage extends StatelessWidget {
const SizedBox(height: 10.5),
TextField(
controller: controller, // 使
onChanged: onChanged,
obscureText: obscureText,
style: const TextStyle(color: Colors.black),
decoration: InputDecoration(
@ -150,7 +119,6 @@ class LoginPage extends StatelessWidget {
}
Widget _buildRememberPasswordRow(AuthController controller) {
// ... ...
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -169,7 +137,6 @@ class LoginPage extends StatelessWidget {
}
Widget _buildLoginButton(AuthController controller) {
// ... ...
return SizedBox(
width: double.infinity,
child: ElevatedButton(

15
lib/modules/home/bindings/home_binding.dart

@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:problem_check_system/data/providers/auth_provider.dart';
import 'package:problem_check_system/data/providers/connectivity_provider.dart';
import 'package:problem_check_system/data/providers/local_database.dart';
import 'package:problem_check_system/modules/auth/controllers/auth_controller.dart';
import 'package:problem_check_system/modules/home/controllers/home_controller.dart';
@ -12,15 +14,24 @@ class HomeBinding implements Bindings {
void dependencies() {
final Dio dio = Get.find<Dio>();
final LocalDatabase database = Get.find<LocalDatabase>();
final ConnectivityProvider connectivityProvider =
Get.find<ConnectivityProvider>();
// HomeController
Get.lazyPut<HomeController>(() => HomeController());
Get.lazyPut<ProblemController>(
() => ProblemController(localDatabase: database, dio: dio),
() => ProblemController(
localDatabase: database,
dio: dio,
connectivityProvider: connectivityProvider,
),
);
Get.lazyPut<MyController>(() => MyController());
Get.lazyPut<AuthProvider>(() => AuthProvider(dio: dio));
Get.lazyPut<AuthController>(
() => AuthController(authProvider: Get.find<AuthProvider>()),
() => AuthController(
authProvider: Get.find<AuthProvider>(),
storage: Get.find<GetStorage>(),
),
);
}
}

5
lib/modules/my/views/my_page.dart

@ -40,10 +40,7 @@ class MyPage extends StatelessWidget {
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
const Color(0xFFE4F0FF),
const Color(0xFFF1F7FF).withValues(alpha: 25.5),
],
colors: [const Color(0xFF418CFC), const Color(0x713DBFFC)],
),
),
),

2
lib/modules/problem/bindings/problem_binding.dart

@ -1,5 +1,6 @@
import 'package:get/get.dart';
import 'package:dio/dio.dart';
import 'package:problem_check_system/data/providers/connectivity_provider.dart';
import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart';
import 'package:problem_check_system/data/providers/local_database.dart';
@ -13,6 +14,7 @@ class ProblemBinding implements Bindings {
() => ProblemController(
localDatabase: Get.find<LocalDatabase>(),
dio: Get.find<Dio>(),
connectivityProvider: Get.find<ConnectivityProvider>(),
),
);
}

43
lib/modules/problem/controllers/problem_controller.dart

@ -1,27 +1,34 @@
// modules/problem/controllers/problem_controller.dart
import 'package:dio/dio.dart';
import 'package:get/get.dart' hide MultipartFile, FormData;
import 'dart:io';
import 'package:path/path.dart' as path;
import '../../../data/models/problem_model.dart';
import '../../../data/providers/local_database.dart';
import '../../../data/providers/connectivity_provider.dart'; //
class ProblemController extends GetxController {
final LocalDatabase _localDatabase;
final RxList<Problem> problems = <Problem>[].obs;
final RxBool isLoading = false.obs;
final Dio _dio;
final ConnectivityProvider _connectivityProvider;
// bindings
ProblemController({required LocalDatabase localDatabase, required Dio dio})
: _localDatabase = localDatabase,
_dio = dio;
ProblemController({
required LocalDatabase localDatabase,
required Dio dio,
required ConnectivityProvider connectivityProvider,
}) : _localDatabase = localDatabase,
_dio = dio,
_connectivityProvider = connectivityProvider;
// 使 provider isOnline
RxBool get isOnline => _connectivityProvider.isOnline;
//
List<Problem> get selectedProblems {
return problems.where((p) => p.isChecked.value).toList();
}
//
List<Problem> get unuploadedProblems {
return problems.where((p) => !p.isUploaded).toList();
}
@ -44,16 +51,13 @@ class ProblemController extends GetxController {
}
}
/// GetX列表中添加一个新问题
Future<void> addProblem(Problem problem) async {
try {
// ID
if (problem.id == null) {
problem = problem.copyWith(
id: DateTime.now().millisecondsSinceEpoch.toString(),
);
}
await _localDatabase.insertProblem(problem);
problems.add(problem);
} catch (e) {
@ -62,7 +66,6 @@ class ProblemController extends GetxController {
}
}
/// GetX列表中更新一个现有问题
Future<void> updateProblem(Problem problem) async {
try {
await _localDatabase.updateProblem(problem);
@ -76,16 +79,11 @@ class ProblemController extends GetxController {
}
}
///
Future<void> deleteProblem(Problem problem) async {
try {
if (problem.id != null) {
await _localDatabase.deleteProblem(problem.id!);
//
problems.remove(problem);
//
await _deleteProblemImages(problem);
}
} catch (e) {
@ -94,14 +92,12 @@ class ProblemController extends GetxController {
}
}
///
Future<void> deleteSelectedProblems() async {
final problemsToDelete = selectedProblems;
if (problemsToDelete.isEmpty) {
Get.snackbar('提示', '请至少选择一个问题进行删除');
return;
}
try {
for (var problem in problemsToDelete) {
await deleteProblem(problem);
@ -112,7 +108,6 @@ class ProblemController extends GetxController {
}
}
///
Future<void> _deleteProblemImages(Problem problem) async {
for (var imagePath in problem.imagePaths) {
try {
@ -126,7 +121,6 @@ class ProblemController extends GetxController {
}
}
///
Future<bool> uploadProblem(Problem problem) async {
try {
final formData = FormData.fromMap({
@ -136,7 +130,6 @@ class ProblemController extends GetxController {
'boundInfo': problem.boundInfo ?? '',
});
//
for (var imagePath in problem.imagePaths) {
final file = File(imagePath);
if (await file.exists()) {
@ -162,7 +155,6 @@ class ProblemController extends GetxController {
);
if (response.statusCode == 200) {
//
final updatedProblem = problem.copyWith(isUploaded: true);
await updateProblem(updatedProblem);
return true;
@ -183,8 +175,12 @@ class ProblemController extends GetxController {
}
}
///
Future<void> uploadAllUnuploaded() async {
if (!isOnline.value) {
Get.snackbar('提示', '当前无网络,无法上传');
return;
}
final unuploaded = unuploadedProblems;
if (unuploaded.isEmpty) {
Get.snackbar('提示', '没有需要上传的问题');
@ -199,7 +195,6 @@ class ProblemController extends GetxController {
if (success) {
successCount++;
}
//
await Future.delayed(const Duration(milliseconds: 500));
}
@ -214,7 +209,6 @@ class ProblemController extends GetxController {
}
}
///
Future<void> bindInfoToProblem(String id, String info) async {
try {
final problem = problems.firstWhere((p) => p.id == id);
@ -226,7 +220,6 @@ class ProblemController extends GetxController {
}
}
///
void clearSelections() {
for (var problem in problems) {
problem.isChecked.value = false;

52
lib/modules/problem/views/problem_page.dart

@ -1,3 +1,4 @@
// problem_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
@ -28,12 +29,9 @@ class ProblemPage extends StatelessWidget {
alignment: Alignment.bottomLeft,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft, //
end: Alignment.centerRight, //
colors: [
Color(0xFF418CFC), //
Color(0xFF3DBFFC), //
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Color(0xFF418CFC), Color(0xFF3DBFFC)],
),
),
child: TabBar(
@ -50,17 +48,17 @@ class ProblemPage extends StatelessWidget {
Tab(text: '历史问题列表'),
],
labelStyle: TextStyle(
fontFamily: 'MyFont', //
fontWeight: FontWeight.w800, //
fontSize: 14.sp, //
fontFamily: 'MyFont',
fontWeight: FontWeight.w800,
fontSize: 14.sp,
),
unselectedLabelStyle: TextStyle(
fontFamily: 'MyFont',
fontWeight: FontWeight.w800,
fontSize: 14.sp,
),
labelColor: Colors.black, //
unselectedLabelColor: Colors.white, //
labelColor: Colors.black,
unselectedLabelColor: Colors.white,
),
),
Expanded(
@ -139,30 +137,32 @@ class ProblemPage extends StatelessWidget {
],
),
),
floatingActionButton: FloatingActionButton(
floatingActionButton: Obx(() {
final bool isOnline = problemController.isOnline.value;
return FloatingActionButton(
heroTag: "abc",
onPressed: () {
//
problemController.uploadAllUnuploaded();
},
onPressed: isOnline
? () => problemController.uploadAllUnuploaded()
: null,
foregroundColor: Colors.white,
backgroundColor: Colors.red[300],
child: const Icon(Icons.file_upload_outlined),
backgroundColor: isOnline ? Colors.red[300] : Colors.grey[400],
child: Icon(
isOnline ? Icons.file_upload_outlined : Icons.cloud_off_outlined,
),
);
}),
),
);
}
// ProblemCard组件
Widget _buildSwipeableProblemCard(
Problem problem,
ProblemController controller, {
ProblemCardViewType viewType = ProblemCardViewType.buttons,
}) {
// 使Dismissible组件实现左滑删除
return Dismissible(
key: Key(problem.id ?? UniqueKey().toString()),
direction: DismissDirection.endToStart, //
direction: DismissDirection.endToStart,
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
@ -170,11 +170,9 @@ class ProblemPage extends StatelessWidget {
child: Icon(Icons.delete, color: Colors.white, size: 30.sp),
),
confirmDismiss: (direction) async {
//
return await _showDeleteConfirmationDialog(problem);
},
onDismissed: (direction) {
//
controller.deleteProblem(problem);
Get.snackbar('成功', '问题已删除');
},
@ -182,9 +180,7 @@ class ProblemPage extends StatelessWidget {
);
}
//
Future<bool> _showDeleteConfirmationDialog(Problem problem) async {
// 使 Get.bottomSheet
return await Get.bottomSheet<bool>(
Container(
padding: EdgeInsets.symmetric(horizontal: 16.0),
@ -200,7 +196,6 @@ class ProblemPage extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//
SizedBox(height: 16),
Text(
'确认删除',
@ -208,14 +203,12 @@ class ProblemPage extends StatelessWidget {
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
//
Text(
'确定要删除这个问题吗?此操作不可撤销。',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
SizedBox(height: 24),
//
ElevatedButton(
onPressed: () => Get.back(result: true),
style: ElevatedButton.styleFrom(
@ -231,7 +224,6 @@ class ProblemPage extends StatelessWidget {
),
),
SizedBox(height: 8),
//
TextButton(
onPressed: () => Get.back(result: false),
style: TextButton.styleFrom(
@ -247,7 +239,7 @@ class ProblemPage extends StatelessWidget {
),
),
),
isDismissible: false, //
isDismissible: false,
) ??
false;
}

2
pubspec.lock

@ -74,7 +74,7 @@ packages:
source: hosted
version: "0.3.4+2"
crypto:
dependency: transitive
dependency: "direct main"
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"

1
pubspec.yaml

@ -8,6 +8,7 @@ environment:
dependencies:
connectivity_plus: ^6.1.5
crypto: ^3.0.6
dio: ^5.9.0
flutter:
sdk: flutter

Loading…
Cancel
Save