Skip to content

Commit a360d70

Browse files
committed
Item Sharing
1 parent f2645e1 commit a360d70

File tree

14 files changed

+2082
-790
lines changed

14 files changed

+2082
-790
lines changed

.dart_tool/build/fcd1995bc647fb959e82ea360c6c2c9a/asset_graph.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

lib/app/layouts/findmy/findmy_page.dart

Lines changed: 144 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import 'package:bluebubbles/utils/logger/logger.dart';
2020
import 'package:dio/dio.dart';
2121
import 'package:flutter/cupertino.dart';
2222
import 'package:flutter/foundation.dart';
23+
import 'package:flutter_slidable/flutter_slidable.dart';
2324
import 'package:geolocator/geolocator.dart';
2425
import 'package:flutter/material.dart';
2526
import 'package:flutter/services.dart';
@@ -333,7 +334,7 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
333334
if (placemark != null) {
334335
cachedAddresses[(cached.lastReport!.lat, cached.lastReport!.long)] = Address(
335336
subAdministrativeArea: placemark.subAdministrativeArea,
336-
label: placemark.thoroughfare,
337+
label: placemark.thoroughfare ?? placemark.name,
337338
streetAddress: placemark.thoroughfare,
338339
country: placemark.country,
339340
countryCode: placemark.isoCountryCode,
@@ -390,10 +391,10 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
390391
audioChannels: [],
391392
lostModeCapable: true,
392393
snd: null,
393-
batteryLevel: e.batteryLevel.toDouble(),
394+
batteryLevel: e.batteryLevel?.toDouble(),
394395
locationEnabled: true,
395396
isConsideredAccessory: true,
396-
address: cachedAddresses[(e.lastReport!.lat, e.lastReport!.long)],
397+
address: e.lastReport == null ? null : cachedAddresses[(e.lastReport!.lat, e.lastReport!.long)],
397398
location: location,
398399
modelDisplayName: null,
399400
deviceColor: null,
@@ -429,6 +430,9 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
429430
crowdSourcedLocation: null,
430431
role: {
431432
"emoji": e.naming.emoji,
433+
"sharingId": e.shared?.shareId,
434+
"sharingActive": e.shared?.acceptanceState,
435+
"sharingName": e.shared?.ownerHandle != null ? RustPushBBUtils.rustHandleToBB(e.shared!.ownerHandle).displayName : null,
432436
},
433437
lostModeMetadata: null
434438
));
@@ -572,6 +576,23 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
572576
);
573577
}
574578

579+
Future<void> deleteShared(FindMyDevice item) async {
580+
await api.deleteBeaconShare(items: pushService.state!.icloudServices!.fmfd!, share: item.role!['sharingId']!);
581+
setState(() {
582+
devices.remove(item);
583+
});
584+
beaconCacheDate = null;
585+
}
586+
587+
Future<void> addShared(FindMyDevice item) async {
588+
await api.acceptBeaconShare(items: pushService.state!.icloudServices!.fmfd!, share: item.role!['sharingId']!);
589+
setState(() {
590+
devices.remove(item); // it'll go away anyways because it has no location
591+
});
592+
beaconCacheDate = null;
593+
getLocations();
594+
}
595+
575596
@override
576597
void dispose() {
577598
locationSub?.cancel();
@@ -592,10 +613,10 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
592613
.toList();
593614
final itemsWithLocation = devices
594615
.where(
595-
(item) => item.location?.latitude != null && item.isConsideredAccessory)
616+
(item) => (item.location?.latitude != null || item.role?["sharingActive"] == 0) && item.isConsideredAccessory)
596617
.toList();
597618
final withoutLocation =
598-
devices.where((item) => item.location?.latitude == null).toList();
619+
devices.where((item) => item.location?.latitude == null && item.role?["sharingActive"] != 0).toList();
599620
final devicesBodySlivers = [
600621
SliverList(
601622
delegate: SliverChildListDelegate([
@@ -748,10 +769,47 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
748769
findChildIndexCallback: (key) => findChildIndexByKey(itemsWithLocation, key, (item) => item.id ?? randomString(6)),
749770
itemBuilder: (context, i) {
750771
final item = itemsWithLocation[i];
751-
return ListTile(
772+
var tile = ListTile(
752773
key: ValueKey(item.id ?? randomString(6)),
753774
title: Text(ss.settings.redactedMode.value ? "Item" : (item.name ?? "Unknown Item")),
754-
subtitle: Text(ss.settings.redactedMode.value ? "Location" : (item.address?.label ?? item.address?.mapItemFullAddress ?? "No location found")),
775+
subtitle: item.role?["sharingActive"] == 0 ? Column(
776+
children: [
777+
Text("${item.role?["sharingName"]} wants to share this item with you."),
778+
Row(
779+
mainAxisAlignment: MainAxisAlignment.end,
780+
children: [
781+
ElevatedButton(
782+
style: ElevatedButton.styleFrom(
783+
backgroundColor: context.theme.colorScheme.outline.withAlpha(64),
784+
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 13),
785+
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
786+
elevation: 0.0,
787+
minimumSize: Size.zero,
788+
),
789+
onPressed: () async {
790+
pushService.wrapPromise(deleteShared(item), "Removing...");
791+
},
792+
child: Text("Don't Add", style: context.theme.textTheme.titleMedium),
793+
),
794+
const SizedBox(width: 10),
795+
ElevatedButton(
796+
style: ElevatedButton.styleFrom(
797+
backgroundColor: context.theme.colorScheme.primary,
798+
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 13),
799+
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
800+
elevation: 0.0,
801+
minimumSize: Size.zero,
802+
),
803+
onPressed: () async {
804+
pushService.wrapPromise(addShared(item), "Adding...");
805+
},
806+
child: Text("Add", style: context.theme.textTheme.titleMedium?.apply(color: context.theme.colorScheme.onPrimary)),
807+
),
808+
],
809+
)
810+
],
811+
)
812+
: Text(ss.settings.redactedMode.value ? "Location" : (item.address?.label ?? item.address?.mapItemFullAddress ?? "No location found")),
755813
trailing: item.location?.latitude != null && item.location?.longitude != null ? ButtonTheme(
756814
minWidth: 1,
757815
child: TextButton(
@@ -820,6 +878,10 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
820878
);
821879
},
822880
);
881+
if (item.role?['sharingId'] != null) {
882+
return wrapDelete(tile, (context) => deleteShared(item));
883+
}
884+
return tile;
823885
},
824886
itemCount: itemsWithLocation.length,
825887
),
@@ -838,7 +900,8 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
838900
shape: const RoundedRectangleBorder(side: BorderSide(color: Colors.transparent)),
839901
title: const Text("Devices without locations"),
840902
children: withoutLocation
841-
.map((item) => ListTile(
903+
.map((item) {
904+
var tile = ListTile(
842905
title: Text(ss.settings.redactedMode.value ? "Device" : (item.name ?? "Unknown Device")),
843906
subtitle: Text(ss.settings.redactedMode.value ? "Location" : (item.address?.label ?? item.address?.mapItemFullAddress ?? "No location found")),
844907
onTap: item.location?.latitude != null && item.location?.longitude != null
@@ -893,7 +956,12 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
893956
),
894957
);
895958
},
896-
))
959+
);
960+
if (item.role?['sharingId'] != null) {
961+
return wrapDelete(tile, (context) => deleteShared(item));
962+
}
963+
return tile;
964+
})
897965
.toList()),
898966
),
899967
],
@@ -1302,6 +1370,44 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
13021370
);
13031371
}
13041372

1373+
Widget wrapDelete(Widget child, Function(BuildContext) onPressed) {
1374+
return Slidable(
1375+
endActionPane: ActionPane(
1376+
motion: const StretchMotion(),
1377+
extentRatio: 0.30,
1378+
children: [
1379+
SlidableAction(
1380+
label: 'Remove',
1381+
backgroundColor: Colors.red,
1382+
icon: ss.settings.skin.value == Skins.iOS ? CupertinoIcons.trash : Icons.delete_outlined,
1383+
onPressed: (_) async {
1384+
showDialog(
1385+
context: context,
1386+
barrierDismissible: false,
1387+
builder: (BuildContext context) {
1388+
return AlertDialog(
1389+
backgroundColor: context.theme.colorScheme.properSurface,
1390+
title: Text(
1391+
"Deleting...",
1392+
style: context.theme.textTheme.titleLarge,
1393+
),
1394+
content: Container(
1395+
height: 70,
1396+
child: Center(child: buildProgressIndicator(context)),
1397+
),
1398+
);
1399+
}
1400+
);
1401+
await onPressed(context);
1402+
Get.back();
1403+
},
1404+
),
1405+
],
1406+
),
1407+
child: child,
1408+
);
1409+
}
1410+
13051411
Widget buildNormal(BuildContext context, List<SliverList> devicesBodySlivers, List<SliverList> friendsBodySlivers) {
13061412
return Obx(
13071413
() => Scaffold(
@@ -1684,16 +1790,38 @@ class _FindMyPageState extends OptimizedState<FindMyPage> with SingleTickerProvi
16841790
borderRadius: BorderRadius.circular(10),
16851791
color: context.theme.colorScheme.properSurface.withOpacity(0.8),
16861792
),
1687-
padding: const EdgeInsets.all(10),
1688-
child: Column(
1793+
padding: const EdgeInsets.fromLTRB(10, 10, 0, 10),
1794+
child: Row(
16891795
mainAxisSize: MainAxisSize.min,
1690-
crossAxisAlignment: CrossAxisAlignment.start,
16911796
children: [
1692-
Text(ss.settings.redactedMode.value ? "Device" : (item.name ?? "Unknown Device"), style: context.theme.textTheme.labelLarge),
1693-
Text(ss.settings.redactedMode.value ? "Location" : (item.location?.latitude != null ? "${item.location?.latitude}, ${item.location?.longitude}" : ""),
1694-
style: context.theme.textTheme.bodySmall),
1797+
Column(
1798+
mainAxisSize: MainAxisSize.min,
1799+
crossAxisAlignment: CrossAxisAlignment.start,
1800+
children: [
1801+
Text(ss.settings.redactedMode.value ? "Device" : (item.name ?? "Unknown Device"), style: context.theme.textTheme.labelLarge),
1802+
Text(ss.settings.redactedMode.value ? "Location" : (item.location?.latitude != null ? "${item.location?.latitude}, ${item.location?.longitude}" : ""),
1803+
style: context.theme.textTheme.bodySmall),
1804+
],
1805+
),
1806+
if (item.location?.latitude != null && item.location?.longitude != null)
1807+
ButtonTheme(
1808+
minWidth: 1,
1809+
child: TextButton(
1810+
style: TextButton.styleFrom(
1811+
shape: const CircleBorder(),
1812+
backgroundColor: context.theme.colorScheme.primaryContainer,
1813+
),
1814+
onPressed: () async {
1815+
await MapsLauncher.launchCoordinates(item.location!.latitude!, item.location!.longitude!);
1816+
},
1817+
child: const Icon(
1818+
Icons.directions,
1819+
size: 20
1820+
),
1821+
),
1822+
)
16951823
],
1696-
),
1824+
)
16971825
),
16981826
);
16991827
} else {

lib/services/backend/java_dart_interop/intents_service.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:async';
22

33
import 'package:bluebubbles/app/layouts/facetime/facetime.dart';
4+
import 'package:bluebubbles/app/layouts/findmy/findmy_page.dart';
45
import 'package:bluebubbles/app/layouts/settings/pages/misc/shared_streams_panel.dart';
56
import 'package:bluebubbles/app/layouts/settings/pages/profile/profile_panel.dart';
67
import 'package:bluebubbles/app/layouts/settings/pages/scheduling/scheduled_messages_panel.dart';
@@ -218,6 +219,9 @@ class IntentsService extends GetxService {
218219
} else if (guid == "-52") {
219220
Logger.debug("Opening shared streams panel...", tag: "IntentsService");
220221
ns.pushLeft(Get.context!, SharedStreamsPanel());
222+
} else if (guid == "-54") {
223+
Logger.debug("Opening find my panel...", tag: "IntentsService");
224+
ns.pushLeft(Get.context!, FindMyPage());
221225
} else if (guid.contains("scheduled")) {
222226
Logger.debug("Opening scheduled messages panel...", tag: "IntentsService");
223227
Navigator.of(Get.context!).push(

lib/services/backend/notifications/notifications_service.dart

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ import 'package:universal_html/html.dart' hide File, Platform, Navigator, Text;
3030
import 'package:universal_io/io.dart';
3131
import 'package:window_manager/window_manager.dart';
3232
import 'package:bluebubbles/src/rust/api/api.dart' as api;
33+
import 'package:bluebubbles/app/layouts/findmy/findmy_page.dart';
3334

3435
NotificationsService notif = Get.isRegistered<NotificationsService>() ? Get.find<NotificationsService>() : Get.put(NotificationsService());
3536

3637
class NotificationsService extends GetxService {
3738
static const String NEW_MESSAGE_CHANNEL = "com.bluebubbles.new_messages";
3839
static const String ERROR_CHANNEL = "com.bluebubbles.errors";
3940
static const String SHARED_STREAMS_CHANNEL = "com.bluebubbles.sharedstreams";
41+
static const String SHARED_BEACONS_CHANNEL = "com.bluebubbles.sharedbeacons";
4042
static const String REMINDER_CHANNEL = "com.bluebubbles.reminders";
4143
static const String FACETIME_CHANNEL = "com.bluebubbles.incoming_facetimes";
4244
static const String FOREGROUND_SERVICE_CHANNEL = "com.bluebubbles.foreground_service";
@@ -120,6 +122,11 @@ class NotificationsService extends GetxService {
120122
"Sync Status",
121123
"View the status of iCloud syncing."
122124
);
125+
createNotificationChannel(
126+
SHARED_BEACONS_CHANNEL,
127+
"Shared Items",
128+
"Displays invitations and updates for shared items."
129+
);
123130
}
124131

125132
// watch for new messages and handle the notification
@@ -1104,6 +1111,48 @@ class NotificationsService extends GetxService {
11041111
);
11051112
}
11061113

1114+
Future<void> createBeaconInvitation(Handle sender, api.BeaconAttributes attributes) async {
1115+
const title = "A new item has been shared with you!";
1116+
1117+
final subtitle =
1118+
"${sender.displayName} has shared the location of ${attributes.name} with you!";
1119+
if (kIsDesktop) {
1120+
failedToast = LocalNotification(
1121+
title: title,
1122+
body: subtitle,
1123+
actions: [],
1124+
);
1125+
1126+
failedToast!.onClick = () async {
1127+
failedToast = null;
1128+
await windowManager.show();
1129+
if (ss.settings.finishedSetup.value) {
1130+
ns.pushLeft(Get.context!, FindMyPage());
1131+
}
1132+
};
1133+
1134+
await failedToast!.show();
1135+
return;
1136+
}
1137+
await flnp.show(
1138+
-4 - 50 /* OB */,
1139+
title,
1140+
subtitle,
1141+
NotificationDetails(
1142+
android: AndroidNotificationDetails(
1143+
SHARED_BEACONS_CHANNEL,
1144+
'Shared Items',
1145+
channelDescription:
1146+
'Displays invitations and updates for shared items.',
1147+
priority: Priority.max,
1148+
importance: Importance.max,
1149+
color: HexColor("4990de"),
1150+
),
1151+
),
1152+
payload: "-54"
1153+
);
1154+
}
1155+
11071156
Future<void> createInvitation(api.SharedAlbum album) async {
11081157
const title = "Shared albums";
11091158
final subtitle =

lib/services/rustpush/rustpush_service.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3297,6 +3297,11 @@ class RustPushService extends GetxService {
32973297
return;
32983298
}
32993299

3300+
if (push is api.PushMessage_BeaconShared) {
3301+
notif.createBeaconInvitation(RustPushBBUtils.rustHandleToBB(push.sender), push.attributes);
3302+
return;
3303+
}
3304+
33003305
if (push is api.PushMessage_NewPhotostream) {
33013306
var state = push.field0;
33023307
notif.createInvitation(state);
@@ -3751,6 +3756,7 @@ class RustPushService extends GetxService {
37513756
"User-Agent": "OpenBubbles"
37523757
}
37533758
));
3759+
// Logger.info("Got location $request");
37543760
return Placemark(
37553761
name: request.data["name"],
37563762
isoCountryCode: request.data["address"]?["country_code"],

0 commit comments

Comments
 (0)