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
4 changes: 4 additions & 0 deletions lib/app/modules/home/controllers/home_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import 'package:taskwarrior/app/utils/themes/theme_extension.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';

class HomeController extends GetxController {
static const String noProjectValue = " (No Project) ";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a stable internal key instead of display text for the no-project sentinel.

Line 44 currently uses a UI string (" (No Project) ") as internal filter state. That makes filtering fragile (collision with real project names and whitespace sensitivity), and Line 266 depends on that exact formatted text.

♻️ Proposed refactor
-  static const String noProjectValue = " (No Project) ";
+  static const String noProjectFilterKey = '__NO_PROJECT__';
@@
-        if (projectFilter.value == noProjectValue) {
-          return task.project == null || task.project!.isEmpty;
+        if (projectFilter.value == noProjectFilterKey) {
+          return task.project == null || task.project!.trim().isEmpty;
         }

Keep localized display labels in the view layer, and keep this controller value purely internal.

Also applies to: 266-268

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/app/modules/home/controllers/home_controller.dart` at line 44, The
controller uses a UI string as an internal sentinel (static const String
noProjectValue = " (No Project) "), which is fragile; change noProjectValue to a
stable internal key (e.g. "__NO_PROJECT__") and update all controller logic that
checks or sets noProjectValue (references in this file, including the
filter/comparison logic around the current usage at lines ~266-268) to use that
internal key; move any user-facing label (the localized " (No Project) " display
text) into the view layer where the project list / dropdown is rendered so the
UI shows the friendly text while the controller/state uses the stable
"__NO_PROJECT__" sentinel. Ensure equality checks, filter construction, and any
serialization still use the new internal constant name noProjectValue.

final SplashController splashController = Get.find<SplashController>();
late Storage storage;
final RxBool pendingFilter = false.obs;
Expand Down Expand Up @@ -262,6 +263,9 @@ class HomeController extends GetxController {

if (projectFilter.value.isNotEmpty) {
queriedTasks.value = queriedTasks.where((task) {
if (projectFilter.value == noProjectValue) {
return task.project == null || task.project!.isEmpty;
}
if (task.project == null) {
return false;
} else {
Expand Down
55 changes: 45 additions & 10 deletions lib/app/modules/home/views/project_column_home_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:taskwarrior/app/utils/app_settings/app_settings.dart';
import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart';
Expand Down Expand Up @@ -63,24 +64,58 @@ class ProjectsColumn extends StatelessWidget {
],
),
),
Padding(
padding: const EdgeInsets.only(left: 10, right: 10, top: 10),
GestureDetector(
onTap: () => callback(''),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(SentenceManager(
currentLanguage: AppSettings.selectedLanguage).sentences.allProjects,
style: TextStyle(
fontFamily: FontFamily.poppins,
fontSize: TaskWarriorFonts.fontSizeSmall,
color: tColors.primaryTextColor,
)),
Radio(
activeColor: tColors.primaryTextColor,
focusColor: tColors.secondaryTextColor,
toggleable: true,
value: '',
groupValue: projectFilter,
onChanged: (_) => callback(''),
),
Text(
SentenceManager(currentLanguage: AppSettings.selectedLanguage)
.sentences
.allProjects,
style: GoogleFonts.poppins(
color: tColors.primaryTextColor,
),
),
],
),
),
GestureDetector(
onTap: () => callback(HomeController.noProjectValue),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Radio(
activeColor: tColors.primaryTextColor,
focusColor: tColors.secondaryTextColor,
toggleable: true,
value: HomeController.noProjectValue,
groupValue: projectFilter,
onChanged: (_) => callback(HomeController.noProjectValue),
),
Text(
SentenceManager(currentLanguage: AppSettings.selectedLanguage)
.sentences
.noProject,
Comment on lines +91 to +107
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Map noProjectValue to a localized display label in the selected-project header.

Line 92/Line 100 sets projectFilter to HomeController.noProjectValue, but Line 53 renders projectFilter directly. This can show an internal token instead of localized “No Project”.

🐛 Suggested fix
   `@override`
   Widget build(BuildContext context) {
     TaskwarriorColorTheme tColors = Theme.of(context).extension<TaskwarriorColorTheme>()!;
+    final sentences =
+        SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences;
+    final selectedProjectLabel = projectFilter.isEmpty
+        ? sentences.notSelected
+        : projectFilter == HomeController.noProjectValue
+            ? sentences.noProject
+            : projectFilter;
     return Column(
       children: [
@@
               Text(
-                "${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.project} : ",
+                "${sentences.project} : ",
@@
                       Text(
-                        projectFilter == "" ? SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.notSelected : projectFilter,
+                        selectedProjectLabel,
                         style: TextStyle(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/app/modules/home/views/project_column_home_page.dart` around lines 91 -
107, The header is rendering projectFilter directly which can show the internal
token HomeController.noProjectValue; instead, when projectFilter ==
HomeController.noProjectValue map it to the localized label from
SentenceManager(currentLanguage:
AppSettings.selectedLanguage).sentences.noProject before rendering. Update the
selected-project header rendering logic to check projectFilter (or the getter
used there) and replace the token with the SentenceManager noProject string so
the UI displays a localized "No Project" label.

style: GoogleFonts.poppins(
color: tColors.primaryTextColor,
),
),
],
),
),
if (projects.isNotEmpty)
...projects.entries
.where((entry) => entry.value.parent == null)
.where((entry) =>
entry.value.parent == null && entry.key.isNotEmpty)
.map((entry) => ProjectTile(
project: entry.key,
projects: projects,
Expand Down
31 changes: 22 additions & 9 deletions lib/app/modules/home/views/project_column_taskc.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart';
import 'package:taskwarrior/app/utils/app_settings/app_settings.dart';
import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart';
import 'package:taskwarrior/app/utils/gen/fonts.gen.dart';
Expand Down Expand Up @@ -65,17 +66,27 @@ class ProjectColumnTaskc extends StatelessWidget {
),
),
ProjectTileTaskc(
project: SentenceManager(
project: '',
projectFilter: projectFilter,
callback: callback,
displayName: SentenceManager(
currentLanguage: AppSettings.selectedLanguage,
).sentences.allProjects,
).sentences.allProjects),
ProjectTileTaskc(
project: HomeController.noProjectValue,
projectFilter: projectFilter,
callback: callback),
callback: (val) => callback(val),
displayName: SentenceManager(
currentLanguage: AppSettings.selectedLanguage,
).sentences.noProject),
Comment on lines +76 to +81
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Translate the selected noProjectValue in the header instead of showing raw filter value.

Line 76 sets the sentinel HomeController.noProjectValue, but the header (Line 51-Line 56) prints projectFilter directly. This may expose internal value text in UI.

🐛 Suggested fix
   `@override`
   Widget build(BuildContext context) {
     TaskwarriorColorTheme tColors =
         Theme.of(context).extension<TaskwarriorColorTheme>()!;
+    final sentences =
+        SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences;
+    final selectedProjectLabel = projectFilter.isEmpty
+        ? sentences.notSelected
+        : projectFilter == HomeController.noProjectValue
+            ? sentences.noProject
+            : projectFilter;
     return Column(
@@
                   child: Text(
-                    projectFilter.isEmpty
-                        ? SentenceManager(
-                                currentLanguage: AppSettings.selectedLanguage)
-                            .sentences
-                            .notSelected
-                        : projectFilter,
+                    selectedProjectLabel,
                     style: TextStyle(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/app/modules/home/views/project_column_taskc.dart` around lines 76 - 81,
The header currently displays the raw projectFilter value instead of translating
the sentinel; update the header rendering logic to detect when projectFilter ==
HomeController.noProjectValue and substitute it with the translated label from
SentenceManager(currentLanguage:
AppSettings.selectedLanguage).sentences.noProject; otherwise continue displaying
projectFilter. Locate the header code that uses projectFilter (the component
that also uses the callback and project props) and implement this conditional
replacement so the UI shows the localized "no project" text rather than the
internal sentinel.

if (projects.isNotEmpty)
...projects.map((entry) => ProjectTileTaskc(
project: entry,
projectFilter: projectFilter,
callback: callback,
))
...projects
.where((entry) => entry.isNotEmpty)
.map((entry) => ProjectTileTaskc(
project: entry,
projectFilter: projectFilter,
callback: callback,
))
else
Column(
children: [
Expand Down Expand Up @@ -105,11 +116,13 @@ class ProjectTileTaskc extends StatelessWidget {
required this.project,
required this.projectFilter,
required this.callback,
this.displayName,
});

final String project;
final String projectFilter;
final void Function(String) callback;
final String? displayName;

@override
Widget build(BuildContext context) {
Expand All @@ -126,7 +139,7 @@ class ProjectTileTaskc extends StatelessWidget {
onChanged: (_) => callback(project),
),
Text(
project,
displayName ?? project,
style: TextStyle(
color: tColors.primaryTextColor,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,16 @@ class SettingsPageSelectDirectoryListTile extends StatelessWidget {
),
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Reset to Default Button
Expanded(
child: TextButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all<Color>(
tColors.secondaryBackgroundColor!,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
side: BorderSide(color: tColors.purpleShade!),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 12),
),
onPressed: () async {
if (await controller.getBaseDirectory() == "Default") {
Expand Down Expand Up @@ -169,7 +170,8 @@ class SettingsPageSelectDirectoryListTile extends StatelessWidget {
);
}
},
child: Text(
icon: Icon(Icons.restore, color: tColors.purpleShade, size: 20),
label: Text(
SentenceManager(
currentLanguage:
controller.selectedLanguage.value)
Expand All @@ -178,24 +180,31 @@ class SettingsPageSelectDirectoryListTile extends StatelessWidget {
textAlign: TextAlign.center,
softWrap: true,
maxLines: 2,
style: TextStyle(
style: GoogleFonts.poppins(
color: tColors.purpleShade,
fontSize: TaskWarriorFonts.fontSizeSmall,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(width: 10),
const SizedBox(width: 12),

// Change Directory Button
Expanded(
child: TextButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all<Color>(
tColors.secondaryBackgroundColor!,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: tColors.purpleShade,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 12),
elevation: 2,
),
onPressed: () => controller.pickDirectory(context),
child: Text(
icon: const Icon(Icons.folder_open, size: 20),
label: Text(
SentenceManager(
currentLanguage:
controller.selectedLanguage.value)
Expand All @@ -205,7 +214,11 @@ class SettingsPageSelectDirectoryListTile extends StatelessWidget {
softWrap: true,
maxLines: 2,
overflow: TextOverflow.visible,
style: TextStyle(color: tColors.purpleShade),
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: TaskWarriorFonts.fontSizeSmall,
fontWeight: FontWeight.w600,
),
),
),
),
Expand Down
2 changes: 2 additions & 0 deletions lib/app/utils/language/bengali_sentences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ class BengaliSentences extends Sentences {
@override
String get noProjectsFound => 'কোনো প্রকল্প পাওয়া যায়নি';
@override
String get noProject => 'কোন প্রকল্প নেই';
@override
String get project => 'প্রকল্প';

@override
Expand Down
2 changes: 2 additions & 0 deletions lib/app/utils/language/english_sentences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ class EnglishSentences extends Sentences {
@override
String get noProjectsFound => 'No Projects Found';
@override
String get noProject => 'No Project';
@override
String get project => 'Project';

@override
Expand Down
2 changes: 2 additions & 0 deletions lib/app/utils/language/french_sentences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,8 @@ class FrenchSentences extends Sentences {
@override
String get noProjectsFound => 'Aucun projet trouvé';
@override
String get noProject => 'Aucun Projet';
@override
String get project => 'Projet';

@override
Expand Down
2 changes: 2 additions & 0 deletions lib/app/utils/language/german_sentences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ class GermanSentences extends Sentences {
@override
String get noProjectsFound => 'Keine Projekte gefunden';
@override
String get noProject => 'Kein Projekt';
@override
String get project => 'Projekt';

@override
Expand Down
2 changes: 2 additions & 0 deletions lib/app/utils/language/hindi_sentences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ class HindiSentences extends Sentences {
@override
String get noProjectsFound => 'कोई परियोजना नहीं मिली';
@override
String get noProject => 'कोई प्रोजेक्ट नहीं';
@override
String get project => 'परियोजना';

@override
Expand Down
2 changes: 2 additions & 0 deletions lib/app/utils/language/marathi_sentences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ class MarathiSentences extends Sentences {
@override
String get noProjectsFound => 'प्रकल्प सापडले नाहीत';
@override
String get noProject => 'कोणताही प्रकल्प नाही';
@override
String get project => 'प्रकल्प';

@override
Expand Down
1 change: 1 addition & 0 deletions lib/app/utils/language/sentences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ abstract class Sentences {

String get allProjects;
String get noProjectsFound;
String get noProject;
String get project;

String get select;
Expand Down
2 changes: 2 additions & 0 deletions lib/app/utils/language/spanish_sentences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ class SpanishSentences extends Sentences {
@override
String get noProjectsFound => 'No se encontraron proyectos';
@override
String get noProject => 'Sin Proyecto';
@override
String get project => 'Proyecto';

@override
Expand Down
2 changes: 2 additions & 0 deletions lib/app/utils/language/urdu_sentences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@ class UrduSentences extends Sentences {
@override
String get noProjectsFound => 'کوئی پراجیکٹ نہیں ملا';
@override
String get noProject => 'کوئی پروجیکٹ نہیں';
@override
String get project => 'پراجیکٹ';

@override
Expand Down