[iOS] Admin Dashboard - User Passwords (#1312)
* resetUserPassword Adjustments * Nest the Password in Advanced because I dunno it looks nicer. * Dismiss Coordinator instead of pop. * Build issues * Rename my local xcode to xcode_16??? * Build plz * Comments * clean up --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
4dc8a31d6d
commit
128381a439
|
@ -37,8 +37,8 @@ final class AdminDashboardCoordinator: NavigationCoordinatable {
|
||||||
var users = makeUsers
|
var users = makeUsers
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var userDetails = makeUserDetails
|
var userDetails = makeUserDetails
|
||||||
@Route(.push)
|
@Route(.modal)
|
||||||
var userDevices = makeUserDevices
|
var resetUserPassword = makeResetUserPassword
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
var addServerUser = makeAddServerUser
|
var addServerUser = makeAddServerUser
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
|
@ -106,9 +106,10 @@ final class AdminDashboardCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
func makeResetUserPassword(userID: String) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
|
||||||
func makeUserDevices() -> some View {
|
NavigationViewCoordinator {
|
||||||
DevicesView()
|
ResetUserPasswordView(userID: userID, requiresCurrentPassword: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -26,7 +26,7 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
var playbackQualitySettings = makePlaybackQualitySettings
|
var playbackQualitySettings = makePlaybackQualitySettings
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var quickConnect = makeQuickConnectAuthorize
|
var quickConnect = makeQuickConnectAuthorize
|
||||||
@Route(.push)
|
@Route(.modal)
|
||||||
var resetUserPassword = makeResetUserPassword
|
var resetUserPassword = makeResetUserPassword
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var localSecurity = makeLocalSecurity
|
var localSecurity = makeLocalSecurity
|
||||||
|
@ -112,9 +112,10 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
QuickConnectAuthorizeView()
|
QuickConnectAuthorizeView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
func makeResetUserPassword(userID: String) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
|
||||||
func makeResetUserPassword() -> some View {
|
NavigationViewCoordinator {
|
||||||
ResetUserPasswordView()
|
ResetUserPasswordView(userID: userID, requiresCurrentPassword: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -220,6 +220,8 @@ internal enum L10n {
|
||||||
internal static let confirm = L10n.tr("Localizable", "confirm", fallback: "Confirm")
|
internal static let confirm = L10n.tr("Localizable", "confirm", fallback: "Confirm")
|
||||||
/// Confirm Close
|
/// Confirm Close
|
||||||
internal static let confirmClose = L10n.tr("Localizable", "confirmClose", fallback: "Confirm Close")
|
internal static let confirmClose = L10n.tr("Localizable", "confirmClose", fallback: "Confirm Close")
|
||||||
|
/// Confirm New Password
|
||||||
|
internal static let confirmNewPassword = L10n.tr("Localizable", "confirmNewPassword", fallback: "Confirm New Password")
|
||||||
/// Confirm Password
|
/// Confirm Password
|
||||||
internal static let confirmPassword = L10n.tr("Localizable", "confirmPassword", fallback: "Confirm Password")
|
internal static let confirmPassword = L10n.tr("Localizable", "confirmPassword", fallback: "Confirm Password")
|
||||||
/// Connect
|
/// Connect
|
||||||
|
@ -250,6 +252,8 @@ internal enum L10n {
|
||||||
internal static let createAPIKeyMessage = L10n.tr("Localizable", "createAPIKeyMessage", fallback: "Enter the application name for the new API key.")
|
internal static let createAPIKeyMessage = L10n.tr("Localizable", "createAPIKeyMessage", fallback: "Enter the application name for the new API key.")
|
||||||
/// Current
|
/// Current
|
||||||
internal static let current = L10n.tr("Localizable", "current", fallback: "Current")
|
internal static let current = L10n.tr("Localizable", "current", fallback: "Current")
|
||||||
|
/// Current Password
|
||||||
|
internal static let currentPassword = L10n.tr("Localizable", "currentPassword", fallback: "Current Password")
|
||||||
/// Current Position
|
/// Current Position
|
||||||
internal static let currentPosition = L10n.tr("Localizable", "currentPosition", fallback: "Current Position")
|
internal static let currentPosition = L10n.tr("Localizable", "currentPosition", fallback: "Current Position")
|
||||||
/// PlaybackCompatibility Custom Category
|
/// PlaybackCompatibility Custom Category
|
||||||
|
@ -550,6 +554,8 @@ internal enum L10n {
|
||||||
internal static let never = L10n.tr("Localizable", "never", fallback: "Never")
|
internal static let never = L10n.tr("Localizable", "never", fallback: "Never")
|
||||||
/// Message shown when a task has never run
|
/// Message shown when a task has never run
|
||||||
internal static let neverRun = L10n.tr("Localizable", "neverRun", fallback: "Never run")
|
internal static let neverRun = L10n.tr("Localizable", "neverRun", fallback: "Never run")
|
||||||
|
/// New Password
|
||||||
|
internal static let newPassword = L10n.tr("Localizable", "newPassword", fallback: "New Password")
|
||||||
/// News
|
/// News
|
||||||
internal static let news = L10n.tr("Localizable", "news", fallback: "News")
|
internal static let news = L10n.tr("Localizable", "news", fallback: "News")
|
||||||
/// New User
|
/// New User
|
||||||
|
@ -636,8 +642,12 @@ internal enum L10n {
|
||||||
}
|
}
|
||||||
/// Password
|
/// Password
|
||||||
internal static let password = L10n.tr("Localizable", "password", fallback: "Password")
|
internal static let password = L10n.tr("Localizable", "password", fallback: "Password")
|
||||||
/// New passwords do not match
|
/// User password has been changed.
|
||||||
internal static let passwordsDoNotMatch = L10n.tr("Localizable", "passwordsDoNotMatch", fallback: "New passwords do not match")
|
internal static let passwordChangedMessage = L10n.tr("Localizable", "passwordChangedMessage", fallback: "User password has been changed.")
|
||||||
|
/// Changes the Jellyfin server user password. This does not change any Swiftfin settings.
|
||||||
|
internal static let passwordChangeWarning = L10n.tr("Localizable", "passwordChangeWarning", fallback: "Changes the Jellyfin server user password. This does not change any Swiftfin settings.")
|
||||||
|
/// New passwords do not match.
|
||||||
|
internal static let passwordsDoNotMatch = L10n.tr("Localizable", "passwordsDoNotMatch", fallback: "New passwords do not match.")
|
||||||
/// Video Player Settings View - Pause on Background
|
/// Video Player Settings View - Pause on Background
|
||||||
internal static let pauseOnBackground = L10n.tr("Localizable", "pauseOnBackground", fallback: "Pause on background")
|
internal static let pauseOnBackground = L10n.tr("Localizable", "pauseOnBackground", fallback: "Pause on background")
|
||||||
/// People
|
/// People
|
||||||
|
|
|
@ -12,29 +12,32 @@ import JellyfinAPI
|
||||||
|
|
||||||
final class ResetUserPasswordViewModel: ViewModel, Eventful, Stateful {
|
final class ResetUserPasswordViewModel: ViewModel, Eventful, Stateful {
|
||||||
|
|
||||||
// MARK: Event
|
// MARK: - Event
|
||||||
|
|
||||||
enum Event {
|
enum Event {
|
||||||
case error(JellyfinAPIError)
|
case error(JellyfinAPIError)
|
||||||
case success
|
case success
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Action
|
// MARK: - Action
|
||||||
|
|
||||||
enum Action: Equatable {
|
enum Action: Equatable {
|
||||||
case cancel
|
case cancel
|
||||||
case reset(current: String, new: String)
|
case reset(current: String, new: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: State
|
// MARK: - State
|
||||||
|
|
||||||
enum State: Hashable {
|
enum State: Hashable {
|
||||||
case initial
|
case initial
|
||||||
case resetting
|
case resetting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Published Variables
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var state: State = .initial
|
var state: State = .initial
|
||||||
|
let userID: String
|
||||||
|
|
||||||
var events: AnyPublisher<Event, Never> {
|
var events: AnyPublisher<Event, Never> {
|
||||||
eventSubject
|
eventSubject
|
||||||
|
@ -45,6 +48,12 @@ final class ResetUserPasswordViewModel: ViewModel, Eventful, Stateful {
|
||||||
private var resetTask: AnyCancellable?
|
private var resetTask: AnyCancellable?
|
||||||
private var eventSubject: PassthroughSubject<Event, Never> = .init()
|
private var eventSubject: PassthroughSubject<Event, Never> = .init()
|
||||||
|
|
||||||
|
// MARK: - Initializer
|
||||||
|
|
||||||
|
init(userID: String) {
|
||||||
|
self.userID = userID
|
||||||
|
}
|
||||||
|
|
||||||
func respond(to action: Action) -> State {
|
func respond(to action: Action) -> State {
|
||||||
switch action {
|
switch action {
|
||||||
case .cancel:
|
case .cancel:
|
||||||
|
@ -79,7 +88,7 @@ final class ResetUserPasswordViewModel: ViewModel, Eventful, Stateful {
|
||||||
|
|
||||||
private func reset(current: String, new: String) async throws {
|
private func reset(current: String, new: String) async throws {
|
||||||
let body = UpdateUserPassword(currentPw: current, newPw: new)
|
let body = UpdateUserPassword(currentPw: current, newPw: new)
|
||||||
let request = Paths.updateUserPassword(userID: userSession.user.id, body)
|
let request = Paths.updateUserPassword(userID: userID, body)
|
||||||
|
|
||||||
try await userSession.client.send(request)
|
try await userSession.client.send(request)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2273,6 +2273,14 @@
|
||||||
path = DevicesView;
|
path = DevicesView;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4EF10D4C2CE2EC5A000ED5F5 /* ResetUserPasswordView */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E1545BD72BDC55C300D9578F /* ResetUserPasswordView.swift */,
|
||||||
|
);
|
||||||
|
path = ResetUserPasswordView;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4EF18B232CB9932F00343666 /* PagingLibraryView */ = {
|
4EF18B232CB9932F00343666 /* PagingLibraryView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -3295,6 +3303,7 @@
|
||||||
E1EDA8D62B92C9D700F9A57E /* PagingLibraryView */,
|
E1EDA8D62B92C9D700F9A57E /* PagingLibraryView */,
|
||||||
E10231342BCF8A3C009D71FC /* ProgramsView */,
|
E10231342BCF8A3C009D71FC /* ProgramsView */,
|
||||||
E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */,
|
E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */,
|
||||||
|
4EF10D4C2CE2EC5A000ED5F5 /* ResetUserPasswordView */,
|
||||||
53EE24E5265060780068F029 /* SearchView.swift */,
|
53EE24E5265060780068F029 /* SearchView.swift */,
|
||||||
E10B1EAF2BD9769500A92EAF /* SelectUserView */,
|
E10B1EAF2BD9769500A92EAF /* SelectUserView */,
|
||||||
E19D41AB2BF288110082B8B2 /* ServerCheckView.swift */,
|
E19D41AB2BF288110082B8B2 /* ServerCheckView.swift */,
|
||||||
|
@ -3387,7 +3396,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
6334175A287DDFB9000603CE /* QuickConnectAuthorizeView.swift */,
|
6334175A287DDFB9000603CE /* QuickConnectAuthorizeView.swift */,
|
||||||
E1545BD72BDC55C300D9578F /* ResetUserPasswordView.swift */,
|
|
||||||
E1EA09872BEE9CF3004CDE76 /* UserLocalSecurityView.swift */,
|
E1EA09872BEE9CF3004CDE76 /* UserLocalSecurityView.swift */,
|
||||||
E14EA1612BF6FF8D00DE757A /* UserProfileImagePicker */,
|
E14EA1612BF6FF8D00DE757A /* UserProfileImagePicker */,
|
||||||
E1BE1CEF2BDB6C97008176A9 /* UserProfileSettingsView.swift */,
|
E1BE1CEF2BDB6C97008176A9 /* UserProfileSettingsView.swift */,
|
||||||
|
|
|
@ -35,6 +35,15 @@ struct ServerUserDetailsView: View {
|
||||||
user: viewModel.user,
|
user: viewModel.user,
|
||||||
lastActivityDate: viewModel.user.lastActivityDate
|
lastActivityDate: viewModel.user.lastActivityDate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Section(L10n.advanced) {
|
||||||
|
if let userId = viewModel.user.id {
|
||||||
|
ChevronButton(L10n.password)
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.resetUserPassword, userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(L10n.user)
|
.navigationTitle(L10n.user)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|
|
@ -13,14 +13,25 @@ import SwiftUI
|
||||||
|
|
||||||
struct ResetUserPasswordView: View {
|
struct ResetUserPasswordView: View {
|
||||||
|
|
||||||
|
private enum Field: Hashable {
|
||||||
|
case currentPassword
|
||||||
|
case newPassword
|
||||||
|
case confirmNewPassword
|
||||||
|
}
|
||||||
|
|
||||||
@Default(.accentColor)
|
@Default(.accentColor)
|
||||||
private var accentColor
|
private var accentColor
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var router: SettingsCoordinator.Router
|
private var router: BasicNavigationViewCoordinator.Router
|
||||||
|
|
||||||
@FocusState
|
@FocusState
|
||||||
private var focusedPassword: Int?
|
private var focusedField: Field?
|
||||||
|
|
||||||
|
@StateObject
|
||||||
|
private var viewModel: ResetUserPasswordViewModel
|
||||||
|
|
||||||
|
// MARK: - Password Variables
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var currentPassword: String = ""
|
private var currentPassword: String = ""
|
||||||
|
@ -29,6 +40,8 @@ struct ResetUserPasswordView: View {
|
||||||
@State
|
@State
|
||||||
private var confirmNewPassword: String = ""
|
private var confirmNewPassword: String = ""
|
||||||
|
|
||||||
|
// MARK: - State Variables
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var error: Error? = nil
|
private var error: Error? = nil
|
||||||
@State
|
@State
|
||||||
|
@ -36,45 +49,54 @@ struct ResetUserPasswordView: View {
|
||||||
@State
|
@State
|
||||||
private var isPresentingSuccess: Bool = false
|
private var isPresentingSuccess: Bool = false
|
||||||
|
|
||||||
@StateObject
|
private let requiresCurrentPassword: Bool
|
||||||
private var viewModel = ResetUserPasswordViewModel()
|
|
||||||
|
// MARK: - Initializer
|
||||||
|
|
||||||
|
init(userID: String, requiresCurrentPassword: Bool) {
|
||||||
|
self._viewModel = StateObject(wrappedValue: ResetUserPasswordViewModel(userID: userID))
|
||||||
|
self.requiresCurrentPassword = requiresCurrentPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
|
if requiresCurrentPassword {
|
||||||
Section("Current Password") {
|
Section(L10n.currentPassword) {
|
||||||
UnmaskSecureField("Current Password", text: $currentPassword) {
|
UnmaskSecureField(L10n.currentPassword, text: $currentPassword) {
|
||||||
focusedPassword = 1
|
focusedField = .newPassword
|
||||||
|
}
|
||||||
|
.autocorrectionDisabled()
|
||||||
|
.textInputAutocapitalization(.none)
|
||||||
|
.focused($focusedField, equals: .currentPassword)
|
||||||
|
.disabled(viewModel.state == .resetting)
|
||||||
}
|
}
|
||||||
.autocorrectionDisabled()
|
|
||||||
.textInputAutocapitalization(.none)
|
|
||||||
.focused($focusedPassword, equals: 0)
|
|
||||||
.disabled(viewModel.state == .resetting)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("New Password") {
|
Section(L10n.newPassword) {
|
||||||
UnmaskSecureField("New Password", text: $newPassword) {
|
UnmaskSecureField(L10n.newPassword, text: $newPassword) {
|
||||||
focusedPassword = 2
|
focusedField = .confirmNewPassword
|
||||||
}
|
}
|
||||||
.autocorrectionDisabled()
|
.autocorrectionDisabled()
|
||||||
.textInputAutocapitalization(.none)
|
.textInputAutocapitalization(.none)
|
||||||
.focused($focusedPassword, equals: 1)
|
.focused($focusedField, equals: .newPassword)
|
||||||
.disabled(viewModel.state == .resetting)
|
.disabled(viewModel.state == .resetting)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
UnmaskSecureField("Confirm New Password", text: $confirmNewPassword) {
|
UnmaskSecureField(L10n.confirmNewPassword, text: $confirmNewPassword) {
|
||||||
viewModel.send(.reset(current: currentPassword, new: confirmNewPassword))
|
viewModel.send(.reset(current: currentPassword, new: confirmNewPassword))
|
||||||
}
|
}
|
||||||
.autocorrectionDisabled()
|
.autocorrectionDisabled()
|
||||||
.textInputAutocapitalization(.none)
|
.textInputAutocapitalization(.none)
|
||||||
.focused($focusedPassword, equals: 2)
|
.focused($focusedField, equals: .confirmNewPassword)
|
||||||
.disabled(viewModel.state == .resetting)
|
.disabled(viewModel.state == .resetting)
|
||||||
} header: {
|
} header: {
|
||||||
Text("Confirm New Password")
|
Text(L10n.confirmNewPassword)
|
||||||
} footer: {
|
} footer: {
|
||||||
if newPassword != confirmNewPassword {
|
if newPassword != confirmNewPassword {
|
||||||
Label("New passwords to not match", systemImage: "exclamationmark.circle.fill")
|
Label(L10n.passwordsDoNotMatch, systemImage: "exclamationmark.circle.fill")
|
||||||
.labelStyle(.sectionFooterWithImage(imageStyle: .orange))
|
.labelStyle(.sectionFooterWithImage(imageStyle: .orange))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,12 +105,17 @@ struct ResetUserPasswordView: View {
|
||||||
if viewModel.state == .resetting {
|
if viewModel.state == .resetting {
|
||||||
ListRowButton(L10n.cancel) {
|
ListRowButton(L10n.cancel) {
|
||||||
viewModel.send(.cancel)
|
viewModel.send(.cancel)
|
||||||
focusedPassword = 0
|
|
||||||
|
if requiresCurrentPassword {
|
||||||
|
focusedField = .currentPassword
|
||||||
|
} else {
|
||||||
|
focusedField = .newPassword
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.foregroundStyle(.red, .red.opacity(0.2))
|
.foregroundStyle(.red, .red.opacity(0.2))
|
||||||
} else {
|
} else {
|
||||||
ListRowButton(L10n.save) {
|
ListRowButton(L10n.save) {
|
||||||
focusedPassword = nil
|
focusedField = nil
|
||||||
viewModel.send(.reset(current: currentPassword, new: confirmNewPassword))
|
viewModel.send(.reset(current: currentPassword, new: confirmNewPassword))
|
||||||
}
|
}
|
||||||
.disabled(newPassword != confirmNewPassword || viewModel.state == .resetting)
|
.disabled(newPassword != confirmNewPassword || viewModel.state == .resetting)
|
||||||
|
@ -96,14 +123,22 @@ struct ResetUserPasswordView: View {
|
||||||
.opacity(newPassword != confirmNewPassword ? 0.5 : 1)
|
.opacity(newPassword != confirmNewPassword ? 0.5 : 1)
|
||||||
}
|
}
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Changes the Jellyfin server user password. This does not change any Swiftfin settings.")
|
Text(L10n.passwordChangeWarning)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.interactiveDismissDisabled(viewModel.state == .resetting)
|
.interactiveDismissDisabled(viewModel.state == .resetting)
|
||||||
.navigationBarBackButtonHidden(viewModel.state == .resetting)
|
.navigationBarBackButtonHidden(viewModel.state == .resetting)
|
||||||
.navigationTitle(L10n.password)
|
.navigationTitle(L10n.password)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.navigationBarCloseButton {
|
||||||
|
router.dismissCoordinator()
|
||||||
|
}
|
||||||
.onFirstAppear {
|
.onFirstAppear {
|
||||||
focusedPassword = 0
|
if requiresCurrentPassword {
|
||||||
|
focusedField = .currentPassword
|
||||||
|
} else {
|
||||||
|
focusedField = .newPassword
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onReceive(viewModel.events) { event in
|
.onReceive(viewModel.events) { event in
|
||||||
switch event {
|
switch event {
|
||||||
|
@ -124,12 +159,12 @@ struct ResetUserPasswordView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alert(
|
.alert(
|
||||||
L10n.error.text,
|
L10n.error,
|
||||||
isPresented: $isPresentingError,
|
isPresented: $isPresentingError,
|
||||||
presenting: error
|
presenting: error
|
||||||
) { _ in
|
) { _ in
|
||||||
Button(L10n.dismiss, role: .cancel) {
|
Button(L10n.dismiss, role: .cancel) {
|
||||||
focusedPassword = 1
|
focusedField = .newPassword
|
||||||
}
|
}
|
||||||
} message: { error in
|
} message: { error in
|
||||||
Text(error.localizedDescription)
|
Text(error.localizedDescription)
|
||||||
|
@ -139,10 +174,10 @@ struct ResetUserPasswordView: View {
|
||||||
isPresented: $isPresentingSuccess
|
isPresented: $isPresentingSuccess
|
||||||
) {
|
) {
|
||||||
Button(L10n.dismiss, role: .cancel) {
|
Button(L10n.dismiss, role: .cancel) {
|
||||||
router.pop()
|
router.dismissCoordinator()
|
||||||
}
|
}
|
||||||
} message: {
|
} message: {
|
||||||
Text("User password has been changed.")
|
Text(L10n.passwordChangedMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -91,7 +91,7 @@ struct UserProfileSettingsView: View {
|
||||||
|
|
||||||
ChevronButton("Password")
|
ChevronButton("Password")
|
||||||
.onSelect {
|
.onSelect {
|
||||||
router.route(to: \.resetUserPassword)
|
router.route(to: \.resetUserPassword, viewModel.userSession.user.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
"ok" = "Ok";
|
"ok" = "Ok";
|
||||||
"otherUser" = "Other User";
|
"otherUser" = "Other User";
|
||||||
"pageOfWithNumbers" = "Page %1$@ of %2$@";
|
"pageOfWithNumbers" = "Page %1$@ of %2$@";
|
||||||
"password" = "Password";
|
|
||||||
"playNext" = "Play Next";
|
"playNext" = "Play Next";
|
||||||
"play" = "Play";
|
"play" = "Play";
|
||||||
"playback" = "Playback";
|
"playback" = "Playback";
|
||||||
|
@ -1213,3 +1212,38 @@
|
||||||
// Button title to edit existing users
|
// Button title to edit existing users
|
||||||
// Used as the button label in the options menu when there are users to edit
|
// Used as the button label in the options menu when there are users to edit
|
||||||
"editUsers" = "Edit Users";
|
"editUsers" = "Edit Users";
|
||||||
|
|
||||||
|
/// Current Password - Placeholder
|
||||||
|
/// Placeholder text for the current password input field
|
||||||
|
/// Used in the ResetUserPasswordView
|
||||||
|
"currentPassword" = "Current Password";
|
||||||
|
|
||||||
|
/// New Password - Placeholder
|
||||||
|
/// Placeholder text for the new password input field
|
||||||
|
/// Used in the ResetUserPasswordView
|
||||||
|
"newPassword" = "New Password";
|
||||||
|
|
||||||
|
/// Confirm New Password - Placeholder
|
||||||
|
/// Placeholder text for confirming the new password input field
|
||||||
|
/// Used in the ResetUserPasswordView
|
||||||
|
"confirmNewPassword" = "Confirm New Password";
|
||||||
|
|
||||||
|
/// Password - Navigation Title
|
||||||
|
/// Title for the password reset view
|
||||||
|
/// Used in the navigation bar
|
||||||
|
"password" = "Password";
|
||||||
|
|
||||||
|
/// Password Changed - Alert Message
|
||||||
|
/// Message displayed in the success alert after changing the password
|
||||||
|
/// Used in the ResetUserPasswordView
|
||||||
|
"passwordChangedMessage" = "User password has been changed.";
|
||||||
|
|
||||||
|
/// Passwords Do Not Match - Footer
|
||||||
|
/// Error message displayed when new passwords do not match
|
||||||
|
/// Used in the ResetUserPasswordView
|
||||||
|
"passwordsDoNotMatch" = "New passwords do not match.";
|
||||||
|
|
||||||
|
/// Password Change Warning - Message
|
||||||
|
/// Message displayed to alert the user what the password change does and does not do
|
||||||
|
/// Used in the ResetUserPasswordView
|
||||||
|
"passwordChangeWarning" = "Changes the Jellyfin server user password. This does not change any Swiftfin settings.";
|
||||||
|
|
Loading…
Reference in New Issue