|
|
@ -12,20 +12,42 @@ import 'package:problem_check_system/data/providers/connectivity_provider.dart'; |
|
|
|
|
|
|
|
|
|
|
|
class ProblemController extends GetxController |
|
|
|
class ProblemController extends GetxController |
|
|
|
with GetSingleTickerProviderStateMixin { |
|
|
|
with GetSingleTickerProviderStateMixin { |
|
|
|
|
|
|
|
/// 本地数据库 |
|
|
|
final LocalDatabase _localDatabase; |
|
|
|
final LocalDatabase _localDatabase; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 最近问题列表 |
|
|
|
final RxList<Problem> problems = <Problem>[].obs; |
|
|
|
final RxList<Problem> problems = <Problem>[].obs; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 历史问题列表 |
|
|
|
final RxList<Problem> historyProblems = <Problem>[].obs; |
|
|
|
final RxList<Problem> historyProblems = <Problem>[].obs; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 未上传的问题列表 |
|
|
|
|
|
|
|
final RxList<Problem> unUploadedProblems = <Problem>[].obs; |
|
|
|
|
|
|
|
final RxList<Problem> selectedUnUploadedProblems = <Problem>[].obs; |
|
|
|
|
|
|
|
final Rx<bool> allSelected = false.obs; |
|
|
|
|
|
|
|
final RxBool isUploadEnabled = false.obs; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 筛选条件 |
|
|
|
final Rx<DateRange> selectedDateRange = DateRange.oneWeek.obs; |
|
|
|
final Rx<DateRange> selectedDateRange = DateRange.oneWeek.obs; |
|
|
|
final RxString selectedUploadStatus = '全部'.obs; |
|
|
|
final RxString selectedUploadStatus = '全部'.obs; |
|
|
|
final RxString selectedBindingStatus = '全部'.obs; |
|
|
|
final RxString selectedBindingStatus = '全部'.obs; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 是否加载中 |
|
|
|
final RxBool isLoading = false.obs; |
|
|
|
final RxBool isLoading = false.obs; |
|
|
|
final Dio _dio; |
|
|
|
final Dio _dio; |
|
|
|
final ConnectivityProvider _connectivityProvider; |
|
|
|
final ConnectivityProvider _connectivityProvider; |
|
|
|
|
|
|
|
|
|
|
|
late TabController tabController; |
|
|
|
late TabController tabController; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// floatingButton 拖动 |
|
|
|
|
|
|
|
final double _fabSize = 56.0; |
|
|
|
|
|
|
|
final double _edgePaddingX = 27.0.w; |
|
|
|
|
|
|
|
final double _edgePaddingY = 111.0.h; |
|
|
|
|
|
|
|
final fabUploadPosition = Offset(337.0, 703.7).obs; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// get 选中的 |
|
|
|
|
|
|
|
RxBool get isOnline => _connectivityProvider.isOnline; |
|
|
|
|
|
|
|
|
|
|
|
ProblemController({ |
|
|
|
ProblemController({ |
|
|
|
required LocalDatabase localDatabase, |
|
|
|
required LocalDatabase localDatabase, |
|
|
|
required Dio dio, |
|
|
|
required Dio dio, |
|
|
@ -34,23 +56,66 @@ class ProblemController extends GetxController |
|
|
|
_dio = dio, |
|
|
|
_dio = dio, |
|
|
|
_connectivityProvider = connectivityProvider; |
|
|
|
_connectivityProvider = connectivityProvider; |
|
|
|
|
|
|
|
|
|
|
|
RxBool get isOnline => _connectivityProvider.isOnline; |
|
|
|
@override |
|
|
|
|
|
|
|
void onInit() { |
|
|
|
|
|
|
|
super.onInit(); |
|
|
|
|
|
|
|
tabController = TabController(length: 2, vsync: this); |
|
|
|
|
|
|
|
tabController.addListener(_onTabChanged); |
|
|
|
|
|
|
|
// 监听 unUploadedProblems 列表的变化,并在变化时更新 selectedProblems |
|
|
|
|
|
|
|
ever(unUploadedProblems, (_) => _updateSelectedList()); |
|
|
|
|
|
|
|
// 监听 selectedProblems 列表,更新上传按钮状态 |
|
|
|
|
|
|
|
ever(selectedUnUploadedProblems, (_) => _updateUploadButtonState()); |
|
|
|
|
|
|
|
loadProblems(); |
|
|
|
|
|
|
|
// 查询未上传问题 |
|
|
|
|
|
|
|
loadUnUploadedProblems(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
List<Problem> get selectedProblems { |
|
|
|
@override |
|
|
|
return historyProblems.where((p) => p.isChecked.value).toList(); |
|
|
|
void onClose() { |
|
|
|
|
|
|
|
tabController.dispose(); |
|
|
|
|
|
|
|
super.onClose(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
List<Problem> get unuploadedProblems { |
|
|
|
/// 当单个问题的选中状态改变时调用 |
|
|
|
return problems.where((p) => !p.isUploaded).toList(); |
|
|
|
void onProblemCheckedChange() { |
|
|
|
|
|
|
|
// 重新计算 selectedUnUploadedProblems 列表 |
|
|
|
|
|
|
|
_updateSelectedList(); |
|
|
|
|
|
|
|
// 因为 _updateSelectedList 已经会触发 selectedUnUploadedProblems 的变化, |
|
|
|
|
|
|
|
// 所以 _updateUploadButtonState() 会被 ever 监听器自动调用。 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 常量:FAB的尺寸和贴靠间距 |
|
|
|
/// 上传问题逻辑 |
|
|
|
final double _fabSize = 56.0; // FloatingActionButton的默认尺寸 |
|
|
|
void _updateSelectedList() { |
|
|
|
final double _edgePaddingX = 27.0.w; // 边缘间距 |
|
|
|
selectedUnUploadedProblems.clear(); |
|
|
|
final double _edgePaddingY = 111.0.h; // 边缘间距 |
|
|
|
for (var problem in unUploadedProblems) { |
|
|
|
// 可拖动按钮的位置,使用 Rx<Offset> |
|
|
|
if (problem.isChecked.value) { |
|
|
|
final fabUploadPosition = Offset(337.0, 703.7).obs; |
|
|
|
selectedUnUploadedProblems.add(problem); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void _updateUploadButtonState() { |
|
|
|
|
|
|
|
isUploadEnabled.value = selectedUnUploadedProblems.isNotEmpty; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void selectAll() { |
|
|
|
|
|
|
|
final bool newState = !allSelected.value; |
|
|
|
|
|
|
|
for (var problem in unUploadedProblems) { |
|
|
|
|
|
|
|
problem.isChecked.value = newState; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
allSelected.value = newState; |
|
|
|
|
|
|
|
_updateSelectedList(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void uploadProblems() { |
|
|
|
|
|
|
|
if (selectedUnUploadedProblems.isEmpty) return; |
|
|
|
|
|
|
|
// 实际的上传逻辑,例如调用 API |
|
|
|
|
|
|
|
print('开始上传 ${selectedUnUploadedProblems.length} 个问题...'); |
|
|
|
|
|
|
|
// 上传完成后,清空列表或更新状态 |
|
|
|
|
|
|
|
selectedUnUploadedProblems.clear(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// floatingButton更新位置 |
|
|
|
void updateFabUploadPosition(Offset delta) { |
|
|
|
void updateFabUploadPosition(Offset delta) { |
|
|
|
final screenWidth = ScreenUtil().screenWidth; |
|
|
|
final screenWidth = ScreenUtil().screenWidth; |
|
|
|
final screenHeight = ScreenUtil().screenHeight; |
|
|
|
final screenHeight = ScreenUtil().screenHeight; |
|
|
@ -72,6 +137,7 @@ class ProblemController extends GetxController |
|
|
|
fabUploadPosition.value = Offset(clampedDx, clampedDy); |
|
|
|
fabUploadPosition.value = Offset(clampedDx, clampedDy); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// floatingButton 贴靠 |
|
|
|
void snapToEdge() { |
|
|
|
void snapToEdge() { |
|
|
|
final screenWidth = ScreenUtil().screenWidth; |
|
|
|
final screenWidth = ScreenUtil().screenWidth; |
|
|
|
|
|
|
|
|
|
|
@ -91,23 +157,6 @@ class ProblemController extends GetxController |
|
|
|
|
|
|
|
|
|
|
|
// 关键:只更新水平位置,垂直位置保持不变 |
|
|
|
// 关键:只更新水平位置,垂直位置保持不变 |
|
|
|
fabUploadPosition.value = Offset(newDx, fabUploadPosition.value.dy); |
|
|
|
fabUploadPosition.value = Offset(newDx, fabUploadPosition.value.dy); |
|
|
|
|
|
|
|
|
|
|
|
print(fabUploadPosition.value); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
|
|
void onInit() { |
|
|
|
|
|
|
|
super.onInit(); |
|
|
|
|
|
|
|
tabController = TabController(length: 2, vsync: this); |
|
|
|
|
|
|
|
tabController.addListener(_onTabChanged); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loadProblems(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
|
|
void onClose() { |
|
|
|
|
|
|
|
tabController.dispose(); |
|
|
|
|
|
|
|
super.onClose(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void _onTabChanged() { |
|
|
|
void _onTabChanged() { |
|
|
@ -119,30 +168,39 @@ class ProblemController extends GetxController |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void loadProblems() async { |
|
|
|
Future<void> loadProblems() async { |
|
|
|
isLoading.value = true; |
|
|
|
isLoading.value = true; |
|
|
|
try { |
|
|
|
try { |
|
|
|
if (tabController.index == 0) { |
|
|
|
// 根据 Tab 索引设置查询参数的默认值 |
|
|
|
// "问题列表" Tab: 使用日期范围和筛选条件 |
|
|
|
final bool isProblemListTab = tabController.index == 0; |
|
|
|
final startDate = selectedDateRange.value.startDate; |
|
|
|
|
|
|
|
final endDate = DateTime.now(); |
|
|
|
final DateTime startDate = isProblemListTab |
|
|
|
|
|
|
|
? selectedDateRange.value.startDate |
|
|
|
|
|
|
|
: DateTime(2000); // 历史列表从很早的日期开始 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final DateTime endDate = DateTime.now(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final String uploadStatus = isProblemListTab |
|
|
|
|
|
|
|
? selectedUploadStatus.value |
|
|
|
|
|
|
|
: '全部'; |
|
|
|
|
|
|
|
|
|
|
|
final problems = await _localDatabase.getProblems( |
|
|
|
final String bindStatus = isProblemListTab |
|
|
|
|
|
|
|
? selectedBindingStatus.value |
|
|
|
|
|
|
|
: '全部'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 只执行一次数据库查询 |
|
|
|
|
|
|
|
final loadedProblems = await _localDatabase.getProblems( |
|
|
|
startDate: startDate, |
|
|
|
startDate: startDate, |
|
|
|
endDate: endDate, |
|
|
|
endDate: endDate, |
|
|
|
uploadStatus: selectedUploadStatus.value, |
|
|
|
uploadStatus: uploadStatus, |
|
|
|
bindStatus: selectedBindingStatus.value, |
|
|
|
bindStatus: bindStatus, |
|
|
|
); |
|
|
|
); |
|
|
|
this.problems.assignAll(problems); |
|
|
|
|
|
|
|
|
|
|
|
// 根据 Tab 索引将数据分配给正确的列表 |
|
|
|
|
|
|
|
if (isProblemListTab) { |
|
|
|
|
|
|
|
problems.assignAll(loadedProblems); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
// "历史问题列表" Tab: 查询所有问题 |
|
|
|
historyProblems.assignAll(loadedProblems); |
|
|
|
final allProblems = await _localDatabase.getProblems( |
|
|
|
|
|
|
|
startDate: DateTime(2000), |
|
|
|
|
|
|
|
endDate: DateTime.now(), |
|
|
|
|
|
|
|
uploadStatus: '全部', |
|
|
|
|
|
|
|
bindStatus: '全部', |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
this.historyProblems.assignAll(allProblems); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
} catch (e) { |
|
|
|
Get.snackbar('错误', '加载问题失败: $e'); |
|
|
|
Get.snackbar('错误', '加载问题失败: $e'); |
|
|
@ -151,7 +209,7 @@ class ProblemController extends GetxController |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 通用方法,用于更新筛选条件并重新加载问题列表。 |
|
|
|
/// 筛选问题 |
|
|
|
void updateFiltersAndLoadProblems({ |
|
|
|
void updateFiltersAndLoadProblems({ |
|
|
|
DateRange? newDateRange, |
|
|
|
DateRange? newDateRange, |
|
|
|
String? newUploadStatus, |
|
|
|
String? newUploadStatus, |
|
|
@ -167,12 +225,23 @@ class ProblemController extends GetxController |
|
|
|
selectedBindingStatus.value = newBindingStatus; |
|
|
|
selectedBindingStatus.value = newBindingStatus; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 只有在参数被传递时才执行 loadProblems |
|
|
|
// 只要调用此方法,就重新加载数据 |
|
|
|
if (newDateRange != null || |
|
|
|
|
|
|
|
newUploadStatus != null || |
|
|
|
|
|
|
|
newBindingStatus != null) { |
|
|
|
|
|
|
|
loadProblems(); |
|
|
|
loadProblems(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 新增方法:查询所有未上传的问题 |
|
|
|
|
|
|
|
Future<void> loadUnUploadedProblems() async { |
|
|
|
|
|
|
|
isLoading.value = true; |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
// 调用 _localDatabase.getProblems 并只筛选 '未上传' 的问题 |
|
|
|
|
|
|
|
unUploadedProblems.value = await _localDatabase.getProblems( |
|
|
|
|
|
|
|
uploadStatus: '未上传', |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
Get.snackbar('错误', '加载未上传问题失败: $e'); |
|
|
|
|
|
|
|
} finally { |
|
|
|
|
|
|
|
isLoading.value = false; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<void> addProblem(Problem problem) async { |
|
|
|
Future<void> addProblem(Problem problem) async { |
|
|
@ -209,7 +278,7 @@ class ProblemController extends GetxController |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<void> deleteSelectedProblems() async { |
|
|
|
Future<void> deleteSelectedProblems() async { |
|
|
|
final problemsToDelete = selectedProblems; |
|
|
|
final problemsToDelete = selectedUnUploadedProblems; |
|
|
|
if (problemsToDelete.isEmpty) { |
|
|
|
if (problemsToDelete.isEmpty) { |
|
|
|
Get.snackbar('提示', '请至少选择一个问题进行删除'); |
|
|
|
Get.snackbar('提示', '请至少选择一个问题进行删除'); |
|
|
|
return; |
|
|
|
return; |
|
|
@ -301,7 +370,7 @@ class ProblemController extends GetxController |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
final unuploaded = unuploadedProblems; |
|
|
|
final unuploaded = unUploadedProblems; |
|
|
|
if (unuploaded.isEmpty) { |
|
|
|
if (unuploaded.isEmpty) { |
|
|
|
Get.snackbar('提示', '没有需要上传的问题'); |
|
|
|
Get.snackbar('提示', '没有需要上传的问题'); |
|
|
|
return; |
|
|
|
return; |
|
|
|