Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions Flinky.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1859,8 +1859,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/getsentry/sentry-cocoa";
requirement = {
kind = exactVersion;
version = 9.15.0;
branch = "feat/feedback-presentation-api";
kind = branch;
};
traits = (
);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions Targets/App/Sources/Environment/Environment+Feedback.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import SwiftUI

struct FeedbackAction {
private let handler: () -> Void

init(handler: @escaping () -> Void = {}) {
self.handler = handler
}

func show() {
handler()
}
}

extension EnvironmentValues {
var feedback: FeedbackAction {
get { self[FeedbackKey.self] }
set { self[FeedbackKey.self] = newValue }
}

private struct FeedbackKey: EnvironmentKey {
static let defaultValue = FeedbackAction()
}
}
20 changes: 20 additions & 0 deletions Targets/App/Sources/Generated/L10n.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ internal enum L10n {
/// New List
internal static let title = L10n.tr("create-list.title", fallback: "New List")
}
internal enum Feedback {
internal enum Toast {
internal enum Submit {
/// Failed to submit feedback
internal static let error = L10n.tr("feedback.toast.submit.error", fallback: "Failed to submit feedback")
/// Feedback submitted successfully
internal static let success = L10n.tr("feedback.toast.submit.success", fallback: "Feedback submitted successfully")
}
}
}
internal enum LinkDetail {
internal enum EditLink {
/// Edit
Expand Down Expand Up @@ -217,6 +227,16 @@ internal enum L10n {
internal static let label = L10n.tr("shared.button.edit.accessibility.label", fallback: "Edit")
}
}
internal enum Feedback {
/// Send Feedback
internal static let label = L10n.tr("shared.button.feedback.label", fallback: "Send Feedback")
internal enum Accessibility {
/// Send feedback to help improve the app
internal static let hint = L10n.tr("shared.button.feedback.accessibility.hint", fallback: "Send feedback to help improve the app")
/// Send Feedback
internal static let label = L10n.tr("shared.button.feedback.accessibility.label", fallback: "Send Feedback")
}
}
internal enum NewLink {
internal enum Accessibility {
/// Create a new link in this list
Expand Down
50 changes: 33 additions & 17 deletions Targets/App/Sources/Main/FlinkyApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,10 @@ struct FlinkyApp: App {
private let sharedModelContainer: ModelContainer

init() {
SentrySDK.start { options in
Self.configureSentry(options: options)
SentrySDK.start { [_toastManager] options in
Self.configureSentry(options: options, toastManager: { _toastManager.wrappedValue })
}

// Start app health observation for system-level metrics
// (thermal state, network reachability, app state transitions)
// Reference: https://github.com/getsentry/sentry-cocoa/issues/7000
AppHealthObserver.shared.startObserving()

do {
sharedModelContainer = try SharedModelContainerFactory.make(
isStoredInMemoryOnly: ProcessInfo.processInfo.isTestingEnabled
Expand All @@ -30,6 +25,11 @@ struct FlinkyApp: App {
fatalError("Failed to create shared ModelContainer: \(error)")
}

// Start app health observation for system-level metrics
// (thermal state, network reachability, app state transitions)
// Reference: https://github.com/getsentry/sentry-cocoa/issues/7000
AppHealthObserver.shared.startObserving()

// Seed if needed on first app launch
DataSeedingService.seedDataIfNeeded(modelContext: sharedModelContainer.mainContext)
}
Expand All @@ -39,7 +39,11 @@ struct FlinkyApp: App {
/// This method is defined as `private static` to because it is called from a non-mutating context.
///
/// - Parameter options: Options structure to configure Sentry.
private static func configureSentry(options: Options) { // swiftlint:disable:this function_body_length
/// - Parameter toastManager: Closure providing lazy access to the toast manager for feedback callbacks.
private static func configureSentry( // swiftlint:disable:this function_body_length
options: Options,
toastManager: @escaping () -> ToastManager
) {
// Disable Sentry for tests because it produces a lot of noise.
if ProcessInfo.processInfo.isTestingEnabled {
Self.logger.warning("Sentry is disabled in test environment")
Expand Down Expand Up @@ -117,15 +121,6 @@ struct FlinkyApp: App {
// Configure User Feedback
options.configureUserFeedback = { feedbackOptions in
feedbackOptions.animations = true
feedbackOptions.configureWidget = { widgetOptions in
widgetOptions.autoInject = false // Disable automatic injection of the widget, because it's not supported in SwiftUI.
widgetOptions.labelText = "Send Feedback"
widgetOptions.showIcon = true
widgetOptions.widgetAccessibilityLabel = "Feedback Widget"
widgetOptions.windowLevel = UIWindow.Level.normal + 1
widgetOptions.location = [.bottom, .trailing]
widgetOptions.layoutUIOffset = .init(horizontal: 18, vertical: 80)
}
feedbackOptions.useShakeGesture = true
feedbackOptions.showFormForScreenshots = true
feedbackOptions.configureForm = { formOptions in
Expand Down Expand Up @@ -203,6 +198,24 @@ struct FlinkyApp: App {
// Track feedback form closing using metrics - better for aggregate counts than individual events
SentryMetricsHelper.trackFeedbackFormClosed()
}
feedbackOptions.onSubmitSuccess = { _ in
let breadcrumb = Breadcrumb(level: .info, category: "user_feedback")
breadcrumb.message = "User successfully submitted feedback"
SentrySDK.addBreadcrumb(breadcrumb)

DispatchQueue.main.async {
toastManager().success(description: L10n.Feedback.Toast.Submit.success)
}
}
feedbackOptions.onSubmitError = { error in
let breadcrumb = Breadcrumb(level: .error, category: "user_feedback")
breadcrumb.message = "Feedback submission failed: \(error.localizedDescription)"
SentrySDK.addBreadcrumb(breadcrumb)

DispatchQueue.main.async {
toastManager().error(description: L10n.Feedback.Toast.Submit.error)
}
}
}

// Configure Logs
Expand All @@ -222,6 +235,9 @@ struct FlinkyApp: App {
var body: some Scene {
WindowGroup {
MainContainerView()
.environment(\.feedback, FeedbackAction {
SentrySDK.feedback.show()
})
.toaster(toastManager)
.configureOnLaunch { options in
options.publicKey = "30d2f7cc2fa469eaf8e4bdf958ad9d66bce491a7da1fb08ff0a7156a8e15a47d"
Expand Down
60 changes: 60 additions & 0 deletions Targets/App/Sources/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,42 @@
}
}
},
"shared.button.feedback.accessibility.hint": {
"comment": "Feedback button accessibility hint",
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Send feedback to help improve the app"
}
}
}
},
"shared.button.feedback.accessibility.label": {
"comment": "Feedback button accessibility label",
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Send Feedback"
}
}
}
},
"shared.button.feedback.label": {
"comment": "Feedback button text",
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Send Feedback"
}
}
}
},
"shared.color-picker.accessibility.label": {
"comment": "Color picker accessibility label",
"extractionState": "manual",
Expand Down Expand Up @@ -1466,6 +1502,30 @@
}
}
}
},
"feedback.toast.submit.success": {
"comment": "Toast message shown when feedback is submitted successfully",
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Feedback submitted successfully"
}
}
}
},
"feedback.toast.submit.error": {
"comment": "Toast message shown when feedback submission fails",
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Failed to submit feedback"
}
}
}
}
},
"version": "1.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: OnLaunch-iOS-Client, nameSpecified: OnLaunch-iOS-Client, owner: kula-app, version: 0.0.6, source: https://github.com/kula-app/OnLaunch-iOS-Client

name: sentry-cocoa, nameSpecified: Sentry, owner: getsentry, version: 9.15.0, source: https://github.com/getsentry/sentry-cocoa
name: sentry-cocoa, nameSpecified: sentry-cocoa, owner: getsentry, version: , source: https://github.com/getsentry/sentry-cocoa

name: SFSafeSymbols, nameSpecified: SFSafeSymbols, owner: SFSafeSymbols, version: 7.0.0, source: https://github.com/SFSafeSymbols/SFSafeSymbols

name: OnLaunch-iOS-Client, nameSpecified: OnLaunch-iOS-Client, owner: kula-app, version: 0.0.6, source: https://github.com/kula-app/OnLaunch-iOS-Client

name: sentry-cocoa, nameSpecified: Sentry, owner: getsentry, version: 9.15.0, source: https://github.com/getsentry/sentry-cocoa
name: sentry-cocoa, nameSpecified: sentry-cocoa, owner: getsentry, version: , source: https://github.com/getsentry/sentry-cocoa

name: SFSafeSymbols, nameSpecified: SFSafeSymbols, owner: SFSafeSymbols, version: 7.0.0, source: https://github.com/SFSafeSymbols/SFSafeSymbols

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<key>File</key>
<string>Licenses/sentry-cocoa</string>
<key>Title</key>
<string>Sentry (9.15.0)</string>
<string>sentry-cocoa</string>
<key>Type</key>
<string>PSChildPaneSpecifier</string>
</dict>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import SFSafeSymbols
import SwiftUI

struct CreateLinkEditorRenderView: View {
Expand All @@ -7,6 +8,7 @@ struct CreateLinkEditorRenderView: View {
}

@Environment(\.dismiss) private var dismiss
@Environment(\.feedback) private var feedback

@Binding var name: String
@Binding var url: String
Expand Down Expand Up @@ -55,6 +57,16 @@ struct CreateLinkEditorRenderView: View {
.accessibilityHint(L10n.Shared.Button.Cancel.Accessibility.hint)
.accessibilityIdentifier("create-link.cancel.button")
}
ToolbarItem(placement: .topBarTrailing) {
Button(action: {
feedback.show()
}, label: {
Label(L10n.Shared.Button.Feedback.label, systemSymbol: .megaphone)
})
.accessibilityLabel(L10n.Shared.Button.Feedback.Accessibility.label)
.accessibilityHint(L10n.Shared.Button.Feedback.Accessibility.hint)
.accessibilityIdentifier("create-link.feedback.button")
}
ToolbarItem(placement: .confirmationAction) {
if #available(iOS 26, *) {
Button(role: .confirm) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import SFSafeSymbols
import SwiftUI

struct CreateLinkListEditorRenderView: View {
Expand All @@ -6,6 +7,7 @@ struct CreateLinkListEditorRenderView: View {
}

@Environment(\.dismiss) private var dismiss
@Environment(\.feedback) private var feedback

@Binding var name: String
@FocusState private var focusedField: CreateField?
Expand All @@ -27,6 +29,16 @@ struct CreateLinkListEditorRenderView: View {
.navigationTitle(L10n.CreateList.title)
.accessibilityIdentifier("create-link-list.container")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(action: {
feedback.show()
}, label: {
Label(L10n.Shared.Button.Feedback.label, systemSymbol: .megaphone)
})
.accessibilityLabel(L10n.Shared.Button.Feedback.Accessibility.label)
.accessibilityHint(L10n.Shared.Button.Feedback.Accessibility.hint)
.accessibilityIdentifier("create-link-list.feedback.button")
}
ToolbarItem(placement: .cancellationAction) {
Button(role: .cancel) {
dismiss()
Expand Down
11 changes: 11 additions & 0 deletions Targets/App/Sources/UI/LinkDetail/LinkDetailRenderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import SwiftUI

struct LinkDetailRenderView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.feedback) private var feedback

let linkId: UUID

Expand All @@ -30,6 +31,16 @@ struct LinkDetailRenderView: View {
}
.background(Color(UIColor.systemGroupedBackground))
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
Button(action: {
feedback.show()
}, label: {
Label(L10n.Shared.Button.Feedback.label, systemSymbol: .megaphone)
})
.accessibilityLabel(L10n.Shared.Button.Feedback.Accessibility.label)
.accessibilityHint(L10n.Shared.Button.Feedback.Accessibility.hint)
.accessibilityIdentifier("link-detail.feedback.button")
}
ToolbarItemGroup(placement: .topBarLeading) {
Menu {
Button {
Expand Down
Loading