Matthias Nott
2026-04-01 98e5695f9c77c594a103e9e81128798d41bae46a
lib/widgets/session_drawer.dart
....@@ -1,6 +1,7 @@
11 import 'package:flutter/material.dart';
22
33 import '../models/session.dart';
4
+import '../services/purchase_service.dart';
45 import '../theme/app_theme.dart';
56
67 /// Side drawer showing session list with reordering, unread badges, and controls.
....@@ -8,24 +9,29 @@
89 final List<Session> sessions;
910 final String? activeSessionId;
1011 final Map<String, int> unreadCounts;
12
+ final bool isPro;
1113 final void Function(Session session) onSelect;
1214 final void Function(Session session) onRemove;
1315 final void Function(Session session, String newName) onRename;
1416 final void Function(int oldIndex, int newIndex) onReorder;
1517 final VoidCallback onNewSession;
1618 final VoidCallback onRefresh;
19
+ /// Called when the user taps the upgrade prompt in the drawer.
20
+ final VoidCallback? onUpgrade;
1721
1822 const SessionDrawer({
1923 super.key,
2024 required this.sessions,
2125 required this.activeSessionId,
2226 required this.unreadCounts,
27
+ required this.isPro,
2328 required this.onSelect,
2429 required this.onRemove,
2530 required this.onRename,
2631 required this.onReorder,
2732 required this.onNewSession,
2833 required this.onRefresh,
34
+ this.onUpgrade,
2935 });
3036
3137 @override
....@@ -73,6 +79,10 @@
7379 final session = sessions[index];
7480 final isActive = session.id == activeSessionId;
7581 final unread = unreadCounts[session.id] ?? 0;
82
+ // Sessions beyond the free limit are locked for free users.
83
+ // We still allow viewing them — just show the upgrade prompt.
84
+ final isLocked =
85
+ !isPro && index >= kFreeTierMaxSessions;
7686
7787 return Dismissible(
7888 key: ValueKey(session.id),
....@@ -111,87 +121,132 @@
111121 onDismissed: (_) => onRemove(session),
112122 child: ListTile(
113123 key: ValueKey('tile_${session.id}'),
114
- leading: Text(
115
- session.icon,
116
- style: const TextStyle(fontSize: 20),
117
- ),
124
+ leading: isLocked
125
+ ? const Icon(Icons.lock_outline,
126
+ size: 20,
127
+ color: AppColors.darkTextTertiary)
128
+ : Text(
129
+ session.icon,
130
+ style: const TextStyle(fontSize: 20),
131
+ ),
118132 title: GestureDetector(
119
- onDoubleTap: () =>
120
- _showRenameDialog(context, session),
133
+ onDoubleTap: isLocked
134
+ ? null
135
+ : () => _showRenameDialog(context, session),
121136 child: Text(
122137 session.name,
123138 style: TextStyle(
124139 fontWeight: isActive
125140 ? FontWeight.bold
126141 : FontWeight.normal,
127
- color: isActive ? AppColors.accent : null,
142
+ color: isLocked
143
+ ? AppColors.darkTextTertiary
144
+ : isActive
145
+ ? AppColors.accent
146
+ : null,
128147 ),
129148 maxLines: 1,
130149 overflow: TextOverflow.ellipsis,
131150 ),
132151 ),
133
- trailing: Row(
134
- mainAxisSize: MainAxisSize.min,
135
- children: [
136
- if (unread > 0)
137
- Container(
138
- padding: const EdgeInsets.symmetric(
139
- horizontal: 6, vertical: 2),
140
- decoration: BoxDecoration(
141
- color: AppColors.unreadBadge,
142
- borderRadius:
143
- BorderRadius.circular(10),
152
+ trailing: isLocked
153
+ ? TextButton(
154
+ onPressed: () {
155
+ Navigator.pop(context);
156
+ onUpgrade?.call();
157
+ },
158
+ style: TextButton.styleFrom(
159
+ foregroundColor: AppColors.accent,
160
+ padding: const EdgeInsets.symmetric(
161
+ horizontal: 8),
162
+ minimumSize: Size.zero,
163
+ tapTargetSize:
164
+ MaterialTapTargetSize.shrinkWrap,
144165 ),
145
- child: Text(
146
- '$unread',
147
- style: const TextStyle(
148
- color: Colors.white,
149
- fontSize: 11,
150
- fontWeight: FontWeight.bold,
166
+ child: const Text('Upgrade',
167
+ style: TextStyle(fontSize: 12)),
168
+ )
169
+ : Row(
170
+ mainAxisSize: MainAxisSize.min,
171
+ children: [
172
+ if (unread > 0)
173
+ Container(
174
+ padding: const EdgeInsets.symmetric(
175
+ horizontal: 6, vertical: 2),
176
+ decoration: BoxDecoration(
177
+ color: AppColors.unreadBadge,
178
+ borderRadius:
179
+ BorderRadius.circular(10),
180
+ ),
181
+ child: Text(
182
+ '$unread',
183
+ style: const TextStyle(
184
+ color: Colors.white,
185
+ fontSize: 11,
186
+ fontWeight: FontWeight.bold,
187
+ ),
188
+ ),
189
+ ),
190
+ const SizedBox(width: 4),
191
+ ReorderableDragStartListener(
192
+ index: index,
193
+ child: Icon(
194
+ Icons.drag_handle,
195
+ color: isDark
196
+ ? AppColors.darkTextTertiary
197
+ : Colors.grey.shade400,
198
+ size: 20,
199
+ ),
151200 ),
152
- ),
201
+ ],
153202 ),
154
- const SizedBox(width: 4),
155
- ReorderableDragStartListener(
156
- index: index,
157
- child: Icon(
158
- Icons.drag_handle,
159
- color: isDark
160
- ? AppColors.darkTextTertiary
161
- : Colors.grey.shade400,
162
- size: 20,
163
- ),
164
- ),
165
- ],
166
- ),
167203 selected: isActive,
168204 selectedTileColor: isDark
169205 ? Colors.white.withAlpha(10)
170206 : Colors.blue.withAlpha(15),
171
- onTap: () {
172
- onSelect(session);
173
- Navigator.pop(context);
174
- },
207
+ onTap: isLocked
208
+ ? () {
209
+ Navigator.pop(context);
210
+ onUpgrade?.call();
211
+ }
212
+ : () {
213
+ onSelect(session);
214
+ Navigator.pop(context);
215
+ },
175216 ),
176217 );
177218 },
178219 ),
179220 ),
180
- // New session button
221
+ // New session button (or upgrade prompt)
181222 const Divider(height: 1),
182223 Padding(
183224 padding: const EdgeInsets.all(12),
184225 child: SizedBox(
185226 width: double.infinity,
186
- child: ElevatedButton.icon(
187
- onPressed: onNewSession,
188
- icon: const Icon(Icons.add, size: 20),
189
- label: const Text('New Session'),
190
- style: ElevatedButton.styleFrom(
191
- backgroundColor: AppColors.accent,
192
- foregroundColor: Colors.white,
193
- ),
194
- ),
227
+ child: !isPro &&
228
+ sessions.length >= kFreeTierMaxSessions
229
+ ? OutlinedButton.icon(
230
+ onPressed: () {
231
+ Navigator.pop(context);
232
+ onUpgrade?.call();
233
+ },
234
+ icon: const Icon(Icons.lock_outline, size: 18),
235
+ label: const Text('Upgrade for More Sessions'),
236
+ style: OutlinedButton.styleFrom(
237
+ foregroundColor: AppColors.accent,
238
+ side: const BorderSide(color: AppColors.accent),
239
+ ),
240
+ )
241
+ : ElevatedButton.icon(
242
+ onPressed: onNewSession,
243
+ icon: const Icon(Icons.add, size: 20),
244
+ label: const Text('New Session'),
245
+ style: ElevatedButton.styleFrom(
246
+ backgroundColor: AppColors.accent,
247
+ foregroundColor: Colors.white,
248
+ ),
249
+ ),
195250 ),
196251 ),
197252 ],