Merge branch 'main' into PangMo5/chapters-slider
# Conflicts: # Swiftfin.xcodeproj/project.pbxproj
This commit is contained in:
commit
add9615aa8
|
@ -29,6 +29,11 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var about = makeAbout
|
var about = makeAbout
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
|
@Route(.push)
|
||||||
|
var quickConnect = makeQuickConnectSettings
|
||||||
|
#endif
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeServerDetail() -> some View {
|
func makeServerDetail() -> some View {
|
||||||
let viewModel = ServerDetailViewModel(server: SessionManager.main.currentLogin.server)
|
let viewModel = ServerDetailViewModel(server: SessionManager.main.currentLogin.server)
|
||||||
|
@ -60,6 +65,14 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
AboutView()
|
AboutView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
|
@ViewBuilder
|
||||||
|
func makeQuickConnectSettings() -> some View {
|
||||||
|
let viewModel = QuickConnectSettingsViewModel()
|
||||||
|
QuickConnectSettingsView(viewModel: viewModel)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
let viewModel = SettingsViewModel(server: SessionManager.main.currentLogin.server, user: SessionManager.main.currentLogin.user)
|
let viewModel = SettingsViewModel(server: SessionManager.main.currentLogin.server, user: SessionManager.main.currentLogin.user)
|
||||||
|
|
|
@ -91,7 +91,7 @@ enum NetworkError: Error {
|
||||||
default:
|
default:
|
||||||
errorMessage = ErrorMessage(code: code,
|
errorMessage = ErrorMessage(code: code,
|
||||||
title: L10n.error,
|
title: L10n.error,
|
||||||
message: L10n.unknownError)
|
message: displayMessage ?? L10n.unknownError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@ internal enum L10n {
|
||||||
internal static var audioAndCaptions: String { return L10n.tr("Localizable", "audioAndCaptions") }
|
internal static var audioAndCaptions: String { return L10n.tr("Localizable", "audioAndCaptions") }
|
||||||
/// Audio Track
|
/// Audio Track
|
||||||
internal static var audioTrack: String { return L10n.tr("Localizable", "audioTrack") }
|
internal static var audioTrack: String { return L10n.tr("Localizable", "audioTrack") }
|
||||||
|
/// Authorize
|
||||||
|
internal static var authorize: String { return L10n.tr("Localizable", "authorize") }
|
||||||
/// Auto Play
|
/// Auto Play
|
||||||
internal static var autoPlay: String { return L10n.tr("Localizable", "autoPlay") }
|
internal static var autoPlay: String { return L10n.tr("Localizable", "autoPlay") }
|
||||||
/// Back
|
/// Back
|
||||||
|
@ -262,6 +264,14 @@ internal enum L10n {
|
||||||
internal static var programs: String { return L10n.tr("Localizable", "programs") }
|
internal static var programs: String { return L10n.tr("Localizable", "programs") }
|
||||||
/// Public Users
|
/// Public Users
|
||||||
internal static var publicUsers: String { return L10n.tr("Localizable", "publicUsers") }
|
internal static var publicUsers: String { return L10n.tr("Localizable", "publicUsers") }
|
||||||
|
/// Quick Connect
|
||||||
|
internal static var quickConnect: String { return L10n.tr("Localizable", "quickConnect") }
|
||||||
|
/// Quick Connect code
|
||||||
|
internal static var quickConnectCode: String { return L10n.tr("Localizable", "quickConnectCode") }
|
||||||
|
/// Invalid Quick Connect code
|
||||||
|
internal static var quickConnectInvalidError: String { return L10n.tr("Localizable", "quickConnectInvalidError") }
|
||||||
|
/// Authorizing Quick Connect successful. Please continue on your other device.
|
||||||
|
internal static var quickConnectSuccessMessage: String { return L10n.tr("Localizable", "quickConnectSuccessMessage") }
|
||||||
/// Rated
|
/// Rated
|
||||||
internal static var rated: String { return L10n.tr("Localizable", "rated") }
|
internal static var rated: String { return L10n.tr("Localizable", "rated") }
|
||||||
/// Recently Added
|
/// Recently Added
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
final class QuickConnectSettingsViewModel: ViewModel {
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var quickConnectCode = ""
|
||||||
|
@Published
|
||||||
|
var showSuccessMessage = false
|
||||||
|
|
||||||
|
var alertTitle: String {
|
||||||
|
var message: String = ""
|
||||||
|
if errorMessage?.code != ErrorMessage.noShowErrorCode {
|
||||||
|
message.append(contentsOf: "\(errorMessage?.code ?? ErrorMessage.noShowErrorCode)\n")
|
||||||
|
}
|
||||||
|
message.append(contentsOf: "\(errorMessage?.title ?? L10n.unknownError)")
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendQuickConnect() {
|
||||||
|
QuickConnectAPI.authorize(code: self.quickConnectCode)
|
||||||
|
.trackActivity(loading)
|
||||||
|
.sink(receiveCompletion: { completion in
|
||||||
|
self.handleAPIRequestError(displayMessage: L10n.quickConnectInvalidError, completion: completion)
|
||||||
|
switch completion {
|
||||||
|
case .failure:
|
||||||
|
LogManager.log.debug("Invalid Quick Connect code entered")
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}, receiveValue: { _ in
|
||||||
|
// receiving a successful HTTP response indicates a valid code
|
||||||
|
LogManager.log.debug("Valid Quick connect code entered")
|
||||||
|
self.showSuccessMessage = true
|
||||||
|
})
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,9 +8,11 @@
|
||||||
|
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
final class SettingsViewModel: ObservableObject {
|
final class SettingsViewModel: ViewModel {
|
||||||
|
|
||||||
var bitrates: [Bitrates] = []
|
var bitrates: [Bitrates] = []
|
||||||
var langs: [TrackLanguage] = []
|
var langs: [TrackLanguage] = []
|
||||||
|
|
|
@ -17,9 +17,6 @@ final class UserSignInViewModel: ViewModel {
|
||||||
var router: UserSignInCoordinator.Router?
|
var router: UserSignInCoordinator.Router?
|
||||||
let server: SwiftfinStore.State.Server
|
let server: SwiftfinStore.State.Server
|
||||||
|
|
||||||
@Published
|
|
||||||
var isLoadingUsers = false
|
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var publicUsers: [UserDto] = []
|
var publicUsers: [UserDto] = []
|
||||||
|
|
||||||
|
@ -33,7 +30,7 @@ final class UserSignInViewModel: ViewModel {
|
||||||
if errorMessage?.code != ErrorMessage.noShowErrorCode {
|
if errorMessage?.code != ErrorMessage.noShowErrorCode {
|
||||||
message.append(contentsOf: "\(errorMessage?.code ?? ErrorMessage.noShowErrorCode)\n")
|
message.append(contentsOf: "\(errorMessage?.code ?? ErrorMessage.noShowErrorCode)\n")
|
||||||
}
|
}
|
||||||
message.append(contentsOf: "\(errorMessage?.title ?? "Unkown Error")")
|
message.append(contentsOf: "\(errorMessage?.title ?? L10n.unknownError)")
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,13 +55,12 @@ final class UserSignInViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadUsers() {
|
func loadUsers() {
|
||||||
self.isLoadingUsers = true
|
|
||||||
UserAPI.getPublicUsers()
|
UserAPI.getPublicUsers()
|
||||||
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, completion: completion)
|
self.handleAPIRequestError(displayMessage: L10n.unableToConnectServer, completion: completion)
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
self.publicUsers = response
|
self.publicUsers = response
|
||||||
self.isLoadingUsers = false
|
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,6 +250,8 @@
|
||||||
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; };
|
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; };
|
||||||
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; };
|
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; };
|
||||||
631759CF2879DB6A00A621AD /* PublicUserSignInCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631759CE2879DB6A00A621AD /* PublicUserSignInCellView.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 */; };
|
637FCAF5287B5B2600C0A353 /* UDPBroadcast.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 637FCAF3287B5B2600C0A353 /* UDPBroadcast.xcframework */; };
|
||||||
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
||||||
C400DB6A27FE894F007B65FE /* LiveTVChannelsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */; };
|
C400DB6A27FE894F007B65FE /* LiveTVChannelsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */; };
|
||||||
|
@ -746,6 +748,8 @@
|
||||||
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.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>"; };
|
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>"; };
|
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>"; };
|
637FCAF3287B5B2600C0A353 /* UDPBroadcast.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = UDPBroadcast.xcframework; path = Carthage/Build/UDPBroadcast.xcframework; sourceTree = "<group>"; };
|
||||||
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
||||||
C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelsView.swift; sourceTree = "<group>"; };
|
C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelsView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1060,6 +1064,7 @@
|
||||||
09389CC626819B4500AE350E /* VideoPlayerModel.swift */,
|
09389CC626819B4500AE350E /* VideoPlayerModel.swift */,
|
||||||
E126F73F278A655300A522BF /* VideoPlayerViewModel */,
|
E126F73F278A655300A522BF /* VideoPlayerViewModel */,
|
||||||
625CB57B2678CE1000530A6E /* ViewModel.swift */,
|
625CB57B2678CE1000530A6E /* ViewModel.swift */,
|
||||||
|
6334175C287DE0D0000603CE /* QuickConnectSettingsViewModel.swift */,
|
||||||
);
|
);
|
||||||
path = ViewModels;
|
path = ViewModels;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1884,6 +1889,7 @@
|
||||||
E176DE6F278E369F001EFD8D /* MissingItemsSettingsView.swift */,
|
E176DE6F278E369F001EFD8D /* MissingItemsSettingsView.swift */,
|
||||||
E1E5D5472783CCF900692DFE /* OverlaySettingsView.swift */,
|
E1E5D5472783CCF900692DFE /* OverlaySettingsView.swift */,
|
||||||
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */,
|
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */,
|
||||||
|
6334175A287DDFB9000603CE /* QuickConnectSettingsView.swift */,
|
||||||
);
|
);
|
||||||
path = SettingsView;
|
path = SettingsView;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2476,12 +2482,14 @@
|
||||||
E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */,
|
E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */,
|
||||||
E10D87DA2784E4F100BD264C /* ItemViewDetailsView.swift in Sources */,
|
E10D87DA2784E4F100BD264C /* ItemViewDetailsView.swift in Sources */,
|
||||||
E1C812C3277A8E5D00918266 /* VLCPlayerOverlayView.swift in Sources */,
|
E1C812C3277A8E5D00918266 /* VLCPlayerOverlayView.swift in Sources */,
|
||||||
|
6334175B287DDFB9000603CE /* QuickConnectSettingsView.swift in Sources */,
|
||||||
E1002B642793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */,
|
E1002B642793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */,
|
||||||
E188460026DECB9E00B0C5B7 /* ItemLandscapeTopBarView.swift in Sources */,
|
E188460026DECB9E00B0C5B7 /* ItemLandscapeTopBarView.swift in Sources */,
|
||||||
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
|
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
|
||||||
E13DD3F5271793BB009D4DAF /* UserSignInView.swift in Sources */,
|
E13DD3F5271793BB009D4DAF /* UserSignInView.swift in Sources */,
|
||||||
5D1603FC278A3D5800D22B99 /* SubtitleSize.swift in Sources */,
|
5D1603FC278A3D5800D22B99 /* SubtitleSize.swift in Sources */,
|
||||||
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */,
|
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */,
|
||||||
|
6334175D287DE0D0000603CE /* QuickConnectSettingsViewModel.swift in Sources */,
|
||||||
53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */,
|
53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */,
|
||||||
E13DD3E127176BD3009D4DAF /* ServerListViewModel.swift in Sources */,
|
E13DD3E127176BD3009D4DAF /* ServerListViewModel.swift in Sources */,
|
||||||
62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
|
62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// 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 SwiftUI
|
||||||
|
|
||||||
|
struct QuickConnectSettingsView: View {
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: QuickConnectSettingsViewModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Form {
|
||||||
|
Section(header: L10n.quickConnect.text) {
|
||||||
|
TextField(L10n.quickConnectCode, text: $viewModel.quickConnectCode)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
.disabled(viewModel.isLoading)
|
||||||
|
|
||||||
|
Button {
|
||||||
|
viewModel.sendQuickConnect()
|
||||||
|
} label: {
|
||||||
|
L10n.authorize.text
|
||||||
|
.font(.callout)
|
||||||
|
.disabled((viewModel.quickConnectCode.count != 6) || viewModel.isLoading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert(isPresented: $viewModel.showSuccessMessage) {
|
||||||
|
Alert(title: L10n.quickConnect.text,
|
||||||
|
message: L10n.quickConnectSuccessMessage.text,
|
||||||
|
dismissButton: .default(L10n.ok.text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert(item: $viewModel.errorMessage) { _ in
|
||||||
|
Alert(title: Text(viewModel.alertTitle),
|
||||||
|
message: Text(viewModel.errorMessage?.message ?? L10n.unknownError),
|
||||||
|
dismissButton: .cancel())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,6 +81,17 @@ struct SettingsView: View {
|
||||||
L10n.switchUser.text
|
L10n.switchUser.text
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
settingsRouter.route(to: \.quickConnect)
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
L10n.quickConnect.text
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement these for playback
|
// TODO: Implement these for playback
|
||||||
|
|
|
@ -71,7 +71,7 @@ struct UserSignInView: View {
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "arrow.clockwise.circle.fill")
|
Image(systemName: "arrow.clockwise.circle.fill")
|
||||||
}
|
}
|
||||||
.disabled(viewModel.isLoadingUsers || viewModel.isLoading)
|
.disabled(viewModel.isLoading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.headerProminence(.increased)
|
.headerProminence(.increased)
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue