mirror of
git://jb55.com/damus
synced 2024-09-30 00:40:45 +00:00
camera: switch to new custom view
Closes: https://github.com/damus-io/damus/pull/1254 Reviewed-by: William Casarin <jb55@jb55.com> Signed-off-by: William Casarin <jb55@jb55.com> Changelog-Added: Added a custom camera view
This commit is contained in:
parent
f341a37902
commit
ca779d472d
@ -441,6 +441,7 @@
|
|||||||
BA4AB0AE2A63B9270070A32A /* AddEmojiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */; };
|
BA4AB0AE2A63B9270070A32A /* AddEmojiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */; };
|
||||||
BA4AB0B02A63B94D0070A32A /* EmojiListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */; };
|
BA4AB0B02A63B94D0070A32A /* EmojiListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */; };
|
||||||
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
|
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
|
||||||
|
BAA578D52AED7F4000EA8BE3 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA578D42AED7F4000EA8BE3 /* CameraView.swift */; };
|
||||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||||
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
|
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
|
||||||
D70A3B172B02DCE5008BD568 /* NotificationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */; };
|
D70A3B172B02DCE5008BD568 /* NotificationFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */; };
|
||||||
@ -1337,6 +1338,7 @@
|
|||||||
BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEmojiView.swift; sourceTree = "<group>"; };
|
BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEmojiView.swift; sourceTree = "<group>"; };
|
||||||
BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiListItemView.swift; sourceTree = "<group>"; };
|
BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiListItemView.swift; sourceTree = "<group>"; };
|
||||||
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
|
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
|
||||||
|
BAA578D42AED7F4000EA8BE3 /* CameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = "<group>"; };
|
||||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
||||||
D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
|
D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
|
||||||
D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFormatter.swift; sourceTree = "<group>"; };
|
D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFormatter.swift; sourceTree = "<group>"; };
|
||||||
@ -2629,6 +2631,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BA3759962ABCCF360018D73B /* CameraPreview.swift */,
|
BA3759962ABCCF360018D73B /* CameraPreview.swift */,
|
||||||
|
BAA578D42AED7F4000EA8BE3 /* CameraView.swift */,
|
||||||
);
|
);
|
||||||
path = Camera;
|
path = Camera;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2638,6 +2641,8 @@
|
|||||||
children = (
|
children = (
|
||||||
D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */,
|
D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */,
|
||||||
D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */,
|
D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */,
|
||||||
|
BAA8C3262AEC570800696158 /* CameraView.swift */,
|
||||||
|
BA3759962ABCCF360018D73B /* CameraPreview.swift */,
|
||||||
);
|
);
|
||||||
path = Mocking;
|
path = Mocking;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -3141,6 +3146,7 @@
|
|||||||
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */,
|
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */,
|
||||||
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */,
|
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */,
|
||||||
4C687C242A5FA86D0092C550 /* SearchHeaderView.swift in Sources */,
|
4C687C242A5FA86D0092C550 /* SearchHeaderView.swift in Sources */,
|
||||||
|
BAA578D52AED7F4000EA8BE3 /* CameraView.swift in Sources */,
|
||||||
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
|
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
|
||||||
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
|
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
|
||||||
4CA352AC2A76C07F003BB08B /* NewUnmutesNotify.swift in Sources */,
|
4CA352AC2A76C07F003BB08B /* NewUnmutesNotify.swift in Sources */,
|
||||||
|
212
damus/Views/Camera/CameraView.swift
Normal file
212
damus/Views/Camera/CameraView.swift
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
//
|
||||||
|
// CameraView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Suhail Saqan on 8/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Combine
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
struct CameraView: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
let action: (([MediaItem]) -> Void)
|
||||||
|
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
|
@StateObject var model: CameraModel
|
||||||
|
|
||||||
|
@State var currentZoomFactor: CGFloat = 1.0
|
||||||
|
|
||||||
|
public init(damus_state: DamusState, action: @escaping (([MediaItem]) -> Void)) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
self.action = action
|
||||||
|
_model = StateObject(wrappedValue: CameraModel())
|
||||||
|
}
|
||||||
|
|
||||||
|
var captureButton: some View {
|
||||||
|
Button {
|
||||||
|
if model.isRecording {
|
||||||
|
withAnimation {
|
||||||
|
model.stopRecording()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
withAnimation {
|
||||||
|
model.capturePhoto()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
|
} label: {
|
||||||
|
ZStack {
|
||||||
|
Circle()
|
||||||
|
.fill( model.isRecording ? .red : DamusColors.black)
|
||||||
|
.frame(width: model.isRecording ? 85 : 65, height: model.isRecording ? 85 : 65, alignment: .center)
|
||||||
|
|
||||||
|
Circle()
|
||||||
|
.stroke( model.isRecording ? .red : DamusColors.white, lineWidth: 4)
|
||||||
|
.frame(width: model.isRecording ? 95 : 75, height: model.isRecording ? 95 : 75, alignment: .center)
|
||||||
|
}
|
||||||
|
.frame(alignment: .center)
|
||||||
|
}
|
||||||
|
.simultaneousGesture(
|
||||||
|
LongPressGesture(minimumDuration: 0.5).onEnded({ value in
|
||||||
|
if (!model.isCameraButtonDisabled) {
|
||||||
|
withAnimation {
|
||||||
|
model.startRecording()
|
||||||
|
model.captureMode = .video
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
var capturedPhotoThumbnail: some View {
|
||||||
|
ZStack {
|
||||||
|
if model.thumbnail != nil {
|
||||||
|
Image(uiImage: model.thumbnail.thumbnailImage!)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: 60, height: 60)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
|
||||||
|
}
|
||||||
|
if model.isPhotoProcessing {
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(CircularProgressViewStyle(tint: DamusColors.white))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var closeButton: some View {
|
||||||
|
Button {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
model.stop()
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "xmark")
|
||||||
|
.font(.system(size: 24))
|
||||||
|
}
|
||||||
|
.frame(minWidth: 40, minHeight: 40)
|
||||||
|
}
|
||||||
|
.accentColor(DamusColors.white)
|
||||||
|
}
|
||||||
|
|
||||||
|
var flipCameraButton: some View {
|
||||||
|
Button(action: {
|
||||||
|
model.flipCamera()
|
||||||
|
}, label: {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "camera.rotate.fill")
|
||||||
|
.font(.system(size: 20))
|
||||||
|
}
|
||||||
|
.frame(minWidth: 40, minHeight: 40)
|
||||||
|
})
|
||||||
|
.accentColor(DamusColors.white)
|
||||||
|
}
|
||||||
|
|
||||||
|
var toggleFlashButton: some View {
|
||||||
|
Button(action: {
|
||||||
|
model.switchFlash()
|
||||||
|
}, label: {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: model.isFlashOn ? "bolt.fill" : "bolt.slash.fill")
|
||||||
|
.font(.system(size: 20))
|
||||||
|
}
|
||||||
|
.frame(minWidth: 40, minHeight: 40)
|
||||||
|
})
|
||||||
|
.accentColor(model.isFlashOn ? .yellow : DamusColors.white)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
GeometryReader { reader in
|
||||||
|
ZStack {
|
||||||
|
DamusColors.black.edgesIgnoringSafeArea(.all)
|
||||||
|
|
||||||
|
CameraPreview(session: model.session)
|
||||||
|
.padding(.bottom, 175)
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
.gesture(
|
||||||
|
DragGesture().onChanged({ (val) in
|
||||||
|
if abs(val.translation.height) > abs(val.translation.width) {
|
||||||
|
let percentage: CGFloat = -(val.translation.height / reader.size.height)
|
||||||
|
let calc = currentZoomFactor + percentage
|
||||||
|
let zoomFactor: CGFloat = min(max(calc, 1), 5)
|
||||||
|
|
||||||
|
currentZoomFactor = zoomFactor
|
||||||
|
model.zoom(with: zoomFactor)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.onAppear {
|
||||||
|
model.configure()
|
||||||
|
}
|
||||||
|
.alert(isPresented: $model.showAlertError, content: {
|
||||||
|
Alert(title: Text(model.alertError.title), message: Text(model.alertError.message), dismissButton: .default(Text(model.alertError.primaryButtonTitle), action: {
|
||||||
|
model.alertError.primaryAction?()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.overlay(
|
||||||
|
Group {
|
||||||
|
if model.willCapturePhoto {
|
||||||
|
Color.black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
if !model.isRecording {
|
||||||
|
HStack {
|
||||||
|
closeButton
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
flipCameraButton
|
||||||
|
toggleFlashButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
if !model.mediaItems.isEmpty {
|
||||||
|
NavigationLink(destination: Text(model.mediaItems.map { $0.url.absoluteString }.joined(separator: ", "))) {
|
||||||
|
capturedPhotoThumbnail
|
||||||
|
}
|
||||||
|
.frame(width: 100, alignment: .leading)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
captureButton
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if !model.mediaItems.isEmpty {
|
||||||
|
Button(action: {
|
||||||
|
action(model.mediaItems)
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
model.stop()
|
||||||
|
}) {
|
||||||
|
Text("Upload")
|
||||||
|
.frame(width: 100, height: 40, alignment: .center)
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
|
.overlay {
|
||||||
|
RoundedRectangle(cornerRadius: 24)
|
||||||
|
.stroke(DamusColors.white, lineWidth: 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 100)
|
||||||
|
.padding([.horizontal, .vertical], 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,7 @@ struct PostView: View {
|
|||||||
@State var newCursorIndex: Int?
|
@State var newCursorIndex: Int?
|
||||||
@State var textHeight: CGFloat? = nil
|
@State var textHeight: CGFloat? = nil
|
||||||
|
|
||||||
@State var mediaToUpload: MediaUpload? = nil
|
@State var mediaToUpload: [MediaUpload] = []
|
||||||
|
|
||||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
||||||
@StateObject var tagModel: TagModel = TagModel()
|
@StateObject var tagModel: TagModel = TagModel()
|
||||||
@ -379,6 +379,15 @@ struct PostView: View {
|
|||||||
pks.append(pk)
|
pks.append(pk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addToMediaToUpload(mediaItem: MediaItem) {
|
||||||
|
switch mediaItem.type {
|
||||||
|
case .image:
|
||||||
|
mediaToUpload.append(.image(mediaItem.url))
|
||||||
|
case .video:
|
||||||
|
mediaToUpload.append(.video(mediaItem.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { (deviceSize: GeometryProxy) in
|
GeometryReader { (deviceSize: GeometryProxy) in
|
||||||
@ -421,36 +430,29 @@ struct PostView: View {
|
|||||||
.background(DamusColors.adaptableWhite.edgesIgnoringSafeArea(.all))
|
.background(DamusColors.adaptableWhite.edgesIgnoringSafeArea(.all))
|
||||||
.sheet(isPresented: $attach_media) {
|
.sheet(isPresented: $attach_media) {
|
||||||
ImagePicker(uploader: damus_state.settings.default_media_uploader, sourceType: .photoLibrary, pubkey: damus_state.pubkey, image_upload_confirm: $image_upload_confirm) { img in
|
ImagePicker(uploader: damus_state.settings.default_media_uploader, sourceType: .photoLibrary, pubkey: damus_state.pubkey, image_upload_confirm: $image_upload_confirm) { img in
|
||||||
self.mediaToUpload = .image(img)
|
self.mediaToUpload.append(.image(img))
|
||||||
} onVideoPicked: { url in
|
} onVideoPicked: { url in
|
||||||
self.mediaToUpload = .video(url)
|
self.mediaToUpload.append(.video(url))
|
||||||
}
|
}
|
||||||
.alert(NSLocalizedString("Are you sure you want to upload this media?", comment: "Alert message asking if the user wants to upload media."), isPresented: $image_upload_confirm) {
|
.alert(NSLocalizedString("Are you sure you want to upload this media?", comment: "Alert message asking if the user wants to upload media."), isPresented: $image_upload_confirm) {
|
||||||
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
|
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
|
||||||
if let mediaToUpload {
|
if !mediaToUpload.isEmpty {
|
||||||
self.handle_upload(media: mediaToUpload)
|
self.handle_upload(media: mediaToUpload[0])
|
||||||
self.attach_media = false
|
self.attach_media = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel the upload."), role: .cancel) {}
|
Button(NSLocalizedString("Cancel", comment: "Button to cancel the upload."), role: .cancel) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $attach_camera) {
|
.fullScreenCover(isPresented: $attach_camera) {
|
||||||
|
CameraView(damus_state: damus_state, action: { items in
|
||||||
ImagePicker(uploader: damus_state.settings.default_media_uploader, sourceType: .camera, pubkey: damus_state.pubkey, image_upload_confirm: $image_upload_confirm) { img in
|
for item in items {
|
||||||
self.mediaToUpload = .image(img)
|
addToMediaToUpload(mediaItem: item)
|
||||||
} onVideoPicked: { url in
|
|
||||||
self.mediaToUpload = .video(url)
|
|
||||||
}
|
|
||||||
.alert(NSLocalizedString("Are you sure you want to upload this media?", comment: "Alert message asking if the user wants to upload media."), isPresented: $image_upload_confirm) {
|
|
||||||
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
|
|
||||||
if let mediaToUpload {
|
|
||||||
self.handle_upload(media: mediaToUpload)
|
|
||||||
self.attach_camera = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel the upload."), role: .cancel) {}
|
for media in mediaToUpload {
|
||||||
}
|
self.handle_upload(media: media)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
let loaded_draft = load_draft()
|
let loaded_draft = load_draft()
|
||||||
|
Loading…
Reference in New Issue
Block a user