Show saving indicator
This commit is contained in:
parent
8d31a629ac
commit
863f3151b3
|
|
@ -53,6 +53,18 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
Timer? _requestedDebounce;
|
Timer? _requestedDebounce;
|
||||||
Timer? _notedDebounce;
|
Timer? _notedDebounce;
|
||||||
Timer? _receivedDebounce;
|
Timer? _receivedDebounce;
|
||||||
|
// Seeding/state tracking for signatory fields
|
||||||
|
String? _seededTaskId;
|
||||||
|
bool _requestedSaving = false;
|
||||||
|
bool _requestedSaved = false;
|
||||||
|
bool _notedSaving = false;
|
||||||
|
bool _notedSaved = false;
|
||||||
|
bool _receivedSaving = false;
|
||||||
|
bool _receivedSaved = false;
|
||||||
|
bool _typeSaving = false;
|
||||||
|
bool _typeSaved = false;
|
||||||
|
bool _categorySaving = false;
|
||||||
|
bool _categorySaved = false;
|
||||||
static const List<String> _statusOptions = [
|
static const List<String> _statusOptions = [
|
||||||
'queued',
|
'queued',
|
||||||
'in_progress',
|
'in_progress',
|
||||||
|
|
@ -100,7 +112,6 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
child: Center(child: Text('Task not found.')),
|
child: Center(child: Text('Task not found.')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ticketId = task.ticketId;
|
final ticketId = task.ticketId;
|
||||||
final typingChannelId = task.id;
|
final typingChannelId = task.id;
|
||||||
final ticket = ticketId == null
|
final ticket = ticketId == null
|
||||||
|
|
@ -139,6 +150,17 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final isWide = constraints.maxWidth >= AppBreakpoints.desktop;
|
final isWide = constraints.maxWidth >= AppBreakpoints.desktop;
|
||||||
|
|
||||||
|
// Seed controllers once per task to reflect persisted values
|
||||||
|
if (_seededTaskId != task.id) {
|
||||||
|
_seededTaskId = task.id;
|
||||||
|
_requestedController.text = task.requestedBy ?? '';
|
||||||
|
_notedController.text = task.notedBy ?? '';
|
||||||
|
_receivedController.text = task.receivedBy ?? '';
|
||||||
|
_requestedSaved = _requestedController.text.isNotEmpty;
|
||||||
|
_notedSaved = _notedController.text.isNotEmpty;
|
||||||
|
_receivedSaved = _receivedController.text.isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
final detailsContent = Column(
|
final detailsContent = Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -158,7 +180,7 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
child: Text(
|
child: Text(
|
||||||
_createdByLabel(profilesAsync, task, ticket),
|
_createdByLabel(profilesAsync, task, ticket),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.labelMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
@ -240,6 +262,24 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
DropdownButtonFormField<String?>(
|
DropdownButtonFormField<String?>(
|
||||||
initialValue: task.requestType,
|
initialValue: task.requestType,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
suffixIcon: _typeSaving
|
||||||
|
? const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: _typeSaved
|
||||||
|
? const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.green,
|
||||||
|
size: 14,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
items: [
|
items: [
|
||||||
const DropdownMenuItem(
|
const DropdownMenuItem(
|
||||||
value: null,
|
value: null,
|
||||||
|
|
@ -251,28 +291,98 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
child: Text(t),
|
child: Text(t),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
onChanged: (v) => ref
|
onChanged: (v) async {
|
||||||
|
setState(() {
|
||||||
|
_typeSaving = true;
|
||||||
|
_typeSaved = false;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
.read(tasksControllerProvider)
|
.read(tasksControllerProvider)
|
||||||
.updateTask(
|
.updateTask(
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
requestType: v,
|
requestType: v,
|
||||||
),
|
);
|
||||||
|
setState(
|
||||||
|
() => _typeSaved =
|
||||||
|
v != null && v.isNotEmpty,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
} finally {
|
||||||
|
setState(() => _typeSaving = false);
|
||||||
|
if (_typeSaved) {
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(seconds: 2),
|
||||||
|
() {
|
||||||
|
if (mounted) {
|
||||||
|
setState(
|
||||||
|
() => _typeSaved = false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (task.requestType == 'Other') ...[
|
if (task.requestType == 'Other') ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
initialValue: task.requestTypeOther,
|
initialValue: task.requestTypeOther,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Details',
|
hintText: 'Details',
|
||||||
|
suffixIcon: _typeSaving
|
||||||
|
? const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeWidth: 1.0,
|
||||||
),
|
),
|
||||||
onChanged: (text) => ref
|
)
|
||||||
|
: _typeSaved
|
||||||
|
? const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.green,
|
||||||
|
size: 14,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
onChanged: (text) async {
|
||||||
|
setState(() {
|
||||||
|
_typeSaving = true;
|
||||||
|
_typeSaved = false;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
.read(tasksControllerProvider)
|
.read(tasksControllerProvider)
|
||||||
.updateTask(
|
.updateTask(
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
requestTypeOther: text.isEmpty
|
requestTypeOther: text.isEmpty
|
||||||
? null
|
? null
|
||||||
: text,
|
: text,
|
||||||
),
|
);
|
||||||
|
setState(
|
||||||
|
() =>
|
||||||
|
_typeSaved = text.isNotEmpty,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
} finally {
|
||||||
|
setState(() => _typeSaving = false);
|
||||||
|
if (_typeSaved) {
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(seconds: 2),
|
||||||
|
() {
|
||||||
|
if (mounted) {
|
||||||
|
setState(
|
||||||
|
() => _typeSaved = false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
@ -280,6 +390,24 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
DropdownButtonFormField<String?>(
|
DropdownButtonFormField<String?>(
|
||||||
initialValue: task.requestCategory,
|
initialValue: task.requestCategory,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
suffixIcon: _categorySaving
|
||||||
|
? const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: _categorySaved
|
||||||
|
? const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.green,
|
||||||
|
size: 14,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
items: [
|
items: [
|
||||||
const DropdownMenuItem(
|
const DropdownMenuItem(
|
||||||
value: null,
|
value: null,
|
||||||
|
|
@ -291,12 +419,42 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
child: Text(c),
|
child: Text(c),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
onChanged: (v) => ref
|
onChanged: (v) async {
|
||||||
|
setState(() {
|
||||||
|
_categorySaving = true;
|
||||||
|
_categorySaved = false;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
.read(tasksControllerProvider)
|
.read(tasksControllerProvider)
|
||||||
.updateTask(
|
.updateTask(
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
requestCategory: v,
|
requestCategory: v,
|
||||||
),
|
);
|
||||||
|
setState(
|
||||||
|
() => _categorySaved =
|
||||||
|
v != null && v.isNotEmpty,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
} finally {
|
||||||
|
setState(
|
||||||
|
() => _categorySaving = false,
|
||||||
|
);
|
||||||
|
if (_categorySaved) {
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(seconds: 2),
|
||||||
|
() {
|
||||||
|
if (mounted) {
|
||||||
|
setState(
|
||||||
|
() =>
|
||||||
|
_categorySaved = false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
@ -320,11 +478,26 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
TypeAheadFormField<String>(
|
TypeAheadFormField<String>(
|
||||||
textFieldConfiguration:
|
textFieldConfiguration: TextFieldConfiguration(
|
||||||
TextFieldConfiguration(
|
|
||||||
controller: _requestedController,
|
controller: _requestedController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Requester name or id',
|
hintText: 'Requester name or id',
|
||||||
|
suffixIcon: _requestedSaving
|
||||||
|
? const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: _requestedSaved
|
||||||
|
? const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.green,
|
||||||
|
size: 14,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
_requestedDebounce?.cancel();
|
_requestedDebounce?.cancel();
|
||||||
|
|
@ -332,10 +505,13 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
const Duration(milliseconds: 700),
|
const Duration(milliseconds: 700),
|
||||||
() async {
|
() async {
|
||||||
final name = v.trim();
|
final name = v.trim();
|
||||||
|
setState(() {
|
||||||
|
_requestedSaving = true;
|
||||||
|
_requestedSaved = false;
|
||||||
|
});
|
||||||
|
try {
|
||||||
await ref
|
await ref
|
||||||
.read(
|
.read(tasksControllerProvider)
|
||||||
tasksControllerProvider,
|
|
||||||
)
|
|
||||||
.updateTask(
|
.updateTask(
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
requestedBy: name.isEmpty
|
requestedBy: name.isEmpty
|
||||||
|
|
@ -352,6 +528,29 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
.upsert({'name': name});
|
.upsert({'name': name});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
setState(() {
|
||||||
|
_requestedSaved =
|
||||||
|
name.isNotEmpty;
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
_requestedSaving = false;
|
||||||
|
});
|
||||||
|
if (_requestedSaved) {
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(seconds: 2),
|
||||||
|
() {
|
||||||
|
if (mounted) {
|
||||||
|
setState(
|
||||||
|
() => _requestedSaved =
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -402,6 +601,11 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
onSuggestionSelected: (suggestion) async {
|
onSuggestionSelected: (suggestion) async {
|
||||||
_requestedDebounce?.cancel();
|
_requestedDebounce?.cancel();
|
||||||
_requestedController.text = suggestion;
|
_requestedController.text = suggestion;
|
||||||
|
setState(() {
|
||||||
|
_requestedSaving = true;
|
||||||
|
_requestedSaved = false;
|
||||||
|
});
|
||||||
|
try {
|
||||||
await ref
|
await ref
|
||||||
.read(tasksControllerProvider)
|
.read(tasksControllerProvider)
|
||||||
.updateTask(
|
.updateTask(
|
||||||
|
|
@ -410,14 +614,36 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
? null
|
? null
|
||||||
: suggestion,
|
: suggestion,
|
||||||
);
|
);
|
||||||
try {
|
|
||||||
if (suggestion.isNotEmpty) {
|
if (suggestion.isNotEmpty) {
|
||||||
|
try {
|
||||||
await ref
|
await ref
|
||||||
.read(supabaseClientProvider)
|
.read(supabaseClientProvider)
|
||||||
.from('clients')
|
.from('clients')
|
||||||
.upsert({'name': suggestion});
|
.upsert({'name': suggestion});
|
||||||
}
|
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
setState(
|
||||||
|
() => _requestedSaved =
|
||||||
|
suggestion.isNotEmpty,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
} finally {
|
||||||
|
setState(
|
||||||
|
() => _requestedSaving = false,
|
||||||
|
);
|
||||||
|
if (_requestedSaved) {
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(seconds: 2),
|
||||||
|
() {
|
||||||
|
if (mounted) {
|
||||||
|
setState(
|
||||||
|
() => _requestedSaved = false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
@ -430,11 +656,26 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
TypeAheadFormField<String>(
|
TypeAheadFormField<String>(
|
||||||
textFieldConfiguration:
|
textFieldConfiguration: TextFieldConfiguration(
|
||||||
TextFieldConfiguration(
|
|
||||||
controller: _notedController,
|
controller: _notedController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Supervisor/Senior',
|
hintText: 'Supervisor/Senior',
|
||||||
|
suffixIcon: _notedSaving
|
||||||
|
? const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: _notedSaved
|
||||||
|
? const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.green,
|
||||||
|
size: 14,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
_notedDebounce?.cancel();
|
_notedDebounce?.cancel();
|
||||||
|
|
@ -442,10 +683,13 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
const Duration(milliseconds: 700),
|
const Duration(milliseconds: 700),
|
||||||
() async {
|
() async {
|
||||||
final name = v.trim();
|
final name = v.trim();
|
||||||
|
setState(() {
|
||||||
|
_notedSaving = true;
|
||||||
|
_notedSaved = false;
|
||||||
|
});
|
||||||
|
try {
|
||||||
await ref
|
await ref
|
||||||
.read(
|
.read(tasksControllerProvider)
|
||||||
tasksControllerProvider,
|
|
||||||
)
|
|
||||||
.updateTask(
|
.updateTask(
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
notedBy: name.isEmpty
|
notedBy: name.isEmpty
|
||||||
|
|
@ -462,6 +706,29 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
.upsert({'name': name});
|
.upsert({'name': name});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
setState(() {
|
||||||
|
_notedSaved = name.isNotEmpty;
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
// ignore
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
_notedSaving = false;
|
||||||
|
});
|
||||||
|
if (_notedSaved) {
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(seconds: 2),
|
||||||
|
() {
|
||||||
|
if (mounted) {
|
||||||
|
setState(
|
||||||
|
() =>
|
||||||
|
_notedSaved = false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -512,6 +779,11 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
onSuggestionSelected: (suggestion) async {
|
onSuggestionSelected: (suggestion) async {
|
||||||
_notedDebounce?.cancel();
|
_notedDebounce?.cancel();
|
||||||
_notedController.text = suggestion;
|
_notedController.text = suggestion;
|
||||||
|
setState(() {
|
||||||
|
_notedSaving = true;
|
||||||
|
_notedSaved = false;
|
||||||
|
});
|
||||||
|
try {
|
||||||
await ref
|
await ref
|
||||||
.read(tasksControllerProvider)
|
.read(tasksControllerProvider)
|
||||||
.updateTask(
|
.updateTask(
|
||||||
|
|
@ -520,14 +792,34 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
? null
|
? null
|
||||||
: suggestion,
|
: suggestion,
|
||||||
);
|
);
|
||||||
try {
|
|
||||||
if (suggestion.isNotEmpty) {
|
if (suggestion.isNotEmpty) {
|
||||||
|
try {
|
||||||
await ref
|
await ref
|
||||||
.read(supabaseClientProvider)
|
.read(supabaseClientProvider)
|
||||||
.from('clients')
|
.from('clients')
|
||||||
.upsert({'name': suggestion});
|
.upsert({'name': suggestion});
|
||||||
}
|
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
setState(
|
||||||
|
() => _notedSaved =
|
||||||
|
suggestion.isNotEmpty,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
} finally {
|
||||||
|
setState(() => _notedSaving = false);
|
||||||
|
if (_notedSaved) {
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(seconds: 2),
|
||||||
|
() {
|
||||||
|
if (mounted) {
|
||||||
|
setState(
|
||||||
|
() => _notedSaved = false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
@ -540,11 +832,26 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
TypeAheadFormField<String>(
|
TypeAheadFormField<String>(
|
||||||
textFieldConfiguration:
|
textFieldConfiguration: TextFieldConfiguration(
|
||||||
TextFieldConfiguration(
|
|
||||||
controller: _receivedController,
|
controller: _receivedController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Receiver name or id',
|
hintText: 'Receiver name or id',
|
||||||
|
suffixIcon: _receivedSaving
|
||||||
|
? const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: _receivedSaved
|
||||||
|
? const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.green,
|
||||||
|
size: 14,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
_receivedDebounce?.cancel();
|
_receivedDebounce?.cancel();
|
||||||
|
|
@ -553,9 +860,7 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
() async {
|
() async {
|
||||||
final name = v.trim();
|
final name = v.trim();
|
||||||
await ref
|
await ref
|
||||||
.read(
|
.read(tasksControllerProvider)
|
||||||
tasksControllerProvider,
|
|
||||||
)
|
|
||||||
.updateTask(
|
.updateTask(
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
receivedBy: name.isEmpty
|
receivedBy: name.isEmpty
|
||||||
|
|
@ -622,6 +927,11 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
onSuggestionSelected: (suggestion) async {
|
onSuggestionSelected: (suggestion) async {
|
||||||
_receivedDebounce?.cancel();
|
_receivedDebounce?.cancel();
|
||||||
_receivedController.text = suggestion;
|
_receivedController.text = suggestion;
|
||||||
|
setState(() {
|
||||||
|
_receivedSaving = true;
|
||||||
|
_receivedSaved = false;
|
||||||
|
});
|
||||||
|
try {
|
||||||
await ref
|
await ref
|
||||||
.read(tasksControllerProvider)
|
.read(tasksControllerProvider)
|
||||||
.updateTask(
|
.updateTask(
|
||||||
|
|
@ -630,14 +940,34 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen> {
|
||||||
? null
|
? null
|
||||||
: suggestion,
|
: suggestion,
|
||||||
);
|
);
|
||||||
try {
|
|
||||||
if (suggestion.isNotEmpty) {
|
if (suggestion.isNotEmpty) {
|
||||||
|
try {
|
||||||
await ref
|
await ref
|
||||||
.read(supabaseClientProvider)
|
.read(supabaseClientProvider)
|
||||||
.from('clients')
|
.from('clients')
|
||||||
.upsert({'name': suggestion});
|
.upsert({'name': suggestion});
|
||||||
}
|
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
setState(
|
||||||
|
() => _receivedSaved =
|
||||||
|
suggestion.isNotEmpty,
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
} finally {
|
||||||
|
setState(() => _receivedSaving = false);
|
||||||
|
if (_receivedSaved) {
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(seconds: 2),
|
||||||
|
() {
|
||||||
|
if (mounted) {
|
||||||
|
setState(
|
||||||
|
() => _receivedSaved = false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user