Skip to content

Commit 2860ec7

Browse files
Merge pull request #1445 from matrix-org/steve/6021_beacon_event
[Location sharing] Handle live location beacon event
2 parents 2cef369 + 60bb2d8 commit 2860ec7

27 files changed

+1346
-30
lines changed

MatrixSDK.xcodeproj/project.pbxproj

Lines changed: 86 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//
2+
// Copyright 2022 The Matrix.org Foundation C.I.C
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
import Foundation
18+
19+
/// MXBeaconAggregations aggregates related beacon info events and beacon info events into a summary object MXBeaconInfoSummary
20+
@objcMembers
21+
public class MXBeaconAggregations: NSObject {
22+
23+
// MARK: - Properties
24+
25+
private unowned let session: MXSession
26+
27+
private var listeners: [MXBeaconInfoSummaryListener] = []
28+
29+
private var beaconInfoSummaryStore: MXBeaconInfoSummaryStoreProtocol
30+
31+
// MARK: - Setup
32+
33+
public init(session: MXSession, store: MXBeaconInfoSummaryStoreProtocol) {
34+
self.session = session
35+
self.beaconInfoSummaryStore = store
36+
37+
super.init()
38+
}
39+
40+
// MARK: - Public
41+
42+
/// Get MXBeaconInfoSummary from the first beacon info event id
43+
public func beaconInfoSummary(for eventId: String, inRoomWithId roomId: String) -> MXBeaconInfoSummaryProtocol? {
44+
return self.beaconInfoSummaryStore.getBeaconInfoSummary(withIdentifier: eventId, inRoomWithId: roomId)
45+
}
46+
47+
/// Update a MXBeaconInfoSummary device id that belongs to the current user.
48+
/// Enables to recognize that a beacon info has been started on the device
49+
public func updateBeaconInfoSummary(with eventId: String, deviceId: String, inRoomWithId roomId: String) {
50+
guard let beaconInfoSummary = self.beaconInfoSummaryStore.getBeaconInfoSummary(withIdentifier: eventId, inRoomWithId: roomId) else {
51+
return
52+
}
53+
54+
guard beaconInfoSummary.userId == session.myUserId else {
55+
return
56+
}
57+
58+
if beaconInfoSummary.updateWithDeviceId(deviceId) {
59+
self.notifyBeaconInfoSummaryListeners(ofRoomWithId: roomId, beaconInfoSummary: beaconInfoSummary)
60+
}
61+
}
62+
63+
public func clearData(inRoomWithId roomId: String) {
64+
// TODO: Notify data clear
65+
self.beaconInfoSummaryStore.deleteAllBeaconInfoSummaries(inRoomWithId: roomId)
66+
}
67+
68+
// MARK: Data update
69+
70+
public func handleBeacon(event: MXEvent) {
71+
guard let roomId = event.roomId else {
72+
return
73+
}
74+
75+
guard let beacon = MXBeacon(mxEvent: event) else {
76+
return
77+
}
78+
79+
guard let beaconInfoSummary = self.getBeaconInfoSummary(withIdentifier: beacon.beaconInfoEventId, inRoomWithId: roomId), self.canAddBeacon(beacon, to: beaconInfoSummary) else {
80+
return
81+
}
82+
83+
if beaconInfoSummary.updateWithLastBeacon(beacon) {
84+
self.notifyBeaconInfoSummaryListeners(ofRoomWithId: roomId, beaconInfoSummary: beaconInfoSummary)
85+
}
86+
}
87+
88+
public func handleBeaconInfo(event: MXEvent) {
89+
guard let roomId = event.roomId else {
90+
return
91+
}
92+
93+
guard let beaconInfo = MXBeaconInfo(mxEvent: event) else {
94+
return
95+
}
96+
97+
self.addOrUpdateBeaconInfo(beaconInfo, inRoomWithId: roomId)
98+
}
99+
100+
// MARK: Data update listener
101+
102+
public func listenToBeaconInfoSummaryUpdateInRoom(withId roomId: String, handler: @escaping (MXBeaconInfoSummaryProtocol) -> Void) -> Any? {
103+
let listener = MXBeaconInfoSummaryListener(roomId: roomId, notificationHandler: handler)
104+
105+
listeners.append(listener)
106+
107+
return listener
108+
}
109+
110+
public func removeListener(_ listener: Any) {
111+
guard let beaconInfoSummaryListener = listener as? MXBeaconInfoSummaryListener else {
112+
return
113+
}
114+
115+
listeners.removeAll(where: { $0 === beaconInfoSummaryListener })
116+
}
117+
118+
// MARK: - Private
119+
120+
private func addOrUpdateBeaconInfo(_ beaconInfo: MXBeaconInfo, inRoomWithId roomId: String) {
121+
122+
guard let eventId = beaconInfo.originalEvent?.eventId else {
123+
return
124+
}
125+
126+
var beaconInfoSummary: MXBeaconInfoSummary?
127+
128+
if let existingBeaconInfoSummary = self.getBeaconInfoSummary(withIdentifier: eventId, inRoomWithId: roomId) {
129+
130+
// If beacon info is older than existing one, do not take it into account
131+
if beaconInfo.timestamp > existingBeaconInfoSummary.beaconInfo.timestamp {
132+
existingBeaconInfoSummary.updateWithBeaconInfo(beaconInfo)
133+
beaconInfoSummary = existingBeaconInfoSummary
134+
}
135+
} else {
136+
beaconInfoSummary = MXBeaconInfoSummary(beaconInfo: beaconInfo)
137+
}
138+
139+
if let beaconInfoSummary = beaconInfoSummary {
140+
self.beaconInfoSummaryStore.addOrUpdateBeaconInfoSummary(beaconInfoSummary, inRoomWithId: roomId)
141+
142+
self.notifyBeaconInfoSummaryListeners(ofRoomWithId: roomId, beaconInfoSummary: beaconInfoSummary)
143+
}
144+
}
145+
146+
private func canAddBeacon(_ beacon: MXBeacon, to beaconInfoSummary: MXBeaconInfoSummary) -> Bool {
147+
148+
guard beaconInfoSummary.hasStopped == false, beaconInfoSummary.hasExpired == false,
149+
beacon.timestamp < beaconInfoSummary.expiryTimestamp else {
150+
return false
151+
}
152+
153+
if let lastBeacon = beaconInfoSummary.lastBeacon, beacon.timestamp < lastBeacon.timestamp {
154+
return false
155+
}
156+
157+
return true
158+
}
159+
160+
private func notifyBeaconInfoSummaryListeners(ofRoomWithId roomId: String, beaconInfoSummary: MXBeaconInfoSummary) {
161+
162+
for listener in listeners where listener.roomId == roomId {
163+
listener.notificationHandler(beaconInfoSummary)
164+
}
165+
}
166+
167+
/// Get MXBeaconInfoSummary class instead of MXBeaconInfoSummaryProtocol to have access to internal methods
168+
private func getBeaconInfoSummary(withIdentifier identifier: String, inRoomWithId roomId: String) -> MXBeaconInfoSummary? {
169+
return self.beaconInfoSummaryStore.getBeaconInfoSummary(withIdentifier: identifier, inRoomWithId: roomId)
170+
}
171+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// Copyright 2022 The Matrix.org Foundation C.I.C
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
import Foundation
18+
19+
@objcMembers
20+
public class MXBeaconInfoSummaryListener: NSObject {
21+
22+
// MARK: - Properties
23+
24+
let roomId: String
25+
let notificationHandler: ((MXBeaconInfoSummary) -> Void)
26+
27+
// MARK: - Setup
28+
29+
init(roomId: String, notificationHandler: @escaping ((MXBeaconInfoSummaryProtocol) -> Void)) {
30+
self.roomId = roomId
31+
self.notificationHandler = notificationHandler
32+
super.init()
33+
}
34+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// Copyright 2022 The Matrix.org Foundation C.I.C
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
import Foundation
18+
19+
/// MXBeaconInfoSummary memory store
20+
public class MXBeaconInfoSummaryMemoryStore: NSObject, MXBeaconInfoSummaryStoreProtocol {
21+
22+
// MARK: - Properties
23+
24+
private var beaconInfoSummaries: [String: [MXBeaconInfoSummary]] = [:]
25+
26+
// MARK: - Public
27+
28+
public func addOrUpdateBeaconInfoSummary(_ beaconInfoSummary: MXBeaconInfoSummary, inRoomWithId roomId: String) {
29+
30+
var beaconInfoSummaries: [MXBeaconInfoSummary] = self.beaconInfoSummaries[roomId] ?? []
31+
32+
let existingIndex = beaconInfoSummaries.firstIndex { summary in
33+
return summary.id == beaconInfoSummary.id
34+
}
35+
36+
if let existingIndex = existingIndex {
37+
beaconInfoSummaries[existingIndex] = beaconInfoSummary
38+
} else {
39+
beaconInfoSummaries.append(beaconInfoSummary)
40+
}
41+
42+
self.beaconInfoSummaries[roomId] = beaconInfoSummaries
43+
}
44+
45+
public func getBeaconInfoSummary(withIdentifier identifier: String, inRoomWithId roomId: String) -> MXBeaconInfoSummary? {
46+
guard let roomBeaconInfoSummaries = self.beaconInfoSummaries[roomId] else {
47+
return nil
48+
}
49+
50+
return roomBeaconInfoSummaries.first { beaconInfoSummary in
51+
return beaconInfoSummary.id == identifier
52+
}
53+
}
54+
55+
public func deleteAllBeaconInfoSummaries(inRoomWithId roomId: String) {
56+
self.beaconInfoSummaries[roomId] = nil
57+
}
58+
59+
public func deleteAllBeaconInfoSummaries() {
60+
self.beaconInfoSummaries = [:]
61+
}
62+
}
63+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// Copyright 2022 The Matrix.org Foundation C.I.C
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
import Foundation
18+
19+
/// Represents MXBeaconInfoSummary store
20+
@objc public protocol MXBeaconInfoSummaryStoreProtocol: AnyObject {
21+
22+
/// Add or update a MXBeaconInfoSummary for a given room
23+
func addOrUpdateBeaconInfoSummary(_ beaconInfoSummary: MXBeaconInfoSummary, inRoomWithId roomId: String)
24+
25+
/// Get a MXBeaconInfoSummary from his identifier in a given room. The identifier is the first beacon info event id.
26+
func getBeaconInfoSummary(withIdentifier identifier: String, inRoomWithId roomId: String) -> MXBeaconInfoSummary?
27+
28+
/// Delete all MXBeaconInfoSummary in a room
29+
func deleteAllBeaconInfoSummaries(inRoomWithId roomId: String)
30+
31+
/// Delete all MXBeaconInfoSummary
32+
func deleteAllBeaconInfoSummaries()
33+
}

MatrixSDK/Aggregations/MXAggregations.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@
2626

2727
NS_ASSUME_NONNULL_BEGIN
2828

29+
@class MXBeaconAggregations;
30+
2931
/**
3032
The `MXAggregations` class instance manages the Matrix aggregations API.
3133
*/
3234
@interface MXAggregations : NSObject
3335

36+
@property (nonatomic, strong, readonly) MXBeaconAggregations *beaconAggegations;
3437

3538
#pragma mark - Reactions
3639

MatrixSDK/Aggregations/MXAggregations.m

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#import "MXEventEditsListener.h"
3131
#import "MXAggregationPaginatedResponse_Private.h"
3232

33+
#import "MatrixSDKSwiftHeader.h"
34+
3335
@interface MXAggregations ()
3436

3537
@property (nonatomic, weak) MXSession *mxSession;
@@ -38,6 +40,9 @@ @interface MXAggregations ()
3840
@property (nonatomic) MXAggregatedEditsUpdater *aggregatedEditsUpdater;
3941
@property (nonatomic) MXAggregatedReferencesUpdater *aggregatedReferencesUpdater;
4042

43+
@property (nonatomic, strong, readwrite) MXBeaconAggregations *beaconAggegations;
44+
@property (nonatomic, strong) id<MXBeaconInfoSummaryStoreProtocol> beaconInfoSummaryStore;
45+
4146
@end
4247

4348

@@ -85,6 +90,10 @@ - (void)removeListener:(id)listener
8590
{
8691
[self.aggregatedEditsUpdater removeListener:listener];
8792
}
93+
else if ([listener isKindOfClass:[MXBeaconInfoSummaryListener class]])
94+
{
95+
[self.beaconAggegations removeListener:listener];
96+
}
8897
}
8998

9099
- (MXHTTPOperation*)reactionsEventsForEvent:(NSString*)eventId
@@ -222,6 +231,7 @@ - (void)resetData
222231
{
223232
MXLogDebug(@"[MXAggregations] Reset data")
224233
[self.store deleteAll];
234+
[self.beaconInfoSummaryStore deleteAllBeaconInfoSummaries];
225235
}
226236

227237

@@ -241,6 +251,13 @@ - (instancetype)initWithMatrixSession:(MXSession *)mxSession
241251
matrixStore:mxSession.store];
242252
self.aggregatedReferencesUpdater = [[MXAggregatedReferencesUpdater alloc] initWithMatrixSession:self.mxSession
243253
matrixStore:mxSession.store];
254+
255+
// TODO: Persist MXBeaconInfoSummary to a file database
256+
id<MXBeaconInfoSummaryStoreProtocol> beaconInfoSummaryStore = [MXBeaconInfoSummaryMemoryStore new];
257+
258+
self.beaconInfoSummaryStore = beaconInfoSummaryStore;
259+
260+
self.beaconAggegations = [[MXBeaconAggregations alloc] initWithSession:self.mxSession store:beaconInfoSummaryStore];
244261

245262
[self registerListener];
246263
}
@@ -260,6 +277,7 @@ - (void)handleOriginalDataOfEvent:(MXEvent *)event
260277
- (void)resetDataInRoom:(NSString *)roomId
261278
{
262279
[self.aggregatedReactionsUpdater resetDataInRoom:roomId];
280+
[self.beaconAggegations clearDataInRoomWithId:roomId];
263281
}
264282

265283

@@ -287,6 +305,15 @@ - (void)registerListener
287305
[self.aggregatedReactionsUpdater handleRedaction:event];
288306
}
289307
break;
308+
case MXEventTypeBeaconInfo:
309+
[self.beaconAggegations handleBeaconInfoWithEvent:event];
310+
break;
311+
case MXEventTypeBeacon:
312+
if (direction == MXTimelineDirectionForwards)
313+
{
314+
[self.beaconAggegations handleBeaconWithEvent:event];
315+
}
316+
break;
290317
default:
291318
break;
292319
}

0 commit comments

Comments
 (0)