[tvOS] Delete User from User Selection Screen (#1359)
* Extract handlers into function * Color Improvements to move away from UIColor * Bring over edit user feature from iOS * Fix UserGridButton overlay when editing * Move advanced menu to be near server select menu * Re-enable context menu * Add bottom button bar * hook up user deletion * improvements * Refactor buttons for highlight hover effect * Pass in user count * Don't cancel editing if delete alert is cancelled * cleanup * Pad bottom of buttons * Cancel editing after user deletion * Revert ServerSelectionMenu back to button * Remove padding that pushed the server selection menu up too far * Make delete button red to match iOS * Update SelectUserView.swift * workaround Menu layout issues * Bring select/deselect all users behavior from iOS * Fixes after merge with main * Fix vertical focus --------- Co-authored-by: chickdan <=> Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
486995b0cf
commit
cd94142a8a
|
@ -22,9 +22,9 @@ extension Color {
|
||||||
|
|
||||||
// TODO: Correct and add colors
|
// TODO: Correct and add colors
|
||||||
#if os(tvOS) // tvOS doesn't have these
|
#if os(tvOS) // tvOS doesn't have these
|
||||||
static let systemFill = Color(UIColor.white)
|
static let systemFill = Color.white
|
||||||
static let secondarySystemFill = Color(UIColor.gray)
|
static let secondarySystemFill = Color.gray
|
||||||
static let tertiarySystemFill = Color(UIColor.black)
|
static let tertiarySystemFill = Color.black
|
||||||
static let lightGray = Color(UIColor.lightGray)
|
static let lightGray = Color(UIColor.lightGray)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -109,7 +109,7 @@ extension Backport where Content: View {
|
||||||
extension ButtonBorderShape {
|
extension ButtonBorderShape {
|
||||||
|
|
||||||
static let circleBackport: ButtonBorderShape = {
|
static let circleBackport: ButtonBorderShape = {
|
||||||
if #available(iOS 17, tvOS 16.4, *) {
|
if #available(iOS 17, *) {
|
||||||
return ButtonBorderShape.circle
|
return ButtonBorderShape.circle
|
||||||
} else {
|
} else {
|
||||||
return ButtonBorderShape.roundedRectangle
|
return ButtonBorderShape.roundedRectangle
|
||||||
|
|
|
@ -57,10 +57,7 @@ extension SelectUserView {
|
||||||
}
|
}
|
||||||
.clipShape(.circle)
|
.clipShape(.circle)
|
||||||
.aspectRatio(1, contentMode: .fill)
|
.aspectRatio(1, contentMode: .fill)
|
||||||
}
|
.hoverEffect(.highlight)
|
||||||
.buttonStyle(.card)
|
|
||||||
.buttonBorderShape(.circleBackport)
|
|
||||||
.disabled(!isEnabled)
|
|
||||||
|
|
||||||
Text(L10n.addUser)
|
Text(L10n.addUser)
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
|
@ -73,6 +70,10 @@ extension SelectUserView {
|
||||||
.hidden()
|
.hidden()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.buttonStyle(.borderless)
|
||||||
|
.buttonBorderShape(.circle)
|
||||||
|
.disabled(!isEnabled)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
//
|
||||||
|
// 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) 2024 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension SelectUserView {
|
||||||
|
|
||||||
|
struct SelectUserBottomBar: View {
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
private var isEditing: Bool
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
private var serverSelection: SelectUserServerSelection
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
private var viewModel: SelectUserViewModel
|
||||||
|
|
||||||
|
private let areUsersSelected: Bool
|
||||||
|
private let userCount: Int
|
||||||
|
|
||||||
|
private let onDelete: () -> Void
|
||||||
|
private let toggleAllUsersSelected: () -> Void
|
||||||
|
|
||||||
|
// MARK: - Advanced Menu
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var advancedMenu: some View {
|
||||||
|
Menu(L10n.advanced, systemImage: "gearshape.fill") {
|
||||||
|
|
||||||
|
Button(L10n.editUsers, systemImage: "person.crop.circle") {
|
||||||
|
isEditing.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Do we want to support a grid view and list view like iOS?
|
||||||
|
// if !viewModel.servers.isEmpty {
|
||||||
|
// Picker(selection: $userListDisplayType) {
|
||||||
|
// ForEach(LibraryDisplayType.allCases, id: \.hashValue) {
|
||||||
|
// Label($0.displayTitle, systemImage: $0.systemImage)
|
||||||
|
// .tag($0)
|
||||||
|
// }
|
||||||
|
// } label: {
|
||||||
|
// Text(L10n.layout)
|
||||||
|
// Text(userListDisplayType.displayTitle)
|
||||||
|
// Image(systemName: userListDisplayType.systemImage)
|
||||||
|
// }
|
||||||
|
// .pickerStyle(.menu)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: Advanced settings on tvOS?
|
||||||
|
// Section {
|
||||||
|
// Button(L10n.advanced, systemImage: "gearshape.fill") {
|
||||||
|
// router.route(to: \.advancedSettings)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var deleteUsersButton: some View {
|
||||||
|
Button {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
.disabled(!areUsersSelected)
|
||||||
|
.buttonStyle(.card)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
isEditing: Binding<Bool>,
|
||||||
|
serverSelection: Binding<SelectUserServerSelection>,
|
||||||
|
areUsersSelected: Bool,
|
||||||
|
viewModel: SelectUserViewModel,
|
||||||
|
userCount: Int,
|
||||||
|
onDelete: @escaping () -> Void,
|
||||||
|
toggleAllUsersSelected: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
self._isEditing = isEditing
|
||||||
|
self._serverSelection = serverSelection
|
||||||
|
self.viewModel = viewModel
|
||||||
|
self.areUsersSelected = areUsersSelected
|
||||||
|
self.userCount = userCount
|
||||||
|
self.onDelete = onDelete
|
||||||
|
self.toggleAllUsersSelected = toggleAllUsersSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var contentView: some View {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
if isEditing {
|
||||||
|
deleteUsersButton
|
||||||
|
|
||||||
|
Button {
|
||||||
|
toggleAllUsersSelected()
|
||||||
|
} label: {
|
||||||
|
Text(areUsersSelected ? L10n.removeAll : L10n.selectAll)
|
||||||
|
.font(.body.weight(.semibold))
|
||||||
|
.foregroundStyle(Color.primary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
isEditing = false
|
||||||
|
} label: {
|
||||||
|
L10n.cancel.text
|
||||||
|
.font(.body.weight(.semibold))
|
||||||
|
.foregroundStyle(Color.primary)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ServerSelectionMenu(
|
||||||
|
selection: $serverSelection,
|
||||||
|
viewModel: viewModel
|
||||||
|
)
|
||||||
|
|
||||||
|
if userCount > 1 {
|
||||||
|
advancedMenu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
// `Menu` with custom label has some weird additional
|
||||||
|
// frame/padding that differs from default label style
|
||||||
|
AlternateLayoutView(alignment: .top) {
|
||||||
|
Color.clear
|
||||||
|
.frame(height: 100)
|
||||||
|
} content: {
|
||||||
|
contentView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,12 +90,9 @@ extension SelectUserView {
|
||||||
}
|
}
|
||||||
.font(.body.weight(.semibold))
|
.font(.body.weight(.semibold))
|
||||||
.foregroundStyle(Color.primary)
|
.foregroundStyle(Color.primary)
|
||||||
.frame(height: 50)
|
.frame(width: 400, height: 50)
|
||||||
.frame(maxWidth: 400)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
|
||||||
}
|
}
|
||||||
.menuOrder(.fixed)
|
.menuOrder(.fixed)
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,15 +60,8 @@ extension SelectUserView {
|
||||||
.aspectRatio(1, contentMode: .fill)
|
.aspectRatio(1, contentMode: .fill)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
@ViewBuilder
|
||||||
VStack {
|
private var userImage: some View {
|
||||||
Button {
|
|
||||||
action()
|
|
||||||
} label: {
|
|
||||||
VStack(alignment: .center) {
|
|
||||||
ZStack {
|
|
||||||
Color.clear
|
|
||||||
|
|
||||||
UserProfileImage(
|
UserProfileImage(
|
||||||
userID: user.id,
|
userID: user.id,
|
||||||
source: user.profileImageSource(
|
source: user.profileImageSource(
|
||||||
|
@ -76,17 +69,23 @@ extension SelectUserView {
|
||||||
maxWidth: 120
|
maxWidth: 120
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
.aspectRatio(1, contentMode: .fill)
|
.aspectRatio(1, contentMode: .fill)
|
||||||
|
.overlay {
|
||||||
|
if isEditing {
|
||||||
|
Color.black
|
||||||
|
.opacity(isSelected ? 0 : 0.5)
|
||||||
|
.clipShape(.circle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.card)
|
}
|
||||||
.buttonBorderShape(.circleBackport)
|
|
||||||
// .contextMenu {
|
var body: some View {
|
||||||
// Button(L10n.delete, role: .destructive) {
|
VStack {
|
||||||
// onDelete()
|
Button {
|
||||||
// }
|
action()
|
||||||
// }
|
} label: {
|
||||||
|
userImage
|
||||||
|
.hoverEffect(.highlight)
|
||||||
|
|
||||||
Text(user.username)
|
Text(user.username)
|
||||||
.font(.title3)
|
.font(.title3)
|
||||||
|
@ -100,14 +99,16 @@ extension SelectUserView {
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.buttonStyle(.borderless)
|
||||||
|
.buttonBorderShape(.circle)
|
||||||
|
.contextMenu {
|
||||||
|
Button("Delete", role: .destructive) {
|
||||||
|
onDelete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.overlay {
|
.overlay {
|
||||||
if isEditing {
|
if isEditing && isSelected {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
|
||||||
Color.black
|
|
||||||
.opacity(isSelected ? 0 : 0.5)
|
|
||||||
.clipShape(.circle)
|
|
||||||
|
|
||||||
if isSelected {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
|
@ -118,6 +119,4 @@ extension SelectUserView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,6 @@ import SwiftUI
|
||||||
|
|
||||||
struct SelectUserView: View {
|
struct SelectUserView: View {
|
||||||
|
|
||||||
// MARK: - Defaults
|
|
||||||
|
|
||||||
@Default(.selectUserServerSelection)
|
|
||||||
private var serverSelection
|
|
||||||
|
|
||||||
// MARK: - User Grid Item Enum
|
// MARK: - User Grid Item Enum
|
||||||
|
|
||||||
private enum UserGridItem: Hashable {
|
private enum UserGridItem: Hashable {
|
||||||
|
@ -29,6 +24,16 @@ struct SelectUserView: View {
|
||||||
case addUser
|
case addUser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Defaults
|
||||||
|
|
||||||
|
@Default(.selectUserServerSelection)
|
||||||
|
private var serverSelection
|
||||||
|
|
||||||
|
// MARK: - Environment Variable
|
||||||
|
|
||||||
|
@Environment(\.colorScheme)
|
||||||
|
private var colorScheme
|
||||||
|
|
||||||
// MARK: - State & Environment Objects
|
// MARK: - State & Environment Objects
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
|
@ -46,14 +51,31 @@ struct SelectUserView: View {
|
||||||
@State
|
@State
|
||||||
private var gridItemSize: CGSize = .zero
|
private var gridItemSize: CGSize = .zero
|
||||||
@State
|
@State
|
||||||
|
private var isEditingUsers: Bool = false
|
||||||
|
@State
|
||||||
private var padGridItemColumnCount: Int = 1
|
private var padGridItemColumnCount: Int = 1
|
||||||
@State
|
@State
|
||||||
private var scrollViewOffset: CGFloat = 0
|
private var scrollViewOffset: CGFloat = 0
|
||||||
@State
|
@State
|
||||||
|
private var selectedUsers: Set<UserState> = []
|
||||||
|
@State
|
||||||
private var splashScreenImageSource: ImageSource? = nil
|
private var splashScreenImageSource: ImageSource? = nil
|
||||||
|
|
||||||
|
private var users: [UserState] {
|
||||||
|
gridItems.compactMap { item in
|
||||||
|
switch item {
|
||||||
|
case let .user(user, _):
|
||||||
|
return user
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Dialog States
|
// MARK: - Dialog States
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var isPresentingConfirmDeleteUsers = false
|
||||||
@State
|
@State
|
||||||
private var isPresentingServers: Bool = false
|
private var isPresentingServers: Bool = false
|
||||||
|
|
||||||
|
@ -175,17 +197,17 @@ struct SelectUserView: View {
|
||||||
server: server,
|
server: server,
|
||||||
showServer: serverSelection == .all
|
showServer: serverSelection == .all
|
||||||
) {
|
) {
|
||||||
// if isEditingUsers {
|
if isEditingUsers {
|
||||||
// selectedUsers.toggle(value: user)
|
selectedUsers.toggle(value: user)
|
||||||
// } else {
|
} else {
|
||||||
viewModel.send(.signIn(user, pin: ""))
|
viewModel.send(.signIn(user, pin: ""))
|
||||||
// }
|
|
||||||
} onDelete: {
|
|
||||||
// selectedUsers.insert(user)
|
|
||||||
// isPresentingConfirmDeleteUsers = true
|
|
||||||
}
|
}
|
||||||
// .environment(\.isEditing, isEditingUsers)
|
} onDelete: {
|
||||||
// .environment(\.isSelected, selectedUsers.contains(user))
|
selectedUsers.insert(user)
|
||||||
|
isPresentingConfirmDeleteUsers = true
|
||||||
|
}
|
||||||
|
.environment(\.isEditing, isEditingUsers)
|
||||||
|
.environment(\.isSelected, selectedUsers.contains(user))
|
||||||
case .addUser:
|
case .addUser:
|
||||||
AddUserButton(
|
AddUserButton(
|
||||||
serverSelection: $serverSelection,
|
serverSelection: $serverSelection,
|
||||||
|
@ -193,6 +215,7 @@ struct SelectUserView: View {
|
||||||
) { server in
|
) { server in
|
||||||
router.route(to: \.userSignIn, server)
|
router.route(to: \.userSignIn, server)
|
||||||
}
|
}
|
||||||
|
.environment(\.isEnabled, !isEditingUsers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,24 +234,34 @@ struct SelectUserView: View {
|
||||||
.frame(height: 100)
|
.frame(height: 100)
|
||||||
|
|
||||||
gridContentView
|
gridContentView
|
||||||
|
.focusSection()
|
||||||
}
|
}
|
||||||
.scrollIfLargerThanContainer(padding: 100)
|
.scrollIfLargerThanContainer(padding: 100)
|
||||||
.scrollViewOffset($scrollViewOffset)
|
.scrollViewOffset($scrollViewOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
SelectUserBottomBar(
|
||||||
ServerSelectionMenu(
|
isEditing: $isEditingUsers,
|
||||||
selection: $serverSelection,
|
serverSelection: $serverSelection,
|
||||||
viewModel: viewModel
|
areUsersSelected: selectedUsers.isNotEmpty,
|
||||||
)
|
viewModel: viewModel,
|
||||||
|
userCount: gridItems.count,
|
||||||
|
onDelete: {
|
||||||
|
isPresentingConfirmDeleteUsers = true
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
if selectedUsers.count == users.count {
|
||||||
|
selectedUsers.removeAll()
|
||||||
|
} else {
|
||||||
|
selectedUsers.insert(contentsOf: users)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.focusSection()
|
||||||
}
|
}
|
||||||
.animation(.linear(duration: 0.1), value: scrollViewOffset)
|
.animation(.linear(duration: 0.1), value: scrollViewOffset)
|
||||||
.background {
|
.background {
|
||||||
if let splashScreenImageSource {
|
if let splashScreenImageSource {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.clear
|
|
||||||
|
|
||||||
ImageView(splashScreenImageSource)
|
ImageView(splashScreenImageSource)
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.id(splashScreenImageSource)
|
.id(splashScreenImageSource)
|
||||||
|
@ -278,6 +311,32 @@ struct SelectUserView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Functions
|
||||||
|
|
||||||
|
private func didDelete(_ server: ServerState) {
|
||||||
|
viewModel.send(.getServers)
|
||||||
|
|
||||||
|
if case let SelectUserServerSelection.server(id: id) = serverSelection, server.id == id {
|
||||||
|
if viewModel.servers.keys.count == 1, let first = viewModel.servers.keys.first {
|
||||||
|
serverSelection = .server(id: first.id)
|
||||||
|
} else {
|
||||||
|
serverSelection = .all
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// change splash screen selection if necessary
|
||||||
|
// selectUserAllServersSplashscreen = serverSelection
|
||||||
|
}
|
||||||
|
|
||||||
|
private func didAppear() {
|
||||||
|
viewModel.send(.getServers)
|
||||||
|
|
||||||
|
splashScreenImageSource = makeSplashScreenImageSource(
|
||||||
|
serverSelection: serverSelection,
|
||||||
|
allServersSelection: .all
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -291,22 +350,11 @@ struct SelectUserView: View {
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.navigationBarBranding()
|
.navigationBarBranding()
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.send(.getServers)
|
didAppear()
|
||||||
|
}
|
||||||
splashScreenImageSource = makeSplashScreenImageSource(
|
.onChange(of: isEditingUsers) { _, newValue in
|
||||||
serverSelection: serverSelection,
|
guard !newValue else { return }
|
||||||
allServersSelection: .all
|
selectedUsers.removeAll()
|
||||||
)
|
|
||||||
|
|
||||||
// gridItems = OrderedSet(
|
|
||||||
// (0 ..< 20)
|
|
||||||
// .map { i in
|
|
||||||
// UserState(accessToken: "", id: "\(i)", serverID: "", username: "\(i)")
|
|
||||||
// }
|
|
||||||
// .map { u in
|
|
||||||
// UserGridItem.user(u, server: .init(urls: [], currentURL: URL(string: "/")!, name: "Test", id: "", usersIDs: []))
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
.onChange(of: serverSelection) { _, newValue in
|
.onChange(of: serverSelection) { _, newValue in
|
||||||
gridItems = makeGridItems(for: newValue)
|
gridItems = makeGridItems(for: newValue)
|
||||||
|
@ -343,19 +391,24 @@ struct SelectUserView: View {
|
||||||
serverSelection = .server(id: server.id)
|
serverSelection = .server(id: server.id)
|
||||||
}
|
}
|
||||||
.onNotification(.didDeleteServer) { server in
|
.onNotification(.didDeleteServer) { server in
|
||||||
viewModel.send(.getServers)
|
didDelete(server)
|
||||||
|
}
|
||||||
if case let SelectUserServerSelection.server(id: id) = serverSelection, server.id == id {
|
.confirmationDialog(
|
||||||
if viewModel.servers.keys.count == 1, let first = viewModel.servers.keys.first {
|
Text(L10n.deleteUser),
|
||||||
serverSelection = .server(id: first.id)
|
isPresented: $isPresentingConfirmDeleteUsers,
|
||||||
|
presenting: selectedUsers
|
||||||
|
) { selectedUsers in
|
||||||
|
Button(L10n.delete, role: .destructive) {
|
||||||
|
viewModel.send(.deleteUsers(Array(selectedUsers)))
|
||||||
|
isEditingUsers = false
|
||||||
|
}
|
||||||
|
} message: { selectedUsers in
|
||||||
|
if selectedUsers.count == 1, let first = selectedUsers.first {
|
||||||
|
Text(L10n.deleteUserSingleConfirmation(first.username))
|
||||||
} else {
|
} else {
|
||||||
serverSelection = .all
|
Text(L10n.deleteUserMultipleConfirmation(selectedUsers.count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// change splash screen selection if necessary
|
|
||||||
// selectUserAllServersSplashscreen = serverSelection
|
|
||||||
}
|
|
||||||
.errorMessage($error)
|
.errorMessage($error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -373,6 +373,7 @@
|
||||||
BD3957792C113EC40078CEF8 /* SubtitleSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3957782C113EC40078CEF8 /* SubtitleSection.swift */; };
|
BD3957792C113EC40078CEF8 /* SubtitleSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3957782C113EC40078CEF8 /* SubtitleSection.swift */; };
|
||||||
BD39577C2C113FAA0078CEF8 /* TimestampSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD39577B2C113FAA0078CEF8 /* TimestampSection.swift */; };
|
BD39577C2C113FAA0078CEF8 /* TimestampSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD39577B2C113FAA0078CEF8 /* TimestampSection.swift */; };
|
||||||
BD39577E2C1140810078CEF8 /* TransitionSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD39577D2C1140810078CEF8 /* TransitionSection.swift */; };
|
BD39577E2C1140810078CEF8 /* TransitionSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD39577D2C1140810078CEF8 /* TransitionSection.swift */; };
|
||||||
|
BDA623532D0D0854009A157F /* SelectUserBottomBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA623522D0D0854009A157F /* SelectUserBottomBar.swift */; };
|
||||||
C40CD926271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */; };
|
C40CD926271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */; };
|
||||||
C44FA6E02AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */; };
|
C44FA6E02AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */; };
|
||||||
C44FA6E12AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */; };
|
C44FA6E12AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */; };
|
||||||
|
@ -1485,6 +1486,7 @@
|
||||||
BD3957782C113EC40078CEF8 /* SubtitleSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSection.swift; sourceTree = "<group>"; };
|
BD3957782C113EC40078CEF8 /* SubtitleSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSection.swift; sourceTree = "<group>"; };
|
||||||
BD39577B2C113FAA0078CEF8 /* TimestampSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampSection.swift; sourceTree = "<group>"; };
|
BD39577B2C113FAA0078CEF8 /* TimestampSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampSection.swift; sourceTree = "<group>"; };
|
||||||
BD39577D2C1140810078CEF8 /* TransitionSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionSection.swift; sourceTree = "<group>"; };
|
BD39577D2C1140810078CEF8 /* TransitionSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionSection.swift; sourceTree = "<group>"; };
|
||||||
|
BDA623522D0D0854009A157F /* SelectUserBottomBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectUserBottomBar.swift; sourceTree = "<group>"; };
|
||||||
C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTypeLibraryViewModel.swift; sourceTree = "<group>"; };
|
C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTypeLibraryViewModel.swift; sourceTree = "<group>"; };
|
||||||
C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveSmallPlaybackButton.swift; sourceTree = "<group>"; };
|
C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveSmallPlaybackButton.swift; sourceTree = "<group>"; };
|
||||||
C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLargePlaybackButtons.swift; sourceTree = "<group>"; };
|
C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLargePlaybackButtons.swift; sourceTree = "<group>"; };
|
||||||
|
@ -4027,6 +4029,7 @@
|
||||||
E1763A282BF3046A004DF6AB /* AddUserButton.swift */,
|
E1763A282BF3046A004DF6AB /* AddUserButton.swift */,
|
||||||
E1763A262BF303C9004DF6AB /* ServerSelectionMenu.swift */,
|
E1763A262BF303C9004DF6AB /* ServerSelectionMenu.swift */,
|
||||||
E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */,
|
E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */,
|
||||||
|
BDA623522D0D0854009A157F /* SelectUserBottomBar.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -5572,6 +5575,7 @@
|
||||||
E1D90D772C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift in Sources */,
|
E1D90D772C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift in Sources */,
|
||||||
E10231582BCF8AF8009D71FC /* WideChannelGridItem.swift in Sources */,
|
E10231582BCF8AF8009D71FC /* WideChannelGridItem.swift in Sources */,
|
||||||
E15D4F082B1B12C300442DB8 /* Backport.swift in Sources */,
|
E15D4F082B1B12C300442DB8 /* Backport.swift in Sources */,
|
||||||
|
BDA623532D0D0854009A157F /* SelectUserBottomBar.swift in Sources */,
|
||||||
E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */,
|
E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */,
|
||||||
E1575E9A293E7B1E001665B1 /* Array.swift in Sources */,
|
E1575E9A293E7B1E001665B1 /* Array.swift in Sources */,
|
||||||
E1575E8D293E7B1E001665B1 /* URLComponents.swift in Sources */,
|
E1575E8D293E7B1E001665B1 /* URLComponents.swift in Sources */,
|
||||||
|
|
Loading…
Reference in New Issue