From 06b1c3c55c67051c1562ca6ef7444f66c9ae44b9 Mon Sep 17 00:00:00 2001 From: ElectricS01 <103579308+ElectricS01@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:22:52 +1100 Subject: [PATCH] 0.1.7 New Login page, better iOS support --- TPU Mac/TPU_Mac.xcodeproj/project.pbxproj | 4 +- TPU Mac/TPU_Mac/CommsView.swift | 434 +++++++++++----------- TPU Mac/TPU_Mac/ContentView.swift | 125 ++++--- TPU Mac/TPU_Mac/GalleryView.swift | 286 +++++++------- 4 files changed, 443 insertions(+), 406 deletions(-) diff --git a/TPU Mac/TPU_Mac.xcodeproj/project.pbxproj b/TPU Mac/TPU_Mac.xcodeproj/project.pbxproj index 46be7e4..bfcd8af 100644 --- a/TPU Mac/TPU_Mac.xcodeproj/project.pbxproj +++ b/TPU Mac/TPU_Mac.xcodeproj/project.pbxproj @@ -501,7 +501,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 0.1.6; + MARKETING_VERSION = 0.1.7; PRODUCT_BUNDLE_IDENTIFIER = "ElectricS01.TPU-Mac"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -542,7 +542,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 0.1.6; + MARKETING_VERSION = 0.1.7; PRODUCT_BUNDLE_IDENTIFIER = "ElectricS01.TPU-Mac"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; diff --git a/TPU Mac/TPU_Mac/CommsView.swift b/TPU Mac/TPU_Mac/CommsView.swift index 8cbccee..3d1e85a 100644 --- a/TPU Mac/TPU_Mac/CommsView.swift +++ b/TPU Mac/TPU_Mac/CommsView.swift @@ -103,8 +103,210 @@ struct CommsView: View { } var body: some View { - #if os(macOS) - HSplitView { + VStack { + #if os(macOS) + HSplitView { + List { + ForEach(0 ..< chatsList.count, id: \.self) { result in + Button(action: { getChat(chatId: result) }) { + HStack { + ProfilePicture(avatar: chatsList[result].recipient?.avatar ?? chatsList[result].icon) + Text(chatsList[result].recipient?.username ?? chatsList[result].name).lineLimit(1) + Spacer() + if chatsList[result].unread != 0 { + Text(String(chatsList[result].unread!)) + .frame(minWidth: 16, minHeight: 16) + .background(Color.red) + .cornerRadius(10) + } + }.contentShape(Rectangle()) + }.buttonStyle(.plain) + } + } + .frame(width: 150) + .padding(EdgeInsets(top: -8, leading: -10, bottom: -8, trailing: 0)) + if chatOpen != -1 { + ScrollViewReader { proxy in + ScrollView { + VStack(alignment: .leading, spacing: 0) { + ForEach(Array(chatMessages.enumerated()), id: \.element) { index, message in + let dontMerge = merge(message: message, previousMessage: index != 0 ? chatMessages[index - 1] : nil) + Spacer(minLength: dontMerge ? 16 : 0) + if message.id == unreadId { + HStack { + VStack { Divider().background(.red) } + Text("New Message").foregroundStyle(.red) + VStack { Divider().background(.red) } + } + } + if message.reply != nil { + HStack { + Image(systemName: "arrow.turn.up.right").frame(width: 16, height: 16) + Text(message.reply?.user?.username ?? "User has been deleted") + Text((message.reply?.content ?? "Message has been deleted").replacingOccurrences(of: "\n", with: "")).textSelection(.enabled).lineLimit(1) + }.padding(EdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 0)) + } + HStack(alignment: .top, spacing: 6) { + if dontMerge { + ProfilePicture(avatar: message.user?.avatar) + } else { + Spacer().frame(width: 32) + } + VStack { + if dontMerge { + HStack { + Text(message.user?.username ?? "User has been deleted") + if let date = inputDateFormatter.date(from: message.createdAt) { + let formattedDate = outputDateFormatter.string(from: date) + Text(formattedDate) + } else { + Text("Invalid Date") + } + }.frame(minWidth: 0, + maxWidth: .infinity, + minHeight: 0, + maxHeight: 6, + alignment: .topLeading) + } + if editingId != message.id { + Text(.init(message.content ?? "Message has been deleted")) + .textSelection(.enabled) + .frame(minWidth: 0, + maxWidth: .infinity, + minHeight: 0, + maxHeight: .infinity, + alignment: .topLeading) + .lineLimit(nil) + } else { + TextField("Keep it civil!", text: $editingMessage) + .onSubmit { + editMessage() + } + .textFieldStyle(RoundedBorderTextFieldStyle()) + } + ForEach(message.embeds, id: \.self) { embed in + if embed.media != [] { + LazyImage(url: URL(string: embed.media?[0].attachment == nil ? ("https://i.electrics01.com" + (embed.media?[0].proxyUrl ?? "")) : ("https://i.electrics01.com/i/" + (embed.media?[0].attachment ?? "")))) { state in + if let image = state.image { + image + .resizable() + .aspectRatio(contentMode: .fill) + .onAppear { + if chatMessages.count != 0 { + proxy.scrollTo(0, anchor: .bottom) + } + } + } else if state.error != nil { + Color.red + } else { + ProgressView() + } + }.frame(minWidth: 0, maxWidth: 400, minHeight: 0, maxHeight: 400) + } + }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) + } + Button(action: { + if replyingId != message.id { + replyingId = message.id + } else { replyingId = -1 } + }) { + Image(systemName: "arrowshape.turn.up.left.fill").frame(width: 16, height: 16) + } + Button(action: { + replyingId = -1 + if editingId != message.id { + editingId = message.id + editingMessage = message.content ?? "" + } else { editingId = -1 } + }) { + Image(systemName: "pencil").frame(width: 16, height: 16) + } + }.padding(EdgeInsets(top: 0, leading: 4, bottom: 0, trailing: 4)) + // .background(Color(hoverItem == message.id ? Color.primary : .clear)) + // .onHover(perform: { _ in + // hoverItem = message.id + // }) + }.padding(EdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 12)) + } + .id(0) + .frame( + minWidth: 0, + maxWidth: .infinity, + minHeight: 0, + maxHeight: .infinity, + alignment: .topLeading + ) + .onAppear { + if chatMessages.count != 0 { + proxy.scrollTo(0, anchor: .bottom) + } + } + .onChange(of: chatMessages) { + proxy.scrollTo(0, anchor: .bottom) + } + } + if replyingId != -1 { + HStack { + Image(systemName: "arrow.turn.up.right").frame(width: 16, height: 16) + Text(chatMessages.last(where: { $0.id == replyingId })?.user?.username ?? "User has been deleted") + Text(chatMessages.last(where: { $0.id == replyingId })?.content ?? "Message has been deleted") + .textSelection(.enabled) + .lineLimit(1) + .onAppear { + if chatMessages.count != 0 { + proxy.scrollTo(0, anchor: .bottom) + } + } + }.padding(EdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 0)) + .frame(minWidth: 0, + maxWidth: .infinity, + alignment: .topLeading) + } + TextField("Keep it civil!", text: $inputMessage) + .onSubmit { + sendMessage() + } + .textFieldStyle(RoundedBorderTextFieldStyle()) + } + .navigationTitle(chatsList[chatOpen].recipient?.username ?? chatsList[chatOpen].name) + List { + ForEach(0 ..< chatsList[chatOpen].users.count, id: \.self) { result in + Button(action: { print("Clicked: " + (chatsList[chatOpen].users[result].user?.username ?? "User's name could not be found")) }) { + HStack { + ProfilePicture(avatar: chatsList[chatOpen].users[result].user?.avatar) + Text(chatsList[chatOpen].users[result].user?.username ?? "User's name could not be found") + Spacer() + }.contentShape(Rectangle()) + }.buttonStyle(.plain) + } + }.frame(width: 150) + .padding(EdgeInsets(top: -8, leading: -10, bottom: -8, trailing: 0)) + } else { + VStack { + Spacer() + HStack { + Spacer() + Text("Comms") + Spacer() + } + Spacer() + } + } + } + .navigationTitle("Comms") + .onAppear { + getChats { result in + switch result { + case .success(let graphQLResult): + if let unwrapped = graphQLResult.data { + chatsList = unwrapped.chats + } + case .failure(let error): + print(error) + } + } + } + #else List { ForEach(0 ..< chatsList.count, id: \.self) { result in Button(action: { getChat(chatId: result) }) { @@ -122,13 +324,14 @@ struct CommsView: View { }.buttonStyle(.plain) } } - .frame(width: 150) .padding(EdgeInsets(top: -8, leading: -10, bottom: -8, trailing: 0)) if chatOpen != -1 { ScrollViewReader { proxy in ScrollView { VStack(alignment: .leading, spacing: 6) { ForEach(Array(chatMessages.enumerated()), id: \.element) { index, message in + let dontMerge = merge(message: message, previousMessage: index != 0 ? chatMessages[index - 1] : nil) + Spacer(minLength: dontMerge ? 16 : 0) if message.id == unreadId { HStack { VStack { Divider().background(.red) } @@ -144,13 +347,13 @@ struct CommsView: View { }.padding(EdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 0)) } HStack(alignment: .top, spacing: 6) { - if merge(message: message, previousMessage: index != 0 ? chatMessages[index - 1] : nil) { + if dontMerge { ProfilePicture(avatar: message.user?.avatar) } else { Spacer().frame(width: 32) } VStack { - if merge(message: message, previousMessage: index != 0 ? chatMessages[index - 1] : nil) { + if dontMerge { HStack { Text(message.user?.username ?? "User has been deleted") if let date = inputDateFormatter.date(from: message.createdAt) { @@ -276,8 +479,8 @@ struct CommsView: View { }.contentShape(Rectangle()) }.buttonStyle(.plain) } - }.frame(width: 150) - .padding(EdgeInsets(top: -8, leading: -10, bottom: -8, trailing: 0)) + } + .padding(EdgeInsets(top: -8, leading: -10, bottom: -8, trailing: 0)) } else { VStack { Spacer() @@ -288,218 +491,21 @@ struct CommsView: View { } Spacer() } - } - } - .navigationTitle("Comms") - .onAppear { - getChats { result in - switch result { - case .success(let graphQLResult): - if let unwrapped = graphQLResult.data { - chatsList = unwrapped.chats - } - case .failure(let error): - print(error) - } - } - } - #else - List { - ForEach(0 ..< chatsList.count, id: \.self) { result in - Button(action: { getChat(chatId: result) }) { - HStack { - ProfilePicture(avatar: chatsList[result].recipient?.avatar ?? chatsList[result].icon) - Text(chatsList[result].recipient?.username ?? chatsList[result].name).lineLimit(1) - Spacer() - if chatsList[result].unread != 0 { - Text(String(chatsList[result].unread!)) - .frame(minWidth: 16, minHeight: 16) - .background(Color.red) - .cornerRadius(10) - } - }.contentShape(Rectangle()) - }.buttonStyle(.plain) - } - } - .padding(EdgeInsets(top: -8, leading: -10, bottom: -8, trailing: 0)) - if chatOpen != -1 { - ScrollViewReader { proxy in - ScrollView { - VStack(alignment: .leading, spacing: 6) { - ForEach(Array(chatMessages.enumerated()), id: \.element) { index, message in - if message.id == unreadId { - HStack { - VStack { Divider().background(.red) } - Text("New Message").foregroundStyle(.red) - VStack { Divider().background(.red) } - } + .navigationTitle("Comms") + .onAppear { + getChats { result in + switch result { + case .success(let graphQLResult): + if let unwrapped = graphQLResult.data { + chatsList = unwrapped.chats } - if message.reply != nil { - HStack { - Image(systemName: "arrow.turn.up.right").frame(width: 16, height: 16) - Text(message.reply?.user?.username ?? "User has been deleted") - Text((message.reply?.content ?? "Message has been deleted").replacingOccurrences(of: "\n", with: "")).textSelection(.enabled).lineLimit(1) - }.padding(EdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 0)) - } - HStack(alignment: .top, spacing: 6) { - if merge(message: message, previousMessage: index != 0 ? chatMessages[index - 1] : nil) { - ProfilePicture(avatar: message.user?.avatar) - } else { - Spacer().frame(width: 32) - } - VStack { - if merge(message: message, previousMessage: index != 0 ? chatMessages[index - 1] : nil) { - HStack { - Text(message.user?.username ?? "User has been deleted") - if let date = inputDateFormatter.date(from: message.createdAt) { - let formattedDate = outputDateFormatter.string(from: date) - Text(formattedDate) - } else { - Text("Invalid Date") - } - }.frame(minWidth: 0, - maxWidth: .infinity, - minHeight: 0, - maxHeight: 6, - alignment: .topLeading) - } - if editingId != message.id { - Text(.init(message.content ?? "Message has been deleted")) - .textSelection(.enabled) - .frame(minWidth: 0, - maxWidth: .infinity, - minHeight: 0, - maxHeight: .infinity, - alignment: .topLeading) - .lineLimit(nil) - } else { - TextField("Keep it civil!", text: $editingMessage) - .onSubmit { - editMessage() - } - .textFieldStyle(RoundedBorderTextFieldStyle()) - } - ForEach(message.embeds, id: \.self) { embed in - if embed.media != [] { - LazyImage(url: URL(string: embed.media?[0].attachment == nil ? ("https://i.electrics01.com" + (embed.media?[0].proxyUrl ?? "")) : ("https://i.electrics01.com/i/" + (embed.media?[0].attachment ?? "")))) { state in - if let image = state.image { - image - .resizable() - .aspectRatio(contentMode: .fill) - .onAppear { - if chatMessages.count != 0 { - proxy.scrollTo(0, anchor: .bottom) - } - } - } else if state.error != nil { - Color.red - } else { - ProgressView() - } - }.frame(minWidth: 0, maxWidth: 400, minHeight: 0, maxHeight: 400) - } - }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) - } - Button(action: { - if replyingId != message.id { - replyingId = message.id - } else { replyingId = -1 } - }) { - Image(systemName: "arrowshape.turn.up.left.fill").frame(width: 16, height: 16) - } - Button(action: { - replyingId = -1 - if editingId != message.id { - editingId = message.id - editingMessage = message.content ?? "" - } else { editingId = -1 } - }) { - Image(systemName: "pencil").frame(width: 16, height: 16) - } - }.padding(4) - // .background(Color(hoverItem == message.id ? Color.primary : .clear)) - // .onHover(perform: { _ in - // hoverItem = message.id - // }) - }.padding(EdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 12)) - } - .id(0) - .frame( - minWidth: 0, - maxWidth: .infinity, - minHeight: 0, - maxHeight: .infinity, - alignment: .topLeading - ) - .onAppear { - if chatMessages.count != 0 { - proxy.scrollTo(0, anchor: .bottom) + case .failure(let error): + print(error) } } - .onChange(of: chatMessages) { - proxy.scrollTo(0, anchor: .bottom) - } - } - if replyingId != -1 { - HStack { - Image(systemName: "arrow.turn.up.right").frame(width: 16, height: 16) - Text(chatMessages.last(where: { $0.id == replyingId })?.user?.username ?? "User has been deleted") - Text(chatMessages.last(where: { $0.id == replyingId })?.content ?? "Message has been deleted") - .textSelection(.enabled) - .lineLimit(1) - .onAppear { - if chatMessages.count != 0 { - proxy.scrollTo(0, anchor: .bottom) - } - } - }.padding(EdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 0)) - .frame(minWidth: 0, - maxWidth: .infinity, - alignment: .topLeading) - } - TextField("Keep it civil!", text: $inputMessage) - .onSubmit { - sendMessage() - } - .textFieldStyle(RoundedBorderTextFieldStyle()) - } - .navigationTitle(chatsList[chatOpen].recipient?.username ?? chatsList[chatOpen].name) - List { - ForEach(0 ..< chatsList[chatOpen].users.count, id: \.self) { result in - Button(action: { print("Clicked: " + (chatsList[chatOpen].users[result].user?.username ?? "User's name could not be found")) }) { - HStack { - ProfilePicture(avatar: chatsList[chatOpen].users[result].user?.avatar) - Text(chatsList[chatOpen].users[result].user?.username ?? "User's name could not be found") - Spacer() - }.contentShape(Rectangle()) - }.buttonStyle(.plain) - } - } - .padding(EdgeInsets(top: -8, leading: -10, bottom: -8, trailing: 0)) - } else { - VStack { - Spacer() - HStack { - Spacer() - Text("Comms") - Spacer() - } - Spacer() - } - .navigationTitle("Comms") - .onAppear { - getChats { result in - switch result { - case .success(let graphQLResult): - if let unwrapped = graphQLResult.data { - chatsList = unwrapped.chats - } - case .failure(let error): - print(error) - } } } + #endif } - #endif } } diff --git a/TPU Mac/TPU_Mac/ContentView.swift b/TPU Mac/TPU_Mac/ContentView.swift index 43eea19..8483738 100644 --- a/TPU Mac/TPU_Mac/ContentView.swift +++ b/TPU Mac/TPU_Mac/ContentView.swift @@ -25,7 +25,7 @@ let outputDateFormatter: DateFormatter = { }() struct ContentView: View { - @State private var showingLogin = false + @State private var showingLogin = keychain.get("token") == nil || keychain.get("token") == "" @State private var coreState: StateQuery.Data.CoreState? func getState() { @@ -42,47 +42,65 @@ struct ContentView: View { } var body: some View { - NavigationSplitView { - List { - NavigationLink(destination: HomeView(coreState: $coreState)) { - Label("Home", systemImage: "house") + if showingLogin { + LoginSheet(showingLogin: $showingLogin) + } else { + #if os(macOS) + NavigationSplitView { + List { + NavigationLink(destination: HomeView(coreState: $coreState)) { + Label("Home", systemImage: "house") + } + NavigationLink(destination: SettingsView(showingLogin: $showingLogin, coreState: $coreState)) { + Label("Settings", systemImage: "gear") + } + NavigationLink(destination: GalleryView(stars: .constant(false))) { + Label("Gallery", systemImage: "photo.on.rectangle") + } + NavigationLink(destination: GalleryView(stars: .constant(true))) { + Label("Stars", systemImage: "star") + } + NavigationLink(destination: CommsView()) { + Label("Comms", systemImage: "message") + } + NavigationLink(destination: AboutView()) { + Label("About", systemImage: "info.circle") + } + } + } detail: { + HomeView(coreState: $coreState) } - NavigationLink(destination: SettingsView(showingLogin: $showingLogin, coreState: $coreState)) { - Label("Settings", systemImage: "gear") - } - NavigationLink(destination: GalleryView(stars: .constant(false))) { - Label("Gallery", systemImage: "photo.on.rectangle") - } - NavigationLink(destination: GalleryView(stars: .constant(true))) { - Label("Stars", systemImage: "star") - } - NavigationLink(destination: CommsView()) { - Label("Comms", systemImage: "message") + .onAppear { + getState() } - NavigationLink(destination: AboutView()) { - Label("About", systemImage: "info.circle") + #else + TabView { + HomeView(coreState: $coreState).tabItem { + Label("Home", systemImage: "house") + } + GalleryView(stars: .constant(false)).tabItem { + Label("Gallery", systemImage: "photo.on.rectangle") + } + GalleryView(stars: .constant(true)).tabItem { + Label("Stars", systemImage: "star") + } + CommsView().tabItem { + Label("Comms", systemImage: "message") + } + SettingsView(showingLogin: $showingLogin, coreState: $coreState).tabItem { + Label("Settings", systemImage: "gear") + } } - } - .onAppear { - showingLogin = (keychain.get("token") == nil || keychain.get("token") == "") - if !showingLogin { + .onAppear { getState() } - } - } detail: { - HomeView(coreState: $coreState) - .sheet(isPresented: $showingLogin) { - LoginSheet() - } - } - .sheet(isPresented: $showingLogin) { - LoginSheet() + #endif } } } struct LoginSheet: View { - @Environment(\.dismiss) var dismiss + @Binding var showingLogin: Bool @State private var username: String = "" @State private var password: String = "" @State private var totp: String = "" @@ -94,7 +112,7 @@ struct LoginSheet: View { case .success(let graphQLResult): if graphQLResult.errors?[0].message == nil { keychain.set(graphQLResult.data?.login.token ?? "", forKey: "token") - dismiss() + showingLogin = false return } errorMessage = graphQLResult.errors?[0].localizedDescription ?? "Error" @@ -148,7 +166,6 @@ struct LoginSheet: View { .lineLimit(4) .fixedSize(horizontal: false, vertical: true) }.padding() - .interactiveDismissDisabled() } } @@ -157,27 +174,39 @@ struct SettingsView: View { @Binding var coreState: StateQuery.Data.CoreState? var body: some View { - Text("Settings") - Text("Coming soon") + VStack { + Text("Settings") + #if os(macOS) + Text("Coming soon") + #else + Text("TPU iOS").font(.system(size: 32, weight: .semibold)) + Text("Version " + (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "") + " (4/3/2024)") + Text("Made by ElectricS01") + Text("[Give it a Star on GitHub](https://github.com/ElectricS01/TPU-Mac)") + #endif + Button("Log out") { + keychain.delete("token") + showingLogin = true + } .navigationTitle("Settings") - Button("Log out") { - keychain.delete("token") } } } struct AboutView: View { var body: some View { - Text("About") - .navigationTitle("About") - #if os(macOS) - Text("TPU Mac").font(.system(size: 32, weight: .semibold)) - #else - Text("TPU iOS").font(.system(size: 32, weight: .semibold)) - #endif - Text("Version " + (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "") + " (3/3/2024)") - Text("Made by ElectricS01") - Text("[Give it a Star on GitHub](https://github.com/ElectricS01/TPU-Mac)") + VStack { + Text("About") + .navigationTitle("About") + #if os(macOS) + Text("TPU Mac").font(.system(size: 32, weight: .semibold)) + #else + Text("TPU iOS").font(.system(size: 32, weight: .semibold)) + #endif + Text("Version " + (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "") + " (4/3/2024)") + Text("Made by ElectricS01") + Text("[Give it a Star on GitHub](https://github.com/ElectricS01/TPU-Mac)") + } } } diff --git a/TPU Mac/TPU_Mac/GalleryView.swift b/TPU Mac/TPU_Mac/GalleryView.swift index c8506a7..f7e61d0 100644 --- a/TPU Mac/TPU_Mac/GalleryView.swift +++ b/TPU Mac/TPU_Mac/GalleryView.swift @@ -56,165 +56,167 @@ struct GalleryView: View { } var body: some View { - HStack { - TextField("Search the Gallery", text: $inputSearch) - .onSubmit { - currentPage = 1 + VStack { + HStack { + TextField("Search the Gallery", text: $inputSearch) + .onSubmit { + currentPage = 1 + getGallery() + }.textFieldStyle(RoundedBorderTextFieldStyle()) + Toggle(isOn: $showImages) { + Text("Images") + } + .onChange(of: showImages) { getGallery() - }.textFieldStyle(RoundedBorderTextFieldStyle()) - Toggle(isOn: $showImages) { - Text("Images") - } - .onChange(of: showImages) { - getGallery() - } - Toggle(isOn: $showVideos) { - Text("Video") - } - .onChange(of: showVideos) { - getGallery() - } - Toggle(isOn: $showOther) { - Text("Other") - } - .onChange(of: showOther) { - getGallery() - } - }.padding(EdgeInsets(top: 10, leading: 10, bottom: -8, trailing: 10)) - ScrollViewReader { proxy in - ScrollView { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 316))], spacing: 10) { - ForEach(galleryItems, id: \.self) { galleryItem in - VStack(alignment: .leading) { - HStack { - Text(galleryItem.name ?? "Unknown").font(.title2).lineLimit(1) - Spacer() - Image(systemName: galleryItem.starred == nil ? "star" : "star.fill").resizable().frame(width: 16, height: 16) - } - HStack(alignment: .center) { - if galleryItem.type == "image" { - LazyImage(url: URL(string: "https://i.electrics01.com/i/" + galleryItem.attachment)) { state in - if let image = state.image { - image.resizable().aspectRatio(contentMode: .fit) - } else if state.error != nil { - Color.red + } + Toggle(isOn: $showVideos) { + Text("Video") + } + .onChange(of: showVideos) { + getGallery() + } + Toggle(isOn: $showOther) { + Text("Other") + } + .onChange(of: showOther) { + getGallery() + } + }.padding(EdgeInsets(top: 10, leading: 10, bottom: -8, trailing: 10)) + ScrollViewReader { proxy in + ScrollView { + LazyVGrid(columns: [GridItem(.adaptive(minimum: 316))], spacing: 10) { + ForEach(galleryItems, id: \.self) { galleryItem in + VStack(alignment: .leading) { + HStack { + Text(galleryItem.name ?? "Unknown").font(.title2).lineLimit(1) + Spacer() + Image(systemName: galleryItem.starred == nil ? "star" : "star.fill").resizable().frame(width: 16, height: 16) + } + HStack(alignment: .center) { + if galleryItem.type == "image" { + LazyImage(url: URL(string: "https://i.electrics01.com/i/" + galleryItem.attachment)) { state in + if let image = state.image { + image.resizable().aspectRatio(contentMode: .fit) + } else if state.error != nil { + Color.red + } else { + ProgressView() + } + }.frame(minWidth: 268, maxWidth: .infinity, minHeight: 160, maxHeight: 160) + } else if galleryItem.type == "video" { + if isPlaying != galleryItem.id { + Button(action: { + isPlaying = galleryItem.id + }) { + Image(systemName: "play.circle.fill") + .resizable() + .frame(width: 160, height: 160) + .foregroundColor(.white) + } } else { - ProgressView() + let player = AVPlayer(url: URL(string: "https://i.electrics01.com/i/" + galleryItem.attachment)!) + VideoPlayer(player: player) + .onAppear { + player.play() + } } - }.frame(minWidth: 268, maxWidth: .infinity, minHeight: 160, maxHeight: 160) - } else if galleryItem.type == "video" { - if isPlaying != galleryItem.id { - Button(action: { - isPlaying = galleryItem.id - }) { - Image(systemName: "play.circle.fill") - .resizable() - .frame(width: 160, height: 160) - .foregroundColor(.white) - } - } else { - let player = AVPlayer(url: URL(string: "https://i.electrics01.com/i/" + galleryItem.attachment)!) - VideoPlayer(player: player) - .onAppear { - player.play() - } + } else if galleryItem.type == "binary" { + Image(systemName: "doc.zipper").resizable().aspectRatio(contentMode: .fit).frame(width: 84, height: 84).font(.largeTitle).padding(38) + } else if galleryItem.type == "text" { + Image(systemName: "doc.plaintext").resizable().aspectRatio(contentMode: .fit).frame(width: 84, height: 84).font(.largeTitle).padding(38) } - } else if galleryItem.type == "binary" { - Image(systemName: "doc.zipper").resizable().aspectRatio(contentMode: .fit).frame(width: 84, height: 84).font(.largeTitle).padding(38) - } else if galleryItem.type == "text" { - Image(systemName: "doc.plaintext").resizable().aspectRatio(contentMode: .fit).frame(width: 84, height: 84).font(.largeTitle).padding(38) } - } - .frame( - minWidth: 0, - maxWidth: .infinity - ) - Text("Type: " + (galleryItem.type)) - Text("Upload name: " + (galleryItem.attachment)) - if let date = inputDateFormatter.date(from: galleryItem.createdAt) { - let formattedDate = outputDateFormatter.string(from: date) - Text("Created at: " + formattedDate) - } else { - Text("Created at: Invalid Date") - } - Text("Size: " + formatFileSize(galleryItem.fileSize)) - HStack { - Button("Copy Link") { - #if os(iOS) - UIPasteboard.general.setValue("https://i.electrics01.com/i/" + galleryItem.attachment, - forPasteboardType: UTType.plainText.identifier) - #elseif os(macOS) - NSPasteboard.general.clearContents(); NSPasteboard.general.setString("https://i.electrics01.com/i/" + galleryItem.attachment, forType: .string) - #endif - } - Button("Open image") { - openURL(URL(string: "https://i.electrics01.com/i/" + galleryItem.attachment)!) + .frame( + minWidth: 0, + maxWidth: .infinity + ) + Text("Type: " + (galleryItem.type)) + Text("Upload name: " + (galleryItem.attachment)) + if let date = inputDateFormatter.date(from: galleryItem.createdAt) { + let formattedDate = outputDateFormatter.string(from: date) + Text("Created at: " + formattedDate) + } else { + Text("Created at: Invalid Date") } - Button("Download") { - let downloadTask = URLSession.shared.downloadTask(with: URL(string: "https://i.electrics01.com/i/" + galleryItem.attachment)!) { location, _, error in - guard let location = location else { - if let error = error { - print("Download failed with error: \(error.localizedDescription)") + Text("Size: " + formatFileSize(galleryItem.fileSize)) + HStack { + Button("Copy Link") { + #if os(iOS) + UIPasteboard.general.setValue("https://i.electrics01.com/i/" + galleryItem.attachment, + forPasteboardType: UTType.plainText.identifier) + #elseif os(macOS) + NSPasteboard.general.clearContents(); NSPasteboard.general.setString("https://i.electrics01.com/i/" + galleryItem.attachment, forType: .string) + #endif + } + Button("Open image") { + openURL(URL(string: "https://i.electrics01.com/i/" + galleryItem.attachment)!) + } + Button("Download") { + let downloadTask = URLSession.shared.downloadTask(with: URL(string: "https://i.electrics01.com/i/" + galleryItem.attachment)!) { location, _, error in + guard let location = location else { + if let error = error { + print("Download failed with error: \(error.localizedDescription)") + } + return + } + do { + let documentsDirectory = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first! + let destinationURL = documentsDirectory.appendingPathComponent(galleryItem.attachment) + try FileManager.default.moveItem(at: location, to: destinationURL) + print("File downloaded successfully and moved to \(destinationURL)") + } catch { + print("Error moving file: \(error.localizedDescription)") } - return - } - do { - let documentsDirectory = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first! - let destinationURL = documentsDirectory.appendingPathComponent(galleryItem.attachment) - try FileManager.default.moveItem(at: location, to: destinationURL) - print("File downloaded successfully and moved to \(destinationURL)") - } catch { - print("Error moving file: \(error.localizedDescription)") } + downloadTask.resume() } - downloadTask.resume() - } - Button("Delete") { - Network.shared.apollo.perform(mutation: DeleteUploadsMutation(input: DeleteUploadInput(items: [Double(galleryItem.id)]))) { result in - switch result { - case .success: - galleryItems.remove(at: galleryItems.firstIndex(of: galleryItem)!) - case .failure(let error): - print("Failure! Error: \(error)") + Button("Delete") { + Network.shared.apollo.perform(mutation: DeleteUploadsMutation(input: DeleteUploadInput(items: [Double(galleryItem.id)]))) { result in + switch result { + case .success: + galleryItems.remove(at: galleryItems.firstIndex(of: galleryItem)!) + case .failure(let error): + print("Failure! Error: \(error)") + } } } } } + .padding() + .frame(minWidth: 300, minHeight: 300) + .background() + .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous)) } - .padding() - .frame(minWidth: 300, minHeight: 300) - .background() - .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous)) - } - } - .id(0) - .padding(EdgeInsets(top: 10, leading: 10, bottom: 0, trailing: 10)) - HStack { - Text("Pages: " + String(galleryData?.pager.totalPages ?? 0)) - Button("Last Page") { - currentPage -= 1 - getGallery() } - .disabled(currentPage < 2) - Button("Next Page") { - currentPage += 1 - getGallery() + .id(0) + .padding(EdgeInsets(top: 10, leading: 10, bottom: 0, trailing: 10)) + HStack { + Text("Pages: " + String(galleryData?.pager.totalPages ?? 0)) + Button("Last Page") { + currentPage -= 1 + getGallery() + } + .disabled(currentPage < 2) + Button("Next Page") { + currentPage += 1 + getGallery() + } + .disabled(currentPage >= galleryData?.pager.totalPages ?? 0) + Text("Page: " + String(currentPage)) } - .disabled(currentPage >= galleryData?.pager.totalPages ?? 0) - Text("Page: " + String(currentPage)) + .padding(EdgeInsets(top: 0, leading: 0, bottom: 10, trailing: 0)) } - .padding(EdgeInsets(top: 0, leading: 0, bottom: 10, trailing: 0)) - } - .navigationTitle("Gallery") - .onAppear { - getGallery() - } - .onChange(of: stars) { - currentPage = 1 - inputSearch = "" - getGallery() - if galleryItems.count != 0 { - proxy.scrollTo(0, anchor: .top) + .navigationTitle("Gallery") + .onAppear { + getGallery() + } + .onChange(of: stars) { + currentPage = 1 + inputSearch = "" + getGallery() + if galleryItems.count != 0 { + proxy.scrollTo(0, anchor: .top) + } } } }