Skip to content
Merged
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
22 changes: 15 additions & 7 deletions Flipcash/Core/Screens/Conversation/AttachKeyboardOverlay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ final class AttachKeyboardOverlay {
self.locator = locator
}

/// Plans how `+` opens the panel and, when it goes over the keyboard, puts the overlay up with
/// `items` as its rows. Returns the mode chosen; ``AttachOverlayMode/dismissKeyboard`` leaves the
/// Plans how `+` opens the panel and, when it goes over the keyboard, puts the overlay up.
/// `acceptsMedia` is whether the chat takes photos, as the bar's rows read it. Returns the mode chosen; ``AttachOverlayMode/dismissKeyboard`` leaves the
/// caller to take the keyboard down.
func begin(
screen: ChatScreenViewController,
items: [AttachMenuItem],
acceptsMedia: Bool,
composer: ComposerModel,
actions: @escaping () -> AttachActions
) -> AttachOverlayMode {
Expand All @@ -66,7 +66,7 @@ final class AttachKeyboardOverlay {
// Before the switch: swapping the input view in announces the keyboard showing again, which
// must not read as the keyboard taking a card's place.
model.attachCard.closesOnKeyboardShow = false
let container = makeContainer(appWindow: appWindow, composer: composer, actions: actions)
let container = makeContainer(appWindow: appWindow, acceptsMedia: acceptsMedia, composer: composer, actions: actions)
switch plan.mode {
case .keyboardWindow:
guard let window = plan.keyboardWindow else { return refuse() }
Expand All @@ -87,7 +87,7 @@ final class AttachKeyboardOverlay {
}

mode = plan.mode
model.overKeyboard.activate(items: items)
model.overKeyboard.activate()
// A tap on the transcript reaches the app's window, not the overlay, and would lower the
// keyboard; while the menu is up it only closes the menu.
screen.setTranscriptShield(
Expand Down Expand Up @@ -145,6 +145,7 @@ final class AttachKeyboardOverlay {

private func makeContainer(
appWindow: UIWindow,
acceptsMedia: Bool,
composer: ComposerModel,
actions: @escaping () -> AttachActions
) -> AttachOverlayContainer {
Expand All @@ -164,6 +165,7 @@ final class AttachKeyboardOverlay {
let root = AttachOverlayRoot(
model: model,
composer: composer,
acceptsMedia: acceptsMedia,
// A card here can't present a sheet, so All Photos hands the card to the bar instead.
actions: { [weak self] in
var actions = actions()
Expand Down Expand Up @@ -313,6 +315,7 @@ private struct AttachOverlayRoot: View {

let model: ConversationBarModel
let composer: ComposerModel
let acceptsMedia: Bool
/// Returns the screen's actions as of its latest render.
let actions: () -> AttachActions
let report: (AttachOverlayRegion, CGRect?) -> Void
Expand All @@ -321,13 +324,18 @@ private struct AttachOverlayRoot: View {

@State private var bounds: CGSize = .zero

// Read live, as the bar reads its own, so a photo staged while the menu is up updates both.
private var rows: ConversationBarBottomRow {
ConversationBarBottomRow(composer: composer, acceptsMedia: acceptsMedia)
}

var body: some View {
ZStack(alignment: .topLeading) {
Color.clear
if model.attachSurfaceIsMounted {
if model.attachSurfaceIsMounted, !rows.plusItems.isEmpty {
AttachSurface(
model: model,
items: model.overKeyboard.items,
items: rows.plusItems,
plus: model.overKeyboard.plusFrame,
card: AttachOverlayLayout.cardFrame(in: CGRect(origin: .zero, size: bounds), height: model.attachCard.height),
landing: model.attachCard.landingChipFrame,
Expand Down
4 changes: 2 additions & 2 deletions Flipcash/Core/Screens/Conversation/AttachMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ struct AttachMenu: View {
var isStoodInFor = false
/// Fired as the panel opens, before it is drawn: the keyboard goes down and the panel rides the bar
/// down with it, or the panel goes up over the keyboard.
let onOpen: ([AttachMenuItem]) -> Void
let onOpen: () -> Void
/// Receives `+`'s frame in window coordinates as it is laid out.
var onPlusFrame: (CGRect) -> Void = { _ in }

var body: some View {
Button {
if !panel.isOpen {
onOpen(items)
onOpen()
}
panel.animate { $0.toggle() }
} label: {
Expand Down
8 changes: 2 additions & 6 deletions Flipcash/Core/Screens/Conversation/AttachOverKeyboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@ final class AttachOverKeyboard {
/// Whether the panel and cards are drawn over the keyboard rather than in the bar.
private(set) var isActive = false

/// The menu rows the panel showed as it opened.
private(set) var items: [AttachMenuItem] = []

/// `+`'s frame in window coordinates, as last laid out.
var plusFrame: CGRect = .zero

Expand All @@ -89,9 +86,8 @@ final class AttachOverKeyboard {
/// Photos hands the card over to the keyboard-down flow, whose sheet the keyboard would cover.
var opensLibraryInBar = false

/// Starts drawing over the keyboard, with `items` as the panel's rows.
func activate(items: [AttachMenuItem]) {
self.items = items
/// Starts drawing over the keyboard.
func activate() {
isActive = true
}

Expand Down
12 changes: 6 additions & 6 deletions Flipcash/Core/Screens/Conversation/ChatScreenRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,8 @@ struct ChatScreenRepresentable: UIViewControllerRepresentable {
isJoiningChat: isJoiningChat,
mentions: mentions,
acceptsMedia: acceptsMedia,
onAttachOpen: { [weak coordinator] items in
coordinator?.openAttach(items: items)
onAttachOpen: { [weak coordinator, acceptsMedia] in
coordinator?.openAttach(acceptsMedia: acceptsMedia)
},
attachActions: attachActions,
onImagesDropped: onImagesDropped,
Expand Down Expand Up @@ -375,15 +375,15 @@ struct ChatScreenRepresentable: UIViewControllerRepresentable {
self.overlay = AttachKeyboardOverlay(model: model)
}

/// `+` is opening the panel with `items` as its rows: over the keyboard while it is up and its
/// window or the field's input view can carry the panel, else with the keyboard taken down.
func openAttach(items: [AttachMenuItem]) {
/// `+` is opening the panel: over the keyboard while it is up and its window or the field's
/// input view can carry the panel, else with the keyboard taken down.
func openAttach(acceptsMedia: Bool) {
guard let screen else { return }
// Sized now, so the card's content is laid out at its final size while the menu is up.
model.attachCard.measure(screenHeight: screen.view.bounds.height)
let mode = overlay.begin(
screen: screen,
items: items,
acceptsMedia: acceptsMedia,
composer: composer,
actions: { [weak self] in self?.attachActions ?? AttachActions() }
)
Expand Down
16 changes: 9 additions & 7 deletions Flipcash/Core/Screens/Conversation/ConversationBottomBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ struct ConversationBarBottomRow: Equatable {
self.init(plusItems: AttachMenuItem.items(showsCash: false, acceptsMedia: acceptsMedia, attachedCount: attachedCount))
}

/// Returns the row for `composer` as it stands, in a chat where `acceptsMedia` says whether
/// photos may be sent. The bar and the overlay over the keyboard both read their rows here.
init(composer: ComposerModel, acceptsMedia: Bool) {
self.init(isEditing: composer.isEditing, acceptsMedia: acceptsMedia, attachedCount: composer.chips.count)
}

private init(plusItems: [AttachMenuItem]) {
self.plusItems = plusItems
}
Expand Down Expand Up @@ -293,7 +299,7 @@ struct ConversationBottomBar: View {
var acceptsMedia: Bool = false
/// Fired as `+` opens the attach panel with the given rows: the panel goes up over the keyboard,
/// or the keyboard goes down under it.
var onAttachOpen: ([AttachMenuItem]) -> Void = { _ in }
var onAttachOpen: () -> Void = {}
/// What the attach menu and its cards hand back, the same value the overlay over the keyboard draws with.
var attachActions = AttachActions()
/// Receives images dropped on the bar, such as the system screenshot thumbnail or an image
Expand Down Expand Up @@ -688,11 +694,7 @@ struct ConversationBottomBar: View {
}

private var bottomRow: ConversationBarBottomRow {
ConversationBarBottomRow(
isEditing: composer.isEditing,
acceptsMedia: acceptsMedia,
attachedCount: composer.chips.count
)
ConversationBarBottomRow(composer: composer, acceptsMedia: acceptsMedia)
}
}

Expand Down Expand Up @@ -1062,7 +1064,7 @@ struct ConversationComposer: View {
/// Whether the attach surface is drawn in `+`'s place, which hides `+`.
var plusIsStoodInFor = false
/// Fired as `+` opens the attach panel with the given rows.
var onAttachOpen: ([AttachMenuItem]) -> Void = { _ in }
var onAttachOpen: () -> Void = {}
/// Whether `$` stands beside the field and merges into it as the draft starts, so the send
/// arrow waits for it to land instead of appearing over it.
var sendWaitsForCashJoin = false
Expand Down
2 changes: 1 addition & 1 deletion FlipcashTests/Chat/AttachOverlayModeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ struct AttachOverlayModeTests {
model.attachCard.open(.photos, screenHeight: 800)
#expect(model.overflow != .none)

model.overKeyboard.activate(items: [.camera, .photos])
model.overKeyboard.activate()
#expect(model.overflow == .none)
}

Expand Down
2 changes: 1 addition & 1 deletion FlipcashTests/Chat/AttachPanelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ struct AttachPanelTests {
@Test("Over the keyboard the bar claims no room, even with the panel up: the overlay takes its touches")
func overKeyboardClaimsNoRoom() {
let model = ConversationBarModel()
model.overKeyboard.activate(items: [.camera, .photos])
model.overKeyboard.activate()
model.attachPanel.open()
#expect(model.overflow == .none)
}
Expand Down
16 changes: 16 additions & 0 deletions FlipcashTests/Chat/ConversationBarLeadingControlTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
//

import Testing
import UIKit
import FlipcashCore
@testable import Flipcash

@MainActor
Expand Down Expand Up @@ -75,4 +77,18 @@ struct ConversationBarBottomRowTests {
func hiddenWhileEditing() {
#expect(row(isEditing: true).isEmpty)
}

@Test("Read from the composer, the rows follow photos staged after the menu opened")
func followsTheComposer() throws {
let composer = ComposerModel()
let uploader = ChatMediaUploader(blob: MockChatMediaBlobStore())
#expect(ConversationBarBottomRow(composer: composer, acceptsMedia: true).plusItems == [.camera, .photos])

let image = UIGraphicsImageRenderer(size: CGSize(width: 4, height: 3)).image { _ in }
for _ in 0..<ComposerModel.maxAttachments {
try #require(composer.stageChip(image: image, uploader: uploader) != nil)
}

#expect(ConversationBarBottomRow(composer: composer, acceptsMedia: true).isEmpty)
}
}
Loading