Skip to content

Commit f459835

Browse files
committed
feat: star workspace
1 parent 8cd2526 commit f459835

File tree

4 files changed

+119
-54
lines changed

4 files changed

+119
-54
lines changed

lib/features/dashboard/pages/desktop/dashboard_desktop.dart

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ class DashboardDesktop extends StatelessWidget {
1717
rh.DeviceType deviceType = rh.ResponsiveLayoutHelper.getDeviceType(context);
1818
return Consumer<DashboardProvider>(
1919
builder: (context, provider, child) {
20+
// NEW: Get the filtered list of workspaces
21+
final displayedWorkspaces = provider.displayedWorkspaces;
22+
2023
return LayoutBuilder(
2124
builder:
2225
(context, constraints) => Row(
@@ -50,7 +53,8 @@ class DashboardDesktop extends StatelessWidget {
5053
Expanded(
5154
child: GridView.builder(
5255
shrinkWrap: true,
53-
itemCount: provider.workspaceList.length,
56+
// UPDATE: Use the length of the filtered list
57+
itemCount: displayedWorkspaces.length,
5458
gridDelegate:
5559
SliverGridDelegateWithFixedCrossAxisCount(
5660
crossAxisCount: 3,
@@ -59,9 +63,9 @@ class DashboardDesktop extends StatelessWidget {
5963
childAspectRatio: 4.5 / 3,
6064
),
6165
itemBuilder: (context, index) {
62-
final workspaceId = provider.workspaceList.keys
63-
.elementAt(index);
64-
return ProjectCard(workspaceId: workspaceId);
66+
// UPDATE: Get the workspace from the filtered list
67+
final workspace = displayedWorkspaces[index];
68+
return ProjectCard(workspaceId: workspace.id);
6569
},
6670
),
6771
),
@@ -75,4 +79,4 @@ class DashboardDesktop extends StatelessWidget {
7579
},
7680
);
7781
}
78-
}
82+
}

lib/features/dashboard/providers/dashboard_provider.dart

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ class DashboardProvider extends StateHandler {
2727
bool get isInitialized => _isInitialized;
2828
Map<String, WorkspaceModel> get workspaceList => _workspaceList;
2929

30+
// NEW: Getter for the filtered list of workspaces
31+
List<WorkspaceModel> get displayedWorkspaces {
32+
final allWorkspaces = _workspaceList.values.toList();
33+
// Sort by last edited date, newest first
34+
allWorkspaces.sort((a, b) => (b.lastEdited ?? DateTime(0)).compareTo(a.lastEdited ?? DateTime(0)));
35+
36+
switch (_tabIndex) {
37+
case 1: // Starred
38+
return allWorkspaces.where((ws) => ws.isStarred).toList();
39+
case 0: // All
40+
default:
41+
return allWorkspaces;
42+
}
43+
}
44+
3045
List<Map<String, dynamic>> tabItems = [
3146
{"label": "All", "icon": Icon(PhosphorIcons.cardsThree())},
3247
{"label": "Starred", "icon": Icon(PhosphorIcons.star())},
@@ -77,7 +92,6 @@ class DashboardProvider extends StateHandler {
7792
.eq('owner', res.id);
7893
_workspaceList.clear();
7994
for (var workspaceData in _dbWorkspace) {
80-
// The updated fromJson factory now handles the 'data' field
8195
WorkspaceModel newWorkspace = WorkspaceModel.fromJson(workspaceData);
8296
_workspaceList[newWorkspace.id] = newWorkspace;
8397
}
@@ -92,7 +106,6 @@ class DashboardProvider extends StateHandler {
92106
}
93107
}
94108

95-
// NEW: Update an entire workspace model, used by WorkspaceProvider
96109
void updateWorkspace(WorkspaceModel workspace) {
97110
if (_workspaceList.containsKey(workspace.id)) {
98111
_workspaceList[workspace.id] = workspace;
@@ -109,6 +122,31 @@ class DashboardProvider extends StateHandler {
109122
}
110123
}
111124

125+
// NEW: Method to toggle the star status and save to DB
126+
Future<void> toggleStar(String workspaceId) async {
127+
final workspace = _workspaceList[workspaceId];
128+
if (workspace == null) return;
129+
130+
// Toggle status locally first for immediate UI feedback
131+
final updatedWorkspace = workspace.copyWith(isStarred: !workspace.isStarred);
132+
_workspaceList[workspaceId] = updatedWorkspace;
133+
notifyListeners();
134+
135+
try {
136+
final dataToSave = updatedWorkspace.toJson()['data'];
137+
await supabase!
138+
.from('workspace')
139+
.update({'data': dataToSave})
140+
.eq('id', workspaceId);
141+
print("Workspace $workspaceId star status updated in DB.");
142+
} catch (e) {
143+
print("Error updating star status for $workspaceId: $e");
144+
// Revert on error
145+
_workspaceList[workspaceId] = workspace;
146+
notifyListeners();
147+
}
148+
}
149+
112150
Future<void> createNewProject(BuildContext context) async {
113151
_isLoading = true;
114152
try {
@@ -125,7 +163,7 @@ class DashboardProvider extends StateHandler {
125163
"name": "New Project",
126164
"editorId": [],
127165
"viewerId": [],
128-
"data": {}, // Initialize with empty data
166+
"data": {'isStarred': false}, // Initialize with isStarred
129167
};
130168

131169
await supabase!.from('workspace').insert(newWorkspaceData);

lib/features/dashboard/widgets/project_card.dart

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import 'package:cookethflow/core/helpers/date_time_helper.dart';
22
import 'package:cookethflow/core/router/app_route_const.dart';
33
import 'package:cookethflow/features/dashboard/providers/dashboard_provider.dart';
44
import 'package:cookethflow/features/dashboard/widgets/workspace_options_dialog.dart';
5-
import 'package:cookethflow/features/workspace/pages/workspace.dart';
5+
import 'package:cookethflow/features/models/workspace_model.dart';
66
import 'package:cookethflow/features/workspace/providers/workspace_provider.dart';
77
import 'package:flutter/material.dart';
88
import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -19,6 +19,13 @@ class ProjectCard extends StatelessWidget {
1919
DateTimeHelper dth = DateTimeHelper();
2020
return Consumer2<DashboardProvider, WorkspaceProvider>(
2121
builder: (context, provider, workspaceProvider, child) {
22+
// Get the specific workspace model to access its properties
23+
final WorkspaceModel? workspace = provider.workspaceList[workspaceId];
24+
if (workspace == null) {
25+
// Return an empty container or a placeholder if the workspace is not found
26+
return Container();
27+
}
28+
2229
return GestureDetector(
2330
onTap: () {
2431
workspaceProvider.setWorkspace(workspaceId);
@@ -39,43 +46,36 @@ class ProjectCard extends StatelessWidget {
3946
Expanded(
4047
flex: 2,
4148
child: Container(
49+
// Use the workspace background color for the thumbnail
4250
decoration: BoxDecoration(
43-
color: const Color(0xFFD3D3D3),
51+
color: workspace.backgroundColor ?? const Color(0xFFD3D3D3),
4452
borderRadius: BorderRadius.only(
4553
topLeft: Radius.circular(12.r),
4654
topRight: Radius.circular(12.r),
4755
),
4856
),
4957
child: Stack(
5058
children: [
51-
// Center(
52-
// child: Image.asset(
53-
// 'assets/images/Frame 400.png',
54-
// fit: BoxFit.cover,
55-
// ),
56-
// ),
5759
Positioned(
5860
top: 12.h,
5961
right: 12.w,
6062
child: Container(
6163
padding: EdgeInsets.all(8.w),
6264
decoration: BoxDecoration(
63-
color: Colors.white,
65+
color: Colors.white.withOpacity(0.8),
6466
borderRadius: BorderRadius.circular(12.r),
6567
),
6668
child: IconButton(
6769
onPressed: () {
6870
showDialog(
6971
context: context,
70-
builder: (dialogContext) => WorkspaceOptionsDialog( // Use dialogContext to pop the dialog
71-
onPressed: () async { // Make onPressed async
72-
Navigator.of(dialogContext).pop(); // Dismiss the dialog first
73-
await provider.deleteWorkspace(workspaceId); // Then delete from DB
74-
// No need to call refreshDashboard here as deleteWorkspace already calls it
72+
builder: (dialogContext) => WorkspaceOptionsDialog(
73+
onPressed: () async {
74+
Navigator.of(dialogContext).pop();
75+
await provider.deleteWorkspace(workspaceId);
7576
},
7677
),
7778
);
78-
// Removed .then(context.pop) here
7979
},
8080
icon: Icon(
8181
PhosphorIconsRegular.dotsThree,
@@ -102,22 +102,18 @@ class ProjectCard extends StatelessWidget {
102102
mainAxisAlignment: MainAxisAlignment.center,
103103
children: [
104104
Text(
105-
provider.workspaceList[workspaceId]?.name ??
106-
"Name not fetched",
105+
workspace.name,
107106
style: TextStyle(
108107
fontFamily: 'Fredrik',
109108
fontSize: 20.sp,
110109
color: Colors.black,
111110
fontWeight: FontWeight.w600,
112111
),
112+
overflow: TextOverflow.ellipsis,
113113
),
114114
SizedBox(height: 4.h),
115115
Text(
116-
dth.formatLastEdited(
117-
provider
118-
.workspaceList[workspaceId]
119-
?.lastEdited,
120-
),
116+
dth.formatLastEdited(workspace.lastEdited),
121117
style: TextStyle(
122118
fontFamily: 'Fredrik',
123119
color: Colors.grey[600],
@@ -128,12 +124,25 @@ class ProjectCard extends StatelessWidget {
128124
],
129125
),
130126
),
127+
// UPDATED: Star IconButton
131128
IconButton(
132-
onPressed: () {},
133-
icon: Icon(
134-
PhosphorIconsRegular.star,
135-
size: 32.sp,
136-
color: Colors.black54,
129+
onPressed: () {
130+
// Call the provider method to toggle the star status
131+
provider.toggleStar(workspaceId);
132+
},
133+
icon: AnimatedSwitcher(
134+
duration: const Duration(milliseconds: 300),
135+
transitionBuilder: (child, animation) {
136+
return ScaleTransition(scale: animation, child: child);
137+
},
138+
child: Icon(
139+
// Conditionally show filled or regular star
140+
workspace.isStarred ? PhosphorIconsFill.star : PhosphorIconsRegular.star,
141+
// Use a key to help AnimatedSwitcher differentiate between the two icons
142+
key: ValueKey<bool>(workspace.isStarred),
143+
size: 32.sp,
144+
color: workspace.isStarred ? Colors.amber : Colors.black54,
145+
),
137146
),
138147
),
139148
],
Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:ui';
2+
import 'package:flutter/material.dart'; // Required for Color
23

34
class WorkspaceModel {
45
String id;
@@ -7,8 +8,9 @@ class WorkspaceModel {
78
List<String> editorIdList;
89
List<String> viewerIdList;
910
DateTime? lastEdited;
10-
// NEW: Property for background color
1111
Color? backgroundColor;
12+
// NEW: Property for starring a workspace
13+
bool isStarred;
1214

1315
WorkspaceModel({
1416
required this.id,
@@ -17,7 +19,8 @@ class WorkspaceModel {
1719
required this.editorIdList,
1820
required this.viewerIdList,
1921
required this.lastEdited,
20-
this.backgroundColor, // Add to constructor
22+
this.backgroundColor,
23+
this.isStarred = false, // Add to constructor with a default value
2124
});
2225

2326
WorkspaceModel copyWith({
@@ -27,7 +30,8 @@ class WorkspaceModel {
2730
List<String>? editorIdList,
2831
List<String>? viewerIdList,
2932
DateTime? lastEdited,
30-
Color? backgroundColor, // Add to copyWith
33+
Color? backgroundColor,
34+
bool? isStarred, // Add to copyWith
3135
}) {
3236
return WorkspaceModel(
3337
id: id ?? this.id,
@@ -36,17 +40,18 @@ class WorkspaceModel {
3640
editorIdList: editorIdList ?? List.from(this.editorIdList),
3741
viewerIdList: viewerIdList ?? List.from(this.viewerIdList),
3842
lastEdited: lastEdited ?? this.lastEdited,
39-
backgroundColor: backgroundColor ?? this.backgroundColor, // Add to copyWith
43+
backgroundColor: backgroundColor ?? this.backgroundColor,
44+
isStarred: isStarred ?? this.isStarred, // Add to copyWith
4045
);
4146
}
4247

4348
Map<String, dynamic> toJson() {
44-
// NEW: Prepare the 'data' jsonb field
4549
final Map<String, dynamic> jsonData = {};
4650
if (backgroundColor != null) {
47-
// Store color as an #AARRGGBB hex string
4851
jsonData['backgroundColor'] = '#${backgroundColor!.value.toRadixString(16).padLeft(8, '0').toUpperCase()}';
4952
}
53+
// NEW: Add isStarred to the data field
54+
jsonData['isStarred'] = isStarred;
5055

5156
return {
5257
'id': id,
@@ -55,23 +60,31 @@ class WorkspaceModel {
5560
'editorId': editorIdList,
5661
'viewerId': viewerIdList,
5762
'last edited': lastEdited?.toIso8601String(),
58-
'data': jsonData, // Add data field to JSON
63+
'data': jsonData,
5964
};
6065
}
6166

6267
factory WorkspaceModel.fromJson(Map<String, dynamic> json) {
63-
// NEW: Parse the background color from the 'data' field
6468
Color? bgColor;
65-
if (json['data'] != null && json['data']['backgroundColor'] != null) {
66-
try {
67-
final colorString = json['data']['backgroundColor'] as String;
68-
final hexCode = colorString.replaceAll('#', '');
69-
// Handle both RRGGBB and AARRGGBB formats
70-
final fullHexCode = hexCode.length == 6 ? 'FF$hexCode' : hexCode;
71-
bgColor = Color(int.parse(fullHexCode, radix: 16));
72-
} catch (e) {
73-
print('Error parsing background color: $e');
74-
bgColor = null; // Default to null on error
69+
// NEW: Parse isStarred from the data field
70+
bool isStarredFlag = false;
71+
72+
if (json['data'] != null) {
73+
// Parse background color
74+
if (json['data']['backgroundColor'] != null) {
75+
try {
76+
final colorString = json['data']['backgroundColor'] as String;
77+
final hexCode = colorString.replaceAll('#', '');
78+
final fullHexCode = hexCode.length == 6 ? 'FF$hexCode' : hexCode;
79+
bgColor = Color(int.parse(fullHexCode, radix: 16));
80+
} catch (e) {
81+
print('Error parsing background color: $e');
82+
bgColor = null;
83+
}
84+
}
85+
// Parse isStarred
86+
if (json['data']['isStarred'] is bool) {
87+
isStarredFlag = json['data']['isStarred'];
7588
}
7689
}
7790

@@ -84,7 +97,8 @@ class WorkspaceModel {
8497
lastEdited: json['last edited'] != null
8598
? DateTime.parse(json['last edited'] as String)
8699
: null,
87-
backgroundColor: bgColor, // Assign parsed color
100+
backgroundColor: bgColor,
101+
isStarred: isStarredFlag, // Assign parsed value
88102
);
89103
}
90104
}

0 commit comments

Comments
 (0)