iOS truncated item overview

This commit is contained in:
Ethan Pippin 2022-01-09 20:00:41 -07:00
parent 14c8aa4101
commit c6886e3272
7 changed files with 214 additions and 10 deletions

View File

@ -19,6 +19,7 @@ final class ItemCoordinator: NavigationCoordinatable {
@Root var start = makeStart
@Route(.push) var item = makeItem
@Route(.push) var library = makeLibrary
@Route(.modal) var itemOverview = makeItemOverview
@Route(.fullScreen) var videoPlayer = makeVideoPlayer
let itemDto: BaseItemDto
@ -35,6 +36,10 @@ final class ItemCoordinator: NavigationCoordinatable {
ItemCoordinator(item: item)
}
func makeItemOverview(item: BaseItemDto) -> NavigationViewCoordinator<ItemOverviewCoordinator> {
NavigationViewCoordinator(ItemOverviewCoordinator(item: itemDto))
}
func makeVideoPlayer(viewModel: VideoPlayerViewModel) -> NavigationViewCoordinator<VideoPlayerCoordinator> {
NavigationViewCoordinator(VideoPlayerCoordinator(viewModel: viewModel))
}

View File

@ -0,0 +1,29 @@
//
/*
* SwiftFin is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/
import Stinsen
import SwiftUI
import JellyfinAPI
final class ItemOverviewCoordinator: NavigationCoordinatable {
let stack = NavigationStack(initial: \ItemOverviewCoordinator.start)
@Root var start = makeStart
let item: BaseItemDto
init(item: BaseItemDto) {
self.item = item
}
@ViewBuilder func makeStart() -> some View {
ItemOverviewView(item: item)
}
}

View File

@ -20,6 +20,7 @@ extension Color {
public static let lightGray = Color(UIColor.lightGray)
#else
public static let systemFill = Color(UIColor.systemFill)
public static let systemBackground = Color(UIColor.systemBackground)
public static let secondarySystemFill = Color(UIColor.secondarySystemBackground)
public static let tertiarySystemFill = Color(UIColor.tertiarySystemBackground)
#endif

View File

@ -430,6 +430,9 @@
E1E5D54F2783E67100692DFE /* OverlaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D54E2783E67100692DFE /* OverlaySettingsView.swift */; };
E1E5D5512783E67700692DFE /* ExperimentalSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5502783E67700692DFE /* ExperimentalSettingsView.swift */; };
E1E5D553278419D900692DFE /* ConfirmCloseOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D552278419D900692DFE /* ConfirmCloseOverlay.swift */; };
E1EBCB42278BD174009FE6E9 /* TruncatedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */; };
E1EBCB44278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */; };
E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */; };
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
E1FA2F7427818A8800B4C270 /* SmallMenuOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FA2F7327818A8800B4C270 /* SmallMenuOverlay.swift */; };
@ -750,6 +753,9 @@
E1E5D54E2783E67100692DFE /* OverlaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlaySettingsView.swift; sourceTree = "<group>"; };
E1E5D5502783E67700692DFE /* ExperimentalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentalSettingsView.swift; sourceTree = "<group>"; };
E1E5D552278419D900692DFE /* ConfirmCloseOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmCloseOverlay.swift; sourceTree = "<group>"; };
E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TruncatedTextView.swift; sourceTree = "<group>"; };
E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemOverviewCoordinator.swift; sourceTree = "<group>"; };
E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemOverviewView.swift; sourceTree = "<group>"; };
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = "<group>"; };
E1FA2F7327818A8800B4C270 /* SmallMenuOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallMenuOverlay.swift; sourceTree = "<group>"; };
E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = "<group>"; };
@ -1202,6 +1208,7 @@
C4BE076D2720FEA8003F4AD1 /* PortraitItemElement.swift */,
53F866432687A45F00DCD1D7 /* PortraitItemView.swift */,
E1AA331C2782541500F6439C /* PrimaryButtonView.swift */,
E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */,
);
path = Components;
sourceTree = "<group>";
@ -1261,11 +1268,12 @@
6220D0B926D6092100B8E046 /* FilterCoordinator.swift */,
62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */,
6220D0BF26D61C5000B8E046 /* ItemCoordinator.swift */,
E1EBCB43278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift */,
6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */,
62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */,
C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */,
C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */,
C4BE07782726EE82003F4AD1 /* LiveTVTabCoordinator.swift */,
C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */,
62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */,
E193D5412719404B00900D82 /* MainCoordinator */,
C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */,
6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */,
@ -1424,18 +1432,19 @@
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
5389276D263C25100035E14B /* ContinueWatchingView.swift */,
625CB56E2678C23300530A6E /* HomeView.swift */,
E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */,
E14F7D0A26DB3714007C3AE6 /* ItemView */,
53FF7F29263CF3F500585C35 /* LatestMediaView.swift */,
C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */,
C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */,
53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */,
6213388F265F83A900A81A2A /* LibraryListView.swift */,
53EE24E5265060780068F029 /* LibrarySearchView.swift */,
53DF641D263D9C0600A7CD1A /* LibraryView.swift */,
C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */,
C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */,
5389276F263C25230035E14B /* NextUpView.swift */,
E1E5D54A2783E26100692DFE /* SettingsView */,
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */,
E13DD3E427177D15009D4DAF /* ServerListView.swift */,
E1E5D54A2783E26100692DFE /* SettingsView */,
E13DD3FB2717EAE8009D4DAF /* UserListView.swift */,
E13DD3F4271793BB009D4DAF /* UserSignInView.swift */,
E193D5452719418B00900D82 /* VideoPlayer */,
@ -2240,6 +2249,7 @@
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */,
E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */,
E1C812C5277A90B200918266 /* URLComponentsExtensions.swift in Sources */,
E1EBCB44278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift in Sources */,
62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */,
@ -2270,6 +2280,7 @@
E19169CE272514760085832A /* HTTPScheme.swift in Sources */,
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
C4AE2C3027498D2300AE13CF /* LiveTVHomeView.swift in Sources */,
E1EBCB42278BD174009FE6E9 /* TruncatedTextView.swift in Sources */,
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */,
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */,
@ -2342,6 +2353,7 @@
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */,
62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */,
C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */,
E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */,
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */,

View File

@ -0,0 +1,106 @@
//
/*
* SwiftFin is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/
import SwiftUI
struct TruncatedTextView: View {
@State private var truncated: Bool = false
@State private var shrinkText: String
private var text: String
let font: UIFont
let lineLimit: Int
let seeMoreAction: () -> Void
private var moreLessText: String {
if !truncated {
return ""
} else {
return "See More"
}
}
init(_ text: String,
lineLimit: Int,
font: UIFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body),
seeMoreAction: @escaping () -> Void) {
self.text = text
self.lineLimit = lineLimit
_shrinkText = State(wrappedValue: text)
self.font = font
self.seeMoreAction = seeMoreAction
}
var body: some View {
VStack(alignment: .center) {
Group {
Text(shrinkText)
.overlay {
if truncated {
LinearGradient(stops: [.init(color: .systemBackground.opacity(0), location: 0.5),
.init(color: .systemBackground.opacity(0.8), location: 0.7),
.init(color: .systemBackground, location: 1)],
startPoint: .top,
endPoint: .bottom)
}
}
}
.lineLimit(lineLimit)
.background {
// Render the limited text and measure its size
Text(text)
.lineLimit(lineLimit + 2)
.background {
GeometryReader { visibleTextGeometry in
Color.clear
.onAppear {
let size = CGSize(width: visibleTextGeometry.size.width, height: .greatestFiniteMagnitude)
let attributes:[NSAttributedString.Key:Any] = [NSAttributedString.Key.font: font]
var low = 0
var heigh = shrinkText.count
var mid = heigh
while ((heigh - low) > 1) {
let attributedText = NSAttributedString(string: shrinkText, attributes: attributes)
let boundingRect = attributedText.boundingRect(with: size, options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)
if boundingRect.size.height > visibleTextGeometry.size.height {
truncated = true
heigh = mid
mid = (heigh + low)/2
} else {
if mid == text.count {
break
} else {
low = mid
mid = (low + heigh)/2
}
}
shrinkText = String(text.prefix(mid))
}
if truncated {
shrinkText = String(shrinkText.prefix(shrinkText.count - 2))
}
}
}
}
.hidden()
}
.font(Font(font))
if truncated {
Button {
seeMoreAction()
} label: {
Text(moreLessText)
}
}
}
}
}

View File

@ -0,0 +1,35 @@
//
/*
* SwiftFin is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/
import JellyfinAPI
import SwiftUI
struct ItemOverviewView: View {
@EnvironmentObject var itemOverviewRouter: ItemOverviewCoordinator.Router
let item: BaseItemDto
var body: some View {
ScrollView(showsIndicators: false) {
Text(item.overview ?? "")
.font(.footnote)
.padding()
}
.navigationBarTitle("Overview", displayMode: .inline)
.toolbar {
ToolbarItemGroup(placement: .navigationBarLeading) {
Button {
itemOverviewRouter.dismissCoordinator()
} label: {
Image(systemName: "xmark.circle.fill")
}
}
}
}
}

View File

@ -13,6 +13,8 @@ import SwiftUI
struct ItemViewBody: View {
@Environment(\.horizontalSizeClass) private var hSizeClass
@Environment(\.verticalSizeClass) private var vSizeClass
@EnvironmentObject var itemRouter: ItemCoordinator.Router
@EnvironmentObject private var viewModel: ItemViewModel
@Default(.showCastAndCrew) var showCastAndCrew
@ -21,10 +23,24 @@ struct ItemViewBody: View {
VStack(alignment: .leading) {
// MARK: Overview
Text(viewModel.item.overview ?? "")
.font(.footnote)
.padding(.horizontal, 16)
.padding(.vertical, 3)
if let itemOverview = viewModel.item.overview {
if hSizeClass == .compact && vSizeClass == .regular {
TruncatedTextView(itemOverview,
lineLimit: 5,
font: UIFont.preferredFont(forTextStyle: .footnote)) {
itemRouter.route(to: \.itemOverview, viewModel.item)
}
.padding()
} else {
Text(itemOverview)
.font(.footnote)
.padding()
}
} else {
Text("No overview available")
.font(.footnote)
.padding()
}
// MARK: Seasons