[tvOS] Login Flow Cleanup - Second Pass (#1403)
* Background on Server User Signin. Button Sizing. More visible deletion notice. Menu ListView insets. * wip * Change Highlight. Move Add User Button. Remove Add User inline option. * Take 2 * Undo user changes. * Remove all changes. * "selectServer" = "Select Server"; * Recommendations Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com> * Update ServerDetailView.swift Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com> * Update ServerDetailView.swift Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com> * build strings --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
757ea4d475
commit
b0b604c4ad
|
@ -1130,6 +1130,8 @@ internal enum L10n {
|
||||||
internal static let selectAll = L10n.tr("Localizable", "selectAll", fallback: "Select All")
|
internal static let selectAll = L10n.tr("Localizable", "selectAll", fallback: "Select All")
|
||||||
/// Select Image
|
/// Select Image
|
||||||
internal static let selectImage = L10n.tr("Localizable", "selectImage", fallback: "Select Image")
|
internal static let selectImage = L10n.tr("Localizable", "selectImage", fallback: "Select Image")
|
||||||
|
/// Select server
|
||||||
|
internal static let selectServer = L10n.tr("Localizable", "selectServer", fallback: "Select server")
|
||||||
/// Series
|
/// Series
|
||||||
internal static let series = L10n.tr("Localizable", "series", fallback: "Series")
|
internal static let series = L10n.tr("Localizable", "series", fallback: "Series")
|
||||||
/// Series Backdrop
|
/// Series Backdrop
|
||||||
|
|
|
@ -11,10 +11,12 @@ import SwiftUI
|
||||||
struct ListRowButton: View {
|
struct ListRowButton: View {
|
||||||
|
|
||||||
let title: String
|
let title: String
|
||||||
|
let role: ButtonRole?
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
init(_ title: String, action: @escaping () -> Void) {
|
init(_ title: String, role: ButtonRole? = nil, action: @escaping () -> Void) {
|
||||||
self.title = title
|
self.title = title
|
||||||
|
self.role = role
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,15 +25,33 @@ struct ListRowButton: View {
|
||||||
action()
|
action()
|
||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
Rectangle()
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.foregroundStyle(.secondary)
|
.fill(secondaryStyle)
|
||||||
|
|
||||||
Text(title)
|
Text(title)
|
||||||
|
.foregroundStyle(primaryStyle)
|
||||||
.font(.body.weight(.bold))
|
.font(.body.weight(.bold))
|
||||||
.foregroundStyle(.primary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.card)
|
.buttonStyle(.card)
|
||||||
.frame(height: 75)
|
.frame(height: 75)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Styles
|
||||||
|
|
||||||
|
private var primaryStyle: some ShapeStyle {
|
||||||
|
if role == .destructive {
|
||||||
|
return AnyShapeStyle(Color.red)
|
||||||
|
} else {
|
||||||
|
return AnyShapeStyle(.primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var secondaryStyle: some ShapeStyle {
|
||||||
|
if role == .destructive {
|
||||||
|
return AnyShapeStyle(Color.red.opacity(0.2))
|
||||||
|
} else {
|
||||||
|
return AnyShapeStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,10 @@ struct SplitLoginWindowView<Leading: View, Trailing: View>: View {
|
||||||
private let trailingTitle: String
|
private let trailingTitle: String
|
||||||
private let trailingContentView: () -> Trailing
|
private let trailingContentView: () -> Trailing
|
||||||
|
|
||||||
|
// MARK: - Background Variable
|
||||||
|
|
||||||
|
private let backgroundImageSource: ImageSource?
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -52,6 +56,21 @@ struct SplitLoginWindowView<Leading: View, Trailing: View>: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarBranding(isLoading: isLoading)
|
.navigationBarBranding(isLoading: isLoading)
|
||||||
|
.background {
|
||||||
|
if let backgroundImageSource {
|
||||||
|
ZStack {
|
||||||
|
ImageView(backgroundImageSource)
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.id(backgroundImageSource)
|
||||||
|
.transition(.opacity)
|
||||||
|
.animation(.linear, value: backgroundImageSource)
|
||||||
|
|
||||||
|
Color.black
|
||||||
|
.opacity(0.9)
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +80,7 @@ extension SplitLoginWindowView {
|
||||||
isLoading: Bool = false,
|
isLoading: Bool = false,
|
||||||
leadingTitle: String,
|
leadingTitle: String,
|
||||||
trailingTitle: String,
|
trailingTitle: String,
|
||||||
|
backgroundImageSource: ImageSource? = nil,
|
||||||
@ViewBuilder leadingContentView: @escaping () -> Leading,
|
@ViewBuilder leadingContentView: @escaping () -> Leading,
|
||||||
@ViewBuilder trailingContentView: @escaping () -> Trailing
|
@ViewBuilder trailingContentView: @escaping () -> Trailing
|
||||||
) {
|
) {
|
||||||
|
@ -69,5 +89,6 @@ extension SplitLoginWindowView {
|
||||||
self.trailingTitle = trailingTitle
|
self.trailingTitle = trailingTitle
|
||||||
self.leadingContentView = leadingContentView
|
self.leadingContentView = leadingContentView
|
||||||
self.trailingContentView = trailingContentView
|
self.trailingContentView = trailingContentView
|
||||||
|
self.backgroundImageSource = backgroundImageSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,37 +42,59 @@ extension SelectUserView {
|
||||||
self.servers = servers
|
self.servers = servers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var content: some View {
|
||||||
|
ZStack {
|
||||||
|
Color.secondarySystemFill
|
||||||
|
|
||||||
|
RelativeSystemImageView(systemName: "plus")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.clipShape(.circle)
|
||||||
|
.aspectRatio(1, contentMode: .fill)
|
||||||
|
.hoverEffect(.highlight)
|
||||||
|
|
||||||
|
Text(L10n.addUser)
|
||||||
|
.font(.title3)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundStyle(isEnabled ? .primary : .secondary)
|
||||||
|
|
||||||
|
if serverSelection == .all {
|
||||||
|
// For layout, not to be localized
|
||||||
|
Text("Hidden")
|
||||||
|
.font(.footnote)
|
||||||
|
.hidden()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
if serverSelection == .all {
|
||||||
|
Menu {
|
||||||
|
Text(L10n.selectServer)
|
||||||
|
|
||||||
|
ForEach(servers) { server in
|
||||||
|
Button {
|
||||||
|
action(server)
|
||||||
|
} label: {
|
||||||
|
Text(server.name)
|
||||||
|
Text(server.currentURL.absoluteString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderless)
|
||||||
|
.buttonBorderShape(.circle)
|
||||||
|
} else {
|
||||||
Button {
|
Button {
|
||||||
if let selectedServer {
|
if let selectedServer {
|
||||||
action(selectedServer)
|
action(selectedServer)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
content
|
||||||
Color.secondarySystemFill
|
|
||||||
|
|
||||||
RelativeSystemImageView(systemName: "plus")
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
.clipShape(.circle)
|
|
||||||
.aspectRatio(1, contentMode: .fill)
|
|
||||||
.hoverEffect(.highlight)
|
|
||||||
|
|
||||||
Text(L10n.addUser)
|
|
||||||
.font(.title3)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.foregroundStyle(isEnabled ? .primary : .secondary)
|
|
||||||
|
|
||||||
if serverSelection == .all {
|
|
||||||
Text(L10n.hidden)
|
|
||||||
.font(.footnote)
|
|
||||||
.hidden()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
.buttonBorderShape(.circle)
|
.buttonBorderShape(.circle)
|
||||||
.disabled(!isEnabled)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,26 @@ extension SelectUserView {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var advancedMenu: some View {
|
private var advancedMenu: some View {
|
||||||
Menu(L10n.advanced, systemImage: "gearshape.fill") {
|
Menu {
|
||||||
|
|
||||||
Button(L10n.editUsers, systemImage: "person.crop.circle") {
|
Button(L10n.editUsers, systemImage: "person.crop.circle") {
|
||||||
isEditing.toggle()
|
isEditing.toggle()
|
||||||
}
|
}
|
||||||
|
// TODO: Advanced settings on tvOS?
|
||||||
|
//
|
||||||
|
// Divider()
|
||||||
|
//
|
||||||
|
// Button(L10n.advanced, systemImage: "gearshape.fill") {
|
||||||
|
// router.route(to: \.advancedSettings)
|
||||||
|
// }
|
||||||
|
} label: {
|
||||||
|
Label(L10n.advanced, systemImage: "gearshape.fill")
|
||||||
|
.font(.body.weight(.semibold))
|
||||||
|
.foregroundStyle(Color.primary)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.frame(width: 50, height: 50)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Do we want to support a grid view and list view like iOS?
|
// TODO: Do we want to support a grid view and list view like iOS?
|
||||||
// if !viewModel.servers.isEmpty {
|
// if !viewModel.servers.isEmpty {
|
||||||
// Picker(selection: $userListDisplayType) {
|
// Picker(selection: $userListDisplayType) {
|
||||||
// ForEach(LibraryDisplayType.allCases, id: \.hashValue) {
|
// ForEach(LibraryDisplayType.allCases, id: \.hashValue) {
|
||||||
|
@ -51,40 +64,20 @@ extension SelectUserView {
|
||||||
// }
|
// }
|
||||||
// .pickerStyle(.menu)
|
// .pickerStyle(.menu)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// TODO: Advanced settings on tvOS?
|
|
||||||
// Section {
|
|
||||||
// Button(L10n.advanced, systemImage: "gearshape.fill") {
|
|
||||||
// router.route(to: \.advancedSettings)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
.labelStyle(.iconOnly)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Delete User Button
|
||||||
|
|
||||||
private var deleteUsersButton: some View {
|
private var deleteUsersButton: some View {
|
||||||
Button {
|
ListRowButton(L10n.delete, role: .destructive) {
|
||||||
onDelete()
|
onDelete()
|
||||||
} label: {
|
|
||||||
ZStack {
|
|
||||||
Color.red
|
|
||||||
|
|
||||||
Text(L10n.delete)
|
|
||||||
.font(.body.weight(.semibold))
|
|
||||||
.foregroundStyle(areUsersSelected ? .primary : .secondary)
|
|
||||||
|
|
||||||
if !areUsersSelected {
|
|
||||||
Color.black
|
|
||||||
.opacity(0.5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: 400, height: 65)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
|
||||||
}
|
}
|
||||||
|
.frame(width: 400, height: 50)
|
||||||
.disabled(!areUsersSelected)
|
.disabled(!areUsersSelected)
|
||||||
.buttonStyle(.card)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Initializer
|
||||||
|
|
||||||
init(
|
init(
|
||||||
isEditing: Binding<Bool>,
|
isEditing: Binding<Bool>,
|
||||||
serverSelection: Binding<SelectUserServerSelection>,
|
serverSelection: Binding<SelectUserServerSelection>,
|
||||||
|
@ -103,6 +96,8 @@ extension SelectUserView {
|
||||||
self.toggleAllUsersSelected = toggleAllUsersSelected
|
self.toggleAllUsersSelected = toggleAllUsersSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Content View
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
|
@ -113,16 +108,20 @@ extension SelectUserView {
|
||||||
toggleAllUsersSelected()
|
toggleAllUsersSelected()
|
||||||
} label: {
|
} label: {
|
||||||
Text(areUsersSelected ? L10n.removeAll : L10n.selectAll)
|
Text(areUsersSelected ? L10n.removeAll : L10n.selectAll)
|
||||||
.font(.body.weight(.semibold))
|
|
||||||
.foregroundStyle(Color.primary)
|
.foregroundStyle(Color.primary)
|
||||||
|
.font(.body.weight(.semibold))
|
||||||
|
.frame(width: 200, height: 50)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
isEditing = false
|
isEditing = false
|
||||||
} label: {
|
} label: {
|
||||||
L10n.cancel.text
|
Text(L10n.cancel)
|
||||||
.font(.body.weight(.semibold))
|
|
||||||
.foregroundStyle(Color.primary)
|
.foregroundStyle(Color.primary)
|
||||||
|
.font(.body.weight(.semibold))
|
||||||
|
.frame(width: 200, height: 50)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ServerSelectionMenu(
|
ServerSelectionMenu(
|
||||||
|
@ -130,13 +129,13 @@ extension SelectUserView {
|
||||||
viewModel: viewModel
|
viewModel: viewModel
|
||||||
)
|
)
|
||||||
|
|
||||||
if userCount > 1 {
|
advancedMenu
|
||||||
advancedMenu
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
// `Menu` with custom label has some weird additional
|
// `Menu` with custom label has some weird additional
|
||||||
// frame/padding that differs from default label style
|
// frame/padding that differs from default label style
|
||||||
|
|
|
@ -28,6 +28,8 @@ extension SelectUserView {
|
||||||
private let action: () -> Void
|
private let action: () -> Void
|
||||||
private let onDelete: () -> Void
|
private let onDelete: () -> Void
|
||||||
|
|
||||||
|
// MARK: - Initializer
|
||||||
|
|
||||||
init(
|
init(
|
||||||
user: UserState,
|
user: UserState,
|
||||||
server: ServerState,
|
server: ServerState,
|
||||||
|
@ -42,49 +44,52 @@ extension SelectUserView {
|
||||||
self.onDelete = onDelete
|
self.onDelete = onDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Label Foreground Style
|
||||||
|
|
||||||
private var labelForegroundStyle: some ShapeStyle {
|
private var labelForegroundStyle: some ShapeStyle {
|
||||||
guard isEditing else { return .primary }
|
guard isEditing else { return .primary }
|
||||||
|
|
||||||
return isSelected ? .primary : .secondary
|
return isSelected ? .primary : .secondary
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
// MARK: - User Portrait
|
||||||
private var personView: some View {
|
|
||||||
ZStack {
|
|
||||||
Color.secondarySystemFill
|
|
||||||
|
|
||||||
RelativeSystemImageView(systemName: "person.fill", ratio: 0.5)
|
private var userPortrait: some View {
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
.clipShape(.circle)
|
|
||||||
.aspectRatio(1, contentMode: .fill)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var userImage: some View {
|
|
||||||
UserProfileImage(
|
UserProfileImage(
|
||||||
userID: user.id,
|
userID: user.id,
|
||||||
source: user.profileImageSource(
|
source: user.profileImageSource(
|
||||||
client: server.client,
|
client: server.client,
|
||||||
maxWidth: 120
|
maxWidth: 120
|
||||||
)
|
),
|
||||||
|
pipeline: .Swiftfin.local
|
||||||
)
|
)
|
||||||
.aspectRatio(1, contentMode: .fill)
|
|
||||||
.overlay {
|
.overlay {
|
||||||
if isEditing {
|
if isEditing {
|
||||||
Color.black
|
ZStack(alignment: .bottom) {
|
||||||
.opacity(isSelected ? 0 : 0.5)
|
Color.black
|
||||||
.clipShape(.circle)
|
.opacity(isSelected ? 0 : 0.5)
|
||||||
|
|
||||||
|
if isSelected {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: 75, height: 75)
|
||||||
|
.symbolRenderingMode(.palette)
|
||||||
|
.foregroundStyle(accentColor.overlayColor, accentColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Button {
|
Button {
|
||||||
action()
|
action()
|
||||||
} label: {
|
} label: {
|
||||||
userImage
|
userPortrait
|
||||||
.hoverEffect(.highlight)
|
.hoverEffect(.highlight)
|
||||||
|
|
||||||
Text(user.username)
|
Text(user.username)
|
||||||
|
@ -107,16 +112,6 @@ extension SelectUserView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay {
|
|
||||||
if isEditing && isSelected {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.frame(width: 40, height: 40, alignment: .bottomTrailing)
|
|
||||||
.symbolRenderingMode(.palette)
|
|
||||||
.foregroundStyle(accentColor.overlayColor, accentColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,34 +306,22 @@ struct SelectUserView: View {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var emptyView: some View {
|
private var emptyView: some View {
|
||||||
ZStack {
|
VStack(spacing: 50) {
|
||||||
VStack {
|
L10n.connectToJellyfinServerStart.text
|
||||||
Image(.jellyfinBlobBlue)
|
.font(.body)
|
||||||
.resizable()
|
.frame(minWidth: 50, maxWidth: 500)
|
||||||
.aspectRatio(contentMode: .fit)
|
.multilineTextAlignment(.center)
|
||||||
.frame(height: 100)
|
|
||||||
.edgePadding()
|
|
||||||
|
|
||||||
Color.clear
|
Button {
|
||||||
}
|
router.route(to: \.connectToServer)
|
||||||
|
} label: {
|
||||||
VStack(spacing: 50) {
|
L10n.connect.text
|
||||||
L10n.connectToJellyfinServerStart.text
|
.font(.callout)
|
||||||
.font(.body)
|
.fontWeight(.bold)
|
||||||
.frame(minWidth: 50, maxWidth: 500)
|
.frame(width: 400, height: 75)
|
||||||
.multilineTextAlignment(.center)
|
.background(Color.jellyfinPurple)
|
||||||
|
|
||||||
Button {
|
|
||||||
router.route(to: \.connectToServer)
|
|
||||||
} label: {
|
|
||||||
L10n.connect.text
|
|
||||||
.font(.callout)
|
|
||||||
.fontWeight(.bold)
|
|
||||||
.frame(width: 400, height: 75)
|
|
||||||
.background(Color.jellyfinPurple)
|
|
||||||
}
|
|
||||||
.buttonStyle(.card)
|
|
||||||
}
|
}
|
||||||
|
.buttonStyle(.card)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,13 +338,20 @@ struct SelectUserView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// change splash screen selection if necessary
|
setSplashScreenImageSource()
|
||||||
// selectUserAllServersSplashscreen = serverSelection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Did Appear
|
||||||
|
|
||||||
private func didAppear() {
|
private func didAppear() {
|
||||||
viewModel.send(.getServers)
|
viewModel.send(.getServers)
|
||||||
|
|
||||||
|
setSplashScreenImageSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Set Splash Screen Image Source
|
||||||
|
|
||||||
|
private func setSplashScreenImageSource() {
|
||||||
splashScreenImageSource = makeSplashScreenImageSource(
|
splashScreenImageSource = makeSplashScreenImageSource(
|
||||||
serverSelection: serverSelection,
|
serverSelection: serverSelection,
|
||||||
allServersSelection: .all
|
allServersSelection: .all
|
||||||
|
@ -385,10 +380,7 @@ struct SelectUserView: View {
|
||||||
.onChange(of: serverSelection) { _, newValue in
|
.onChange(of: serverSelection) { _, newValue in
|
||||||
gridItems = makeGridItems(for: newValue)
|
gridItems = makeGridItems(for: newValue)
|
||||||
|
|
||||||
splashScreenImageSource = makeSplashScreenImageSource(
|
setSplashScreenImageSource()
|
||||||
serverSelection: newValue,
|
|
||||||
allServersSelection: .all
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.onChange(of: isPresentingLocalPin) { _, newValue in
|
.onChange(of: isPresentingLocalPin) { _, newValue in
|
||||||
if newValue {
|
if newValue {
|
||||||
|
|
|
@ -75,14 +75,17 @@ struct EditServerView: View {
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.listRowBackground(Color.clear)
|
||||||
|
.listRowInsets(.zero)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isEditing {
|
if isEditing {
|
||||||
Section {
|
Section {
|
||||||
ListRowButton(L10n.delete) {
|
ListRowButton(L10n.delete, role: .destructive) {
|
||||||
isPresentingConfirmDeletion = true
|
isPresentingConfirmDeletion = true
|
||||||
}
|
}
|
||||||
.foregroundStyle(.primary, .red.opacity(0.5))
|
.listRowBackground(Color.clear)
|
||||||
|
.listRowInsets(.zero)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,8 @@ struct UserSignInView: View {
|
||||||
SplitLoginWindowView(
|
SplitLoginWindowView(
|
||||||
isLoading: viewModel.state == .signingIn,
|
isLoading: viewModel.state == .signingIn,
|
||||||
leadingTitle: L10n.signInToServer(viewModel.server.name),
|
leadingTitle: L10n.signInToServer(viewModel.server.name),
|
||||||
trailingTitle: L10n.publicUsers
|
trailingTitle: L10n.publicUsers,
|
||||||
|
backgroundImageSource: viewModel.server.splashScreenImageSource()
|
||||||
) {
|
) {
|
||||||
signInSection
|
signInSection
|
||||||
} trailingContentView: {
|
} trailingContentView: {
|
||||||
|
|
|
@ -80,7 +80,7 @@ extension SelectUserView {
|
||||||
if serverSelection == .all {
|
if serverSelection == .all {
|
||||||
Menu {
|
Menu {
|
||||||
|
|
||||||
Text("Select Server")
|
Text(L10n.selectServer)
|
||||||
|
|
||||||
ForEach(servers) { server in
|
ForEach(servers) { server in
|
||||||
Button {
|
Button {
|
||||||
|
|
|
@ -99,7 +99,7 @@ extension SelectUserView {
|
||||||
if serverSelection == .all {
|
if serverSelection == .all {
|
||||||
Menu {
|
Menu {
|
||||||
|
|
||||||
Text("Select Server")
|
Text(L10n.selectServer)
|
||||||
|
|
||||||
ForEach(servers) { server in
|
ForEach(servers) { server in
|
||||||
Button {
|
Button {
|
||||||
|
|
|
@ -1618,6 +1618,9 @@
|
||||||
/// Select Image
|
/// Select Image
|
||||||
"selectImage" = "Select Image";
|
"selectImage" = "Select Image";
|
||||||
|
|
||||||
|
/// Select server
|
||||||
|
"selectServer" = "Select server";
|
||||||
|
|
||||||
/// Series
|
/// Series
|
||||||
"series" = "Series";
|
"series" = "Series";
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue