iOS/iPadOS Quick Connect (#522)

This commit is contained in:
Ethan Pippin 2022-08-09 11:22:52 -06:00 committed by GitHub
parent 599cff4c95
commit 6a23570d93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 313 additions and 227 deletions

View File

@ -0,0 +1,30 @@
//
// 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 (c) 2022 Jellyfin & Jellyfin Contributors
//
import Foundation
import Stinsen
import SwiftUI
final class QuickConnectCoordinator: NavigationCoordinatable {
let stack = NavigationStack(initial: \QuickConnectCoordinator.start)
@Root
var start = makeStart
private let viewModel: UserSignInViewModel
init(viewModel: UserSignInViewModel) {
self.viewModel = viewModel
}
@ViewBuilder
func makeStart() -> some View {
QuickConnectView(viewModel: viewModel)
}
}

View File

@ -16,6 +16,10 @@ final class UserSignInCoordinator: NavigationCoordinatable {
@Root
var start = makeStart
#if !os(tvOS)
@Route(.modal)
var quickConnect = makeQuickConnect
#endif
let viewModel: UserSignInViewModel
@ -23,6 +27,12 @@ final class UserSignInCoordinator: NavigationCoordinatable {
self.viewModel = viewModel
}
#if !os(tvOS)
func makeQuickConnect() -> NavigationViewCoordinator<QuickConnectCoordinator> {
NavigationViewCoordinator(QuickConnectCoordinator(viewModel: viewModel))
}
#endif
@ViewBuilder
func makeStart() -> some View {
UserSignInView(viewModel: viewModel)

View File

@ -0,0 +1,26 @@
//
// 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 (c) 2022 Jellyfin & Jellyfin Contributors
//
import Foundation
import JellyfinAPI
import UIKit
extension UserDto {
func profileImageSource(maxWidth: CGFloat, maxHeight: CGFloat) -> ImageSource {
let scaleWidth = UIScreen.main.scale(maxWidth)
let scaleHeight = UIScreen.main.scale(maxHeight)
let profileImageURL = ImageAPI.getUserImageWithRequestBuilder(
userId: id ?? "",
imageType: .primary,
maxWidth: scaleWidth,
maxHeight: scaleHeight
).url
return ImageSource(url: profileImageURL, blurHash: nil)
}
}

View File

@ -0,0 +1,41 @@
//
// 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 (c) 2022 Jellyfin & Jellyfin Contributors
//
import Foundation
class RepeatingTimer {
let action: () -> Void
private let interval: TimeInterval
private var timer: Timer?
init(interval: TimeInterval, _ action: @escaping () -> Void) {
self.action = action
self.interval = interval
}
@objc
private func runAction() {
action()
}
func start() {
self.timer = Timer.scheduledTimer(
timeInterval: interval,
target: self,
selector: #selector(runAction),
userInfo: nil,
repeats: true
)
}
func stop() {
self.timer?.invalidate()
self.timer = nil
}
}

View File

@ -14,7 +14,7 @@ import Stinsen
final class UserSignInViewModel: ViewModel {
@RouterObject
private var Router: UserSignInCoordinator.Router?
private var router: UserSignInCoordinator.Router?
@Published
var publicUsers: [UserDto] = []
@ -24,7 +24,7 @@ final class UserSignInViewModel: ViewModel {
var quickConnectEnabled = false
let server: SwiftfinStore.State.Server
private var quickConnectTimer: Timer?
private var quickConnectTimer: RepeatingTimer?
private var quickConnectSecret: String?
init(server: SwiftfinStore.State.Server) {
@ -33,6 +33,7 @@ final class UserSignInViewModel: ViewModel {
JellyfinAPIAPI.basePath = server.currentURI
checkQuickConnect()
getPublicUsers()
}
var alertTitle: String {
@ -64,7 +65,7 @@ final class UserSignInViewModel: ViewModel {
self.isLoading = false
}
func loadUsers() {
func getPublicUsers() {
UserAPI.getPublicUsers()
.trackActivity(loading)
.sink(receiveCompletion: { completion in
@ -75,35 +76,17 @@ final class UserSignInViewModel: ViewModel {
.store(in: &cancellables)
}
func getProfileImageUrl(user: UserDto) -> URL? {
let urlString = ImageAPI.getUserImageWithRequestBuilder(
userId: user.id ?? "--",
imageType: .primary,
width: 200,
quality: 90
).URLString
return URL(string: urlString)
}
func getSplashscreenUrl() -> URL? {
let urlString = ImageAPI.getSplashscreenWithRequestBuilder().URLString
return URL(string: urlString)
}
func checkQuickConnect() {
QuickConnectAPI.getEnabled()
.sink(receiveCompletion: { completion in
self.handleAPIRequestError(completion: completion)
}, receiveValue: { enabled in
self.quickConnectEnabled = enabled
if enabled {
self.startQuickConnect()
}
})
.store(in: &cancellables)
}
private func startQuickConnect() {
func startQuickConnect(_ onSuccess: @escaping () -> Void) {
QuickConnectAPI.initiate()
.sink(receiveCompletion: { completion in
self.handleAPIRequestError(completion: completion)
@ -113,19 +96,17 @@ final class UserSignInViewModel: ViewModel {
self.quickConnectCode = response.code
LogManager.log.debug("QuickConnect code: \(response.code ?? "--")")
self.quickConnectTimer = Timer.scheduledTimer(
timeInterval: 5,
target: self,
selector: #selector(self.checkAuthStatus),
userInfo: nil,
repeats: true
)
self.quickConnectTimer = RepeatingTimer(interval: 5) {
self.checkAuthStatus(onSuccess)
}
self.quickConnectTimer?.start()
})
.store(in: &cancellables)
}
@objc
private func checkAuthStatus() {
private func checkAuthStatus(_ onSuccess: @escaping () -> Void) {
guard let quickConnectSecret = quickConnectSecret else { return }
QuickConnectAPI.connect(secret: quickConnectSecret)
@ -138,7 +119,8 @@ final class UserSignInViewModel: ViewModel {
return
}
self.quickConnectTimer?.invalidate()
self.stopQuickConnectAuthCheck()
onSuccess()
SessionManager.main.signInUser(server: self.server, quickConnectSecret: quickConnectSecret)
.trackActivity(self.loading)
@ -150,4 +132,11 @@ final class UserSignInViewModel: ViewModel {
})
.store(in: &cancellables)
}
func stopQuickConnectAuthCheck() {
DispatchQueue.main.async {
self.quickConnectTimer?.stop()
self.quickConnectTimer = nil
}
}
}

View File

@ -6,6 +6,7 @@
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import JellyfinAPI
import Stinsen
import SwiftUI
@ -20,7 +21,7 @@ struct UserSignInView: View {
var body: some View {
ZStack {
ImageView(viewModel.getSplashscreenUrl())
ImageView(ImageAPI.getSplashscreenWithRequestBuilder().url)
.ignoresSafeArea()
Color.black
@ -96,14 +97,20 @@ struct UserSignInView: View {
.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity)
.onAppear {
viewModel.startQuickConnect {}
}
.onDisappear {
viewModel.stopQuickConnectAuthCheck()
}
}
}
}
}
}
struct UserSignInView_Preivews: PreviewProvider {
static var previews: some View {
UserSignInView(viewModel: .init(server: .sample))
}
}
// struct UserSignInView_Preivews: PreviewProvider {
// static var previews: some View {
// UserSignInView(viewModel: .init(server: .sample))
// }
// }

View File

@ -182,7 +182,6 @@
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; };
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; };
631759CF2879DB6A00A621AD /* PublicUserSignInCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631759CE2879DB6A00A621AD /* PublicUserSignInCellView.swift */; };
6334175B287DDFB9000603CE /* QuickConnectSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6334175A287DDFB9000603CE /* QuickConnectSettingsView.swift */; };
6334175D287DE0D0000603CE /* QuickConnectSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6334175C287DE0D0000603CE /* QuickConnectSettingsViewModel.swift */; };
637FCAF5287B5B2600C0A353 /* UDPBroadcast.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 637FCAF3287B5B2600C0A353 /* UDPBroadcast.xcframework */; };
@ -254,6 +253,7 @@
E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */; };
E1101177281B1E8A006A3584 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1101176281B1E8A006A3584 /* Puppy */; };
E111DE222790BB46008118A3 /* DetectBottomScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E111DE212790BB46008118A3 /* DetectBottomScrollView.swift */; };
E1171A1928A2212600FA1AF5 /* QuickConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */; };
E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; };
E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; };
E11895A9289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */; };
@ -337,6 +337,11 @@
E184C161288C5C08000B25BA /* RequestBuilderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */; };
E18845F526DD631E00B0C5B7 /* BaseItemDto+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18845F426DD631E00B0C5B7 /* BaseItemDto+Poster.swift */; };
E18845F626DD631E00B0C5B7 /* BaseItemDto+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18845F426DD631E00B0C5B7 /* BaseItemDto+Poster.swift */; };
E18CE0AF28A222240092E7F1 /* PublicUserSignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0AE28A222240092E7F1 /* PublicUserSignInView.swift */; };
E18CE0B228A229E70092E7F1 /* UserDtoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0B128A229E70092E7F1 /* UserDtoExtensions.swift */; };
E18CE0B428A22EDA0092E7F1 /* RepeatingTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */; };
E18CE0B528A22EDD0092E7F1 /* RepeatingTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */; };
E18CE0B928A2322D0092E7F1 /* QuickConnectCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0B828A2322D0092E7F1 /* QuickConnectCoordinator.swift */; };
E18E01A9288746AF0022598C /* PortraitPosterHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01A3288746AF0022598C /* PortraitPosterHStack.swift */; };
E18E01AA288746AF0022598C /* RefreshableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01A4288746AF0022598C /* RefreshableScrollView.swift */; };
E18E01AB288746AF0022598C /* PillHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01A5288746AF0022598C /* PillHStack.swift */; };
@ -690,7 +695,6 @@
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
62ECA01726FA685A00E8EBB7 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
631759CE2879DB6A00A621AD /* PublicUserSignInCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicUserSignInCellView.swift; sourceTree = "<group>"; };
6334175A287DDFB9000603CE /* QuickConnectSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectSettingsView.swift; sourceTree = "<group>"; };
6334175C287DE0D0000603CE /* QuickConnectSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectSettingsViewModel.swift; sourceTree = "<group>"; };
637FCAF3287B5B2600C0A353 /* UDPBroadcast.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = UDPBroadcast.xcframework; path = Carthage/Build/UDPBroadcast.xcframework; sourceTree = "<group>"; };
@ -742,6 +746,7 @@
E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSizeExtensions.swift; sourceTree = "<group>"; };
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+VideoPlayerViewModel.swift"; sourceTree = "<group>"; };
E111DE212790BB46008118A3 /* DetectBottomScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectBottomScrollView.swift; sourceTree = "<group>"; };
E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectView.swift; sourceTree = "<group>"; };
E118959C289312020042947B /* BaseItemPerson+Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemPerson+Poster.swift"; sourceTree = "<group>"; };
E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewOffsetModifier.swift; sourceTree = "<group>"; };
E11895AB289383EE0042947B /* NavBarOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarOffsetModifier.swift; sourceTree = "<group>"; };
@ -794,6 +799,10 @@
E17885A3278105170094FBCF /* SFSymbolButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFSymbolButton.swift; sourceTree = "<group>"; };
E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestBuilderExtensions.swift; sourceTree = "<group>"; };
E18845F426DD631E00B0C5B7 /* BaseItemDto+Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+Poster.swift"; sourceTree = "<group>"; };
E18CE0AE28A222240092E7F1 /* PublicUserSignInView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicUserSignInView.swift; sourceTree = "<group>"; };
E18CE0B128A229E70092E7F1 /* UserDtoExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDtoExtensions.swift; sourceTree = "<group>"; };
E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepeatingTimer.swift; sourceTree = "<group>"; };
E18CE0B828A2322D0092E7F1 /* QuickConnectCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectCoordinator.swift; sourceTree = "<group>"; };
E18E01A3288746AF0022598C /* PortraitPosterHStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PortraitPosterHStack.swift; sourceTree = "<group>"; };
E18E01A4288746AF0022598C /* RefreshableScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshableScrollView.swift; sourceTree = "<group>"; };
E18E01A5288746AF0022598C /* PillHStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PillHStack.swift; sourceTree = "<group>"; };
@ -1145,15 +1154,16 @@
children = (
E1D4BF802719D22800A11E64 /* AppAppearance.swift */,
E1D4BF862719D27100A11E64 /* Bitrates.swift */,
E1C925F328875037002A7A66 /* ItemViewType.swift */,
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */,
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */,
E19169CD272514760085832A /* HTTPScheme.swift */,
E1C925F328875037002A7A66 /* ItemViewType.swift */,
E1AA331E2782639D00F6439C /* OverlayType.swift */,
E1C925F62887504B002A7A66 /* PanDirectionGestureRecognizer.swift */,
E193D4DA27193CCA00900D82 /* PillStackable.swift */,
E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */,
E1937A60288F32DB00CB80AA /* Poster.swift */,
E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */,
5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */,
E1D4BF832719D25A00A11E64 /* TrackLanguage.swift */,
535870AC2669D8DD00D05A09 /* Typings.swift */,
@ -1405,8 +1415,8 @@
E111DE212790BB46008118A3 /* DetectBottomScrollView.swift */,
E18E01A7288746AF0022598C /* DotHStack.swift */,
E18E01A5288746AF0022598C /* PillHStack.swift */,
E18E01A3288746AF0022598C /* PortraitPosterHStack.swift */,
53F866432687A45F00DCD1D7 /* PortraitPosterButton.swift */,
E18E01A3288746AF0022598C /* PortraitPosterHStack.swift */,
E1AA331C2782541500F6439C /* PrimaryButton.swift */,
E18E01A4288746AF0022598C /* RefreshableScrollView.swift */,
);
@ -1425,7 +1435,6 @@
621338912660106C00A81A2A /* Extensions */ = {
isa = PBXGroup;
children = (
E1AD105226D96D5F003E4A08 /* JellyfinAPIExtensions */,
E1A2C157279A7D76005EC829 /* BundleExtensions.swift */,
E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */,
6267B3D526710B8900A7371D /* CollectionExtensions.swift */,
@ -1433,11 +1442,12 @@
E1399473289B1EA900401ABC /* Defaults+Workaround.swift */,
E1E00A34278628A40022235B /* DoubleExtensions.swift */,
E11CEB8C28999B4A003E74C7 /* FontExtensions.swift */,
E1AD105226D96D5F003E4A08 /* JellyfinAPIExtensions */,
621338922660107500A81A2A /* StringExtensions.swift */,
E1A2C153279A7D5A005EC829 /* UIApplicationExtensions.swift */,
E13DD3C727164B1E009D4DAF /* UIDeviceExtensions.swift */,
E18E0239288749540022598C /* UIScrollViewExtensions.swift */,
E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */,
E18E0239288749540022598C /* UIScrollViewExtensions.swift */,
E1C812C4277A90B200918266 /* URLComponentsExtensions.swift */,
62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */,
E11895A22893409D0042947B /* ViewExtensions */,
@ -1466,11 +1476,12 @@
6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */,
62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */,
C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */,
C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */,
C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */,
C4BE07782726EE82003F4AD1 /* LiveTVTabCoordinator.swift */,
C45942C427F67DA400C54FE7 /* LiveTVCoordinator.swift */,
E193D5412719404B00900D82 /* MainCoordinator */,
C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */,
E18CE0B828A2322D0092E7F1 /* QuickConnectCoordinator.swift */,
6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */,
E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */,
E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */,
@ -1544,6 +1555,15 @@
path = ItemViewModel;
sourceTree = "<group>";
};
E1171A1A28A2215800FA1AF5 /* UserSignInView */ = {
isa = PBXGroup;
children = (
E18CE0B028A222310092E7F1 /* Components */,
E13DD3F4271793BB009D4DAF /* UserSignInView.swift */,
);
path = UserSignInView;
sourceTree = "<group>";
};
E11895A22893409D0042947B /* ViewExtensions */ = {
isa = PBXGroup;
children = (
@ -1675,12 +1695,12 @@
C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */,
C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */,
C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */,
631759CE2879DB6A00A621AD /* PublicUserSignInCellView.swift */,
E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */,
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */,
E13DD3E427177D15009D4DAF /* ServerListView.swift */,
E1E5D54A2783E26100692DFE /* SettingsView */,
E13DD3FB2717EAE8009D4DAF /* UserListView.swift */,
E13DD3F4271793BB009D4DAF /* UserSignInView.swift */,
E1171A1A28A2215800FA1AF5 /* UserSignInView */,
E193D5452719418B00900D82 /* VideoPlayer */,
);
path = Views;
@ -1776,6 +1796,14 @@
path = Overlays;
sourceTree = "<group>";
};
E18CE0B028A222310092E7F1 /* Components */ = {
isa = PBXGroup;
children = (
E18CE0AE28A222240092E7F1 /* PublicUserSignInView.swift */,
);
path = Components;
sourceTree = "<group>";
};
E18E01B4288747230022598C /* iPadOS */ = {
isa = PBXGroup;
children = (
@ -1964,6 +1992,7 @@
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */,
E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */,
E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */,
E18CE0B128A229E70092E7F1 /* UserDtoExtensions.swift */,
);
path = JellyfinAPIExtensions;
sourceTree = "<group>";
@ -2502,6 +2531,7 @@
E1FA2F7427818A8800B4C270 /* SmallMenuOverlay.swift in Sources */,
E193D53C27193F9500900D82 /* UserListCoordinator.swift in Sources */,
E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */,
E18CE0B528A22EDD0092E7F1 /* RepeatingTimer.swift in Sources */,
E13DD3C927164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */,
E193D53A27193F9000900D82 /* ServerListCoordinator.swift in Sources */,
6220D0AE26D5EABB00B8E046 /* ViewExtensions.swift in Sources */,
@ -2579,6 +2609,7 @@
C45942CD27F6994A00C54FE7 /* LiveTVPlayerView.swift in Sources */,
62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */,
E18E01E8288747230022598C /* SeriesItemContentView.swift in Sources */,
E18CE0AF28A222240092E7F1 /* PublicUserSignInView.swift in Sources */,
E18E01E5288747230022598C /* CinematicScrollView.swift in Sources */,
E1C812C0277A8E5D00918266 /* VLCPlayerView.swift in Sources */,
E176DE6D278E30D2001EFD8D /* EpisodeCard.swift in Sources */,
@ -2636,11 +2667,13 @@
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */,
E1171A1928A2212600FA1AF5 /* QuickConnectView.swift in Sources */,
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */,
E11CEB8D28999B4A003E74C7 /* FontExtensions.swift in Sources */,
E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */,
E11895A9289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */,
E1C812C3277A8E5D00918266 /* VLCPlayerOverlayView.swift in Sources */,
E18CE0B228A229E70092E7F1 /* UserDtoExtensions.swift in Sources */,
E18E01F0288747230022598C /* AttributeHStack.swift in Sources */,
6334175B287DDFB9000603CE /* QuickConnectSettingsView.swift in Sources */,
E18E0205288749200022598C /* AppIcon.swift in Sources */,
@ -2657,6 +2690,7 @@
5D1603FC278A3D5800D22B99 /* SubtitleSize.swift in Sources */,
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */,
E1FA891E289A305D00176FEB /* iPadOSCollectionItemContentView.swift in Sources */,
E18CE0B928A2322D0092E7F1 /* QuickConnectCoordinator.swift in Sources */,
6334175D287DE0D0000603CE /* QuickConnectSettingsViewModel.swift in Sources */,
E18E01E0288747230022598C /* iPadOSMovieItemContentView.swift in Sources */,
53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */,
@ -2670,7 +2704,6 @@
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */,
631759CF2879DB6A00A621AD /* PublicUserSignInCellView.swift in Sources */,
E1D4BF842719D25A00A11E64 /* TrackLanguage.swift in Sources */,
E1002B5F2793C3BE00E47059 /* VLCPlayerChapterOverlayView.swift in Sources */,
E18E01DC288747230022598C /* iPadOSCinematicScrollView.swift in Sources */,
@ -2713,6 +2746,7 @@
E176DE70278E369F001EFD8D /* MissingItemsSettingsView.swift in Sources */,
C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */,
E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */,
E18CE0B428A22EDA0092E7F1 /* RepeatingTimer.swift in Sources */,
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */,

View File

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "628B951F2670CABD0091AF3B"
BuildableName = "Swiftfin Widget.appex"
BlueprintName = "Swiftfin Widget"
ReferencedContainer = "container:Swiftfin.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
BuildableName = "Swiftfin iOS.app"
BlueprintName = "Swiftfin iOS"
ReferencedContainer = "container:Swiftfin.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.springboard">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "628B951F2670CABD0091AF3B"
BuildableName = "Swiftfin Widget.appex"
BlueprintName = "Swiftfin Widget"
ReferencedContainer = "container:Swiftfin.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
BuildableName = "Swiftfin iOS.app"
BlueprintName = "Swiftfin iOS"
ReferencedContainer = "container:Swiftfin.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = ""
isEnabled = "NO">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetDefaultView"
value = "snapshot"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetFamily"
value = "medium"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
BuildableName = "Swiftfin iOS.app"
BlueprintName = "Swiftfin iOS"
ReferencedContainer = "container:Swiftfin.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,45 +0,0 @@
//
// 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 (c) 2022 Jellyfin & Jellyfin Contributors
//
import JellyfinAPI
import SwiftUI
struct UserLoginCellView: View {
@ObservedObject
var viewModel: UserSignInViewModel
@State
private var enteredPassword: String = ""
var user: UserDto
var body: some View {
DisclosureGroup {
SecureField(L10n.password, text: $enteredPassword)
Button {
viewModel.signIn(username: user.name ?? "--", password: enteredPassword)
} label: {
L10n.signIn.text
}
} label: {
HStack {
ImageView(viewModel.getProfileImageUrl(user: user)) {
Image(systemName: "person.circle")
.resizable()
.frame(width: 50, height: 50)
}
.frame(width: 50, height: 50)
.clipShape(Circle())
Text(user.name ?? "--")
Spacer()
}
}
}
}

View File

@ -0,0 +1,55 @@
//
// 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 (c) 2022 Jellyfin & Jellyfin Contributors
//
import SwiftUI
struct QuickConnectView: View {
@EnvironmentObject
private var router: QuickConnectCoordinator.Router
@ObservedObject
var viewModel: UserSignInViewModel
var body: some View {
VStack(alignment: .leading, spacing: 20) {
L10n.quickConnectStep1.text
L10n.quickConnectStep2.text
L10n.quickConnectStep3.text
.padding(.bottom)
Text(viewModel.quickConnectCode ?? "------")
.tracking(10)
.font(.largeTitle)
.monospacedDigit()
.frame(maxWidth: .infinity)
Spacer()
}
.padding(.horizontal)
.navigationTitle(L10n.quickConnect)
.onAppear {
viewModel.startQuickConnect {
self.router.dismissCoordinator()
}
}
.onDisappear {
viewModel.stopQuickConnectAuthCheck()
}
.toolbar {
ToolbarItemGroup(placement: .navigationBarLeading) {
Button {
router.dismissCoordinator()
} label: {
Image(systemName: "xmark.circle.fill")
}
}
}
}
}

View File

@ -67,15 +67,6 @@ struct SettingsView: View {
}
}
Button {
settingsRouter.dismissCoordinator {
SessionManager.main.logout()
}
} label: {
L10n.switchUser.text
.font(.callout)
}
Button {
settingsRouter.route(to: \.quickConnect)
} label: {
@ -86,6 +77,15 @@ struct SettingsView: View {
Image(systemName: "chevron.right")
}
}
Button {
settingsRouter.dismissCoordinator {
SessionManager.main.logout()
}
} label: {
L10n.switchUser.text
.font(.callout)
}
}
Section(header: L10n.videoPlayer.text) {

View File

@ -0,0 +1,48 @@
//
// 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 (c) 2022 Jellyfin & Jellyfin Contributors
//
import JellyfinAPI
import SwiftUI
extension UserSignInView {
struct PublicUserSignInView: View {
@ObservedObject
var viewModel: UserSignInViewModel
@State
private var enteredPassword: String = ""
let publicUser: UserDto
var body: some View {
DisclosureGroup {
SecureField(L10n.password, text: $enteredPassword)
Button {
viewModel.signIn(username: publicUser.name ?? "--", password: enteredPassword)
} label: {
L10n.signIn.text
}
} label: {
HStack {
ImageView(publicUser.profileImageSource(maxWidth: 50, maxHeight: 50)) {
Image(systemName: "person.circle")
.resizable()
.frame(width: 50, height: 50)
}
.frame(width: 50, height: 50)
.clipShape(Circle())
Text(publicUser.name ?? "--")
Spacer()
}
}
}
}
}

View File

@ -11,6 +11,8 @@ import SwiftUI
struct UserSignInView: View {
@EnvironmentObject
private var router: UserSignInCoordinator.Router
@ObservedObject
var viewModel: UserSignInViewModel
@State
@ -47,10 +49,18 @@ struct UserSignInView: View {
L10n.signInToServer(viewModel.server.name).text
}
if viewModel.quickConnectEnabled {
Button {
router.route(to: \.quickConnect)
} label: {
L10n.quickConnect.text
}
}
Section {
if !viewModel.publicUsers.isEmpty {
ForEach(viewModel.publicUsers, id: \.id) { user in
UserLoginCellView(viewModel: viewModel, user: user)
PublicUserSignInView(viewModel: viewModel, publicUser: user)
.disabled(viewModel.isLoading)
}
} else {
@ -65,9 +75,11 @@ struct UserSignInView: View {
} header: {
HStack {
L10n.publicUsers.text
Spacer()
Button {
viewModel.loadUsers()
viewModel.getPublicUsers()
} label: {
Image(systemName: "arrow.clockwise.circle.fill")
}
@ -85,6 +97,8 @@ struct UserSignInView: View {
}
.navigationTitle(L10n.signIn)
.navigationBarBackButtonHidden(viewModel.isLoading)
.onAppear(perform: viewModel.loadUsers)
.onDisappear {
viewModel.stopQuickConnectAuthCheck()
}
}
}