Documents tab with file sharing

This commit is contained in:
Arkadiusz Fal
2022-11-13 00:01:04 +01:00
parent ccded28468
commit 4657af2f3d
15 changed files with 474 additions and 59 deletions

View File

@@ -22,6 +22,7 @@ extension Defaults.Keys {
static let enableReturnYouTubeDislike = Key<Bool>("enableReturnYouTubeDislike", default: false)
static let showHome = Key<Bool>("showHome", default: true)
static let showDocuments = Key<Bool>("showDocuments", default: true)
static let showOpenActionsInHome = Key<Bool>("showOpenActionsInHome", default: true)
static let showOpenActionsToolbarItem = Key<Bool>("showOpenActionsToolbarItem", default: false)
static let showFavoritesInHome = Key<Bool>("showFavoritesInHome", default: true)

View File

@@ -0,0 +1,82 @@
import SwiftUI
struct DocumentsView: View {
@ObservedObject private var model = DocumentsModel.shared
var body: some View {
BrowserPlayerControls {
ScrollView(.vertical, showsIndicators: false) {
if model.directoryContents.isEmpty {
VStack(alignment: .center, spacing: 20) {
HStack {
Image(systemName: "doc")
Text("No documents")
}
Text("Share files from Finder on a Mac\nor iTunes on Windows")
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity)
.foregroundColor(.secondary)
} else {
ForEach(model.sortedDirectoryContents, id: \.absoluteString) { url in
let video = Video.local(model.replacePrivateVar(url) ?? url)
PlayerQueueRow(
item: PlayerQueueItem(video)
)
.contextMenu {
VideoContextMenuView(video: video)
}
}
.id(model.refreshID)
.transition(.opacity)
}
Color.clear.padding(.bottom, 50)
}
.onAppear {
if model.directoryURL.isNil {
model.goToTop()
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
model.goBack()
}
} label: {
HStack(spacing: 6) {
Label("Go back", systemImage: "chevron.left")
}
}
.transaction { t in t.animation = .none }
.disabled(!model.canGoBack)
}
}
.navigationTitle(model.directoryLabel)
.padding()
.navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode)
.backport
.refreshable {
DispatchQueue.main.async {
self.refresh()
}
}
}
}
func refresh() {
withAnimation {
model.refresh()
}
}
}
struct DocumentsView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
DocumentsView()
}
.injectFixtureEnvironmentObjects()
.navigationViewStyle(.stack)
}
}

View File

@@ -14,6 +14,7 @@ struct AppTabNavigation: View {
@EnvironmentObject<ThumbnailsModel> private var thumbnailsModel
@Default(.showHome) private var showHome
@Default(.showDocuments) private var showDocuments
@Default(.showOpenActionsToolbarItem) private var showOpenActionsToolbarItem
@Default(.visibleSections) private var visibleSections
@@ -26,6 +27,10 @@ struct AppTabNavigation: View {
homeNavigationView
}
if showDocuments {
documentsNavigationView
}
if !accounts.isEmpty {
if subscriptionsVisible {
subscriptionsNavigationView
@@ -73,6 +78,18 @@ struct AppTabNavigation: View {
.tag(TabSelection.home)
}
private var documentsNavigationView: some View {
NavigationView {
LazyView(DocumentsView())
.toolbar { toolbarContent }
}
.tabItem {
Label("Documents", systemImage: "folder")
.accessibility(label: Text("Documents"))
}
.tag(TabSelection.documents)
}
private var subscriptionsNavigationView: some View {
NavigationView {
LazyView(SubscriptionsView())

View File

@@ -6,6 +6,7 @@ struct Sidebar: View {
@EnvironmentObject<NavigationModel> private var navigation
@Default(.showHome) private var showHome
@Default(.showDocuments) private var showDocuments
@Default(.visibleSections) private var visibleSections
var body: some View {
@@ -48,6 +49,18 @@ struct Sidebar: View {
}
.id("favorites")
}
#if os(iOS)
if showDocuments {
NavigationLink(destination: LazyView(DocumentsView()), tag: TabSelection.documents, selection: $navigation.tabSelection) {
Label("Documents", systemImage: "folder")
.accessibility(label: Text("Documents"))
}
.id("documents")
}
#endif
}
if !accounts.isEmpty {
if visibleSections.contains(.subscriptions),
accounts.app.supportsSubscriptions && accounts.signedIn

View File

@@ -29,6 +29,22 @@ struct PlayerQueueRow: View {
var body: some View {
Button {
#if os(iOS)
guard !item.video.localStreamIsDirectory else {
if let url = item.video?.localStream?.localURL {
withAnimation {
DocumentsModel.shared.goToURL(url)
}
}
return
}
#endif
}
if item.video.localStreamIsFile, let url = item.video.localStream?.localURL {
URLBookmarkModel.shared.saveBookmark(url)
}
player.prepareCurrentItemForHistory()
player.avPlayerBackend.startPictureInPictureOnPlay = player.playingInPictureInPicture

View File

@@ -14,6 +14,7 @@ struct BrowsingSettings: View {
@Default(.channelOnThumbnail) private var channelOnThumbnail
@Default(.timeOnThumbnail) private var timeOnThumbnail
@Default(.showHome) private var showHome
@Default(.showDocuments) private var showDocuments
@Default(.showFavoritesInHome) private var showFavoritesInHome
@Default(.showOpenActionsInHome) private var showOpenActionsInHome
@Default(.showOpenActionsToolbarItem) private var showOpenActionsToolbarItem
@@ -85,6 +86,7 @@ struct BrowsingSettings: View {
}
}
.multilineTextAlignment(.trailing)
if !accounts.isEmpty {
Toggle("Show Favorites", isOn: $showFavoritesInHome)
@@ -124,6 +126,8 @@ struct BrowsingSettings: View {
Toggle("Show Open Videos toolbar button", isOn: $showOpenActionsToolbarItem)
#endif
#if os(iOS)
Toggle("Show Documents", isOn: $showDocuments)
Toggle("Lock portrait mode", isOn: $lockPortraitWhenBrowsing)
.onChange(of: lockPortraitWhenBrowsing) { lock in
if lock {

View File

@@ -22,7 +22,7 @@ struct VideoBanner: View {
var body: some View {
HStack(alignment: stackAlignment, spacing: 12) {
VStack(spacing: thumbnailStackSpacing) {
ZStack(alignment: .bottom) {
smallThumbnail
#if !os(tvOS)
@@ -55,6 +55,20 @@ struct VideoBanner: View {
if let video {
if !video.isLocal || video.localStreamIsRemoteURL {
Text(video.displayAuthor)
} else {
#if os(iOS)
if DocumentsModel.shared.isDocument(video) {
HStack(spacing: 6) {
if let date = DocumentsModel.shared.formattedCreationDate(video) {
Text(date)
}
if let size = DocumentsModel.shared.formattedSize(video) {
Text("")
Text(size)
}
}
}
#endif
}
} else {
Text("Video Author")
@@ -69,8 +83,10 @@ struct VideoBanner: View {
progressView
#endif
Text((videoDuration ?? video?.length ?? 0).formattedAsPlaybackTime() ?? PlayerTimeModel.timePlaceholder)
.fontWeight(.light)
if !(video?.localStreamIsDirectory ?? false) {
Text(videoDurationLabel)
.fontWeight(.light)
}
}
.foregroundColor(.secondary)
}
@@ -98,24 +114,15 @@ struct VideoBanner: View {
#endif
}
private var thumbnailStackSpacing: Double {
#if os(tvOS)
8
#else
2
#endif
}
@ViewBuilder private var smallThumbnail: some View {
Group {
ZStack {
Color("PlaceholderColor")
if let video {
if let thumbnail = video.thumbnailURL(quality: .medium) {
WebImage(url: thumbnail, options: [.lowPriority])
.resizable()
} else if video.localStreamIsFile {
Image(systemName: "folder")
} else if video.localStreamIsRemoteURL {
Image(systemName: "globe")
} else if video.isLocal {
Image(systemName: video.localStreamImageSystemName)
}
} else {
Image(systemName: "ellipsis")
@@ -146,6 +153,11 @@ struct VideoBanner: View {
#endif
}
private var videoDurationLabel: String {
guard videoDuration != 0 else { return PlayerTimeModel.timePlaceholder }
return (videoDuration ?? video?.length ?? 0).formattedAsPlaybackTime() ?? PlayerTimeModel.timePlaceholder
}
private var progressView: some View {
Group {
if !playbackTime.isNil, !(video?.live ?? false) {
@@ -157,11 +169,13 @@ struct VideoBanner: View {
}
private var progressViewValue: Double {
[playbackTime?.seconds, videoDuration].compactMap { $0 }.min() ?? 0
guard videoDuration != 0 else { return 1 }
return [playbackTime?.seconds, videoDuration].compactMap { $0 }.min() ?? 0
}
private var progressViewTotal: Double {
videoDuration ?? video?.length ?? 1
guard videoDuration != 0 else { return 1 }
return videoDuration ?? video?.length ?? 1
}
}

View File

@@ -288,11 +288,7 @@ struct ControlsBar: View {
Group {
if let video = model.currentItem?.video, video.isLocal {
if video.localStreamIsFile {
Image(systemName: "folder")
} else if video.localStreamIsRemoteURL {
Image(systemName: "globe")
}
Image(systemName: video.localStreamImageSystemName)
} else {
Image(systemName: "play.rectangle")
}

View File

@@ -37,54 +37,64 @@ struct VideoContextMenuView: View {
}
@ViewBuilder var contextMenu: some View {
if saveHistory {
Section {
if let watchedAtString {
Text(watchedAtString)
}
if !video.localStreamIsDirectory {
if saveHistory {
Section {
if let watchedAtString {
Text(watchedAtString)
}
if !watch.isNil, !watch!.finished, !watchingNow {
continueButton
}
if !watch.isNil, !watch!.finished, !watchingNow {
continueButton
}
if !(watch?.finished ?? false) {
markAsWatchedButton
}
if !(watch?.finished ?? false) {
markAsWatchedButton
}
if !watch.isNil, !watchingNow {
removeFromHistoryButton
if !watch.isNil, !watchingNow {
removeFromHistoryButton
}
}
}
Section {
playNowButton
#if !os(tvOS)
playNowInPictureInPictureButton
playNowInMusicMode
#endif
}
Section {
playNextButton
addToQueueButton
}
if accounts.app.supportsUserPlaylists, accounts.signedIn, !video.isLocal {
Section {
addToPlaylistButton
addToLastPlaylistButton
if let id = navigation.tabSelection?.playlistID ?? playlistID {
removeFromPlaylistButton(playlistID: id)
}
}
}
}
Section {
playNowButton
#if !os(tvOS)
playNowInPictureInPictureButton
playNowInMusicMode
Section {
ShareButton(contentItem: .init(video: video))
}
#endif
}
Section {
playNextButton
addToQueueButton
}
if accounts.app.supportsUserPlaylists, accounts.signedIn, !video.isLocal {
Section {
addToPlaylistButton
addToLastPlaylistButton
if let id = navigation.tabSelection?.playlistID ?? playlistID {
removeFromPlaylistButton(playlistID: id)
#if os(iOS)
if video.isLocal, let url = video.localStream?.localURL, DocumentsModel.shared.isDocument(url) {
Section {
removeDocumentButton
}
}
}
#if !os(tvOS)
Section {
ShareButton(contentItem: .init(video: video))
}
#endif
if !inChannelView, !inChannelPlaylistView, !video.isLocal {
@@ -215,6 +225,31 @@ struct VideoContextMenuView: View {
}
}
#if os(iOS)
private var removeDocumentButton: some View {
Button {
if let url = video.localStream?.localURL {
NavigationModel.shared.presentAlert(
Alert(
title: Text("Are you sure you want to remove this document?"),
primaryButton: .destructive(Text("Remove")) {
do {
try DocumentsModel.shared.removeDocument(url)
} catch {
NavigationModel.shared.presentAlert(title: "Could not delete document", message: error.localizedDescription)
}
},
secondaryButton: .cancel()
)
)
}
} label: {
Label("Remove...", systemImage: "trash.fill")
.foregroundColor(Color("AppRedColor"))
}
}
#endif
private var openChannelButton: some View {
Button {
NavigationModel.openChannel(