@tduncan / Cortana / commits / 5241d5c

feat (exp): integrate device calendar access and tools for AI chat interactions #55

abdulmominsakib committed Aug 16, 2026 at 18:50 UTC 5241d5cd73e96b05da3c8e9bfdc4bc350bf835da
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: