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