Skip to content

Commit 0480d29

Browse files
authored
[Demo]Integrate Gleap for better feedback reporting (#722)
1 parent 2e5c856 commit 0480d29

File tree

6 files changed

+193
-23
lines changed

6 files changed

+193
-23
lines changed

DemoApp/Sources/Components/AppState.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ final class AppState: ObservableObject {
1212

1313
@Injected(\.callKitPushNotificationAdapter) private var callKitPushNotificationAdapter
1414
@Injected(\.callKitAdapter) private var callKitAdapter
15+
@Injected(\.gleap) private var gleap
1516

1617
// MARK: - Properties
1718

@@ -117,6 +118,9 @@ final class AppState: ObservableObject {
117118
do {
118119
loading = true
119120
try await streamVideo?.connect()
121+
if let currentUser = self.currentUser {
122+
gleap.login(currentUser)
123+
}
120124
loading = false
121125
} catch {
122126
loading = false
@@ -145,6 +149,7 @@ final class AppState: ObservableObject {
145149
unsecureRepository.removeCurrentUser()
146150
streamVideo = nil
147151
userState = .notLoggedIn
152+
gleap.logout()
148153
}
149154

150155
func dispatchLogout() {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
import StreamVideo
7+
#if canImport(Gleap)
8+
import Gleap
9+
#endif
10+
11+
final class GleapAdapter {
12+
13+
var isAvailable: Bool {
14+
#if canImport(Gleap)
15+
return true
16+
#else
17+
return false
18+
#endif
19+
}
20+
21+
init() {
22+
#if canImport(Gleap)
23+
Gleap.initialize(withToken: "qsoyideVaZ7ZOehLLl8LqDC6YOPagLvr")
24+
Gleap.showFeedbackButton(false)
25+
log.debug("✅ Gleap has been activated.")
26+
#else
27+
log.warning("Cannot enable Gleap as the module hasn't been imported.")
28+
#endif
29+
}
30+
31+
func login(_ user: User) {
32+
guard isAvailable else {
33+
return
34+
}
35+
let userProperty = GleapUserProperty()
36+
userProperty.name = user.name
37+
userProperty.customData = [
38+
"userId": user.id,
39+
"userType": "\(user.type)",
40+
"userRole": user.role,
41+
"userOriginalName": user.originalName ?? "-"
42+
]
43+
44+
Gleap.identifyContact(user.id, andData: userProperty)
45+
}
46+
47+
func logout() {
48+
guard isAvailable else {
49+
return
50+
}
51+
Gleap.clearIdentity()
52+
}
53+
54+
func showBugReport(
55+
with attachment: URL
56+
) {
57+
guard isAvailable else {
58+
return
59+
}
60+
Gleap.addAttachment(withPath: attachment.path)
61+
Gleap.startFeedbackFlow("bugreporting", showBackButton: true)
62+
}
63+
}
64+
65+
extension GleapAdapter: InjectionKey {
66+
static var currentValue = GleapAdapter()
67+
}
68+
69+
extension InjectedValues {
70+
var gleap: GleapAdapter {
71+
get { InjectedValues[GleapAdapter.self] }
72+
set { InjectedValues[GleapAdapter.self] = newValue }
73+
}
74+
}

DemoApp/Sources/Components/MemoryLogDestination/LogQueue.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,35 @@ enum LogQueue {
1414
get { queue.maxCount }
1515
set { queue.maxCount = newValue }
1616
}
17+
18+
static func createLogFile() throws -> URL {
19+
let temporaryDirectoryURL = FileManager.default.temporaryDirectory
20+
let fileName = "stream_video_logs_\(Date().timeIntervalSince1970).txt"
21+
let fileURL = temporaryDirectoryURL.appendingPathComponent(fileName)
22+
23+
// Delete any existing temporary file first
24+
deleteTemporaryLogFile(at: fileURL)
25+
26+
// Add all logs to the content
27+
let logs = LogQueue.queue.elements
28+
let logContent = """
29+
Stream Video Logs - Generated: \(Date())
30+
\(logs.reversed().map { "\($0.level) - [\($0.fileName):\($0.lineNumber):\($0.functionName)] \($0.message)" }
31+
.joined(separator: "\n"))
32+
"""
33+
34+
try logContent.write(to: fileURL, atomically: true, encoding: .utf8)
35+
return fileURL
36+
}
37+
38+
static func deleteTemporaryLogFile(at path: URL) {
39+
do {
40+
try FileManager.default.removeItem(at: path)
41+
print("Temporary log file deleted successfully")
42+
} catch {
43+
print("Error deleting temporary log file: \(error)")
44+
}
45+
}
1746
}
1847

1948
final class Queue<T>: ObservableObject {

DemoApp/Sources/ViewModifiers/MoreControls/DemoMoreControlsViewModifier.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ struct DemoMoreControlsViewModifier: ViewModifier {
6464
VStack {
6565
Divider()
6666

67+
DemoMoreLogsAndGleapButtonView()
68+
6769
DemoBroadcastMoreControlsListButtonView(
6870
viewModel: viewModel,
6971
preferredExtension: "io.getstream.iOS.VideoDemoApp.ScreenSharing"
@@ -132,3 +134,47 @@ struct DemoMoreControlsViewModifier: ViewModifier {
132134
}
133135
}
134136
}
137+
138+
private struct DemoMoreLogsAndGleapButtonView: View {
139+
140+
@Injected(\.gleap) private var gleap
141+
@Injected(\.appearance) private var appearance
142+
143+
@State private var areLogsPresented = false
144+
@State private var activeLogsTask: Task<Void, Error>?
145+
146+
var body: some View {
147+
HStack {
148+
gleapButtonView
149+
logsViewButtonView
150+
}
151+
}
152+
153+
private var gleapButtonView: some View {
154+
DemoMoreControlListButtonView(
155+
action: {
156+
activeLogsTask?.cancel()
157+
activeLogsTask = Task { @MainActor in
158+
let logURL = try LogQueue.createLogFile()
159+
gleap.showBugReport(with: logURL)
160+
}
161+
},
162+
label: "Report a bug"
163+
) {
164+
Image(systemName: "ladybug.fill")
165+
}
166+
}
167+
168+
private var logsViewButtonView: some View {
169+
DemoMoreControlListButtonView(
170+
action: { areLogsPresented = true },
171+
label: "Logs Viewer"
172+
) {
173+
Image(systemName: "text.page")
174+
}.sheet(isPresented: $areLogsPresented) {
175+
NavigationView {
176+
MemoryLogViewer()
177+
}
178+
}
179+
}
180+
}

DemoApp/Sources/Views/CallTopView/DemoCallTopView.swift

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ struct DemoCallTopView: View {
2929
}
3030

3131
ToggleCameraIconView(viewModel: viewModel)
32-
if AppEnvironment.configuration == .debug {
33-
LogsViewerButtonView()
34-
}
3532

3633
Spacer()
3734
}
@@ -181,23 +178,3 @@ private struct ShadowModifier: ViewModifier {
181178
.shadow(color: Color.black.opacity(0.1), radius: 1, x: 0, y: 1)
182179
}
183180
}
184-
185-
private struct LogsViewerButtonView: View {
186-
@State private var isPresented = false
187-
188-
var body: some View {
189-
Button {
190-
isPresented.toggle()
191-
} label: {
192-
CallIconView(
193-
icon: Image(systemName: "ladybug.fill"),
194-
size: 44,
195-
iconStyle: .secondary
196-
)
197-
}.sheet(isPresented: $isPresented) {
198-
NavigationView {
199-
MemoryLogViewer()
200-
}
201-
}
202-
}
203-
}

StreamVideo.xcodeproj/project.pbxproj

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
40149DCC2B7E814300473176 /* AVAudioRecorderBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40149DCB2B7E814300473176 /* AVAudioRecorderBuilder.swift */; };
5151
40149DCE2B7E837A00473176 /* StreamCallAudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40149DCD2B7E837A00473176 /* StreamCallAudioRecorder.swift */; };
5252
40149DD02B7E839500473176 /* AVAudioSession+RequestRecordPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40149DCF2B7E839500473176 /* AVAudioSession+RequestRecordPermission.swift */; };
53+
4014F1032D8C2EBC004E7EFD /* Gleap in Frameworks */ = {isa = PBXBuildFile; productRef = 4014F1022D8C2EBC004E7EFD /* Gleap */; };
54+
4014F1062D8C2F07004E7EFD /* GleapAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4014F1052D8C2F07004E7EFD /* GleapAdapter.swift */; };
55+
4014F1072D8C2F07004E7EFD /* GleapAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4014F1052D8C2F07004E7EFD /* GleapAdapter.swift */; };
5356
401A0F032AB1C1B600BE2DBD /* ThermalStateObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A0F022AB1C1B600BE2DBD /* ThermalStateObserver.swift */; };
5457
401A64A52A9DF79E00534ED1 /* StreamChatSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 401A64A42A9DF79E00534ED1 /* StreamChatSwiftUI */; };
5558
401A64A82A9DF7B400534ED1 /* EffectsLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 401A64A72A9DF7B400534ED1 /* EffectsLibrary */; };
@@ -561,6 +564,7 @@
561564
40C4DF532C1C60A80035DBC2 /* StreamVideo.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84F737ED287C13AC00A363F4 /* StreamVideo.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
562565
40C4DF572C1C61BD0035DBC2 /* URL+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401A64B22A9DF86200534ED1 /* URL+Convenience.swift */; };
563566
40C689182C64DDC70054528A /* Publisher+TaskSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C689172C64DDC70054528A /* Publisher+TaskSink.swift */; };
567+
40C708D62D8D729500D3501F /* Gleap in Frameworks */ = {isa = PBXBuildFile; productRef = 40C708D52D8D729500D3501F /* Gleap */; };
564568
40C75BB72CB4044600C167C3 /* MockThermalStateObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C75BB62CB4044600C167C3 /* MockThermalStateObserver.swift */; };
565569
40C75BB82CB4045100C167C3 /* MockThermalStateObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C75BB62CB4044600C167C3 /* MockThermalStateObserver.swift */; };
566570
40C75BB92CB40D8700C167C3 /* Mockable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4013387B2BF248E9007318BD /* Mockable.swift */; };
@@ -1588,6 +1592,7 @@
15881592
40149DCB2B7E814300473176 /* AVAudioRecorderBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVAudioRecorderBuilder.swift; sourceTree = "<group>"; };
15891593
40149DCD2B7E837A00473176 /* StreamCallAudioRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamCallAudioRecorder.swift; sourceTree = "<group>"; };
15901594
40149DCF2B7E839500473176 /* AVAudioSession+RequestRecordPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVAudioSession+RequestRecordPermission.swift"; sourceTree = "<group>"; };
1595+
4014F1052D8C2F07004E7EFD /* GleapAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GleapAdapter.swift; sourceTree = "<group>"; };
15911596
401A0F022AB1C1B600BE2DBD /* ThermalStateObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThermalStateObserver.swift; sourceTree = "<group>"; };
15921597
401A64AA2A9DF7EC00534ED1 /* DemoChatAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoChatAdapter.swift; sourceTree = "<group>"; };
15931598
401A64B02A9DF83200534ED1 /* TokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenResponse.swift; sourceTree = "<group>"; };
@@ -2730,6 +2735,7 @@
27302735
isa = PBXFrameworksBuildPhase;
27312736
buildActionMask = 2147483647;
27322737
files = (
2738+
4014F1032D8C2EBC004E7EFD /* Gleap in Frameworks */,
27332739
844ADA652AD3F1AB00769F6A /* GoogleSignInSwift in Frameworks */,
27342740
4046DEF02A9F469100CA6D2F /* GDPerformanceView-Swift in Frameworks */,
27352741
401A64A52A9DF79E00534ED1 /* StreamChatSwiftUI in Frameworks */,
@@ -2756,6 +2762,7 @@
27562762
isa = PBXFrameworksBuildPhase;
27572763
buildActionMask = 2147483647;
27582764
files = (
2765+
40C708D62D8D729500D3501F /* Gleap in Frameworks */,
27592766
406303462AD9432D0091AE77 /* GoogleSignInSwift in Frameworks */,
27602767
82E1C14F2AEA7DD50076D7BE /* GDPerformanceView-Swift in Frameworks */,
27612768
40F0178A2BC014EC00E89FD1 /* Sentry in Frameworks */,
@@ -2916,6 +2923,14 @@
29162923
path = AudioRecorder;
29172924
sourceTree = "<group>";
29182925
};
2926+
4014F1042D8C2EFE004E7EFD /* Gleap */ = {
2927+
isa = PBXGroup;
2928+
children = (
2929+
4014F1052D8C2F07004E7EFD /* GleapAdapter.swift */,
2930+
);
2931+
path = Gleap;
2932+
sourceTree = "<group>";
2933+
};
29192934
401A64A92A9DF7E600534ED1 /* Chat */ = {
29202935
isa = PBXGroup;
29212936
children = (
@@ -3186,6 +3201,7 @@
31863201
4030E5962A9DF48C003E8CBA /* Components */ = {
31873202
isa = PBXGroup;
31883203
children = (
3204+
4014F1042D8C2EFE004E7EFD /* Gleap */,
31893205
845C098F2C0E0B6B00F725B3 /* SessionTimer */,
31903206
403EFC9D2BDBFDEE0057C248 /* Feedback */,
31913207
40CB9FA52B7FB1B2006BED93 /* Snapshot */,
@@ -6241,6 +6257,7 @@
62416257
844ADA642AD3F1AB00769F6A /* GoogleSignInSwift */,
62426258
82EB8F582B0277E70038B5A2 /* StreamWebRTC */,
62436259
40AC73B32BE0062B00C57517 /* StreamVideoNoiseCancellation */,
6260+
4014F1022D8C2EBC004E7EFD /* Gleap */,
62446261
);
62456262
productName = StreamVideoSwiftUI;
62466263
productReference = 842D8BC32865B31B00801910 /* StreamVideoCallApp-Debug.app */;
@@ -6287,6 +6304,7 @@
62876304
82EB8F5A2B0277EC0038B5A2 /* StreamWebRTC */,
62886305
40AB35682B738D3D00E465CC /* EffectsLibrary */,
62896306
40F017892BC014EC00E89FD1 /* Sentry */,
6307+
40C708D52D8D729500D3501F /* Gleap */,
62906308
);
62916309
productName = DemoAppUIKit;
62926310
productReference = 8493224C290837890013C029 /* DemoAppUIKit.app */;
@@ -6493,6 +6511,7 @@
64936511
844ADA612AD3F1AB00769F6A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */,
64946512
82EB8F552B0277730038B5A2 /* XCRemoteSwiftPackageReference "stream-video-swift-webrtc" */,
64956513
40AC73B22BE0062B00C57517 /* XCRemoteSwiftPackageReference "stream-video-noise-cancellation-swift" */,
6514+
4014F1012D8C2EBC004E7EFD /* XCRemoteSwiftPackageReference "Gleap-iOS-SDK" */,
64966515
);
64976516
productRefGroup = 842D8BC42865B31B00801910 /* Products */;
64986517
projectDirPath = "";
@@ -6706,6 +6725,7 @@
67066725
4030E5A02A9DF5BD003E8CBA /* AppEnvironment.swift in Sources */,
67076726
40F445B42A9E01B2004BE3DA /* AuthenticationProvider.swift in Sources */,
67086727
4093861C2AA0A11500FF5AF4 /* LogQueue.swift in Sources */,
6728+
4014F1062D8C2F07004E7EFD /* GleapAdapter.swift in Sources */,
67096729
844ADA6D2AD443AC00769F6A /* DemoCallsViewModel.swift in Sources */,
67106730
845C09912C0E0B7600F725B3 /* SessionTimer.swift in Sources */,
67116731
401C1EE72D48F20F00304609 /* DemoClosedCaptionsView.swift in Sources */,
@@ -6850,6 +6870,7 @@
68506870
406A8E9E2AA1D7E9001F598A /* URL+Convenience.swift in Sources */,
68516871
40AB353F2B738C1500E465CC /* CodeScanner.swift in Sources */,
68526872
40382F252C889EB200C2D00F /* View+PresentDemoMoreMenu.swift in Sources */,
6873+
4014F1072D8C2F07004E7EFD /* GleapAdapter.swift in Sources */,
68536874
406A8EB52AA1D91E001F598A /* JoinCallView.swift in Sources */,
68546875
406A8EAB2AA1D80C001F598A /* RobotVoiceFilter.swift in Sources */,
68556876
40AB35602B738C7D00E465CC /* ReactionIcon.swift in Sources */,
@@ -9514,6 +9535,14 @@
95149535
/* End XCConfigurationList section */
95159536

95169537
/* Begin XCRemoteSwiftPackageReference section */
9538+
4014F1012D8C2EBC004E7EFD /* XCRemoteSwiftPackageReference "Gleap-iOS-SDK" */ = {
9539+
isa = XCRemoteSwiftPackageReference;
9540+
repositoryURL = "https://github.com/GleapSDK/Gleap-iOS-SDK";
9541+
requirement = {
9542+
kind = upToNextMajorVersion;
9543+
minimumVersion = 14.2.4;
9544+
};
9545+
};
95179546
401A64A32A9DF79E00534ED1 /* XCRemoteSwiftPackageReference "stream-chat-swiftui" */ = {
95189547
isa = XCRemoteSwiftPackageReference;
95199548
repositoryURL = "https://github.com/GetStream/stream-chat-swiftui";
@@ -9589,6 +9618,11 @@
95899618
/* End XCRemoteSwiftPackageReference section */
95909619

95919620
/* Begin XCSwiftPackageProductDependency section */
9621+
4014F1022D8C2EBC004E7EFD /* Gleap */ = {
9622+
isa = XCSwiftPackageProductDependency;
9623+
package = 4014F1012D8C2EBC004E7EFD /* XCRemoteSwiftPackageReference "Gleap-iOS-SDK" */;
9624+
productName = Gleap;
9625+
};
95929626
401A64A42A9DF79E00534ED1 /* StreamChatSwiftUI */ = {
95939627
isa = XCSwiftPackageProductDependency;
95949628
package = 401A64A32A9DF79E00534ED1 /* XCRemoteSwiftPackageReference "stream-chat-swiftui" */;
@@ -9624,6 +9658,11 @@
96249658
package = 40AC73B22BE0062B00C57517 /* XCRemoteSwiftPackageReference "stream-video-noise-cancellation-swift" */;
96259659
productName = StreamVideoNoiseCancellation;
96269660
};
9661+
40C708D52D8D729500D3501F /* Gleap */ = {
9662+
isa = XCSwiftPackageProductDependency;
9663+
package = 4014F1012D8C2EBC004E7EFD /* XCRemoteSwiftPackageReference "Gleap-iOS-SDK" */;
9664+
productName = Gleap;
9665+
};
96279666
40F017892BC014EC00E89FD1 /* Sentry */ = {
96289667
isa = XCSwiftPackageProductDependency;
96299668
package = 8423B7542950BB0A00012F8D /* XCRemoteSwiftPackageReference "sentry-cocoa" */;

0 commit comments

Comments
 (0)