UI Adjustments

This commit is contained in:
Thomas Ricouard 2023-12-18 18:08:32 +01:00
parent fdf15e60f6
commit 1c9cf4077d
7 changed files with 249 additions and 180 deletions

View file

@ -96,7 +96,12 @@ public struct AppAccountsSelectorView: View {
AppAccountView(viewModel: viewModel) AppAccountView(viewModel: viewModel)
} }
} }
.listRowBackground(theme.primaryBackgroundColor) #if os(visionOS)
.listRowBackground(RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Material.regular))
#else
.listRowBackground(theme.primaryBackgroundColor)
#endif
if accountCreationEnabled { if accountCreationEnabled {
Section { Section {
@ -111,7 +116,12 @@ public struct AppAccountsSelectorView: View {
} }
settingsButton settingsButton
} }
.listRowBackground(theme.primaryBackgroundColor) #if os(visionOS)
.listRowBackground(Rectangle()
.foregroundStyle(Material.regular))
#else
.listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }
.listStyle(.insetGrouped) .listStyle(.insetGrouped)

View file

@ -132,8 +132,11 @@ public struct NotificationsListView: View {
leading: .layoutPadding + 4, leading: .layoutPadding + 4,
bottom: 12, bottom: 12,
trailing: .layoutPadding)) trailing: .layoutPadding))
#if !os(visionOS) #if os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Material.regular))
#else
.listRowBackground(theme.primaryBackgroundColor)
#endif #endif
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.allowsHitTesting(false) .allowsHitTesting(false)
@ -158,7 +161,10 @@ public struct NotificationsListView: View {
leading: .layoutPadding + 4, leading: .layoutPadding + 4,
bottom: 12, bottom: 12,
trailing: .layoutPadding)) trailing: .layoutPadding))
#if !os(visionOS) #if os(visionOS)
.listRowBackground(RoundedRectangle(cornerRadius: 8)
.foregroundStyle(notification.type == .mention && lockedType != .mention ? Material.thick : Material.regular))
#else
.listRowBackground(notification.type == .mention && lockedType != .mention ? .listRowBackground(notification.type == .mention && lockedType != .mention ?
theme.secondaryBackgroundColor : theme.primaryBackgroundColor) theme.secondaryBackgroundColor : theme.primaryBackgroundColor)
#endif #endif

View file

@ -59,7 +59,9 @@ public struct StatusDetailView: View {
.foregroundColor(theme.secondaryBackgroundColor) .foregroundColor(theme.secondaryBackgroundColor)
.frame(minHeight: reader.frame(in: .local).size.height - statusHeight) .frame(minHeight: reader.frame(in: .local).size.height - statusHeight)
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
#if !os(visionOS)
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
#endif
.listRowInsets(.init()) .listRowInsets(.init())
.accessibilityHidden(true) .accessibilityHidden(true)
@ -69,8 +71,10 @@ public struct StatusDetailView: View {
} }
.environment(\.defaultMinListRowHeight, 1) .environment(\.defaultMinListRowHeight, 1)
.listStyle(.plain) .listStyle(.plain)
#if !os(visionOS)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.onChange(of: viewModel.scrollToId) { _, newValue in .onChange(of: viewModel.scrollToId) { _, newValue in
if let newValue { if let newValue {
viewModel.scrollToId = nil viewModel.scrollToId = nil
@ -132,7 +136,9 @@ public struct StatusDetailView: View {
} }
} }
.id(status.id) .id(status.id)
#if !os(visionOS)
.listRowBackground(viewModel.highlightRowColor) .listRowBackground(viewModel.highlightRowColor)
#endif
.listRowInsets(.init(top: 12, .listRowInsets(.init(top: 12,
leading: .layoutPadding, leading: .layoutPadding,
bottom: 12, bottom: 12,
@ -149,7 +155,9 @@ public struct StatusDetailView: View {
await viewModel.fetch() await viewModel.fetch()
} }
} }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
} }
@ -169,13 +177,17 @@ public struct StatusDetailView: View {
} }
.frame(height: 50) .frame(height: 50)
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
#if !os(visionOS)
.listRowBackground(theme.secondaryBackgroundColor) .listRowBackground(theme.secondaryBackgroundColor)
#endif
.listRowInsets(.init()) .listRowInsets(.init())
} }
private var topPaddingView: some View { private var topPaddingView: some View {
HStack { EmptyView() } HStack { EmptyView() }
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
#endif
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.listRowInsets(.init()) .listRowInsets(.init())
.frame(height: .layoutPadding) .frame(height: .layoutPadding)

View file

@ -31,192 +31,220 @@ struct StatusEditorAccessoryView: View {
var body: some View { var body: some View {
@Bindable var viewModel = focusedSEVM @Bindable var viewModel = focusedSEVM
VStack(spacing: 0) { VStack(spacing: 0) {
#if os(visionOS)
HStack {
contentView
}
.frame(height: 24)
.padding(16)
.background(.ultraThinMaterial)
.cornerRadius(8)
#else
Divider() Divider()
HStack { HStack {
ScrollView(.horizontal) { contentView
HStack(alignment: .center, spacing: 16) {
Menu {
Button {
isPhotosPickerPresented = true
} label: {
Label("status.editor.photo-library", systemImage: "photo")
}
#if !targetEnvironment(macCatalyst)
Button {
isCameraPickerPresented = true
} label: {
Label("status.editor.camera-picker", systemImage: "camera")
}
#endif
Button {
isFileImporterPresented = true
} label: {
Label("status.editor.browse-file", systemImage: "folder")
}
#if !os(visionOS)
Button {
isGIFPickerPresented = true
} label: {
Label("GIPHY", systemImage: "party.popper")
}
#endif
} label: {
if viewModel.isMediasLoading {
ProgressView()
} else {
Image(systemName: "photo.on.rectangle.angled")
}
}
.photosPicker(isPresented: $isPhotosPickerPresented,
selection: $viewModel.mediaPickers,
maxSelectionCount: 4,
matching: .any(of: [.images, .videos]),
photoLibrary: .shared())
.fileImporter(isPresented: $isFileImporterPresented,
allowedContentTypes: [.image, .video],
allowsMultipleSelection: true)
{ result in
if let urls = try? result.get() {
viewModel.processURLs(urls: urls)
}
}
.fullScreenCover(isPresented: $isCameraPickerPresented, content: {
StatusEditorCameraPickerView(selectedImage: .init(get: {
nil
}, set: { image in
if let image {
viewModel.processCameraPhoto(image: image)
}
}))
.background(.black)
})
.sheet(isPresented: $isGIFPickerPresented, content: {
#if !os(visionOS)
GifPickerView { url in
GPHCache.shared.downloadAssetData(url) { data, _ in
guard let data else { return }
viewModel.processGIFData(data: data)
}
isGIFPickerPresented = false
} onShouldDismissGifPicker: {
isGIFPickerPresented = false
}
.presentationDetents([.medium, .large])
#else
EmptyView()
#endif
})
.accessibilityLabel("accessibility.editor.button.attach-photo")
.disabled(viewModel.showPoll)
Button {
// all SEVM have the same visibility value
followUpSEVMs.append(StatusEditorViewModel(mode: .new(visibility: focusedSEVM.visibility)))
} label: {
Image(systemName: "arrowshape.turn.up.left.circle.fill")
}
.disabled(!canAddNewSEVM)
Button {
withAnimation {
viewModel.showPoll.toggle()
viewModel.resetPollDefaults()
}
} label: {
Image(systemName: "chart.bar")
}
.accessibilityLabel("accessibility.editor.button.poll")
.disabled(viewModel.shouldDisablePollButton)
Button {
withAnimation {
viewModel.spoilerOn.toggle()
}
isSpoilerTextFocused = viewModel.id
} label: {
Image(systemName: viewModel.spoilerOn ? "exclamationmark.triangle.fill" : "exclamationmark.triangle")
}
.accessibilityLabel("accessibility.editor.button.spoiler")
if !viewModel.mode.isInShareExtension {
Button {
isDraftsSheetDisplayed = true
} label: {
Image(systemName: "archivebox")
}
.accessibilityLabel("accessibility.editor.button.drafts")
.popover(isPresented: $isDraftsSheetDisplayed) {
if UIDevice.current.userInterfaceIdiom == .phone {
draftsListView
.presentationDetents([.medium])
} else {
draftsListView
.frame(width: 400, height: 500)
}
}
}
if !viewModel.customEmojiContainer.isEmpty {
Button {
isCustomEmojisSheetDisplay = true
} label: {
// This is a workaround for an apparent bug in the `face.smiling` SF Symbol.
// See https://github.com/Dimillian/IceCubesApp/issues/1193
let customEmojiSheetIconName = colorScheme == .light ? "face.smiling" : "face.smiling.inverse"
Image(systemName: customEmojiSheetIconName)
}
.accessibilityLabel("accessibility.editor.button.custom-emojis")
.popover(isPresented: $isCustomEmojisSheetDisplay) {
if UIDevice.current.userInterfaceIdiom == .phone {
customEmojisSheet
} else {
customEmojisSheet
.frame(width: 400, height: 500)
}
}
}
Button {
isLanguageSheetDisplayed.toggle()
} label: {
if let language = viewModel.selectedLanguage {
Text(language.uppercased())
} else {
Image(systemName: "globe")
}
}
.accessibilityLabel("accessibility.editor.button.language")
.popover(isPresented: $isLanguageSheetDisplayed) {
if UIDevice.current.userInterfaceIdiom == .phone {
languageSheetView
} else {
languageSheetView
.frame(width: 400, height: 500)
}
}
if preferences.isOpenAIEnabled {
AIMenu.disabled(!viewModel.canPost)
}
}
.padding(.horizontal, .layoutPadding)
}
Spacer()
characterCountView
.padding(.trailing, .layoutPadding)
} }
.frame(height: 20) .frame(height: 20)
.padding(.vertical, 12) .padding(.vertical, 12)
.background(.ultraThinMaterial) .background(.ultraThinMaterial)
#endif
} }
.onAppear { .onAppear {
viewModel.setInitialLanguageSelection(preference: preferences.recentlyUsedLanguages.first ?? preferences.serverPreferences?.postLanguage) viewModel.setInitialLanguageSelection(preference: preferences.recentlyUsedLanguages.first ?? preferences.serverPreferences?.postLanguage)
} }
} }
@ViewBuilder
private var contentView: some View {
#if os(visionOS)
HStack(spacing: 8) {
actionsView
characterCountView
.padding(.leading, 16)
}
#else
ScrollView(.horizontal) {
HStack(alignment: .center, spacing: 16) {
actionsView
}
.padding(.horizontal, .layoutPadding)
}
Spacer()
characterCountView
.padding(.trailing, .layoutPadding)
#endif
}
@ViewBuilder
private var actionsView: some View {
@Bindable var viewModel = focusedSEVM
Menu {
Button {
isPhotosPickerPresented = true
} label: {
Label("status.editor.photo-library", systemImage: "photo")
}
#if !targetEnvironment(macCatalyst)
Button {
isCameraPickerPresented = true
} label: {
Label("status.editor.camera-picker", systemImage: "camera")
}
#endif
Button {
isFileImporterPresented = true
} label: {
Label("status.editor.browse-file", systemImage: "folder")
}
#if !os(visionOS)
Button {
isGIFPickerPresented = true
} label: {
Label("GIPHY", systemImage: "party.popper")
}
#endif
} label: {
if viewModel.isMediasLoading {
ProgressView()
} else {
Image(systemName: "photo.on.rectangle.angled")
}
}
.photosPicker(isPresented: $isPhotosPickerPresented,
selection: $viewModel.mediaPickers,
maxSelectionCount: 4,
matching: .any(of: [.images, .videos]),
photoLibrary: .shared())
.fileImporter(isPresented: $isFileImporterPresented,
allowedContentTypes: [.image, .video],
allowsMultipleSelection: true)
{ result in
if let urls = try? result.get() {
viewModel.processURLs(urls: urls)
}
}
.fullScreenCover(isPresented: $isCameraPickerPresented, content: {
StatusEditorCameraPickerView(selectedImage: .init(get: {
nil
}, set: { image in
if let image {
viewModel.processCameraPhoto(image: image)
}
}))
.background(.black)
})
.sheet(isPresented: $isGIFPickerPresented, content: {
#if !os(visionOS)
GifPickerView { url in
GPHCache.shared.downloadAssetData(url) { data, _ in
guard let data else { return }
viewModel.processGIFData(data: data)
}
isGIFPickerPresented = false
} onShouldDismissGifPicker: {
isGIFPickerPresented = false
}
.presentationDetents([.medium, .large])
#else
EmptyView()
#endif
})
.accessibilityLabel("accessibility.editor.button.attach-photo")
.disabled(viewModel.showPoll)
Button {
// all SEVM have the same visibility value
followUpSEVMs.append(StatusEditorViewModel(mode: .new(visibility: focusedSEVM.visibility)))
} label: {
Image(systemName: "arrowshape.turn.up.left.circle.fill")
}
.disabled(!canAddNewSEVM)
Button {
withAnimation {
viewModel.showPoll.toggle()
viewModel.resetPollDefaults()
}
} label: {
Image(systemName: "chart.bar")
}
.accessibilityLabel("accessibility.editor.button.poll")
.disabled(viewModel.shouldDisablePollButton)
Button {
withAnimation {
viewModel.spoilerOn.toggle()
}
isSpoilerTextFocused = viewModel.id
} label: {
Image(systemName: viewModel.spoilerOn ? "exclamationmark.triangle.fill" : "exclamationmark.triangle")
}
.accessibilityLabel("accessibility.editor.button.spoiler")
if !viewModel.mode.isInShareExtension {
Button {
isDraftsSheetDisplayed = true
} label: {
Image(systemName: "archivebox")
}
.accessibilityLabel("accessibility.editor.button.drafts")
.popover(isPresented: $isDraftsSheetDisplayed) {
if UIDevice.current.userInterfaceIdiom == .phone {
draftsListView
.presentationDetents([.medium])
} else {
draftsListView
.frame(width: 400, height: 500)
}
}
}
if !viewModel.customEmojiContainer.isEmpty {
Button {
isCustomEmojisSheetDisplay = true
} label: {
// This is a workaround for an apparent bug in the `face.smiling` SF Symbol.
// See https://github.com/Dimillian/IceCubesApp/issues/1193
let customEmojiSheetIconName = colorScheme == .light ? "face.smiling" : "face.smiling.inverse"
Image(systemName: customEmojiSheetIconName)
}
.accessibilityLabel("accessibility.editor.button.custom-emojis")
.popover(isPresented: $isCustomEmojisSheetDisplay) {
if UIDevice.current.userInterfaceIdiom == .phone {
customEmojisSheet
} else {
customEmojisSheet
.frame(width: 400, height: 500)
}
}
}
Button {
isLanguageSheetDisplayed.toggle()
} label: {
if let language = viewModel.selectedLanguage {
Text(language.uppercased())
} else {
Image(systemName: "globe")
}
}
.accessibilityLabel("accessibility.editor.button.language")
.popover(isPresented: $isLanguageSheetDisplayed) {
if UIDevice.current.userInterfaceIdiom == .phone {
languageSheetView
} else {
languageSheetView
.frame(width: 400, height: 500)
}
}
if preferences.isOpenAIEnabled {
AIMenu.disabled(!viewModel.canPost)
}
}
private var canAddNewSEVM: Bool { private var canAddNewSEVM: Bool {
guard followUpSEVMs.count < 5 else { return false } guard followUpSEVMs.count < 5 else { return false }

View file

@ -52,7 +52,9 @@ struct StatusEditorCoreView: View {
} }
.opacity(editorFocusState == assignedFocusState ? 1 : 0.6) .opacity(editorFocusState == assignedFocusState ? 1 : 0.6)
} }
#if !os(visionOS)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.focused($editorFocusState, equals: assignedFocusState) .focused($editorFocusState, equals: assignedFocusState)
.onAppear { setupViewModel() } .onAppear { setupViewModel() }
} }

View file

@ -73,13 +73,21 @@ public struct StatusEditorView: View {
.scrollPosition(id: $scrollID, anchor: .top) .scrollPosition(id: $scrollID, anchor: .top)
.animation(.bouncy(duration: 0.3), value: editorFocusState) .animation(.bouncy(duration: 0.3), value: editorFocusState)
.animation(.bouncy(duration: 0.3), value: followUpSEVMs) .animation(.bouncy(duration: 0.3), value: followUpSEVMs)
#if !os(visionOS)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif
.safeAreaInset(edge: .bottom) { .safeAreaInset(edge: .bottom) {
StatusEditorAutoCompleteView(viewModel: focusedSEVM) StatusEditorAutoCompleteView(viewModel: focusedSEVM)
} }
#if os(visionOS)
.ornament(attachmentAnchor: .scene(.bottom)) {
StatusEditorAccessoryView(isSpoilerTextFocused: $isSpoilerTextFocused, focusedSEVM: focusedSEVM, followUpSEVMs: $followUpSEVMs)
}
#else
.safeAreaInset(edge: .bottom) { .safeAreaInset(edge: .bottom) {
StatusEditorAccessoryView(isSpoilerTextFocused: $isSpoilerTextFocused, focusedSEVM: focusedSEVM, followUpSEVMs: $followUpSEVMs) StatusEditorAccessoryView(isSpoilerTextFocused: $isSpoilerTextFocused, focusedSEVM: focusedSEVM, followUpSEVMs: $followUpSEVMs)
} }
#endif
.accessibilitySortPriority(1) // Ensure that all elements inside the `ScrollView` occur earlier than the accessory views .accessibilitySortPriority(1) // Ensure that all elements inside the `ScrollView` occur earlier than the accessory views
.navigationTitle(focusedSEVM.mode.title) .navigationTitle(focusedSEVM.mode.title)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)

View file

@ -149,8 +149,11 @@ public struct StatusRowView: View {
StatusRowSwipeView(viewModel: viewModel, mode: .leading) StatusRowSwipeView(viewModel: viewModel, mode: .leading)
} }
} }
#if !os(visionOS) #if os(visionOS)
.listRowBackground(viewModel.highlightRowColor) .listRowBackground(RoundedRectangle(cornerRadius: 8)
.foregroundStyle(Material.regular))
#else
.listRowBackground(viewModel.highlightRowColor)
#endif #endif
.listRowInsets(.init(top: 12, .listRowInsets(.init(top: 12,
leading: .layoutPadding, leading: .layoutPadding,