From 810e9fbe6b6ee82b1f169e192cbd20bfb25fa74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Danthinne?= Date: Sat, 31 Dec 2022 12:29:19 +0100 Subject: [PATCH] Add avatar position setting (#14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jérôme Danthinne --- .../App/Tabs/Settings/SettingsTab.swift | 6 +++ .../Sources/DesignSystem/DesignSystem.swift | 1 + .../Sources/DesignSystem/Theme.swift | 32 +++++++++++++++- .../DesignSystem/Views/AvatarView.swift | 2 +- .../Status/Row/StatusMediaPreviewView.swift | 5 ++- .../Sources/Status/Row/StatusRowView.swift | 37 ++++++++++++------- 6 files changed, 66 insertions(+), 17 deletions(-) diff --git a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift index 1458d841..b1e01e23 100644 --- a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift +++ b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift @@ -71,11 +71,17 @@ struct SettingsTabs: View { ColorPicker("Tint color", selection: $theme.tintColor) ColorPicker("Background color", selection: $theme.primaryBackgroundColor) ColorPicker("Secondary Background color", selection: $theme.secondaryBackgroundColor) + Picker("Avatar position", selection: $theme.avatarPosition) { + ForEach(Theme.AvatarPosition.allCases, id: \.rawValue) { position in + Text(position.description).tag(position) + } + } Button { theme.colorScheme = "dark" theme.tintColor = .brand theme.primaryBackgroundColor = .primaryBackground theme.secondaryBackgroundColor = .secondaryBackground + theme.avatarPosition = .top } label: { Text("Restore default") } diff --git a/Packages/DesignSystem/Sources/DesignSystem/DesignSystem.swift b/Packages/DesignSystem/Sources/DesignSystem/DesignSystem.swift index ba89ddfb..585b6fe0 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/DesignSystem.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/DesignSystem.swift @@ -4,5 +4,6 @@ public struct DS { public enum Constants { public static let layoutPadding: CGFloat = 20 public static let dividerPadding: CGFloat = 4 + public static let statusColumnsSpacing: CGFloat = 8 } } diff --git a/Packages/DesignSystem/Sources/DesignSystem/Theme.swift b/Packages/DesignSystem/Sources/DesignSystem/Theme.swift index cf8d2947..2e558066 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/Theme.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/Theme.swift @@ -1,8 +1,23 @@ +import Combine import SwiftUI public class Theme: ObservableObject { enum ThemeKey: String { case colorScheme, tint, label, primaryBackground, secondaryBackground + case avatarPosition + } + + public enum AvatarPosition: String, CaseIterable { + case leading, top + + public var description: LocalizedStringKey { + switch self { + case .leading: + return "Leading" + case .top: + return "Top" + } + } } @AppStorage("is_previously_set") var isSet: Bool = false @@ -19,12 +34,27 @@ public class Theme: ObservableObject { @AppStorage(ThemeKey.primaryBackground.rawValue) public var primaryBackgroundColor: Color = .white @AppStorage(ThemeKey.secondaryBackground.rawValue) public var secondaryBackgroundColor: Color = .gray @AppStorage(ThemeKey.label.rawValue) public var labelColor: Color = .black - + @AppStorage(ThemeKey.avatarPosition.rawValue) var rawAvatarPosition: String = AvatarPosition.top.rawValue + + @Published public var avatarPosition: AvatarPosition = .top + + private var cancellables = Set() + public init() { if !isSet { setColor(set: DarkSet()) isSet.toggle() } + + avatarPosition = AvatarPosition(rawValue: rawAvatarPosition) ?? .top + + $avatarPosition + .dropFirst() + .map(\.rawValue) + .sink { [weak self] position in + self?.rawAvatarPosition = position + } + .store(in: &cancellables) } public func setColor(set: ColorSet) { diff --git a/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift b/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift index 3f76bfbd..7b69bd60 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift @@ -6,7 +6,7 @@ public struct AvatarView: View { public enum Size { case account, status, embed, badge, boost - var size: CGSize { + public var size: CGSize { switch self { case .account: return .init(width: 80, height: 80) diff --git a/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift b/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift index 00dd156b..8e29dd00 100644 --- a/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift +++ b/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift @@ -7,6 +7,7 @@ import DesignSystem public struct StatusMediaPreviewView: View { @EnvironmentObject private var quickLook: QuickLook + @EnvironmentObject private var theme: Theme public let attachements: [MediaAttachement] public let isCompact: Bool @@ -95,8 +96,10 @@ public struct StatusMediaPreviewView: View { switch attachement.supportedType { case .image: if let size = size(for: attachement) { + let avatarColumnWidth = theme.avatarPosition == .leading ? AvatarView.Size.status.size.width + DS.Constants.statusColumnsSpacing : 0 + let availableWidth = UIScreen.main.bounds.width - (DS.Constants.layoutPadding * 2) - avatarColumnWidth let newSize = imageSize(from: size, - newWidth: UIScreen.main.bounds.width - (DS.Constants.layoutPadding * 2)) + newWidth: availableWidth) LazyImage(url: attachement.url) { state in if let image = state.image { image diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index 9abcdb7a..d040cf6f 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -18,20 +18,27 @@ public struct StatusRowView: View { } public var body: some View { - VStack(alignment: .leading) { - if !viewModel.isCompact { - reblogView - replyView + HStack(alignment: .top, spacing: DS.Constants.statusColumnsSpacing) { + if !viewModel.isCompact, + theme.avatarPosition == .leading, + let status: AnyStatus = viewModel.status.reblog ?? viewModel.status { + AvatarView(url: status.account.avatar, size: .status) } - statusView - if !viewModel.isCompact { - StatusActionsView(viewModel: viewModel) - .padding(.vertical, 8) - .tint(viewModel.isFocused ? theme.tintColor : .gray) - .contentShape(Rectangle()) - .onTapGesture { - routeurPath.navigate(to: .statusDetail(id: viewModel.status.reblog?.id ?? viewModel.status.id)) - } + VStack(alignment: .leading) { + if !viewModel.isCompact { + reblogView + replyView + } + statusView + if !viewModel.isCompact { + StatusActionsView(viewModel: viewModel) + .padding(.vertical, 8) + .tint(viewModel.isFocused ? theme.tintColor : .gray) + .contentShape(Rectangle()) + .onTapGesture { + routeurPath.navigate(to: .statusDetail(id: viewModel.status.reblog?.id ?? viewModel.status.id)) + } + } } } .onAppear { @@ -155,7 +162,9 @@ public struct StatusRowView: View { @ViewBuilder private func accountView(status: AnyStatus) -> some View { HStack(alignment: .center) { - AvatarView(url: status.account.avatar, size: .status) + if theme.avatarPosition == .top { + AvatarView(url: status.account.avatar, size: .status) + } VStack(alignment: .leading, spacing: 0) { status.account.displayNameWithEmojis .font(.headline)