15 Commits
Author SHA1 Message Date
Puranjay Savar MattasandClaude Sonnet 5 aaa6dbf966 fix(outlinekit): correct preferences wire keys to the real server shape
All preference toggles showed as off regardless of server state, and
saving notificationBadge 400'd with "notificationBadge: Invalid Input" —
the guessed wire keys/values from the earlier speculative commit were
wrong. Fixed against a live network capture of Outline's own web app
toggling every one of these settings:

- separateEditing -> seamlessEdit, and inverted (seamlessEdit is
  separate editing's negation, confirmed by toggling it live)
- showCommentMarker -> commentsInGutter
- smartText -> enableSmartText
- notificationBadge values -> "disabled"/"indicator"/"count", not the
  guessed "none"/"unread"/"all"
- rememberLastPath/useCursorPointer/codeBlockLineNumbers were already
  correct

Also preserves fullWidthDocuments (a real preference this app has no UI
for) on round-trip, since the client always sends the whole preferences
object back on save — dropping an unrecognized key during decode would
otherwise silently clear it the next time any toggle here gets saved.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:35:17 +01:00
Puranjay Savar MattasandClaude Sonnet 5 b1681c8ffb fix(settings): language picker breaks on a server locale not in our list
Live warning: "en_GB" invalid tag, undefined Picker display — the curated
OutlineLocale.all didn't include it. Added en_GB explicitly, and made the
Picker's item list always include whatever code the server actually
reports (falling back to the code itself as the label) so any future
unlisted locale degrades gracefully instead of breaking the control.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:26:12 +01:00
Puranjay Savar MattasandClaude Sonnet 5 79aed97f7d feat(settings): Preferences delete-account action
Danger subsection, confirmation dialog before calling users.delete
(deleteAccount()), signs out locally on success. Closes out the
Preferences page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:31:50 +01:00
Puranjay Savar MattasandClaude Sonnet 5 7ff0f333c8 feat(settings): Preferences notification-badge picker
Closes out the Behavior subsection. Uses NotificationBadgeStyle
(None/Unread Indicator/Unread Count) — wire values are a guess same as
the other preference keys, flagged in OutlineUserPreferences.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:31:24 +01:00
Puranjay Savar MattasandClaude Sonnet 5 ead3fb4365 feat(settings): Preferences smart-text-replacements toggle
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:31:11 +01:00
Puranjay Savar MattasandClaude Sonnet 5 218da50769 feat(settings): Preferences remember-previous-location toggle
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:31:05 +01:00
Puranjay Savar MattasandClaude Sonnet 5 77bf75babb feat(settings): Preferences separate-editing toggle
Starts the Behavior subsection.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:30:58 +01:00
Puranjay Savar MattasandClaude Sonnet 5 5a8fea73bf feat(settings): Preferences show-comment-marker toggle
Closes out the Display subsection.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:30:51 +01:00
Puranjay Savar MattasandClaude Sonnet 5 55a4883f02 feat(settings): Preferences show-line-numbers toggle
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:30:44 +01:00
Puranjay Savar MattasandClaude Sonnet 5 48cb804cc9 feat(settings): Preferences use-pointer-cursor toggle
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:30:38 +01:00
Puranjay Savar MattasandClaude Sonnet 5 b6ed8ec996 feat(settings): shared save path for preference toggles
savePreference(_:) reads the current OutlineUserPreferences, flips one
field, sends the whole object via updateUserPreferences. Every Behavior/
Display toggle added next reuses this instead of its own copy of the
same read-modify-write.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:30:31 +01:00
Puranjay Savar MattasandClaude Sonnet 5 e694415b65 feat(settings): Preferences appearance setting
Reuses the existing local Appearance color-scheme picker instead of a
second divergent implementation — this has always been a device-side
preference in this app, not a server-synced one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:30:08 +01:00
Puranjay Savar MattasandClaude Sonnet 5 41be6e25b7 feat(settings): Preferences section skeleton + Language setting
Marks .preferences implemented, adds the section header/description and
a Display subsection. Language is real and wired end-to-end (users.update
language field, high confidence per Outline's public API docs) — the rest
of the toggles land in follow-up commits, one setting at a time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:29:54 +01:00
Puranjay Savar MattasandClaude Sonnet 5 e7cdc9f0b7 feat(session): track user language + preferences
Mirrors the existing name/avatar/id fields — Settings' Preferences page
needs somewhere to read current values from and applyUpdatedProfile
already refreshes everything else on save.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:29:06 +01:00
Puranjay Savar MattasandClaude Sonnet 5 353afd93fb feat(outlinekit): add language/preferences plumbing for Preferences settings
OutlineUser gains language + preferences fields, new updateUserLanguage/
updateUserPreferences/deleteAccount client methods. Preference wire keys
are best-effort against Outline's own naming, same speculative treatment
as OutlinePin/OutlineDocumentMember — expect a correction round once
tested against a live server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:28:38 +01:00
11 changed files with 2 additions and 325 deletions
@@ -367,14 +367,6 @@ public actor CachingOutlineAPIClient: OutlineAPIClient {
try await live.deleteAccount()
}
public func subscribeToNotifications(eventType: NotificationEventType?) async throws -> OutlineUser {
try await live.subscribeToNotifications(eventType: eventType)
}
public func unsubscribeFromNotifications(eventType: NotificationEventType?) async throws -> OutlineUser {
try await live.unsubscribeFromNotifications(eventType: eventType)
}
// MARK: - Sync management (Settings surface)
public func pendingOperations() async -> [PendingOperationSummary] {
@@ -91,7 +91,4 @@ public protocol OutlineAPIClient: Sendable {
/// confirmation code param confirmed live, matches every other simple
/// no-body delete in this API.
func deleteAccount() async throws
/// `nil` targets every notification event. See `NotificationEventType`.
func subscribeToNotifications(eventType: NotificationEventType?) async throws -> OutlineUser
func unsubscribeFromNotifications(eventType: NotificationEventType?) async throws -> OutlineUser
}
@@ -302,14 +302,6 @@ public actor LiveOutlineAPIClient: OutlineAPIClient {
try await postForSuccess("users.delete", body: EmptyParams())
}
public func subscribeToNotifications(eventType: NotificationEventType?) async throws -> OutlineUser {
try await post("users.notificationsSubscribe", body: NotificationSubscriptionRequest(eventType: eventType?.rawValue))
}
public func unsubscribeFromNotifications(eventType: NotificationEventType?) async throws -> OutlineUser {
try await post("users.notificationsUnsubscribe", body: NotificationSubscriptionRequest(eventType: eventType?.rawValue))
}
private func post<Body: Encodable, Response: Decodable>(_ path: String, body: Body) async throws -> Response {
guard let token = try? tokenStore.token() else {
throw OutlineAPIError.tokenUnavailable
@@ -1,28 +0,0 @@
import Foundation
/// The subset of Outline's notification event types this app exposes a
/// toggle for. Wire values confirmed live (captured `users.update`
/// responses showing the full `notificationSettings` dictionary after
/// toggling each one in Outline's own web app). The server tracks more
/// event types than this app has UI for (`revisions.create`,
/// `emails.onboarding`, `emails.features` were also seen live) those are
/// left alone since `users.notificationsSubscribe`/`Unsubscribe` are
/// per-event, not a whole-object replace like `preferences`, so there's no
/// clobbering risk in only covering a subset.
public enum NotificationEventType: String, CaseIterable, Sendable {
case documentPublish = "documents.publish"
case documentUpdate = "documents.update"
case commentCreate = "comments.create"
case commentMentioned = "comments.mentioned"
case documentMentioned = "documents.mentioned"
case commentGroupMentioned = "comments.group_mentioned"
case documentGroupMentioned = "documents.group_mentioned"
case commentResolve = "comments.resolve"
case reactionCreate = "reactions.create"
case collectionCreate = "collections.create"
case emailsInviteAccepted = "emails.invite_accepted"
case documentAddUser = "documents.add_user"
case collectionAddUser = "collections.add_user"
case emailsExportCompleted = "emails.export_completed"
case accessRequestCreate = "access_requests.create"
}
@@ -8,10 +8,6 @@ public struct OutlineUser: Codable, Identifiable, Hashable, Sendable {
public let role: String?
public let language: String?
public let preferences: OutlineUserPreferences?
/// Keyed by `NotificationEventType`'s raw values, plus event types this
/// app has no UI for kept as a flexible dictionary rather than a
/// fixed struct for that reason. `true` means subscribed.
public let notificationSettings: [String: Bool]?
public init(
id: String,
@@ -20,8 +16,7 @@ public struct OutlineUser: Codable, Identifiable, Hashable, Sendable {
avatarUrl: String? = nil,
role: String? = nil,
language: String? = nil,
preferences: OutlineUserPreferences? = nil,
notificationSettings: [String: Bool]? = nil
preferences: OutlineUserPreferences? = nil
) {
self.id = id
self.name = name
@@ -30,6 +25,5 @@ public struct OutlineUser: Codable, Identifiable, Hashable, Sendable {
self.role = role
self.language = language
self.preferences = preferences
self.notificationSettings = notificationSettings
}
}
@@ -1,13 +0,0 @@
import Foundation
/// `users.notificationsSubscribe` / `users.notificationsUnsubscribe`. A
/// `nil` `eventType` targets every notification event at once confirmed
/// live via Outline's own "All notifications" master toggle, which sends
/// no `eventType` at all.
public struct NotificationSubscriptionRequest: Encodable, Sendable {
public let eventType: String?
public init(eventType: String? = nil) {
self.eventType = eventType
}
}
@@ -97,8 +97,6 @@ private final class StubOutlineAPIClient: OutlineAPIClient, @unchecked Sendable
func updateUserLanguage(_ request: UpdateUserLanguageRequest) async throws -> OutlineUser { throw NotStubbed() }
func updateUserPreferences(_ request: UpdateUserPreferencesRequest) async throws -> OutlineUser { throw NotStubbed() }
func deleteAccount() async throws { throw NotStubbed() }
func subscribeToNotifications(eventType: NotificationEventType?) async throws -> OutlineUser { throw NotStubbed() }
func unsubscribeFromNotifications(eventType: NotificationEventType?) async throws -> OutlineUser { throw NotStubbed() }
}
private struct StubTransportError: Error {}
@@ -1178,57 +1178,6 @@ final class LiveOutlineAPIClientTests: XCTestCase {
XCTAssertNil(object?["smartText"])
}
func testSubscribeToNotificationsSendsEventTypeAndDecodesResult() async throws {
let httpClient = MockHTTPClient()
httpClient.responseData = """
{
"data": {
"id": "user-1",
"name": "Jane Doe",
"notificationSettings": { "documents.publish": true }
}
}
""".data(using: .utf8)!
let client = LiveOutlineAPIClient(
configuration: OutlineConfiguration(baseURL: URL(string: "https://outline.example.com")!),
tokenStore: StaticTokenStore(),
httpClient: httpClient
)
let user = try await client.subscribeToNotifications(eventType: .documentPublish)
XCTAssertEqual(user.notificationSettings?["documents.publish"], true)
XCTAssertEqual(httpClient.lastRequest?.url?.path, "/api/users.notificationsSubscribe")
let sentBody = try JSONSerialization.jsonObject(with: httpClient.lastRequest!.httpBody!) as? [String: Any]
XCTAssertEqual(sentBody?["eventType"] as? String, "documents.publish")
}
func testUnsubscribeFromNotificationsWithNilEventTypeTargetsAll() async throws {
let httpClient = MockHTTPClient()
httpClient.responseData = """
{
"data": {
"id": "user-1",
"name": "Jane Doe",
"notificationSettings": { "documents.publish": false }
}
}
""".data(using: .utf8)!
let client = LiveOutlineAPIClient(
configuration: OutlineConfiguration(baseURL: URL(string: "https://outline.example.com")!),
tokenStore: StaticTokenStore(),
httpClient: httpClient
)
_ = try await client.unsubscribeFromNotifications(eventType: nil)
XCTAssertEqual(httpClient.lastRequest?.url?.path, "/api/users.notificationsUnsubscribe")
let sentBody = try JSONSerialization.jsonObject(with: httpClient.lastRequest!.httpBody!) as? [String: Any]
XCTAssertNil(sentBody?["eventType"])
}
func testDeleteAccountSendsRequest() async throws {
let httpClient = MockHTTPClient()
httpClient.responseData = """
-200
View File
@@ -39,8 +39,6 @@ struct SettingsView: View {
@State private var isShowingDeleteAccountConfirmation = false
@State private var isDeletingAccount = false
@State private var deleteAccountErrorMessage: String?
@State private var isSavingNotifications = false
@State private var notificationsErrorMessage: String?
/// Full Local Sync and cache-clearing both need a real connection to be
/// safe clearing while offline (or letting Full Local Sync think it
@@ -81,7 +79,6 @@ struct SettingsView: View {
case .appearance: appearanceDetail
case .profile: profileDetail
case .preferences: preferencesDetail
case .notifications: notificationsDetail
case .offlineSync: offlineSyncDetail
case .advanced: advancedDetail
case .about: aboutDetail
@@ -130,9 +127,6 @@ struct SettingsView: View {
private var profileDetail: some View {
VStack(alignment: .leading, spacing: 24) {
sectionHeader
if !isEffectivelyOnline {
offlineSettingsHint
}
avatarRow
Divider().frame(maxWidth: 420)
nameRow
@@ -190,7 +184,6 @@ struct SettingsView: View {
ProgressView().controlSize(.small)
}
}
.disabled(!isEffectivelyOnline)
if let avatarErrorMessage {
Text(avatarErrorMessage)
.font(.caption)
@@ -219,7 +212,6 @@ struct SettingsView: View {
}
}
}
.disabled(!isEffectivelyOnline)
if let nameErrorMessage {
Text(nameErrorMessage)
.font(.caption)
@@ -256,13 +248,8 @@ struct SettingsView: View {
.font(.subheadline)
.foregroundStyle(.secondary)
if !isEffectivelyOnline {
offlineSettingsHint
}
preferencesSubsection("Display") {
languageRow
.disabled(!isEffectivelyOnline)
Divider()
appearanceRow
Divider()
@@ -272,7 +259,6 @@ struct SettingsView: View {
isOn: session.userPreferences?.useCursorPointer ?? false,
onChange: { newValue in Task { await savePreference { $0.useCursorPointer = newValue } } }
)
.disabled(!isEffectivelyOnline)
Divider()
preferenceToggleRow(
"Show line numbers",
@@ -280,7 +266,6 @@ struct SettingsView: View {
isOn: session.userPreferences?.codeBlockLineNumbers ?? false,
onChange: { newValue in Task { await savePreference { $0.codeBlockLineNumbers = newValue } } }
)
.disabled(!isEffectivelyOnline)
Divider()
preferenceToggleRow(
"Show comment marker",
@@ -288,7 +273,6 @@ struct SettingsView: View {
isOn: session.userPreferences?.showCommentMarker ?? false,
onChange: { newValue in Task { await savePreference { $0.showCommentMarker = newValue } } }
)
.disabled(!isEffectivelyOnline)
}
preferencesSubsection("Behavior") {
@@ -315,7 +299,6 @@ struct SettingsView: View {
Divider()
notificationBadgeRow
}
.disabled(!isEffectivelyOnline)
if let preferencesErrorMessage {
Text(preferencesErrorMessage)
@@ -326,7 +309,6 @@ struct SettingsView: View {
preferencesSubsection("Danger") {
deleteAccountRow
}
.disabled(!isEffectivelyOnline)
}
.frame(maxWidth: 480, alignment: .leading)
.task { await refreshProfile() }
@@ -547,174 +529,6 @@ struct SettingsView: View {
}
}
// MARK: - Notifications
private var notificationsDetail: some View {
VStack(alignment: .leading, spacing: 14) {
sectionHeader
Text("Manage when and where you receive email notifications.")
.font(.subheadline)
.foregroundStyle(.secondary)
if !isEffectivelyOnline {
offlineSettingsHint
}
allNotificationsRow
Divider()
notificationToggleRow(
"Document published",
description: "Receive a notification whenever a new document is published",
isOn: session.userNotificationSettings?[NotificationEventType.documentPublish.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.documentPublish], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Document updated",
description: "Receive a notification when a document you are subscribed to is edited",
isOn: session.userNotificationSettings?[NotificationEventType.documentUpdate.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.documentUpdate], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Comment posted",
description: "Receive a notification when a document you are subscribed to or a thread you participated in receives a comment",
isOn: session.userNotificationSettings?[NotificationEventType.commentCreate.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.commentCreate], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Mentioned",
description: "Receive a notification when someone mentions you in a document or comment",
isOn: [NotificationEventType.commentMentioned, .documentMentioned].allSatisfy { session.userNotificationSettings?[$0.rawValue] ?? false },
onChange: { newValue in Task { await setNotifications([.commentMentioned, .documentMentioned], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Group mentions",
description: "Receive a notification when someone mentions a group you are a member of in a document or comment",
isOn: [NotificationEventType.commentGroupMentioned, .documentGroupMentioned].allSatisfy { session.userNotificationSettings?[$0.rawValue] ?? false },
onChange: { newValue in Task { await setNotifications([.commentGroupMentioned, .documentGroupMentioned], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Resolved",
description: "Receive a notification when a comment thread you were involved in is resolved",
isOn: session.userNotificationSettings?[NotificationEventType.commentResolve.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.commentResolve], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Reaction added",
description: "Receive a notification when someone reacts to your comment",
isOn: session.userNotificationSettings?[NotificationEventType.reactionCreate.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.reactionCreate], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Collection created",
description: "Receive a notification whenever a new collection is created",
isOn: session.userNotificationSettings?[NotificationEventType.collectionCreate.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.collectionCreate], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Invite accepted",
description: "Receive a notification when someone you invited creates an account",
isOn: session.userNotificationSettings?[NotificationEventType.emailsInviteAccepted.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.emailsInviteAccepted], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Invited to document",
description: "Receive a notification when a document is shared with you",
isOn: session.userNotificationSettings?[NotificationEventType.documentAddUser.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.documentAddUser], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Invited to collection",
description: "Receive a notification when you are given access to a collection",
isOn: session.userNotificationSettings?[NotificationEventType.collectionAddUser.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.collectionAddUser], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Export completed",
description: "Receive a notification when an export you requested has been completed",
isOn: session.userNotificationSettings?[NotificationEventType.emailsExportCompleted.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.emailsExportCompleted], subscribed: newValue) } }
)
Divider()
notificationToggleRow(
"Document access requested",
description: "Receive a notification when a user requests access to a document you manage",
isOn: session.userNotificationSettings?[NotificationEventType.accessRequestCreate.rawValue] ?? false,
onChange: { newValue in Task { await setNotifications([.accessRequestCreate], subscribed: newValue) } }
)
if let notificationsErrorMessage {
Text(notificationsErrorMessage)
.font(.caption)
.foregroundStyle(.red)
}
}
.disabled(!isEffectivelyOnline)
.frame(maxWidth: 480, alignment: .leading)
.task { await refreshProfile() }
}
private var allNotificationsRow: some View {
let isOn = NotificationEventType.allCases.allSatisfy { session.userNotificationSettings?[$0.rawValue] ?? false }
return notificationToggleRow(
"All notifications",
description: nil,
isOn: isOn,
onChange: { newValue in Task { await setNotifications(nil, subscribed: newValue) } }
)
}
private func notificationToggleRow(
_ title: String,
description: String?,
isOn: Bool,
onChange: @escaping (Bool) -> Void
) -> some View {
Toggle(isOn: Binding(get: { isOn }, set: onChange)) {
VStack(alignment: .leading, spacing: 2) {
Text(title)
if let description {
Text(description)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
/// `eventTypes` groups more than one wire event under a single visible
/// toggle (e.g. "Mentioned" covers both `comments.mentioned` and
/// `documents.mentioned`) confirmed as the right grouping against
/// Outline's own settings copy, applied with one subscribe/unsubscribe
/// call per underlying event type, sequentially.
private func setNotifications(_ eventTypes: [NotificationEventType]?, subscribed: Bool) async {
guard let apiClient = session.apiClient else { return }
isSavingNotifications = true
defer { isSavingNotifications = false }
let targets: [NotificationEventType?] = eventTypes ?? [nil]
do {
for target in targets {
let updated = subscribed
? try await apiClient.subscribeToNotifications(eventType: target)
: try await apiClient.unsubscribeFromNotifications(eventType: target)
session.applyUpdatedProfile(updated)
}
notificationsErrorMessage = nil
} catch {
notificationsErrorMessage = outlineErrorMessage(error, fallback: "Couldn't update your notification settings.")
}
}
// MARK: - Offline & Sync
private var offlineSyncDetail: some View {
@@ -964,20 +778,6 @@ struct SettingsView: View {
// MARK: - Helpers
/// Everything backed by a live `users.update`/`users.notifications*`
/// call (Profile's name/avatar, all of Preferences, all of
/// Notifications) shows this and disables its controls while offline
/// there's nothing to optimistically apply here the way document edits
/// can be, and no offline queue for it (see `TODO.local.md`'s own
/// "things that actually require an internet connection" framing,
/// already applied to Share/Permissions/Search). Local-only settings
/// (Appearance, Offline Mode itself) are deliberately left enabled.
private var offlineSettingsHint: some View {
Text("You're offline — these settings need a connection to change.")
.font(.caption)
.foregroundStyle(.orange)
}
private func labeledRow(_ label: String, _ value: String) -> some View {
HStack {
Text(label)
+1 -1
View File
@@ -122,7 +122,7 @@ enum SettingsSection: String, CaseIterable, Identifiable, Hashable {
/// placeholder until its content is specified and built.
var isImplemented: Bool {
switch self {
case .appearance, .offlineSync, .advanced, .about, .profile, .preferences, .notifications:
case .appearance, .offlineSync, .advanced, .about, .profile, .preferences:
return true
default:
return false
-4
View File
@@ -17,7 +17,6 @@ final class SessionStore {
var userAvatarURL: URL?
var userLanguage: String?
var userPreferences: OutlineUserPreferences?
var userNotificationSettings: [String: Bool]?
var teamName: String?
var teamAvatarURL: URL?
private(set) var apiClient: OutlineAPIClient?
@@ -78,7 +77,6 @@ final class SessionStore {
userAvatarURL = nil
userLanguage = nil
userPreferences = nil
userNotificationSettings = nil
teamName = nil
teamAvatarURL = nil
apiClient = nil
@@ -103,7 +101,6 @@ final class SessionStore {
userAvatarURL = user.avatarUrl.flatMap { URL(string: $0, relativeTo: serverURL)?.absoluteURL }
userLanguage = user.language
userPreferences = user.preferences
userNotificationSettings = user.notificationSettings
}
private func apply(user: OutlineUser, team: OutlineTeam, serverURL: URL) {
@@ -116,7 +113,6 @@ final class SessionStore {
userAvatarURL = user.avatarUrl.flatMap { URL(string: $0, relativeTo: serverURL)?.absoluteURL }
userLanguage = user.language
userPreferences = user.preferences
userNotificationSettings = user.notificationSettings
teamName = team.name
teamAvatarURL = team.avatarUrl.flatMap { URL(string: $0, relativeTo: serverURL)?.absoluteURL }
}