Files
buzz/mobile/ios/Runner/NativeAttachmentPopoverMenu.swift
klopez4212 6f5ec4a731 Add mobile voice notes (#7121)
## Summary
- add iOS and Android voice-note recording and preview directly in the
mobile composer
- add waveform playback, scrubbing, speed controls, haptics, and
one-shot playback in chat
- package recordings in a canonical H.264/AAC MP4 envelope on both
platforms so existing relays accept them
- preserve the shared composer interaction and attachment-card treatment
across mobile platforms

Mobile counterpart to #6978.

## Testing
- `just ci`
- `just mobile-check`
- `just mobile-test` (2,026 tests)
- Android debug build compiled, installed, launched, and Voice note
verified in the attachment menu on Pixel 10
- signed iOS device build installed on iPhone

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz>
2026-09-03 08:07:04 +00:00

143 lines
4.3 KiB
Swift

import Flutter
import UIKit
@available(iOS 26.0, *)
extension NativeAttachmentPopoverViewController {
func makeMenuView() -> UIView {
let container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false
let scrollView = UIScrollView()
scrollView.alwaysBounceVertical = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(scrollView)
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fillEqually
stack.spacing = NativeAttachmentMenuLayout.itemSpacing
stack.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stack)
let stackHeightConstraint = stack.heightAnchor.constraint(
equalToConstant: NativeAttachmentMenuLayout.itemsHeight(
compatibleWith: traitCollection
)
)
menuStackHeightConstraint = stackHeightConstraint
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: container.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
stack.leadingAnchor.constraint(
equalTo: scrollView.frameLayoutGuide.leadingAnchor,
constant: NativeAttachmentMenuLayout.contentPadding
),
stack.trailingAnchor.constraint(
equalTo: scrollView.frameLayoutGuide.trailingAnchor,
constant: -NativeAttachmentMenuLayout.contentPadding
),
stack.topAnchor.constraint(
equalTo: scrollView.contentLayoutGuide.topAnchor,
constant: NativeAttachmentMenuLayout.contentPadding
),
stack.bottomAnchor.constraint(
equalTo: scrollView.contentLayoutGuide.bottomAnchor,
constant: -NativeAttachmentMenuLayout.contentPadding
),
stackHeightConstraint,
])
stack.addArrangedSubview(
makeNativeAttachmentMenuButton(
title: "Camera",
symbol: "camera",
action: { [weak self] in self?.showCamera() }
)
)
stack.addArrangedSubview(
makeNativeAttachmentMenuButton(
title: "Photos",
symbol: "photo.on.rectangle.angled",
action: { [weak self] in self?.showPhotos() }
)
)
stack.addArrangedSubview(
makeNativeAttachmentMenuButton(
title: "Video",
symbol: "video",
action: { [weak self] in
self?.finish(method: "pickVideo")
}
)
)
stack.addArrangedSubview(
makeNativeAttachmentMenuButton(
title: "Voice note",
symbol: "mic",
action: { [weak self] in
self?.finish(
method: "recordVoiceNote",
notifyBeforeDismissal: true
)
}
)
)
stack.addArrangedSubview(
makeNativeAttachmentMenuButton(
title: "Files",
symbol: "doc",
action: { [weak self] in
self?.finish(method: "pickFiles")
}
)
)
return container
}
func makeGlassControl(
title: String?,
symbol: String?,
accessibilityLabel: String,
prominent: Bool = false,
action: @escaping () -> Void
) -> UIButton {
var configuration =
prominent
? UIButton.Configuration.prominentGlass()
: UIButton.Configuration.glass()
configuration.title = title
if prominent {
configuration.baseBackgroundColor = .black
}
if let symbol {
configuration.image = UIImage(systemName: symbol)
}
configuration.imagePadding = 8
configuration.baseForegroundColor = .white
configuration.titleTextAttributesTransformer =
UIConfigurationTextAttributesTransformer { attributes in
var interAttributes = attributes
interAttributes.font = NativeAttachmentMenuTypography.font(
forTextStyle: .body
)
return interAttributes
}
configuration.contentInsets = NSDirectionalEdgeInsets(
top: 11,
leading: 15,
bottom: 11,
trailing: 15
)
let button = UIButton(
configuration: configuration,
primaryAction: UIAction { _ in
UISelectionFeedbackGenerator().selectionChanged()
action()
}
)
button.accessibilityLabel = accessibilityLabel
return button
}
}