e376c1c436
Review detail: - Add review risk actions with status tracking and skip/apply support - Pull-to-refresh and auto-navigate after review submit - Report download, open, share, and copy-link actions Contract lifecycle: - Add contract lifecycle models and controller for mobile - Version chain display and lifecycle status transitions - Contract result screen now shows lifecycle tasks File service: - New unified file_service.dart for file management API client: - Add review risk actions and contract lifecycle endpoints Screen polish: - Login screen visual polish with brand assets - Dashboard redesign with updated layout - Profile screen updates - Review entry/history screen improvements - Theme and brand asset updates Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
120 lines
3.2 KiB
Dart
120 lines
3.2 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
|
|
import '../../../shared/network/api_client.dart';
|
|
import '../../../shared/storage/session_store.dart';
|
|
import '../domain/contract_lifecycle_models.dart';
|
|
|
|
class ContractLifecycleController {
|
|
ContractLifecycleController({ApiClient? apiClient})
|
|
: _apiClient = apiClient ?? const ApiClient();
|
|
|
|
final ApiClient _apiClient;
|
|
|
|
final ValueNotifier<bool> isLoading = ValueNotifier<bool>(false);
|
|
final ValueNotifier<String?> errorMessage = ValueNotifier<String?>(null);
|
|
final ValueNotifier<ContractLifecycleTaskListResponse?> tasks =
|
|
ValueNotifier<ContractLifecycleTaskListResponse?>(null);
|
|
|
|
Future<void> loadTasks({
|
|
required String contractId,
|
|
String? statusValue,
|
|
String? taskType,
|
|
}) async {
|
|
final session = SessionStore.current;
|
|
if (session == null || !session.isAuthenticated) {
|
|
errorMessage.value = '请先登录';
|
|
return;
|
|
}
|
|
|
|
isLoading.value = true;
|
|
errorMessage.value = null;
|
|
|
|
try {
|
|
final response = await _apiClient.listContractLifecycleTasks(
|
|
accessToken: session.accessToken,
|
|
contractId: contractId,
|
|
statusValue: statusValue,
|
|
taskType: taskType,
|
|
);
|
|
tasks.value = response;
|
|
} on ApiException catch (error) {
|
|
if (error.isUnauthorized) {
|
|
await SessionStore.clear();
|
|
}
|
|
errorMessage.value = error.message;
|
|
} catch (_) {
|
|
errorMessage.value = '获取合同任务列表失败,请稍后重试';
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
Future<void> updateTask({
|
|
required String contractId,
|
|
required String taskId,
|
|
String? taskType,
|
|
String? title,
|
|
String? description,
|
|
String? priority,
|
|
String? statusValue,
|
|
String? dueAt,
|
|
String? evidenceFileId,
|
|
String? evidenceNote,
|
|
}) async {
|
|
final session = SessionStore.current;
|
|
if (session == null || !session.isAuthenticated) {
|
|
errorMessage.value = '请先登录';
|
|
return;
|
|
}
|
|
|
|
isLoading.value = true;
|
|
errorMessage.value = null;
|
|
|
|
try {
|
|
await _apiClient.updateContractLifecycleTask(
|
|
accessToken: session.accessToken,
|
|
contractId: contractId,
|
|
taskId: taskId,
|
|
taskType: taskType,
|
|
title: title,
|
|
description: description,
|
|
priority: priority,
|
|
statusValue: statusValue,
|
|
dueAt: dueAt,
|
|
evidenceFileId: evidenceFileId,
|
|
evidenceNote: evidenceNote,
|
|
);
|
|
// Fire-and-forget refresh
|
|
_refreshTasks(contractId);
|
|
} on ApiException catch (error) {
|
|
errorMessage.value = error.message;
|
|
} catch (_) {
|
|
errorMessage.value = '更新合同任务失败,请稍后重试';
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
Future<void> _refreshTasks(String contractId) async {
|
|
final session = SessionStore.current;
|
|
if (session == null || !session.isAuthenticated) {
|
|
return;
|
|
}
|
|
try {
|
|
final response = await _apiClient.listContractLifecycleTasks(
|
|
accessToken: session.accessToken,
|
|
contractId: contractId,
|
|
);
|
|
tasks.value = response;
|
|
} catch (_) {
|
|
// Non-blocking refresh failure
|
|
}
|
|
}
|
|
|
|
void dispose() {
|
|
isLoading.dispose();
|
|
errorMessage.dispose();
|
|
tasks.dispose();
|
|
}
|
|
}
|