FullScreenCarouselView.swift (6187B)
1 // 2 // FullScreenCarouselView.swift 3 // damus 4 // 5 // Created by William Casarin on 2023-03-23. 6 // 7 8 import SwiftUI 9 10 struct FullScreenCarouselView<Content: View>: View { 11 let video_controller: VideoController 12 let urls: [MediaUrl] 13 14 @Environment(\.presentationMode) var presentationMode 15 16 @State var showMenu = true 17 18 let settings: UserSettingsStore 19 @Binding var selectedIndex: Int 20 let content: (() -> Content)? 21 22 init(video_controller: VideoController, urls: [MediaUrl], showMenu: Bool = true, settings: UserSettingsStore, selectedIndex: Binding<Int>, @ViewBuilder content: @escaping () -> Content) { 23 self.video_controller = video_controller 24 self.urls = urls 25 self._showMenu = State(initialValue: showMenu) 26 self.settings = settings 27 _selectedIndex = selectedIndex 28 self.content = content 29 } 30 31 init(video_controller: VideoController, urls: [MediaUrl], showMenu: Bool = true, settings: UserSettingsStore, selectedIndex: Binding<Int>) { 32 self.video_controller = video_controller 33 self.urls = urls 34 self._showMenu = State(initialValue: showMenu) 35 self.settings = settings 36 _selectedIndex = selectedIndex 37 self.content = nil 38 } 39 40 var background: some ShapeStyle { 41 if case .video = urls[safe: selectedIndex] { 42 return AnyShapeStyle(Color.black) 43 } 44 else { 45 return AnyShapeStyle(.regularMaterial) 46 } 47 } 48 49 var background_color: UIColor { 50 return .black 51 } 52 53 var body: some View { 54 ZStack { 55 Color(self.background_color) 56 .ignoresSafeArea() 57 58 TabView(selection: $selectedIndex) { 59 ForEach(urls.indices, id: \.self) { index in 60 VStack { 61 if case .video = urls[safe: index] { 62 ImageContainerView(video_controller: video_controller, url: urls[index], settings: settings) 63 .clipped() // SwiftUI hack from https://stackoverflow.com/a/74401288 to make playback controls show up within the TabView 64 .aspectRatio(contentMode: .fit) 65 .padding(.top, Theme.safeAreaInsets?.top) 66 .padding(.bottom, Theme.safeAreaInsets?.bottom) 67 .modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: { 68 presentationMode.wrappedValue.dismiss() 69 })) 70 .ignoresSafeArea() 71 } 72 else { 73 ZoomableScrollView { 74 ImageContainerView(video_controller: video_controller, url: urls[index], settings: settings) 75 .aspectRatio(contentMode: .fit) 76 .padding(.top, Theme.safeAreaInsets?.top) 77 .padding(.bottom, Theme.safeAreaInsets?.bottom) 78 } 79 .modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: { 80 presentationMode.wrappedValue.dismiss() 81 })) 82 .ignoresSafeArea() 83 } 84 }.tag(index) 85 } 86 } 87 .ignoresSafeArea() 88 .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) 89 .gesture(TapGesture(count: 2).onEnded { 90 // Prevents menu from hiding on double tap 91 }) 92 .gesture(TapGesture(count: 1).onEnded { 93 showMenu.toggle() 94 }) 95 .overlay( 96 GeometryReader { geo in 97 VStack { 98 if showMenu { 99 NavDismissBarView(showBackgroundCircle: false) 100 .foregroundColor(.white) 101 Spacer() 102 103 if urls.count > 1 { 104 PageControlView(currentPage: $selectedIndex, numberOfPages: urls.count) 105 .frame(maxWidth: 0, maxHeight: 0) 106 .padding(.top, 5) 107 } 108 109 self.content?() 110 } 111 } 112 .animation(.easeInOut, value: showMenu) 113 .padding(.bottom, geo.safeAreaInsets.bottom == 0 ? 12 : 0) 114 } 115 ) 116 } 117 } 118 } 119 120 fileprivate struct FullScreenCarouselPreviewView<Content: View>: View { 121 @State var selectedIndex: Int = 0 122 let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!) 123 let test_video_url: MediaUrl = .video(URL(string: "http://cdn.jb55.com/s/zaps-build.mp4")!) 124 let custom_content: (() -> Content)? 125 126 init(content: (() -> Content)? = nil) { 127 self.custom_content = content 128 } 129 130 var body: some View { 131 FullScreenCarouselView(video_controller: test_damus_state.video, urls: [test_video_url, url], settings: test_damus_state.settings, selectedIndex: $selectedIndex) { 132 self.custom_content?() 133 } 134 .environmentObject(OrientationTracker()) 135 } 136 } 137 138 struct FullScreenCarouselView_Previews: PreviewProvider { 139 static var previews: some View { 140 Group { 141 FullScreenCarouselPreviewView<AnyView>() 142 .previewDisplayName("No custom content on overlay") 143 144 FullScreenCarouselPreviewView(content: { 145 HStack { 146 Spacer() 147 148 Text(verbatim: "Some content") 149 .padding() 150 .foregroundColor(.white) 151 152 Spacer() 153 }.background(.ultraThinMaterial) 154 }) 155 .previewDisplayName("Custom content on overlay") 156 } 157 } 158 }