Add better views and flow for no servers/users

This commit is contained in:
Ethan Pippin 2021-10-14 14:17:50 -06:00
parent 03edfe7e7c
commit a5e6cdf998
5 changed files with 217 additions and 79 deletions

View File

@ -11,10 +11,10 @@ import SwiftUI
import MessageUI
class EmailHelper: NSObject, MFMailComposeViewControllerDelegate {
public static let shared = EmailHelper()
override private init() {
//
}
override private init() { }
func sendLogs(logURL: URL) {
if !MFMailComposeViewController.canSendMail() {

View File

@ -148,31 +148,20 @@ struct JellyfinPlayerApp: App {
var body: some Scene {
WindowGroup {
MainCoordinator().view()
EmptyView()
.onAppear {
setupAppearance()
}
.withHostingWindow { window in
window?.rootViewController = PreferenceUIHostingController(wrappedView: MainCoordinator().view())
}
.onShake {
EmailHelper.shared.sendLogs(logURL: LogManager.shared.logFileURL())
}
.onOpenURL { url in
AppURLHandler.shared.processDeepLink(url: url)
}
}
// WindowGroup {
// EmptyView()
// .environment(\.managedObjectContext, persistenceController.container.viewContext)
// .onAppear(perform: {
// setupAppearance()
// })
// .withHostingWindow { window in
// window?
// .rootViewController = PreferenceUIHostingController(wrappedView: MainCoordinator().view()
// .environment(\.managedObjectContext, persistenceController.container.viewContext))
// }
// .onShake {
// EmailHelper.shared.sendLogs(logURL: LogManager.shared.logFileURL())
// }
// .onOpenURL { url in
// AppURLHandler.shared.processDeepLink(url: url)
// }
// }
}
private func setupAppearance() {

View File

@ -15,47 +15,120 @@ struct ServerListView: View {
@EnvironmentObject var serverListRouter: ServerListCoordinator.Router
@ObservedObject var viewModel: ServerListViewModel
var body: some View {
List {
ForEach(viewModel.servers, id: \.id) { server in
Button {
serverListRouter.route(to: \.userList, server)
} label: {
Text(server.name)
@ViewBuilder
private var listView: some View {
ScrollView {
VStack {
ForEach(viewModel.servers, id: \.id) { server in
Button {
serverListRouter.route(to: \.userList, server)
} label: {
ZStack(alignment: Alignment.leading) {
Rectangle()
.foregroundColor(Color(UIColor.secondarySystemFill))
.frame(height: 100)
.cornerRadius(10)
HStack {
Image(systemName: "server.rack")
.font(.system(size: 36))
.foregroundColor(.primary)
VStack(alignment: .leading, spacing: 5) {
Text(server.name)
.font(.title2)
.foregroundColor(.primary)
Text(server.uri)
.font(.footnote)
.disabled(true)
.foregroundColor(.secondary)
Text(viewModel.userTextFor(server: server))
.font(.footnote)
.foregroundColor(.primary)
}
}.padding([.leading])
}
.padding()
}
}
}
}
.navigationTitle("Servers")
.toolbar {
ToolbarItemGroup(placement: .navigation) {
HStack {
Button {
serverListRouter.route(to: \.connectToServer)
} label: {
Text("Connect")
}
}
@ViewBuilder
private var noServerView: some View {
VStack {
Text("Connect to a Jellyfin server to get started.")
.frame(minWidth: 50, maxWidth: 240)
.multilineTextAlignment(.center)
Button {
serverListRouter.route(to: \.connectToServer)
} label: {
ZStack {
Rectangle()
.foregroundColor(Color.jellyfinPurple)
.frame(maxWidth: 500, maxHeight: 50)
.frame(height: 50)
.cornerRadius(10)
.padding([.leading, .trailing], 30)
.padding([.top, .bottom], 20)
Button {
SwiftfinStore.dataStack.perform(asynchronous: { transaction in
try! transaction.deleteAll(From<SwiftfinStore.Models.StoredServer>())
try! transaction.deleteAll(From<SwiftfinStore.Models.StoredUser>())
try! transaction.deleteAll(From<SwiftfinStore.Models.StoredAccessToken>())
}) { _ in
SwiftfinStore.Defaults.suite[.lastServerUserID] = nil
viewModel.fetchServers()
}
} label: {
Text("Purge")
}
Text("Connect")
.foregroundColor(Color.white)
.bold()
}
}
// ToolbarItem(placement: .navigationBarTrailing) {
// Button {
// serverListRouter.route(to: \.connectToServer)
// } label: {
// Text("Connect")
// }
// }
}
}
@ViewBuilder
private var innerBody: some View {
if viewModel.servers.isEmpty {
noServerView
.offset(y: -50)
} else {
listView
}
}
@ViewBuilder
private var toolbarContent: some View {
if viewModel.servers.isEmpty {
EmptyView()
} else {
HStack {
Button {
SwiftfinStore.dataStack.perform(asynchronous: { transaction in
try! transaction.deleteAll(From<SwiftfinStore.Models.StoredServer>())
try! transaction.deleteAll(From<SwiftfinStore.Models.StoredUser>())
try! transaction.deleteAll(From<SwiftfinStore.Models.StoredAccessToken>())
}) { _ in
SwiftfinStore.Defaults.suite[.lastServerUserID] = nil
viewModel.fetchServers()
}
} label: {
Text("Purge")
}
Button {
serverListRouter.route(to: \.connectToServer)
} label: {
Image(systemName: "plus.circle.fill")
}
}
}
}
var body: some View {
innerBody
.navigationTitle("Servers")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
toolbarContent
}
}
.onAppear {
viewModel.fetchServers()

View File

@ -14,34 +14,102 @@ struct UserListView: View {
@EnvironmentObject var userListRouter: UserListCoordinator.Router
@ObservedObject var viewModel: UserListViewModel
var body: some View {
List {
ForEach(viewModel.users, id: \.id) { user in
Button {
viewModel.login(user: user)
} label: {
HStack {
Text(user.username)
Spacer()
if viewModel.isLoading {
ProgressView()
@ViewBuilder
private var listView: some View {
ScrollView {
VStack {
ForEach(viewModel.users, id: \.id) { user in
Button {
viewModel.login(user: user)
} label: {
ZStack(alignment: Alignment.leading) {
Rectangle()
.foregroundColor(Color(UIColor.secondarySystemFill))
.frame(height: 70)
.cornerRadius(10)
HStack {
Image(systemName: "person.crop.circle.fill")
.font(.system(size: 46))
.foregroundColor(.primary)
Text(user.username)
.font(.title2)
Spacer()
if viewModel.isLoading {
ProgressView()
}
}.padding(.leading)
}
.padding()
}
}
}
}
.navigationTitle("Users")
.toolbar {
ToolbarItem(placement: .navigation) {
HStack {
Button {
userListRouter.route(to: \.userLogin, viewModel.server)
} label: {
Text("Connect")
}
}
@ViewBuilder
private var noUserView: some View {
VStack {
Text("Login to a user to get started.")
.frame(minWidth: 50, maxWidth: 240)
.multilineTextAlignment(.center)
Button {
userListRouter.route(to: \.userLogin, viewModel.server)
} label: {
ZStack {
Rectangle()
.foregroundColor(Color.jellyfinPurple)
.frame(maxWidth: 500, maxHeight: 50)
.frame(height: 50)
.cornerRadius(10)
.padding([.leading, .trailing], 30)
.padding([.top, .bottom], 20)
Text("Login")
.foregroundColor(Color.white)
.bold()
}
}
}
}
@ViewBuilder
private var innerBody: some View {
if viewModel.users.isEmpty {
noUserView
.offset(y: -50)
} else {
listView
}
}
@ViewBuilder
private var toolbarContent: some View {
if viewModel.users.isEmpty {
EmptyView()
} else {
HStack {
Button {
userListRouter.route(to: \.userLogin, viewModel.server)
} label: {
Image(systemName: "person.crop.circle.fill.badge.plus")
}
}
}
}
var body: some View {
innerBody
.navigationTitle(viewModel.server.name)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
toolbarContent
}
}
.onAppear {
viewModel.fetchUsers()
}

View File

@ -17,4 +17,12 @@ class ServerListViewModel: ObservableObject {
func fetchServers() {
self.servers = SessionManager.main.fetchServers()
}
func userTextFor(server: SwiftfinStore.State.Server) -> String {
if server.userIDs.count == 1 {
return "1 user"
} else {
return "\(server.userIDs.count) users"
}
}
}