Skip to content

Add Static Location and Live Location Support #3531

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 118 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
340f10c
Add static and live location payloads
nuno-vieira Dec 11, 2024
92d15ac
Update the demo app to use static location attachment
nuno-vieira Dec 11, 2024
962ffb4
Add new `ChannelController.sendStaticLocation()` to instantly send a …
nuno-vieira Dec 11, 2024
cb365fc
Change the Demo App to send the current location instead of dummy ones
nuno-vieira Dec 11, 2024
8d3b662
Add location background mode to Demo App
nuno-vieira Dec 11, 2024
b35f54e
Add staticLocation to the attachments register
nuno-vieira Dec 12, 2024
afc3f68
Create a CurrentUserLocationProvider to make it easier to fetch the c…
nuno-vieira Dec 12, 2024
736c08e
Fix not being able to part live location payload
nuno-vieira Dec 12, 2024
154e394
Update DemoApp to support live location attachments
nuno-vieira Dec 13, 2024
3b4d322
Add support for partial message update in MessageUpdater
nuno-vieira Dec 13, 2024
b9e1f2c
Expose the Throttler (Revert this, and use it internally)
nuno-vieira Dec 13, 2024
ec2f59a
Add `shareLiveLocation()` and `stopLiveLocation()` to `ChannelControl…
nuno-vieira Jan 3, 2025
0a1e120
Refactor logic to fetch current active locations + Improve API
nuno-vieira Dec 16, 2024
61a6a70
Add extra data to location attachment payloads
nuno-vieira Dec 17, 2024
64881a1
Add `LocationAttachmentInfo` to be used as argument
nuno-vieira Dec 17, 2024
6f57eb9
Add `text` to partial message update endpoint
nuno-vieira Dec 17, 2024
caf76ab
Add `ChatMessageController.updateMessage()` to support partially upda…
nuno-vieira Dec 17, 2024
5d09df8
Change location live updates APIs
nuno-vieira Dec 17, 2024
667739f
Improve Demo App Location Provider
nuno-vieira Dec 18, 2024
713c5e8
Improve API by observing active live locations messages
nuno-vieira Dec 18, 2024
54d623f
Fixed starting monitoring location whenever an active location was up…
nuno-vieira Dec 18, 2024
495b358
Make the API even easier and add additional delegate methods to know …
nuno-vieira Dec 18, 2024
b09bf38
Move the Throttler to LLC, just like the Debouncer
nuno-vieira Dec 18, 2024
6468d0d
Move the Throttling to the current user controller to automatically p…
nuno-vieira Dec 18, 2024
effff35
Some minor cleanups
nuno-vieira Dec 18, 2024
1e684db
Simplify currentUserControllerDidStartSharingLiveLocation API
nuno-vieira Dec 19, 2024
81ad0f4
Improve location attachment view
nuno-vieira Dec 20, 2024
9ded30e
Add live location map when tapping the live location attachmen
nuno-vieira Dec 23, 2024
7df6c3e
Animate the user location tracking
nuno-vieira Dec 23, 2024
b91fa09
Optimal animation
nuno-vieira Dec 23, 2024
e50a63b
Optmizate animation smoothness and server spamm
nuno-vieira Dec 23, 2024
1e9ef44
Add CoreData concurrency flag to StreamDevelopers scheme
nuno-vieira Dec 26, 2024
98afdba
Fix crash when creating the MessageController from a background thread
nuno-vieira Dec 26, 2024
7a0bd66
Fix snapshot chaching
nuno-vieira Dec 26, 2024
ecbb7e2
Fix map detail view controller not showing initial position
nuno-vieira Dec 26, 2024
ec15a92
Fix avatar view in map view
nuno-vieira Jan 2, 2025
5e3881d
Add pulse animation when live sharing
nuno-vieira Jan 2, 2025
64bcf7e
Refactor LocationDetailViewController to only use the message controller
nuno-vieira Jan 2, 2025
f3460be
Refactor code structure of the map detail view controller
nuno-vieira Jan 2, 2025
0af646e
Add bottom sheet to stop location sharing
nuno-vieira Jan 3, 2025
544b506
Add stopLiveLocationSharing() to Message Controller
nuno-vieira Jan 3, 2025
0777d9f
Fix stop sharing button not working
nuno-vieira Jan 3, 2025
b2dc128
FIx bottom sheet logic
nuno-vieira Jan 3, 2025
45ad2f1
Add static pin in detail view
nuno-vieira Jan 3, 2025
a3a252e
Fix sharing location for other users active location messages
nuno-vieira Jan 3, 2025
6cf17a3
Finish logic for location snapshot view when static vs live
nuno-vieira Jan 3, 2025
26a5e72
Add live location status view in the snapshot view
nuno-vieira Jan 3, 2025
e9be9de
Minor cleanup
nuno-vieira Jan 3, 2025
37713b8
Fix copyright
nuno-vieira Jan 3, 2025
ef4a214
Fix loading indicator snapshot view
nuno-vieira Jan 3, 2025
722cbfc
Remove support of mixed attachments to locations
nuno-vieira Jan 3, 2025
a347d00
Add MessageEndpoints test coverage
nuno-vieira Jan 6, 2025
c58a3c3
Add test coverage to message updater
nuno-vieira Jan 6, 2025
776a991
Add test coverage to Message Repository
nuno-vieira Jan 6, 2025
d3dfd2e
Add test coverage to message attachments extensions
nuno-vieira Jan 6, 2025
047f962
Add test coverage to parsing attachments
nuno-vieira Jan 6, 2025
ea5802e
Add test coverage to MessageDTO
nuno-vieira Jan 6, 2025
4712649
Add message updater mock
nuno-vieira Jan 6, 2025
0a46f19
ActiveLiveLocationAlreadyExists init should not be public
nuno-vieira Jan 6, 2025
6be1283
Add test coverage to Message Controller
nuno-vieira Jan 6, 2025
5044362
Add test coverage to Channel Controller
nuno-vieira Jan 6, 2025
2957975
Fix concurrency issues when stopping and updating the live location a…
nuno-vieira Jan 7, 2025
a73406b
Fix Message Controller Tests
nuno-vieira Jan 7, 2025
e203dfa
Change updateMessage -> partialUpdateMessage
nuno-vieira Jan 7, 2025
5c30dbe
Add unset support for partial update message
nuno-vieira Jan 7, 2025
1d7ab64
Update CHANGELOG.md
nuno-vieira Jan 7, 2025
251fa84
Fix tests, not compiling because of unset
nuno-vieira Jan 7, 2025
a211f5d
Fix test_updatePartialMessage_makesCorrectAPICall
nuno-vieira Jan 7, 2025
5b64e9a
Make `ChatMessageController.updateLiveLocation()` internal
nuno-vieira Jan 7, 2025
95d359f
Update CHANGELOG.md
nuno-vieira Jan 7, 2025
a8bf8de
Fix reloading the snapshot when not necesasry
nuno-vieira Jan 8, 2025
9c351a8
Fix avatar view showing for a split second in the snapshot view for s…
nuno-vieira Jan 8, 2025
b035ed6
Change location attachment to have dynamic height depending on messag…
nuno-vieira Jan 8, 2025
2ff83a0
Extract avatar size in snapshot view
nuno-vieira Jan 8, 2025
3c0cf34
Fix minor typo
nuno-vieira Jan 8, 2025
601fded
Add fixed width to map snapshot and simplify caching logic
nuno-vieira Jan 8, 2025
e8b6587
Use a banner view instead of a sheet in the map detail view
nuno-vieira Jan 8, 2025
61882ef
Present the map instead of pushing when on iPad
nuno-vieira Jan 8, 2025
f7dbf60
Add more documentation on how "Tracking" behaviour works
nuno-vieira Jan 8, 2025
7b60602
Enable locations by default in the demo app
nuno-vieira Jan 8, 2025
cb135d0
Fix quote message for live location
nuno-vieira Jan 9, 2025
f3d7025
Fix location attachments should not be editable
nuno-vieira Jan 9, 2025
5952dd6
Do not show location attachment picker when inside thread
nuno-vieira Jan 9, 2025
cd18446
Fix preview message for location attachments
nuno-vieira Jan 9, 2025
77baae2
Fix detail map show Stop Sharing button for another user
nuno-vieira Jan 9, 2025
0765272
Disable locations feature by default
nuno-vieira Jan 9, 2025
d06eda0
Add experimental flag
nuno-vieira Jan 9, 2025
37bd1d1
Update CHANGELOG.md
nuno-vieira Jan 9, 2025
81cc2ee
Revert "Disable locations feature by default"
nuno-vieira Jan 10, 2025
2c5b331
Revert "Add experimental flag"
nuno-vieira Jan 10, 2025
d7728de
Revert "Update CHANGELOG.md"
nuno-vieira Jan 10, 2025
70fda95
Merge branch 'develop' into add/location-attachments
nuno-vieira May 9, 2025
6f277d5
Fix missing stuff from merge conflicts
nuno-vieira May 9, 2025
9d2b557
Merge branch 'develop' into add/location-attachments
nuno-vieira May 9, 2025
6748eed
Update CHANGELOG.md
nuno-vieira May 9, 2025
1048c22
Rename LocationAttachmentInfo to LocationInfo
nuno-vieira May 12, 2025
25d3705
Add LocationDTO
nuno-vieira May 14, 2025
46c5fc6
Update Atlantis
nuno-vieira May 26, 2025
d4de8f8
Refactor to new SharedLocation object
nuno-vieira May 26, 2025
58653d8
Demo App new Static Location integration
nuno-vieira Jun 2, 2025
5775d32
Merge branch 'develop' into add/location-attachments
nuno-vieira Jun 2, 2025
ce36472
Fix Merge Conflicts Errors
nuno-vieira Jun 2, 2025
7ab0dd8
Add new location endpoints
nuno-vieira Jun 2, 2025
356cb17
Implement new stop live location sharing
nuno-vieira Jun 2, 2025
001e2af
Remove stopLiveLocationSharing from ChannelController
nuno-vieira Jun 2, 2025
9311f09
Implement optimistic stop live location sharing
nuno-vieira Jun 2, 2025
9a24297
Add `CurrentUserController.loadLiveLocations()`
nuno-vieira Jun 4, 2025
6c1f1b0
Add new updateLiveLocation endpoint instead of using partial update m…
nuno-vieira Jun 4, 2025
9a4d314
Migrate existing unit tests to the new implementation
nuno-vieira Jun 4, 2025
334c6e4
Fix not possible to open location detail view
nuno-vieira Jun 6, 2025
47cc6c2
Remove unnecessary location optimistic update
nuno-vieira Jun 6, 2025
62fe047
Auto-centering feature
nuno-vieira Jun 6, 2025
875edd8
Fix sharing location of failed messages
nuno-vieira Jun 6, 2025
4b8181f
Fix stopping a live location not triggering didStopLiveLocationSharin…
nuno-vieira Jun 6, 2025
c07f7ad
Update CHANGELOG.md
nuno-vieira Jun 6, 2025
97c1e79
Fix parsing active current active location messages
nuno-vieira Jun 6, 2025
b4033af
Remove unused message updater in ChannelController
nuno-vieira Jun 6, 2025
96e5fc6
Fix docs typo
nuno-vieira Jun 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
# Upcoming

## StreamChat
### ✅ Added
- Add `ChatMessageController.partialUpdateMessage()` [#3531](https://github.com/GetStream/stream-chat-swift/pull/3531)
- Add Location Sharing Support [#3531](https://github.com/GetStream/stream-chat-swift/pull/3531)
- Add `ChatMessage.sharedLocation`
- Add `ChatMessageController.stopLiveLocationSharing()`
- Add `ChatChannelController`:
- `sendStaticLocation()`
- `startLiveLocationSharing()`
- Add `CurrentChatUserController`:
- `loadActiveLiveLocationMessages()`
- `updateLiveLocation()`
- Add `CurrentChatUserControllerDelegate`:
- `didStartSharingLiveLocation()`
- `didStopSharingLiveLocation()`
- `didChangeActiveLiveLocationMessages()`
### 🐞 Fixed
- Fix an issue where completion handler was called twice after waiting for token refresh [#3683](https://github.com/GetStream/stream-chat-swift/pull/3683)

Expand Down
34 changes: 22 additions & 12 deletions DemoApp/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSBonjourServices</key>
<array>
<string>_Proxyman._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>Atlantis would use Bonjour Service to discover Proxyman app from your local network.</string>
<key>NSCameraUsageDescription</key>
<string>We need access to your camera for sending photo attachments.</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need access to your microphone for taking a video.</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
Expand All @@ -30,8 +20,26 @@
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string></string>
Comment on lines +23 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove or set proper value for LSApplicationCategoryType.

The LSApplicationCategoryType key is set to an empty string, which is not a valid App Store category. Either remove this key entirely or set it to a proper category like "public.app-category.social-networking".

-	<key>LSApplicationCategoryType</key>
-	<string></string>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<key>LSApplicationCategoryType</key>
<string></string>
🤖 Prompt for AI Agents
In DemoApp/Info.plist at lines 23 to 24, the LSApplicationCategoryType key is
set to an empty string, which is invalid. Fix this by either removing the
LSApplicationCategoryType key entirely or setting its value to a valid App Store
category string such as "public.app-category.social-networking".

<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSBonjourServices</key>
<array>
<string>_Proxyman._tcp</string>
</array>
<key>NSCameraUsageDescription</key>
<string>We need access to your camera for sending photo attachments.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Atlantis would use Bonjour Service to discover Proxyman app from your local network.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We need access to your location to share it in the chat.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need access to your location to share it in the chat.</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need access to your microphone for taking a video.</string>
<key>PushNotification-Configuration</key>
<string>APN-Configuration</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
Expand All @@ -51,6 +59,10 @@
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
Expand All @@ -70,7 +82,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>PushNotification-Configuration</key>
<string>APN-Configuration</string>
</dict>
</plist>
99 changes: 99 additions & 0 deletions DemoApp/LocationProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// Copyright © 2025 Stream.io Inc. All rights reserved.
//

import CoreLocation
import Foundation

enum LocationPermissionError: Error {
case permissionDenied
case permissionRestricted
}

class LocationProvider: NSObject {
private let locationManager: CLLocationManager
private var onCurrentLocationFetch: ((Result<CLLocation, Error>) -> Void)?

var didUpdateLocation: ((CLLocation) -> Void)?
var lastLocation: CLLocation?
var onError: ((Error) -> Void)?

private init(locationManager: CLLocationManager = CLLocationManager()) {
self.locationManager = locationManager
super.init()
}

static let shared = LocationProvider()

var isMonitoringLocation: Bool {
locationManager.delegate != nil
}

func startMonitoringLocation() {
locationManager.allowsBackgroundLocationUpdates = true
locationManager.delegate = self
requestPermission { [weak self] error in
guard let error else { return }
self?.onError?(error)
}
}

func stopMonitoringLocation() {
locationManager.allowsBackgroundLocationUpdates = false
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
}

func getCurrentLocation(completion: @escaping (Result<CLLocation, Error>) -> Void) {
onCurrentLocationFetch = completion
if let lastLocation = lastLocation {
onCurrentLocationFetch?(.success(lastLocation))
onCurrentLocationFetch = nil
} else {
requestPermission { [weak self] error in
guard let error else { return }
self?.onCurrentLocationFetch?(.failure(error))
self?.onCurrentLocationFetch = nil
}
}
}

func requestPermission(completion: @escaping (Error?) -> Void) {
locationManager.delegate = self
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
completion(nil)
case .authorizedWhenInUse, .authorizedAlways:
locationManager.startUpdatingLocation()
completion(nil)
case .denied:
completion(LocationPermissionError.permissionDenied)
case .restricted:
completion(LocationPermissionError.permissionRestricted)
@unknown default:
break
}
}
Comment on lines +61 to +77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Premature success callback can mis-signal permission status

Inside requestPermission the .notDetermined branch calls completion(nil) before the user makes a choice.
Callers such as getCurrentLocation interpret a nil error as success and therefore keep waiting silently until locationManager(_:didUpdateLocations:) fires – which might never happen if the user denies permission.

Consider:

case .notDetermined:
-    locationManager.requestWhenInUseAuthorization()
-    completion(nil)
+    locationManager.requestWhenInUseAuthorization()
+    // Defer completion until we know the final status in
+    // `locationManagerDidChangeAuthorization`.
+    return

and move the completion invocation to locationManagerDidChangeAuthorization once the status is no longer .notDetermined.
This prevents hanging promises and provides deterministic error reporting.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func requestPermission(completion: @escaping (Error?) -> Void) {
locationManager.delegate = self
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
completion(nil)
case .authorizedWhenInUse, .authorizedAlways:
locationManager.startUpdatingLocation()
completion(nil)
case .denied:
completion(LocationPermissionError.permissionDenied)
case .restricted:
completion(LocationPermissionError.permissionRestricted)
@unknown default:
break
}
}
func requestPermission(completion: @escaping (Error?) -> Void) {
locationManager.delegate = self
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
// Defer completion until we know the final status in
// `locationManagerDidChangeAuthorization`.
return
case .authorizedWhenInUse, .authorizedAlways:
locationManager.startUpdatingLocation()
completion(nil)
case .denied:
completion(LocationPermissionError.permissionDenied)
case .restricted:
completion(LocationPermissionError.permissionRestricted)
@unknown default:
break
}
}
🤖 Prompt for AI Agents
In DemoApp/LocationProvider.swift around lines 61 to 77, the requestPermission
function calls completion(nil) immediately when the authorization status is
.notDetermined, which signals success prematurely before the user grants or
denies permission. To fix this, remove the completion(nil) call from the
.notDetermined case and instead invoke the completion handler inside the
locationManagerDidChangeAuthorization delegate method once the authorization
status changes from .notDetermined to a definitive state, passing nil on success
or an appropriate error on failure. This ensures the completion callback
accurately reflects the user's permission decision and prevents hanging
promises.

}

extension LocationProvider: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus
if status == .authorizedWhenInUse || status == .authorizedAlways {
manager.startUpdatingLocation()
}
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
didUpdateLocation?(location)
lastLocation = location
onCurrentLocationFetch?(.success(location))
onCurrentLocationFetch = nil
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
onError?(error)
}
Comment on lines +88 to +98
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Dispatch location callbacks on the main queue

CLLocationManager delivers delegate callbacks on a private queue.
Forwarding those values directly to didUpdateLocation / onError can easily lead to UIKit-related crashes if the receiver performs UI work.

-        didUpdateLocation?(location)
+        DispatchQueue.main.async { [weak self] in
+            self?.didUpdateLocation?(location)
+        }
...
-        onError?(error)
+        DispatchQueue.main.async { [weak self] in
+            self?.onError?(error)
+        }

Making the dispatch explicit keeps UI handling code safe without forcing every caller to hop to the main thread themselves.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
didUpdateLocation?(location)
lastLocation = location
onCurrentLocationFetch?(.success(location))
onCurrentLocationFetch = nil
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
onError?(error)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
DispatchQueue.main.async { [weak self] in
self?.didUpdateLocation?(location)
}
lastLocation = location
onCurrentLocationFetch?(.success(location))
onCurrentLocationFetch = nil
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
DispatchQueue.main.async { [weak self] in
self?.onError?(error)
}
}
🤖 Prompt for AI Agents
In DemoApp/LocationProvider.swift around lines 88 to 98, the delegate callbacks
from CLLocationManager are forwarded directly to didUpdateLocation,
onCurrentLocationFetch, and onError closures, which may cause UIKit crashes
since these callbacks are not guaranteed to be on the main thread. To fix this,
wrap all calls to these closures inside DispatchQueue.main.async blocks to
ensure they are executed on the main queue, making UI updates safe.

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class AppConfig {
isHardDeleteEnabled: false,
isAtlantisEnabled: false,
isMessageDebuggerEnabled: false,
isLocationAttachmentsEnabled: false,
isLocationAttachmentsEnabled: true,
tokenRefreshDetails: nil,
shouldShowConnectionBanner: false,
isPremiumMemberFeatureEnabled: false,
Expand All @@ -59,8 +59,6 @@ class AppConfig {
if StreamRuntimeCheck.isStreamInternalConfiguration {
demoAppConfig.isAtlantisEnabled = true
demoAppConfig.isMessageDebuggerEnabled = true
demoAppConfig.isLocationAttachmentsEnabled = true
demoAppConfig.isLocationAttachmentsEnabled = true
demoAppConfig.isHardDeleteEnabled = true
demoAppConfig.shouldShowConnectionBanner = true
demoAppConfig.isPremiumMemberFeatureEnabled = true
Expand Down
42 changes: 42 additions & 0 deletions DemoApp/Screens/DemoAppTabBarController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
// Copyright © 2025 Stream.io Inc. All rights reserved.
//

import Combine
import StreamChat
import StreamChatUI
import UIKit

class DemoAppTabBarController: UITabBarController, CurrentChatUserControllerDelegate {
private var locationProvider = LocationProvider.shared

let channelListVC: UIViewController
let threadListVC: UIViewController
let draftListVC: UIViewController
Expand Down Expand Up @@ -50,6 +53,7 @@ class DemoAppTabBarController: UITabBarController, CurrentChatUserControllerDele
super.viewDidLoad()

currentUserController.delegate = self
currentUserController.loadActiveLiveLocationMessages()
unreadCount = currentUserController.unreadCount

tabBar.backgroundColor = Appearance.default.colorPalette.background
Expand All @@ -63,6 +67,13 @@ class DemoAppTabBarController: UITabBarController, CurrentChatUserControllerDele
threadListVC.tabBarItem.image = UIImage(systemName: "text.bubble")
threadListVC.tabBarItem.badgeColor = .red

locationProvider.didUpdateLocation = { [weak self] location in
let newLocation = LocationInfo(
latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude
)
self?.currentUserController.updateLiveLocation(newLocation)
}
draftListVC.tabBarItem.title = "Drafts"
draftListVC.tabBarItem.image = UIImage(systemName: "bubble.and.pencil")

Expand All @@ -75,4 +86,35 @@ class DemoAppTabBarController: UITabBarController, CurrentChatUserControllerDele
let totalUnreadBadge = unreadCount.channels + unreadCount.threads
UIApplication.shared.applicationIconBadgeNumber = totalUnreadBadge
}

func currentUserControllerDidStartSharingLiveLocation(
_ controller: CurrentChatUserController
) {
debugPrint("[Location] Started sharing live location.")
locationProvider.startMonitoringLocation()
}

func currentUserControllerDidStopSharingLiveLocation(_ controller: CurrentChatUserController) {
debugPrint("[Location] Stopped sharing live location.")
locationProvider.stopMonitoringLocation()
}

func currentUserController(
_ controller: CurrentChatUserController,
didChangeActiveLiveLocationMessages messages: [ChatMessage]
) {
guard !messages.isEmpty else {
return
}

let locations: [String] = messages.compactMap {
guard let location = $0.sharedLocation else {
return nil
}

return "(lat:\(location.latitude), lon:\(location.longitude), endAt: \(location.endAt?.description ?? "nil"))"
}

debugPrint("[Location] Updated live locations to the server: \(locations)")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import StreamChatUI
class DemoAttachmentViewCatalog: AttachmentViewCatalog {
override class func attachmentViewInjectorClassFor(message: ChatMessage, components: Components) -> AttachmentViewInjector.Type? {
let hasMultipleAttachmentTypes = message.attachmentCounts.keys.count > 1
let hasLocationAttachment = message.attachmentCounts.keys.contains(.location)
let hasLocationAttachment = message.sharedLocation != nil
if AppConfig.shared.demoAppConfig.isLocationAttachmentsEnabled && hasLocationAttachment {
if hasMultipleAttachmentTypes {
return MixedAttachmentViewInjector.self
Expand Down
Loading