Add collapsible details

This commit is contained in:
Marc Rejohn Castillano 2026-02-21 15:19:15 +08:00
parent f8b8723d26
commit d2f1bcf9b3

View File

@ -232,7 +232,12 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
Text(description), Text(description),
], ],
const SizedBox(height: 16), const SizedBox(height: 16),
// Tabbed details: Assignees / Type & Category / Signatories // Collapsible tabbed details: Assignees / Type & Category / Signatories
ExpansionTile(
title: const Text('Details'),
initiallyExpanded: isWide,
childrenPadding: const EdgeInsets.symmetric(horizontal: 0),
children: [
DefaultTabController( DefaultTabController(
length: 3, length: 3,
child: Column( child: Column(
@ -257,7 +262,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
TaskAssignmentSection( TaskAssignmentSection(
taskId: task.id, taskId: task.id,
@ -278,7 +284,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
if (!canUpdateStatus) ...[ if (!canUpdateStatus) ...[
_MetaBadge( _MetaBadge(
@ -313,7 +320,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
width: 16, width: 16,
height: 16, height: 16,
child: Stack( child: Stack(
alignment: Alignment.center, alignment:
Alignment.center,
children: const [ children: const [
Icon( Icon(
Icons.save, Icons.save,
@ -363,14 +371,17 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
); );
} catch (_) { } catch (_) {
} finally { } finally {
setState(() => _typeSaving = false); setState(
() => _typeSaving = false,
);
if (_typeSaved) { if (_typeSaved) {
Future.delayed( Future.delayed(
const Duration(seconds: 2), const Duration(seconds: 2),
() { () {
if (mounted) { if (mounted) {
setState( setState(
() => _typeSaved = false, () =>
_typeSaved = false,
); );
} }
}, },
@ -402,7 +413,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
width: 16, width: 16,
height: 16, height: 16,
child: Stack( child: Stack(
alignment: Alignment.center, alignment:
Alignment.center,
children: const [ children: const [
Icon( Icon(
Icons.save, Icons.save,
@ -415,7 +427,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
child: Icon( child: Icon(
Icons.check, Icons.check,
size: 10, size: 10,
color: Colors.white, color:
Colors.white,
), ),
), ),
], ],
@ -430,27 +443,33 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
}); });
try { try {
await ref 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( setState(
() => () => _typeSaved =
_typeSaved = text.isNotEmpty, text.isNotEmpty,
); );
} catch (_) { } catch (_) {
} finally { } finally {
setState(() => _typeSaving = false); setState(
() => _typeSaving = false,
);
if (_typeSaved) { if (_typeSaved) {
Future.delayed( Future.delayed(
const Duration(seconds: 2), const Duration(seconds: 2),
() { () {
if (mounted) { if (mounted) {
setState( setState(
() => _typeSaved = false, () => _typeSaved =
false,
); );
} }
}, },
@ -483,7 +502,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
width: 16, width: 16,
height: 16, height: 16,
child: Stack( child: Stack(
alignment: Alignment.center, alignment:
Alignment.center,
children: const [ children: const [
Icon( Icon(
Icons.save, Icons.save,
@ -509,7 +529,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
value: null, value: null,
child: Text('None'), child: Text('None'),
), ),
for (final c in requestCategoryOptions) for (final c
in requestCategoryOptions)
DropdownMenuItem( DropdownMenuItem(
value: c, value: c,
child: Text(c), child: Text(c),
@ -542,8 +563,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
() { () {
if (mounted) { if (mounted) {
setState( setState(
() => () => _categorySaved =
_categorySaved = false, false,
); );
} }
}, },
@ -564,7 +585,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
child: Padding( child: Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Requested by', 'Requested by',
@ -595,7 +617,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
width: 16, width: 16,
height: 16, height: 16,
child: Stack( child: Stack(
alignment: Alignment.center, alignment:
Alignment.center,
children: const [ children: const [
Icon( Icon(
Icons.save, Icons.save,
@ -628,10 +651,13 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
}); });
try { try {
await ref await ref
.read(tasksControllerProvider) .read(
tasksControllerProvider,
)
.updateTask( .updateTask(
taskId: task.id, taskId: task.id,
requestedBy: name.isEmpty requestedBy:
name.isEmpty
? null ? null
: name, : name,
); );
@ -642,7 +668,9 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
supabaseClientProvider, supabaseClientProvider,
) )
.from('clients') .from('clients')
.upsert({'name': name}); .upsert({
'name': name,
});
} catch (_) {} } catch (_) {}
} }
setState(() { setState(() {
@ -656,11 +684,14 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
}); });
if (_requestedSaved) { if (_requestedSaved) {
Future.delayed( Future.delayed(
const Duration(seconds: 2), const Duration(
seconds: 2,
),
() { () {
if (mounted) { if (mounted) {
setState( setState(
() => _requestedSaved = () =>
_requestedSaved =
false, false,
); );
} }
@ -699,7 +730,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
final clientNames = final clientNames =
(clientRows as List<dynamic>?) (clientRows as List<dynamic>?)
?.map( ?.map(
(r) => r['name'] as String, (r) =>
r['name'] as String,
) )
.whereType<String>() .whereType<String>()
.toList() ?? .toList() ??
@ -715,28 +747,37 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
}, },
itemBuilder: (context, suggestion) => itemBuilder: (context, suggestion) =>
ListTile(title: Text(suggestion)), ListTile(title: Text(suggestion)),
onSuggestionSelected: (suggestion) async { onSuggestionSelected:
(suggestion) async {
_requestedDebounce?.cancel(); _requestedDebounce?.cancel();
_requestedController.text = suggestion; _requestedController.text =
suggestion;
setState(() { setState(() {
_requestedSaving = true; _requestedSaving = true;
_requestedSaved = false; _requestedSaved = false;
}); });
try { try {
await ref await ref
.read(tasksControllerProvider) .read(
tasksControllerProvider,
)
.updateTask( .updateTask(
taskId: task.id, taskId: task.id,
requestedBy: suggestion.isEmpty requestedBy:
suggestion.isEmpty
? null ? null
: suggestion, : suggestion,
); );
if (suggestion.isNotEmpty) { if (suggestion.isNotEmpty) {
try { try {
await ref await ref
.read(supabaseClientProvider) .read(
supabaseClientProvider,
)
.from('clients') .from('clients')
.upsert({'name': suggestion}); .upsert({
'name': suggestion,
});
} catch (_) {} } catch (_) {}
} }
setState( setState(
@ -746,7 +787,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
} catch (_) { } catch (_) {
} finally { } finally {
setState( setState(
() => _requestedSaving = false, () =>
_requestedSaving = false,
); );
if (_requestedSaved) { if (_requestedSaved) {
Future.delayed( Future.delayed(
@ -754,7 +796,9 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
() { () {
if (mounted) { if (mounted) {
setState( setState(
() => _requestedSaved = false, () =>
_requestedSaved =
false,
); );
} }
}, },
@ -794,7 +838,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
width: 16, width: 16,
height: 16, height: 16,
child: Stack( child: Stack(
alignment: Alignment.center, alignment:
Alignment.center,
children: const [ children: const [
Icon( Icon(
Icons.save, Icons.save,
@ -827,7 +872,9 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
}); });
try { try {
await ref await ref
.read(tasksControllerProvider) .read(
tasksControllerProvider,
)
.updateTask( .updateTask(
taskId: task.id, taskId: task.id,
notedBy: name.isEmpty notedBy: name.isEmpty
@ -841,11 +888,14 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
supabaseClientProvider, supabaseClientProvider,
) )
.from('clients') .from('clients')
.upsert({'name': name}); .upsert({
'name': name,
});
} catch (_) {} } catch (_) {}
} }
setState(() { setState(() {
_notedSaved = name.isNotEmpty; _notedSaved =
name.isNotEmpty;
}); });
} catch (_) { } catch (_) {
// ignore // ignore
@ -855,12 +905,14 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
}); });
if (_notedSaved) { if (_notedSaved) {
Future.delayed( Future.delayed(
const Duration(seconds: 2), const Duration(
seconds: 2,
),
() { () {
if (mounted) { if (mounted) {
setState( setState(
() => () => _notedSaved =
_notedSaved = false, false,
); );
} }
}, },
@ -898,7 +950,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
final clientNames = final clientNames =
(clientRows as List<dynamic>?) (clientRows as List<dynamic>?)
?.map( ?.map(
(r) => r['name'] as String, (r) =>
r['name'] as String,
) )
.whereType<String>() .whereType<String>()
.toList() ?? .toList() ??
@ -914,28 +967,37 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
}, },
itemBuilder: (context, suggestion) => itemBuilder: (context, suggestion) =>
ListTile(title: Text(suggestion)), ListTile(title: Text(suggestion)),
onSuggestionSelected: (suggestion) async { onSuggestionSelected:
(suggestion) async {
_notedDebounce?.cancel(); _notedDebounce?.cancel();
_notedController.text = suggestion; _notedController.text =
suggestion;
setState(() { setState(() {
_notedSaving = true; _notedSaving = true;
_notedSaved = false; _notedSaved = false;
}); });
try { try {
await ref await ref
.read(tasksControllerProvider) .read(
tasksControllerProvider,
)
.updateTask( .updateTask(
taskId: task.id, taskId: task.id,
notedBy: suggestion.isEmpty notedBy:
suggestion.isEmpty
? null ? null
: suggestion, : suggestion,
); );
if (suggestion.isNotEmpty) { if (suggestion.isNotEmpty) {
try { try {
await ref await ref
.read(supabaseClientProvider) .read(
supabaseClientProvider,
)
.from('clients') .from('clients')
.upsert({'name': suggestion}); .upsert({
'name': suggestion,
});
} catch (_) {} } catch (_) {}
} }
setState( setState(
@ -944,14 +1006,17 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
); );
} catch (_) { } catch (_) {
} finally { } finally {
setState(() => _notedSaving = false); setState(
() => _notedSaving = false,
);
if (_notedSaved) { if (_notedSaved) {
Future.delayed( Future.delayed(
const Duration(seconds: 2), const Duration(seconds: 2),
() { () {
if (mounted) { if (mounted) {
setState( setState(
() => _notedSaved = false, () => _notedSaved =
false,
); );
} }
}, },
@ -991,7 +1056,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
width: 16, width: 16,
height: 16, height: 16,
child: Stack( child: Stack(
alignment: Alignment.center, alignment:
Alignment.center,
children: const [ children: const [
Icon( Icon(
Icons.save, Icons.save,
@ -1024,7 +1090,9 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
}); });
try { try {
await ref await ref
.read(tasksControllerProvider) .read(
tasksControllerProvider,
)
.updateTask( .updateTask(
taskId: task.id, taskId: task.id,
receivedBy: name.isEmpty receivedBy: name.isEmpty
@ -1038,7 +1106,9 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
supabaseClientProvider, supabaseClientProvider,
) )
.from('clients') .from('clients')
.upsert({'name': name}); .upsert({
'name': name,
});
} catch (_) {} } catch (_) {}
} }
setState(() { setState(() {
@ -1053,11 +1123,14 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
}); });
if (_receivedSaved) { if (_receivedSaved) {
Future.delayed( Future.delayed(
const Duration(seconds: 2), const Duration(
seconds: 2,
),
() { () {
if (mounted) { if (mounted) {
setState( setState(
() => _receivedSaved = () =>
_receivedSaved =
false, false,
); );
} }
@ -1096,7 +1169,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
final clientNames = final clientNames =
(clientRows as List<dynamic>?) (clientRows as List<dynamic>?)
?.map( ?.map(
(r) => r['name'] as String, (r) =>
r['name'] as String,
) )
.whereType<String>() .whereType<String>()
.toList() ?? .toList() ??
@ -1112,28 +1186,37 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
}, },
itemBuilder: (context, suggestion) => itemBuilder: (context, suggestion) =>
ListTile(title: Text(suggestion)), ListTile(title: Text(suggestion)),
onSuggestionSelected: (suggestion) async { onSuggestionSelected:
(suggestion) async {
_receivedDebounce?.cancel(); _receivedDebounce?.cancel();
_receivedController.text = suggestion; _receivedController.text =
suggestion;
setState(() { setState(() {
_receivedSaving = true; _receivedSaving = true;
_receivedSaved = false; _receivedSaved = false;
}); });
try { try {
await ref await ref
.read(tasksControllerProvider) .read(
tasksControllerProvider,
)
.updateTask( .updateTask(
taskId: task.id, taskId: task.id,
receivedBy: suggestion.isEmpty receivedBy:
suggestion.isEmpty
? null ? null
: suggestion, : suggestion,
); );
if (suggestion.isNotEmpty) { if (suggestion.isNotEmpty) {
try { try {
await ref await ref
.read(supabaseClientProvider) .read(
supabaseClientProvider,
)
.from('clients') .from('clients')
.upsert({'name': suggestion}); .upsert({
'name': suggestion,
});
} catch (_) {} } catch (_) {}
} }
setState( setState(
@ -1142,14 +1225,17 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
); );
} catch (_) { } catch (_) {
} finally { } finally {
setState(() => _receivedSaving = false); setState(
() => _receivedSaving = false,
);
if (_receivedSaved) { if (_receivedSaved) {
Future.delayed( Future.delayed(
const Duration(seconds: 2), const Duration(seconds: 2),
() { () {
if (mounted) { if (mounted) {
setState( setState(
() => _receivedSaved = false, () => _receivedSaved =
false,
); );
} }
}, },
@ -1169,6 +1255,8 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
), ),
), ),
], ],
),
],
); );
final detailsCard = Card( final detailsCard = Card(