Skip to content

Commit b13882c

Browse files
Teodor Ciuraruclaude
andcommitted
feat(flutter): add attachment support and macOS P2P permissions
Add basic attachment functionality to Flutter quickstart app and configure macOS for P2P sync. Attachment implementation: - Add optional attachment field to Task model - Add attachment note input field in task dialog - Implement attachment creation with store.newAttachment() - Implement attachment fetching with store.fetchAttachment() - Display attachment indicator and view button in task list macOS P2P permissions: - Add NSBluetoothAlwaysUsageDescription for BLE sync - Add NSLocalNetworkUsageDescription for WiFi P2P - Add NSBonjourServices for local network discovery - Add com.apple.security.network.client entitlement for cloud sync - Add com.apple.security.network.server entitlement for P2P sync iOS simulator support: - Add iphonesimulator to SUPPORTED_PLATFORMS for Release/Profile builds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 4f49349 commit b13882c

File tree

8 files changed

+99
-3
lines changed

8 files changed

+99
-3
lines changed

flutter_app/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@
478478
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
479479
MTL_ENABLE_DEBUG_INFO = NO;
480480
SDKROOT = iphoneos;
481-
SUPPORTED_PLATFORMS = iphoneos;
481+
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
482482
TARGETED_DEVICE_FAMILY = "1,2";
483483
VALIDATE_PRODUCT = YES;
484484
};
@@ -665,7 +665,7 @@
665665
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
666666
MTL_ENABLE_DEBUG_INFO = NO;
667667
SDKROOT = iphoneos;
668-
SUPPORTED_PLATFORMS = iphoneos;
668+
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
669669
SWIFT_COMPILATION_MODE = wholemodule;
670670
SWIFT_OPTIMIZATION_LEVEL = "-O";
671671
TARGETED_DEVICE_FAMILY = "1,2";

flutter_app/lib/dialog.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class _Dialog extends StatefulWidget {
1818

1919
class _DialogState extends State<_Dialog> {
2020
late final _name = TextEditingController(text: widget.taskToEdit?.title);
21+
late final _attachmentNote = TextEditingController();
2122
late var _done = widget.taskToEdit?.done ?? false;
2223

2324
@override
@@ -30,6 +31,7 @@ class _DialogState extends State<_Dialog> {
3031
mainAxisSize: MainAxisSize.min,
3132
children: [
3233
_textInput(_name, "Name"),
34+
_textInput(_attachmentNote, "Attachment Note (optional)"),
3335
_doneSwitch,
3436
],
3537
),
@@ -45,6 +47,8 @@ class _DialogState extends State<_Dialog> {
4547
title: _name.text,
4648
done: _done,
4749
deleted: false,
50+
// Pass the attachment note text if provided
51+
attachment: _attachmentNote.text.isNotEmpty ? {"note": _attachmentNote.text} : null,
4852
);
4953
Navigator.of(context).pop(task);
5054
},

flutter_app/lib/main.dart

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:io' show Platform;
2+
import 'dart:typed_data';
23

34
import 'package:ditto_live/ditto_live.dart';
45
import 'package:flutter/foundation.dart';
@@ -100,10 +101,24 @@ class _DittoExampleState extends State<DittoExample> {
100101
final task = await showAddTaskDialog(context);
101102
if (task == null) return;
102103

104+
Map<String, dynamic> taskJson = task.toJson();
105+
106+
// If the task has an attachment note, create a Ditto attachment
107+
if (task.attachment != null && task.attachment!['note'] != null) {
108+
final noteText = task.attachment!['note'] as String;
109+
final noteBytes = Uint8List.fromList(noteText.codeUnits);
110+
111+
// Create attachment from the note bytes
112+
final attachment = await _ditto!.store.newAttachment(noteBytes);
113+
114+
// Replace the placeholder with the actual attachment token
115+
taskJson['attachment'] = attachment;
116+
}
117+
103118
// https://docs.ditto.live/sdk/latest/crud/create
104119
await _ditto!.store.execute(
105120
"INSERT INTO tasks DOCUMENTS (:task)",
106-
arguments: {"task": task.toJson()},
121+
arguments: {"task": taskJson},
107122
);
108123
}
109124

@@ -112,6 +127,50 @@ class _DittoExampleState extends State<DittoExample> {
112127
await _ditto!.store.execute("EVICT FROM tasks WHERE true");
113128
}
114129

130+
Future<void> _showAttachment(Map<String, dynamic> attachmentToken) async {
131+
// Show loading dialog
132+
showDialog(
133+
context: context,
134+
barrierDismissible: false,
135+
builder: (context) => const Center(child: CircularProgressIndicator()),
136+
);
137+
138+
String? attachmentContent;
139+
140+
// Fetch the attachment using the Ditto attachment API
141+
_ditto!.store.fetchAttachment(
142+
attachmentToken,
143+
(event) async {
144+
if (event is AttachmentFetchEventCompleted) {
145+
// Get the attachment data
146+
final bytes = await event.attachment.data;
147+
// Convert bytes back to string
148+
attachmentContent = String.fromCharCodes(bytes);
149+
150+
// Close loading dialog
151+
if (mounted) Navigator.of(context).pop();
152+
153+
// Show the attachment content in a dialog
154+
if (mounted) {
155+
showDialog(
156+
context: context,
157+
builder: (context) => AlertDialog(
158+
title: const Text("Attachment Content"),
159+
content: Text(attachmentContent ?? "No content"),
160+
actions: [
161+
TextButton(
162+
onPressed: () => Navigator.of(context).pop(),
163+
child: const Text("Close"),
164+
),
165+
],
166+
),
167+
);
168+
}
169+
}
170+
},
171+
);
172+
}
173+
115174
@override
116175
Widget build(BuildContext context) {
117176
if (_ditto == null) return _loading;
@@ -207,6 +266,20 @@ class _DittoExampleState extends State<DittoExample> {
207266
secondaryBackground: _dismissibleBackground(false),
208267
child: CheckboxListTile(
209268
title: Text(task.title),
269+
subtitle: task.attachment != null
270+
? Row(
271+
children: [
272+
const Icon(Icons.attachment, size: 16),
273+
const SizedBox(width: 4),
274+
Expanded(
275+
child: TextButton(
276+
onPressed: () => _showAttachment(task.attachment!),
277+
child: const Text("View Attachment"),
278+
),
279+
),
280+
],
281+
)
282+
: null,
210283
value: task.done,
211284
onChanged: (value) => _ditto!.store.execute(
212285
"UPDATE tasks SET done = $value WHERE _id = '${task.id}'",

flutter_app/lib/task.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ class Task {
99
final String title;
1010
final bool done;
1111
final bool deleted;
12+
@JsonKey(includeIfNull: false)
13+
final Map<String, dynamic>? attachment;
1214

1315
const Task({
1416
this.id,
1517
required this.title,
1618
required this.done,
1719
required this.deleted,
20+
this.attachment,
1821
});
1922

2023
factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json);

flutter_app/lib/task.g.dart

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flutter_app/macos/Runner/DebugProfile.entitlements

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88
<true/>
99
<key>com.apple.security.network.server</key>
1010
<true/>
11+
<key>com.apple.security.network.client</key>
12+
<true/>
1113
</dict>
1214
</plist>

flutter_app/macos/Runner/Info.plist

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,13 @@
2828
<string>MainMenu</string>
2929
<key>NSPrincipalClass</key>
3030
<string>NSApplication</string>
31+
<key>NSBluetoothAlwaysUsageDescription</key>
32+
<string>Uses Bluetooth to connect and sync with nearby devices.</string>
33+
<key>NSLocalNetworkUsageDescription</key>
34+
<string>Uses WiFi to connect and sync with nearby devices.</string>
35+
<key>NSBonjourServices</key>
36+
<array>
37+
<string>_http-alt._tcp.</string>
38+
</array>
3139
</dict>
3240
</plist>

flutter_app/macos/Runner/Release.entitlements

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@
44
<dict>
55
<key>com.apple.security.app-sandbox</key>
66
<true/>
7+
<key>com.apple.security.network.client</key>
8+
<true/>
9+
<key>com.apple.security.network.server</key>
10+
<true/>
711
</dict>
812
</plist>

0 commit comments

Comments
 (0)