Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ class DetailRouteController extends GetxController {
}

modify.set(name, newValue);
onEdit.value = true;

// onEdit must reflect REAL changes only
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment should be properly formatted with consistent spacing. Comments should start with a space after the double-slash to follow Dart style conventions.

Suggested change
// onEdit must reflect REAL changes only
// onEdit must reflect REAL changes only

Copilot uses AI. Check for mistakes.
onEdit.value = modify.changes.isNotEmpty;

// If status is being changed, update read-only state
if (name == 'status') {
Expand All @@ -68,7 +70,7 @@ class DetailRouteController extends GetxController {
}
var now = DateTime.now().toUtc();
modify.save(modified: () => now);
onEdit.value = false;
onEdit.value = modify.changes.isNotEmpty;

// Show snackbar
Get.snackbar(
Expand Down
238 changes: 122 additions & 116 deletions lib/app/modules/detailRoute/views/detail_route_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,122 +119,128 @@ class DetailRouteView extends GetView<DetailRouteController> {
return true;
},
child: Scaffold(
backgroundColor: tColors.primaryBackgroundColor,
appBar: AppBar(
leading: BackButton(color: TaskWarriorColors.white),
backgroundColor: Palette.kToDark,
title: Text(
'${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageID}: ${(controller.modify.id == 0) ? '-' : controller.modify.id}',
style: TextStyle(
color: TaskWarriorColors.white,
),
)),
body: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: Obx(
() => ListView(
padding:
const EdgeInsets.symmetric(vertical: 4, horizontal: 2),
children: [
for (var entry in {
'description': controller.descriptionValue.value,
'status': controller.statusValue.value,
'entry': controller.entryValue.value,
'modified': controller.modifiedValue.value,
'start': controller.startValue.value,
'end': controller.endValue.value,
'due': controller.dueValue.value,
'wait': controller.waitValue.value,
'until': controller.untilValue.value,
'priority': controller.priorityValue?.value,
'project': controller.projectValue?.value,
'tags': controller.tagsValue?.value,
'urgency': controller.urgencyValue.value,
}.entries)
AttributeWidget(
name: entry.key,
value: entry.value,
callback: (newValue) =>
controller.setAttribute(entry.key, newValue),
waitKey: controller.waitKey,
dueKey: controller.dueKey,
untilKey: controller.untilKey,
priorityKey: controller.priorityKey,
),
],
),
)),
floatingActionButton: controller.modify.changes.isEmpty
? const SizedBox.shrink()
: FloatingActionButton(
backgroundColor: tColors.primaryTextColor,
foregroundColor: tColors.secondaryBackgroundColor,
splashColor: tColors.primaryTextColor,
heroTag: "btn1",
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
scrollable: true,
title: Text(
'${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reviewChanges}:',
style: TextStyle(
color: tColors.primaryTextColor,
),
),
content: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Text(
controller.modify.changes.entries
.map((entry) => '${entry.key}:\n'
' ${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.oldChanges}: ${entry.value['old']}\n'
' ${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.newChanges}: ${entry.value['new']}')
.toList()
.join('\n'),
style: TextStyle(
color: tColors.primaryTextColor,
),
),
),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: Text(
SentenceManager(
currentLanguage:
AppSettings.selectedLanguage)
.sentences
.cancel,
style: TextStyle(
color: tColors.primaryTextColor,
),
),
),
TextButton(
onPressed: () {
controller.saveChanges();
},
child: Text(
SentenceManager(
currentLanguage:
AppSettings.selectedLanguage)
.sentences
.submit,
style: TextStyle(
color: tColors.primaryBackgroundColor,
),
),
),
],
);
},
);
},
child: const Icon(Icons.save),
)),
backgroundColor: tColors.primaryBackgroundColor,
appBar: AppBar(
leading: BackButton(color: TaskWarriorColors.white),
backgroundColor: Palette.kToDark,
title: Text(
'${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageID}: ${(controller.modify.id == 0) ? '-' : controller.modify.id}',
style: TextStyle(
color: TaskWarriorColors.white,
),
)),
body: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: Obx(
() => ListView(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 2),
children: [
for (var entry in {
'description': controller.descriptionValue.value,
'status': controller.statusValue.value,
'entry': controller.entryValue.value,
'modified': controller.modifiedValue.value,
'start': controller.startValue.value,
'end': controller.endValue.value,
'due': controller.dueValue.value,
'wait': controller.waitValue.value,
'until': controller.untilValue.value,
'priority': controller.priorityValue?.value,
'project': controller.projectValue?.value,
'tags': controller.tagsValue?.value,
'urgency': controller.urgencyValue.value,
}.entries)
AttributeWidget(
name: entry.key,
value: entry.value,
callback: (newValue) =>
controller.setAttribute(entry.key, newValue),
waitKey: controller.waitKey,
dueKey: controller.dueKey,
untilKey: controller.untilKey,
priorityKey: controller.priorityKey,
),
],
),
)),

// SAVE BUTTON — Bottom Right

floatingActionButton: Obx(() {
if (!controller.onEdit.value) {
return const SizedBox.shrink();
}

return FloatingActionButton(
onPressed: () => _showReviewChangesDialog(context, tColors),
backgroundColor: tColors.primaryTextColor,
foregroundColor: tColors.secondaryBackgroundColor,
splashColor: tColors.primaryTextColor,
child: const Icon(Icons.save),
);
}),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
),
);
}

// REVIEW CHANGES DIALOG
void _showReviewChangesDialog(
BuildContext context, TaskwarriorColorTheme tColors) {
final sentences =
SentenceManager(currentLanguage: AppSettings.selectedLanguage)
.sentences;

showDialog(
context: context,
builder: (context) {
return AlertDialog(
scrollable: true,
title: Text(
'${sentences.reviewChanges}:',
style: TextStyle(color: tColors.primaryTextColor),
),
content: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Text(
controller.modify.changes.entries
.map((entry) => '${entry.key}:\n'
' ${sentences.oldChanges}: ${entry.value['old']}\n'
' ${sentences.newChanges}: ${entry.value['new']}')
.toList()
.join('\n'),
style: TextStyle(color: tColors.primaryTextColor),
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
sentences.cancel,
style: TextStyle(color: tColors.primaryTextColor),
),
),
TextButton(
onPressed: () {
Get.back();
controller.saveChanges();

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(sentences.taskUpdated),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
),
);
Comment on lines +224 to +234
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The navigation flow is broken. When the dialog is closed with Get.back() at line 225, and then controller.saveChanges() is called which contains another Get.back() at line 84 of detail_route_controller.dart, this will navigate back twice. The second Get.back() in saveChanges() will close the detail route view itself instead of just the dialog. Additionally, the SnackBar at lines 228-234 will be displayed after the view has already been popped, which may not work correctly. Consider removing the Get.back() call from the controller's saveChanges() method or restructuring this flow.

Copilot uses AI. Check for mistakes.
Comment on lines +227 to +234
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate SnackBar notifications will be shown to the user. The controller.saveChanges() method already displays a SnackBar (lines 76-81 in detail_route_controller.dart), and this code adds another SnackBar at lines 228-234. This will result in two "Task Updated" messages appearing to the user, creating a confusing experience. Consider either removing the SnackBar from the controller's saveChanges() method or removing it from this location.

Suggested change
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(sentences.taskUpdated),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
),
);

Copilot uses AI. Check for mistakes.
},
child: Text(
sentences.submit,
style: const TextStyle(color: Colors.white),
),
),
],
);
},
);
}
}
Expand Down
39 changes: 25 additions & 14 deletions lib/app/modules/detailRoute/views/priority_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@ class PriorityWidget extends StatelessWidget {

@override
Widget build(BuildContext context) {
TaskwarriorColorTheme tColors = Theme.of(context).extension<TaskwarriorColorTheme>()!;
TaskwarriorColorTheme tColors =
Theme.of(context).extension<TaskwarriorColorTheme>()!;
final Color? textColor = isEditable
? tColors.primaryTextColor
: tColors.primaryDisabledTextColor;

// Normalize value: null → X
final String priority =
(value == null || value == '') ? 'X' : value.toString();

return Card(
key: globalKey,
color: tColors.secondaryBackgroundColor,
Expand All @@ -48,7 +53,7 @@ class PriorityWidget extends StatelessWidget {
),
),
TextSpan(
text: value ?? "not selected",
text: priority, // Always show X / H / M / L
style: GoogleFonts.poppins(
fontSize: TaskWarriorFonts.fontSizeMedium,
color: textColor,
Expand All @@ -60,18 +65,24 @@ class PriorityWidget extends StatelessWidget {
],
),
),
onTap: () {
switch (value) {
case 'H':
return callback('M');
case 'M':
return callback('L');
case 'L':
return callback(null);
default:
return callback('H');
}
},
onTap: isEditable
? () {
switch (priority) {
case 'X':
callback('H');
break;
case 'H':
callback('M');
break;
case 'M':
callback('L');
break;
case 'L':
callback('X');
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The callback should be called with null instead of 'X'. The value 'X' is a display-only representation for null priority, but the underlying Task model expects priority to be null, 'H', 'M', or 'L'. Passing 'X' as a string will not be recognized as a valid priority value and may cause issues with the task data model.

Suggested change
callback('X');
callback(null);

Copilot uses AI. Check for mistakes.
break;
}
}
: null,
),
);
}
Expand Down