damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

FullScreenCarouselView.swift (8370B)


      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     @ObservedObject var video_coordinator: DamusVideoCoordinator
     12     let urls: [MediaUrl]
     13     
     14     @Environment(\.presentationMode) var presentationMode
     15     
     16     @State var showMenu = true
     17     @State private var imageDict: [URL: UIImage] = [:]
     18     let settings: UserSettingsStore
     19     @ObservedObject var carouselSelection: CarouselSelection
     20     let content: (() -> Content)?
     21     
     22     init(video_coordinator: DamusVideoCoordinator, urls: [MediaUrl], showMenu: Bool = true, settings: UserSettingsStore, selectedIndex: Binding<Int>, @ViewBuilder content: @escaping () -> Content) {
     23         self.video_coordinator = video_coordinator
     24         self.urls = urls
     25         self._showMenu = State(initialValue: showMenu)
     26         self.settings = settings
     27         self._carouselSelection = ObservedObject(initialValue: CarouselSelection(index: selectedIndex.wrappedValue))
     28         self.content = content
     29     }
     30     
     31     init(video_coordinator: DamusVideoCoordinator, urls: [MediaUrl], showMenu: Bool = true, settings: UserSettingsStore, selectedIndex: Binding<Int>) {
     32         self.video_coordinator = video_coordinator
     33         self.urls = urls
     34         self._showMenu = State(initialValue: showMenu)
     35         self.settings = settings
     36         self._carouselSelection = ObservedObject(initialValue: CarouselSelection(index: selectedIndex.wrappedValue))
     37         self.content = nil
     38     }
     39     
     40     var background: some ShapeStyle {
     41         if case .video = urls[safe: carouselSelection.index] {
     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: $carouselSelection.index) {
     59                 ForEach(urls.indices, id: \.self) { index in
     60                     VStack {
     61                         if case .video = urls[safe: index] {
     62                             ImageContainerView(
     63                                 video_coordinator: video_coordinator,
     64                                 url: urls[index],
     65                                 settings: settings,
     66                                 imageDict: $imageDict
     67                             )
     68                             .modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
     69                                 presentationMode.wrappedValue.dismiss()
     70                             }))
     71                             .ignoresSafeArea()
     72                         }
     73                         else {
     74                             ZoomableScrollView {
     75                                 ImageContainerView(video_coordinator: video_coordinator, url: urls[index], settings: settings, imageDict: $imageDict)
     76                                     .aspectRatio(contentMode: .fit)
     77                                     .padding(.top, Theme.safeAreaInsets?.top)
     78                                     .padding(.bottom, Theme.safeAreaInsets?.bottom)
     79                             }
     80                             .modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
     81                                 presentationMode.wrappedValue.dismiss()
     82                             }))
     83                             .ignoresSafeArea()
     84                         }
     85                     }.tag(index)
     86                 }
     87             }
     88             .ignoresSafeArea()
     89             .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
     90             .gesture(TapGesture(count: 2).onEnded {
     91                 // Prevents menu from hiding on double tap
     92             })
     93             .gesture(TapGesture(count: 1).onEnded {
     94                 showMenu.toggle()
     95             })
     96             .overlay(
     97                 GeometryReader { geo in
     98                     VStack {
     99                         if showMenu {
    100                             HStack {
    101                                 Button(action: {
    102                                     presentationMode.wrappedValue.dismiss()
    103                                 }, label: {
    104                                     Image(systemName: "xmark")
    105                                         .frame(width: 30, height: 30)
    106                                 })
    107                                 .buttonStyle(PlayerCircleButtonStyle())
    108                                 
    109                                 Spacer()
    110 
    111                                 if let url = urls[safe: carouselSelection.index],
    112                                    let image = imageDict[url.url] {
    113                                     
    114                                     ShareLink(item: Image(uiImage: image),
    115                                               preview: SharePreview(NSLocalizedString("Shared Picture",
    116                                                                                       comment: "Label for the preview of the image being picture"),
    117                                                                     image: Image(uiImage: image))) {
    118                                         Image(systemName: "ellipsis")
    119                                             .frame(width: 30, height: 30)
    120                                     }
    121                                     .buttonStyle(PlayerCircleButtonStyle())
    122                                 }
    123                             }
    124                             .padding()
    125                             
    126                             Spacer()
    127                             
    128                             VStack {
    129                                 if urls.count > 1 {
    130                                     PageControlView(currentPage: $carouselSelection.index, numberOfPages: urls.count)
    131                                         .frame(maxWidth: 0, maxHeight: 0)
    132                                         .padding(.top, 5)
    133                                 }
    134                                 
    135                                 if let focused_video = video_coordinator.focused_video {
    136                                     DamusVideoControlsView(video: focused_video)
    137                                 }
    138                                 
    139                                 self.content?()
    140                             }
    141                             .padding(.top, 5)
    142                             .background(Color.black.opacity(0.7))
    143                         }
    144                     }
    145                     .animation(.easeInOut, value: showMenu)
    146                     .padding(.bottom, geo.safeAreaInsets.bottom == 0 ? 12 : 0)
    147                 }
    148             )
    149         }
    150     }
    151 }
    152 
    153 fileprivate struct FullScreenCarouselPreviewView<Content: View>: View {
    154     @State var selectedIndex: Int = 0
    155     let url: MediaUrl = .image(URL(string: "https://jb55.com/red-me.jpg")!)
    156     let test_video_url: MediaUrl = .video(URL(string: "http://cdn.jb55.com/s/zaps-build.mp4")!)
    157     let custom_content: (() -> Content)?
    158     
    159     init(content: (() -> Content)? = nil) {
    160         self.custom_content = content
    161     }
    162     
    163     var body: some View {
    164         FullScreenCarouselView(video_coordinator: test_damus_state.video, urls: [test_video_url, url], settings: test_damus_state.settings, selectedIndex: $selectedIndex) {
    165             self.custom_content?()
    166         }
    167             .environmentObject(OrientationTracker())
    168     }
    169 }
    170 
    171 struct FullScreenCarouselView_Previews: PreviewProvider {
    172     static var previews: some View {
    173         Group {
    174             FullScreenCarouselPreviewView<AnyView>()
    175                 .previewDisplayName("No custom content on overlay")
    176             
    177             FullScreenCarouselPreviewView(content: {
    178                 HStack {
    179                     Spacer()
    180                     
    181                     Text(verbatim: "Some content")
    182                         .padding()
    183                         .foregroundColor(.white)
    184                         
    185                     Spacer()
    186                 }.background(.ultraThinMaterial)
    187             })
    188                 .previewDisplayName("Custom content on overlay")
    189         }
    190     }
    191 }
    192 
    193 /// Class to define object for monitoring selectedIndex and updating mutlples views
    194 final class CarouselSelection: ObservableObject {
    195     @Published var index: Int
    196     init(index: Int) {
    197         self.index = index
    198     }
    199 }