26 files changed
+645
-44
android/app/src/main/AndroidManifest.xml
+2
@@ -8,6 +8,8 @@
8
<uses-permission android:name="android.permission.WAKE_LOCK" />
9
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
10
<uses-permission android:name="android.permission.RECORD_AUDIO" />
11
+ <uses-permission android:name="android.permission.READ_CALENDAR" />
12
+ <uses-permission android:name="android.permission.WRITE_CALENDAR" />
13
14
<application
15
android:label="LocalMind"
ios/Runner/Info.plist
+6
@@ -88,6 +88,12 @@
88
<string>This app requires microphone access for offline voice interaction and speech-to-text features.</string>
89
<key>NSSpeechRecognitionUsageDescription</key>
90
<string>This app requires speech recognition access to convert your voice messages into text.</string>
91
+ <key>NSCalendarsUsageDescription</key>
92
+ <string>Access your calendar to view and manage events through AI chat.</string>
93
+ <key>NSCalendarsFullAccessUsageDescription</key>
94
+ <string>Full access to view and edit your calendar events through AI chat.</string>
95
+ <key>NSCalendarsWriteOnlyAccessUsageDescription</key>
96
+ <string>Add calendar events through AI chat without reading existing events.</string>
97
<key>ITSAppUsesNonExemptEncryption</key>
98
<false/>
99
</dict>
lib/core/providers/app_providers.dart
+2
@@ -144,6 +144,8 @@ class SettingsNotifier extends Notifier<AppSettings> {
144
_update(state.copyWith(roleSwapButtonEnabled: value));
145
void setShowSystemMessagesInChat(bool value) =>
146
_update(state.copyWith(showSystemMessagesInChat: value));
147
+ void setCalendarToolsEnabled(bool value) =>
148
+ _update(state.copyWith(calendarToolsEnabled: value));
149
150
void addSavedMcpIntegration(McpIntegration integration) {
151
final current = List<McpIntegration>.from(state.savedMcpIntegrations);
lib/features/chat/data/tools/builtin_tool_provider.dart
+219
-12
@@ -1,36 +1,131 @@
1
+import 'dart:convert';
2
+
3
import 'tool_definition.dart';
4
import 'tool_registry.dart';
5
+import 'calendar_service.dart';
6
7
class BuiltInToolProvider implements ToolProvider {
8
+ final bool calendarToolsEnabled;
9
+
10
+ BuiltInToolProvider({this.calendarToolsEnabled = false});
11
+
12
@override
6
- Future<List<ToolDefinition>> listTools() async => const [
13
+ Future<List<ToolDefinition>> listTools() async {
14
+ final tools = <ToolDefinition>[
15
+ const ToolDefinition(
16
+ name: 'calc.add',
17
+ description: 'Add two numbers',
18
+ inputSchema: {
19
+ 'type': 'object',
20
+ 'properties': {
21
+ 'a': {'type': 'number'},
22
+ 'b': {'type': 'number'},
23
+ },
24
+ 'required': ['a', 'b'],
25
+ },
26
+ providerType: ToolProviderType.builtIn,
27
+ ),
28
+ const ToolDefinition(
29
+ name: 'calc.multiply',
30
+ description: 'Multiply two numbers',
31
+ inputSchema: {
32
+ 'type': 'object',
33
+ 'properties': {
34
+ 'a': {'type': 'number'},
35
+ 'b': {'type': 'number'},
36
+ },
37
+ 'required': ['a', 'b'],
38
+ },
39
+ providerType: ToolProviderType.builtIn,
40
+ ),
41
+ ];
42
+
43
+ if (calendarToolsEnabled) {
44
+ tools.addAll(const [
45
ToolDefinition(
8
- name: 'calc.add',
9
- description: 'Add two numbers',
46
+ name: 'calendar.list_events',
47
+ description:
48
+ 'List upcoming calendar events from the user\'s device calendar. '
49
+ 'Returns events with title, start/end time, location, and description. '
50
+ 'If no dates are provided, returns events for the next 7 days.',
51
inputSchema: {
52
'type': 'object',
53
'properties': {
13
- 'a': {'type': 'number'},
14
- 'b': {'type': 'number'},
54
+ 'start_date': {
55
+ 'type': 'string',
56
+ 'description':
57
+ 'Start date in ISO 8601 format (e.g. 2025-01-15T09:00:00). '
58
+ 'Defaults to today.',
59
+ },
60
+ 'end_date': {
61
+ 'type': 'string',
62
+ 'description':
63
+ 'End date in ISO 8601 format (e.g. 2025-01-22T23:59:59). '
64
+ 'Defaults to 7 days from start.',
65
+ },
66
},
16
- 'required': ['a', 'b'],
67
},
68
providerType: ToolProviderType.builtIn,
69
),
70
ToolDefinition(
21
- name: 'calc.multiply',
22
- description: 'Multiply two numbers',
71
+ name: 'calendar.create_event',
72
+ description:
73
+ 'Create a new event on the user\'s device calendar. '
74
+ 'Returns the event ID on success.',
75
inputSchema: {
76
'type': 'object',
77
'properties': {
26
- 'a': {'type': 'number'},
27
- 'b': {'type': 'number'},
78
+ 'title': {
79
+ 'type': 'string',
80
+ 'description': 'Event title / name.',
81
+ },
82
+ 'start_date': {
83
+ 'type': 'string',
84
+ 'description':
85
+ 'Event start date/time in ISO 8601 format (e.g. 2025-01-15T14:00:00).',
86
+ },
87
+ 'end_date': {
88
+ 'type': 'string',
89
+ 'description':
90
+ 'Event end date/time in ISO 8601 format (e.g. 2025-01-15T15:00:00).',
91
+ },
92
+ 'description': {
93
+ 'type': 'string',
94
+ 'description': 'Optional event description / notes.',
95
+ },
96
+ 'location': {
97
+ 'type': 'string',
98
+ 'description': 'Optional event location.',
99
+ },
100
+ 'calendar_id': {
101
+ 'type': 'string',
102
+ 'description':
103
+ 'Optional calendar ID to create the event in. '
104
+ 'Use calendar.list_calendars to see available calendars. '
105
+ 'If omitted, the device default calendar is used.',
106
+ },
107
},
29
- 'required': ['a', 'b'],
108
+ 'required': ['title', 'start_date', 'end_date'],
109
},
110
providerType: ToolProviderType.builtIn,
111
),
33
- ];
112
+ ToolDefinition(
113
+ name: 'calendar.list_calendars',
114
+ description:
115
+ 'List all calendars available on the user\'s device. '
116
+ 'Returns calendar ID, name, account, and read-only status. '
117
+ 'Use this to find the right calendar_id for creating events.',
118
+ inputSchema: {
119
+ 'type': 'object',
120
+ 'properties': {},
121
+ },
122
+ providerType: ToolProviderType.builtIn,
123
+ ),
124
+ ]);
125
+ }
126
+
127
+ return tools;
128
+ }
129
130
@override
131
Future<ToolExecutionResult> execute(
@@ -44,8 +139,120 @@ class BuiltInToolProvider implements ToolProvider {
139
case 'calc.multiply':
140
final product = (args['a'] as num) * (args['b'] as num);
141
return ToolExecutionResult.success(product.toString());
142
+
143
+ // -- Calendar tools --
144
+ case 'calendar.list_events':
145
+ return _listEvents(args);
146
+ case 'calendar.create_event':
147
+ return _createEvent(args);
148
+ case 'calendar.list_calendars':
149
+ return _listCalendars();
150
+
151
default:
152
return const ToolExecutionResult.failure('Unknown built-in tool');
153
}
154
}
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // Calendar tool implementations
158
+ // ---------------------------------------------------------------------------
159
+
160
+ Future<ToolExecutionResult> _listEvents(Map<String, dynamic> args) async {
161
+ try {
162
+ final cal = CalendarService.instance;
163
+ if (!await cal.hasAccess()) {
164
+ return const ToolExecutionResult.failure(
165
+ 'Calendar permission not granted. '
166
+ 'Please enable Calendar Access in Settings.',
167
+ );
168
+ }
169
+
170
+ DateTime? start;
171
+ DateTime? end;
172
+ if (args['start_date'] is String) {
173
+ start = DateTime.tryParse(args['start_date'] as String);
174
+ }
175
+ if (args['end_date'] is String) {
176
+ end = DateTime.tryParse(args['end_date'] as String);
177
+ }
178
+
179
+ final events = await cal.listEvents(start: start, end: end);
180
+ if (events.isEmpty) {
181
+ return ToolExecutionResult.success(
182
+ 'No events found in the specified date range.',
183
+ );
184
+ }
185
+ return ToolExecutionResult.success(
186
+ const JsonEncoder.withIndent(' ').convert(events),
187
+ );
188
+ } catch (e) {
189
+ return ToolExecutionResult.failure('Failed to list events: $e');
190
+ }
191
+ }
192
+
193
+ Future<ToolExecutionResult> _createEvent(Map<String, dynamic> args) async {
194
+ try {
195
+ final cal = CalendarService.instance;
196
+ if (!await cal.hasAccess()) {
197
+ return const ToolExecutionResult.failure(
198
+ 'Calendar permission not granted. '
199
+ 'Please enable Calendar Access in Settings.',
200
+ );
201
+ }
202
+
203
+ final title = args['title'] as String?;
204
+ final startStr = args['start_date'] as String?;
205
+ final endStr = args['end_date'] as String?;
206
+
207
+ if (title == null || startStr == null || endStr == null) {
208
+ return const ToolExecutionResult.failure(
209
+ 'title, start_date, and end_date are required.',
210
+ );
211
+ }
212
+
213
+ final startDate = DateTime.tryParse(startStr);
214
+ final endDate = DateTime.tryParse(endStr);
215
+ if (startDate == null || endDate == null) {
216
+ return const ToolExecutionResult.failure(
217
+ 'Invalid date format. Use ISO 8601 (e.g. 2025-01-15T14:00:00).',
218
+ );
219
+ }
220
+
221
+ final result = await cal.createEvent(
222
+ title: title,
223
+ startDate: startDate,
224
+ endDate: endDate,
225
+ description: args['description'] as String?,
226
+ location: args['location'] as String?,
227
+ calendarId: args['calendar_id'] as String?,
228
+ );
229
+ return ToolExecutionResult.success(
230
+ const JsonEncoder.withIndent(' ').convert(result),
231
+ );
232
+ } catch (e) {
233
+ return ToolExecutionResult.failure('Failed to create event: $e');
234
+ }
235
+ }
236
+
237
+ Future<ToolExecutionResult> _listCalendars() async {
238
+ try {
239
+ final cal = CalendarService.instance;
240
+ if (!await cal.hasAccess()) {
241
+ return const ToolExecutionResult.failure(
242
+ 'Calendar permission not granted. '
243
+ 'Please enable Calendar Access in Settings.',
244
+ );
245
+ }
246
+
247
+ final calendars = await cal.listCalendars();
248
+ if (calendars.isEmpty) {
249
+ return ToolExecutionResult.success('No calendars found on device.');
250
+ }
251
+ return ToolExecutionResult.success(
252
+ const JsonEncoder.withIndent(' ').convert(calendars),
253
+ );
254
+ } catch (e) {
255
+ return ToolExecutionResult.failure('Failed to list calendars: $e');
256
+ }
257
+ }
258
}
lib/features/chat/data/tools/calendar_service.dart
new
+118
@@ -0,0 +1,118 @@
1
+import 'package:device_calendar_plus/device_calendar_plus.dart';
2
+
3
+/// Thin wrapper around [DeviceCalendar] for the built-in calendar tools.
4
+///
5
+/// All methods return plain Dart types (strings / maps) so tool results can be
6
+/// serialised without leaking platform model classes into the tool layer.
7
+class CalendarService {
8
+ CalendarService._();
9
+ static final instance = CalendarService._();
10
+
11
+ final _plugin = DeviceCalendar.instance;
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Permissions
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /// Request full calendar access. Returns `true` when granted.
18
+ Future<bool> requestAccess() async {
19
+ final status = await _plugin.requestPermissions();
20
+ return status == CalendarPermissionStatus.granted;
21
+ }
22
+
23
+ /// Check current permission status without prompting.
24
+ Future<bool> hasAccess() async {
25
+ final status = await _plugin.hasPermissions();
26
+ return status == CalendarPermissionStatus.granted;
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Calendars
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /// Returns a JSON-friendly list of available calendars.
34
+ Future<List<Map<String, dynamic>>> listCalendars() async {
35
+ final calendars = await _plugin.listCalendars();
36
+ return calendars
37
+ .map((c) => {
38
+ 'id': c.id,
39
+ 'name': c.name,
40
+ 'is_read_only': c.readOnly,
41
+ 'is_primary': c.isPrimary,
42
+ 'account_name': c.accountName,
43
+ })
44
+ .toList();
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Events – read
49
+ // ---------------------------------------------------------------------------
50
+
51
+ /// Lists events in [start]..[end]. Both default to "today → +7 days".
52
+ Future<List<Map<String, dynamic>>> listEvents({
53
+ DateTime? start,
54
+ DateTime? end,
55
+ }) async {
56
+ final from = start ?? _todayStart();
57
+ final to = end ?? from.add(const Duration(days: 7));
58
+
59
+ final events = await _plugin.listEvents(from, to);
60
+ return events
61
+ .map((e) => {
62
+ 'id': e.eventId,
63
+ 'title': e.title,
64
+ 'start': e.startDate.toIso8601String(),
65
+ 'end': e.endDate.toIso8601String(),
66
+ 'all_day': e.isAllDay,
67
+ 'location': e.location,
68
+ 'description': e.description,
69
+ 'calendar_id': e.calendarId,
70
+ })
71
+ .toList();
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Events – write
76
+ // ---------------------------------------------------------------------------
77
+
78
+ /// Creates a new event and returns a result map with the event ID.
79
+ Future<Map<String, dynamic>> createEvent({
80
+ required String title,
81
+ required DateTime startDate,
82
+ required DateTime endDate,
83
+ String? description,
84
+ String? location,
85
+ String? calendarId,
86
+ }) async {
87
+ try {
88
+ final eventId = await _plugin.createEvent(
89
+ title: title,
90
+ startDate: startDate,
91
+ endDate: endDate,
92
+ description: description,
93
+ location: location,
94
+ calendarId: calendarId,
95
+ );
96
+
97
+ return {
98
+ 'success': true,
99
+ 'event_id': eventId,
100
+ 'message': 'Event "$title" created successfully.',
101
+ };
102
+ } on DeviceCalendarException catch (e) {
103
+ return {
104
+ 'success': false,
105
+ 'error': e.toString(),
106
+ };
107
+ }
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Helpers
112
+ // ---------------------------------------------------------------------------
113
+
114
+ DateTime _todayStart() {
115
+ final now = DateTime.now();
116
+ return DateTime(now.year, now.month, now.day);
117
+ }
118
+}
lib/features/chat/providers/tooling_providers.dart
+4
-1
@@ -13,7 +13,10 @@ final mcpServerManagerProvider = Provider<McpServerManager>((ref) {
13
});
14
15
final builtInToolProviderProvider = Provider<BuiltInToolProvider>((ref) {
16
- return BuiltInToolProvider();
16
+ final settings = ref.watch(settingsProvider);
17
+ return BuiltInToolProvider(
18
+ calendarToolsEnabled: settings.calendarToolsEnabled,
19
+ );
20
});
21
22
final toolRegistryProvider = Provider<ToolRegistry>((ref) {
lib/features/mcp/views/mcp_tools_screen.dart
+42
-4
@@ -5,6 +5,7 @@ import 'package:localmind/l10n/app_localizations.dart';
5
import 'package:shadcn_ui/shadcn_ui.dart';
6
7
import '../../chat/data/mcp_server_manager.dart';
8
+import '../../chat/data/tools/calendar_service.dart';
9
import '../../chat/data/tools/tool_definition.dart';
10
import '../../chat/providers/chat_mcp_providers.dart';
11
import '../../chat/providers/tooling_providers.dart';
@@ -114,9 +115,12 @@ class _McpToolsScreenState extends ConsumerState<McpToolsScreen> {
115
label: l10n.enable_mcp,
116
value: settings.mcpEnabled,
117
badges: [_FeatureBadge(label: l10n.experimental_label)],
117
- onChanged: (value) => ref
118
- .read(settingsProvider.notifier)
119
- .setMcpEnabled(value),
118
+ onChanged: (value) {
119
+ ref
120
+ .read(settingsProvider.notifier)
121
+ .setMcpEnabled(value);
122
+ _refreshTools();
123
+ },
124
),
125
if (settings.mcpEnabled) ...[
126
_McpToggleSetting(
@@ -126,6 +130,38 @@ class _McpToolsScreenState extends ConsumerState<McpToolsScreen> {
130
.read(settingsProvider.notifier)
131
.setNewChatMcpEnabled(value),
132
),
133
+ _McpToggleSetting(
134
+ label: l10n.calendar_access,
135
+ description: l10n.calendar_access_desc,
136
+ value: settings.calendarToolsEnabled,
137
+ badges: [_FeatureBadge(label: l10n.experimental_label)],
138
+ onChanged: (value) async {
139
+ if (value) {
140
+ final cal = CalendarService.instance;
141
+ final granted = await cal.requestAccess();
142
+ if (granted) {
143
+ ref
144
+ .read(settingsProvider.notifier)
145
+ .setCalendarToolsEnabled(true);
146
+ _refreshTools();
147
+ } else {
148
+ if (context.mounted) {
149
+ ScaffoldMessenger.of(context).showSnackBar(
150
+ SnackBar(
151
+ content:
152
+ Text(l10n.calendar_permission_denied),
153
+ ),
154
+ );
155
+ }
156
+ }
157
+ } else {
158
+ ref
159
+ .read(settingsProvider.notifier)
160
+ .setCalendarToolsEnabled(false);
161
+ _refreshTools();
162
+ }
163
+ },
164
+ ),
165
_McpToggleSetting(
166
label: l10n.enable_example_server,
167
description: l10n.example_mcp_server_desc,
@@ -395,7 +431,9 @@ class _ToolRow extends StatelessWidget {
431
HugeIcon(
432
icon: isMcp
433
? HugeIcons.strokeRoundedShare01
398
- : HugeIcons.strokeRoundedCalculate,
434
+ : (tool.name.startsWith('calendar.')
435
+ ? HugeIcons.strokeRoundedCalendar01
436
+ : HugeIcons.strokeRoundedCalculate),
437
size: 18,
438
color: isMcp
439
? theme.colorScheme.primary
lib/features/settings/data/models/app_settings.dart
+7
@@ -57,6 +57,7 @@ class AppSettings {
57
final bool keepPersonaOnNewChat;
58
final bool roleSwapButtonEnabled;
59
final bool showSystemMessagesInChat;
60
+ final bool calendarToolsEnabled;
61
final List<McpIntegration> savedMcpIntegrations;
62
63
AppSettings({
@@ -104,6 +105,7 @@ class AppSettings {
105
this.keepPersonaOnNewChat = false,
106
this.roleSwapButtonEnabled = false,
107
this.showSystemMessagesInChat = true,
108
+ this.calendarToolsEnabled = false,
109
this.savedMcpIntegrations = const [],
110
});
111
@@ -152,6 +154,7 @@ class AppSettings {
154
bool? keepPersonaOnNewChat,
155
bool? roleSwapButtonEnabled,
156
bool? showSystemMessagesInChat,
157
+ bool? calendarToolsEnabled,
158
List<McpIntegration>? savedMcpIntegrations,
159
}) {
160
return AppSettings(
@@ -222,6 +225,8 @@ class AppSettings {
225
roleSwapButtonEnabled ?? this.roleSwapButtonEnabled,
226
showSystemMessagesInChat:
227
showSystemMessagesInChat ?? this.showSystemMessagesInChat,
228
+ calendarToolsEnabled:
229
+ calendarToolsEnabled ?? this.calendarToolsEnabled,
230
savedMcpIntegrations:
231
savedMcpIntegrations ?? this.savedMcpIntegrations,
232
);
@@ -273,6 +278,7 @@ class AppSettings {
278
'keepPersonaOnNewChat': keepPersonaOnNewChat,
279
'roleSwapButtonEnabled': roleSwapButtonEnabled,
280
'showSystemMessagesInChat': showSystemMessagesInChat,
281
+ 'calendarToolsEnabled': calendarToolsEnabled,
282
'savedMcpIntegrations':
283
savedMcpIntegrations.map((i) => i.toJson()).toList(),
284
};
@@ -326,6 +332,7 @@ class AppSettings {
332
keepPersonaOnNewChat: map['keepPersonaOnNewChat'] ?? false,
333
roleSwapButtonEnabled: map['roleSwapButtonEnabled'] ?? false,
334
showSystemMessagesInChat: map['showSystemMessagesInChat'] ?? true,
335
+ calendarToolsEnabled: map['calendarToolsEnabled'] ?? false,
336
savedMcpIntegrations:
337
_parseSavedMcpIntegrations(map['savedMcpIntegrations']),
338
);
lib/features/settings/views/settings_screen.dart
-16
@@ -274,22 +274,6 @@ class SettingsViews extends ConsumerWidget {
274
if (value) ref.read(appHapticsProvider).medium();
275
},
276
),
277
- _ToggleSetting(
278
- label: l10n.enable_mcp,
279
- value: settings.mcpEnabled,
280
- badges: [_FeatureBadge(label: l10n.experimental_label)],
281
- onChanged: (value) => ref
282
- .read(settingsProvider.notifier)
283
- .setMcpEnabled(value),
284
- ),
285
- if (settings.mcpEnabled)
286
- _ToggleSetting(
287
- label: l10n.new_chat_mcp_default,
288
- value: settings.newChatMcpEnabled,
289
- onChanged: (value) => ref
290
- .read(settingsProvider.notifier)
291
- .setNewChatMcpEnabled(value),
292
- ),
277
_ToggleSetting(
278
label: l10n.temp_chat_keyboard_incognito,
279
description: l10n.temp_chat_keyboard_incognito_desc,
lib/l10n/app_en.arb
+12
@@ -3348,6 +3348,18 @@
3348
"type": "String"
3349
}
3350
}
3351
+ },
3352
+ "calendar_access": "Calendar Access",
3353
+ "@calendar_access": {
3354
+ "description": "Toggle for calendar tools feature"
3355
+ },
3356
+ "calendar_access_desc": "Allow AI to read and create calendar events",
3357
+ "@calendar_access_desc": {
3358
+ "description": "Description for calendar tools toggle"
3359
+ },
3360
+ "calendar_permission_denied": "Calendar permission denied. Please grant calendar access in your device settings.",
3361
+ "@calendar_permission_denied": {
3362
+ "description": "Snackbar message when calendar permission is denied"
3363
}
3364
}
3365
lib/l10n/app_localizations.dart
+18
@@ -5626,6 +5626,24 @@ abstract class AppLocalizations {
5626
/// In en, this message translates to:
5627
/// **'Failed to select image: {error}'**
5628
String image_pick_failed(String error);
5629
+
5630
+ /// Toggle for calendar tools feature
5631
+ ///
5632
+ /// In en, this message translates to:
5633
+ /// **'Calendar Access'**
5634
+ String get calendar_access;
5635
+
5636
+ /// Description for calendar tools toggle
5637
+ ///
5638
+ /// In en, this message translates to:
5639
+ /// **'Allow AI to read and create calendar events'**
5640
+ String get calendar_access_desc;
5641
+
5642
+ /// Snackbar message when calendar permission is denied
5643
+ ///
5644
+ /// In en, this message translates to:
5645
+ /// **'Calendar permission denied. Please grant calendar access in your device settings.'**
5646
+ String get calendar_permission_denied;
5647
}
5648
5649
class _AppLocalizationsDelegate
lib/l10n/app_localizations_ar.dart
+11
@@ -3007,4 +3007,15 @@ class AppLocalizationsAr extends AppLocalizations {
3007
String image_pick_failed(String error) {
3008
return 'فشل اختيار الصورة: $error';
3009
}
3010
+
3011
+ @override
3012
+ String get calendar_access => 'Calendar Access';
3013
+
3014
+ @override
3015
+ String get calendar_access_desc =>
3016
+ 'Allow AI to read and create calendar events';
3017
+
3018
+ @override
3019
+ String get calendar_permission_denied =>
3020
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
3021
}
lib/l10n/app_localizations_bn.dart
+11
@@ -3032,4 +3032,15 @@ class AppLocalizationsBn extends AppLocalizations {
3032
String image_pick_failed(String error) {
3033
return 'Failed to select image: $error';
3034
}
3035
+
3036
+ @override
3037
+ String get calendar_access => 'Calendar Access';
3038
+
3039
+ @override
3040
+ String get calendar_access_desc =>
3041
+ 'Allow AI to read and create calendar events';
3042
+
3043
+ @override
3044
+ String get calendar_permission_denied =>
3045
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
3046
}
lib/l10n/app_localizations_de.dart
+11
@@ -3022,4 +3022,15 @@ class AppLocalizationsDe extends AppLocalizations {
3022
String image_pick_failed(String error) {
3023
return 'Failed to select image: $error';
3024
}
3025
+
3026
+ @override
3027
+ String get calendar_access => 'Calendar Access';
3028
+
3029
+ @override
3030
+ String get calendar_access_desc =>
3031
+ 'Allow AI to read and create calendar events';
3032
+
3033
+ @override
3034
+ String get calendar_permission_denied =>
3035
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
3036
}
lib/l10n/app_localizations_en.dart
+11
@@ -3012,4 +3012,15 @@ class AppLocalizationsEn extends AppLocalizations {
3012
String image_pick_failed(String error) {
3013
return 'Failed to select image: $error';
3014
}
3015
+
3016
+ @override
3017
+ String get calendar_access => 'Calendar Access';
3018
+
3019
+ @override
3020
+ String get calendar_access_desc =>
3021
+ 'Allow AI to read and create calendar events';
3022
+
3023
+ @override
3024
+ String get calendar_permission_denied =>
3025
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
3026
}
lib/l10n/app_localizations_es.dart
+11
@@ -3036,4 +3036,15 @@ class AppLocalizationsEs extends AppLocalizations {
3036
String image_pick_failed(String error) {
3037
return 'Failed to select image: $error';
3038
}
3039
+
3040
+ @override
3041
+ String get calendar_access => 'Calendar Access';
3042
+
3043
+ @override
3044
+ String get calendar_access_desc =>
3045
+ 'Allow AI to read and create calendar events';
3046
+
3047
+ @override
3048
+ String get calendar_permission_denied =>
3049
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
3050
}
lib/l10n/app_localizations_fr.dart
+11
@@ -3025,4 +3025,15 @@ class AppLocalizationsFr extends AppLocalizations {
3025
String image_pick_failed(String error) {
3026
return 'Failed to select image: $error';
3027
}
3028
+
3029
+ @override
3030
+ String get calendar_access => 'Calendar Access';
3031
+
3032
+ @override
3033
+ String get calendar_access_desc =>
3034
+ 'Allow AI to read and create calendar events';
3035
+
3036
+ @override
3037
+ String get calendar_permission_denied =>
3038
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
3039
}
lib/l10n/app_localizations_hi.dart
+11
@@ -3023,4 +3023,15 @@ class AppLocalizationsHi extends AppLocalizations {
3023
String image_pick_failed(String error) {
3024
return 'Failed to select image: $error';
3025
}
3026
+
3027
+ @override
3028
+ String get calendar_access => 'Calendar Access';
3029
+
3030
+ @override
3031
+ String get calendar_access_desc =>
3032
+ 'Allow AI to read and create calendar events';
3033
+
3034
+ @override
3035
+ String get calendar_permission_denied =>
3036
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
3037
}
lib/l10n/app_localizations_it.dart
+11
@@ -3039,4 +3039,15 @@ class AppLocalizationsIt extends AppLocalizations {
3039
String image_pick_failed(String error) {
3040
return 'Failed to select image: $error';
3041
}
3042
+
3043
+ @override
3044
+ String get calendar_access => 'Calendar Access';
3045
+
3046
+ @override
3047
+ String get calendar_access_desc =>
3048
+ 'Allow AI to read and create calendar events';
3049
+
3050
+ @override
3051
+ String get calendar_permission_denied =>
3052
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
3053
}
lib/l10n/app_localizations_ja.dart
+11
@@ -2962,4 +2962,15 @@ class AppLocalizationsJa extends AppLocalizations {
2962
String image_pick_failed(String error) {
2963
return 'Failed to select image: $error';
2964
}
2965
+
2966
+ @override
2967
+ String get calendar_access => 'Calendar Access';
2968
+
2969
+ @override
2970
+ String get calendar_access_desc =>
2971
+ 'Allow AI to read and create calendar events';
2972
+
2973
+ @override
2974
+ String get calendar_permission_denied =>
2975
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
2976
}
lib/l10n/app_localizations_ru.dart
+11
@@ -3037,4 +3037,15 @@ class AppLocalizationsRu extends AppLocalizations {
3037
String image_pick_failed(String error) {
3038
return 'Failed to select image: $error';
3039
}
3040
+
3041
+ @override
3042
+ String get calendar_access => 'Calendar Access';
3043
+
3044
+ @override
3045
+ String get calendar_access_desc =>
3046
+ 'Allow AI to read and create calendar events';
3047
+
3048
+ @override
3049
+ String get calendar_permission_denied =>
3050
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
3051
}
lib/l10n/app_localizations_tr.dart
+11
@@ -3042,4 +3042,15 @@ class AppLocalizationsTr extends AppLocalizations {
3042
String image_pick_failed(String error) {
3043
return 'Failed to select image: $error';
3044
}
3045
+
3046
+ @override
3047
+ String get calendar_access => 'Calendar Access';
3048
+
3049
+ @override
3050
+ String get calendar_access_desc =>
3051
+ 'Allow AI to read and create calendar events';
3052
+
3053
+ @override
3054
+ String get calendar_permission_denied =>
3055
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
3056
}
lib/l10n/app_localizations_zh.dart
+11
@@ -2939,6 +2939,17 @@ class AppLocalizationsZh extends AppLocalizations {
2939
String image_pick_failed(String error) {
2940
return 'Failed to select image: $error';
2941
}
2942
+
2943
+ @override
2944
+ String get calendar_access => 'Calendar Access';
2945
+
2946
+ @override
2947
+ String get calendar_access_desc =>
2948
+ 'Allow AI to read and create calendar events';
2949
+
2950
+ @override
2951
+ String get calendar_permission_denied =>
2952
+ 'Calendar permission denied. Please grant calendar access in your device settings.';
2953
}
2954
2955
/// The translations for Chinese, as used in Taiwan (`zh_TW`).
lib/l10n/untranslated.txt
+50
-11
@@ -1,78 +1,117 @@
1
{
2
+ "ar": [
3
+ "calendar_access",
4
+ "calendar_access_desc",
5
+ "calendar_permission_denied"
6
+ ],
7
+
8
"bn": [
9
"file_explorer_not_found",
10
"export_data_failed",
11
"file_pick_failed",
6
- "image_pick_failed"
12
+ "image_pick_failed",
13
+ "calendar_access",
14
+ "calendar_access_desc",
15
+ "calendar_permission_denied"
16
],
17
18
"de": [
19
"file_explorer_not_found",
20
"export_data_failed",
21
"file_pick_failed",
13
- "image_pick_failed"
22
+ "image_pick_failed",
23
+ "calendar_access",
24
+ "calendar_access_desc",
25
+ "calendar_permission_denied"
26
],
27
28
"es": [
29
"file_explorer_not_found",
30
"export_data_failed",
31
"file_pick_failed",
20
- "image_pick_failed"
32
+ "image_pick_failed",
33
+ "calendar_access",
34
+ "calendar_access_desc",
35
+ "calendar_permission_denied"
36
],
37
38
"fr": [
39
"file_explorer_not_found",
40
"export_data_failed",
41
"file_pick_failed",
27
- "image_pick_failed"
42
+ "image_pick_failed",
43
+ "calendar_access",
44
+ "calendar_access_desc",
45
+ "calendar_permission_denied"
46
],
47
48
"hi": [
49
"file_explorer_not_found",
50
"export_data_failed",
51
"file_pick_failed",
34
- "image_pick_failed"
52
+ "image_pick_failed",
53
+ "calendar_access",
54
+ "calendar_access_desc",
55
+ "calendar_permission_denied"
56
],
57
58
"it": [
59
"file_explorer_not_found",
60
"export_data_failed",
61
"file_pick_failed",
41
- "image_pick_failed"
62
+ "image_pick_failed",
63
+ "calendar_access",
64
+ "calendar_access_desc",
65
+ "calendar_permission_denied"
66
],
67
68
"ja": [
69
"file_explorer_not_found",
70
"export_data_failed",
71
"file_pick_failed",
48
- "image_pick_failed"
72
+ "image_pick_failed",
73
+ "calendar_access",
74
+ "calendar_access_desc",
75
+ "calendar_permission_denied"
76
],
77
78
"ru": [
79
"file_explorer_not_found",
80
"export_data_failed",
81
"file_pick_failed",
55
- "image_pick_failed"
82
+ "image_pick_failed",
83
+ "calendar_access",
84
+ "calendar_access_desc",
85
+ "calendar_permission_denied"
86
],
87
88
"tr": [
89
"file_explorer_not_found",
90
"export_data_failed",
91
"file_pick_failed",
62
- "image_pick_failed"
92
+ "image_pick_failed",
93
+ "calendar_access",
94
+ "calendar_access_desc",
95
+ "calendar_permission_denied"
96
],
97
98
"zh": [
99
"file_explorer_not_found",
100
"export_data_failed",
101
"file_pick_failed",
69
- "image_pick_failed"
102
+ "image_pick_failed",
103
+ "calendar_access",
104
+ "calendar_access_desc",
105
+ "calendar_permission_denied"
106
],
107
108
"zh_TW": [
109
"file_explorer_not_found",
110
"export_data_failed",
111
"file_pick_failed",
76
- "image_pick_failed"
112
+ "image_pick_failed",
113
+ "calendar_access",
114
+ "calendar_access_desc",
115
+ "calendar_permission_denied"
116
]
117
}
pubspec.lock
+32
@@ -377,6 +377,38 @@ packages:
377
url: "https://pub.dev"
378
source: hosted
379
version: "3.2.6"
380
+ device_calendar_plus:
381
+ dependency: "direct main"
382
+ description:
383
+ name: device_calendar_plus
384
+ sha256: "7f3418209e6678e8d9e8a8235e9beda34c1c0c35b0e071644283b300e7939a22"
385
+ url: "https://pub.dev"
386
+ source: hosted
387
+ version: "0.8.0"
388
+ device_calendar_plus_android:
389
+ dependency: transitive
390
+ description:
391
+ name: device_calendar_plus_android
392
+ sha256: "7143fa08a3e1b57a1c81f478af1f4c6a1c47eee768fd5d322a69ed7abb43dcf8"
393
+ url: "https://pub.dev"
394
+ source: hosted
395
+ version: "0.7.1"
396
+ device_calendar_plus_ios:
397
+ dependency: transitive
398
+ description:
399
+ name: device_calendar_plus_ios
400
+ sha256: e261e7238d46ca6d1f07fda86a3c4abdd0ff02cef08313c401a2eb203db30d62
401
+ url: "https://pub.dev"
402
+ source: hosted
403
+ version: "0.7.0"
404
+ device_calendar_plus_platform_interface:
405
+ dependency: transitive
406
+ description:
407
+ name: device_calendar_plus_platform_interface
408
+ sha256: "474169d6d32654e190c2f1d50c82a0090d475c33c904177f96af831ba46c306e"
409
+ url: "https://pub.dev"
410
+ source: hosted
411
+ version: "0.7.0"
412
device_info_plus:
413
dependency: "direct main"
414
description:
pubspec.yaml
+1
@@ -87,6 +87,7 @@ dependencies:
87
pdfrx: ^2.4.7
88
permission_handler: ^11.4.0
89
auto_size_text_plus: ^3.0.2
90
+ device_calendar_plus: ^0.8.0
91
92
dev_dependencies:
93
flutter_test: