diff --git a/AnimeGen.xcodeproj/project.pbxproj b/AnimeGen.xcodeproj/project.pbxproj index 75b68c54..0ad769e3 100644 --- a/AnimeGen.xcodeproj/project.pbxproj +++ b/AnimeGen.xcodeproj/project.pbxproj @@ -529,7 +529,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 41; + CURRENT_PROJECT_VERSION = 51; DEVELOPMENT_TEAM = 399LMK6Q2Y; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = AnimeGen/Info.plist; @@ -560,7 +560,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 41; + CURRENT_PROJECT_VERSION = 51; DEVELOPMENT_TEAM = 399LMK6Q2Y; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = AnimeGen/Info.plist; diff --git a/AnimeGen.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate b/AnimeGen.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate index da13ea8d..a0a2210c 100644 Binary files a/AnimeGen.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate and b/AnimeGen.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/AnimeGen/Buttons func/HeartButton.swift b/AnimeGen/Buttons func/HeartButton.swift index 9a69272e..2c67cb8b 100644 --- a/AnimeGen/Buttons func/HeartButton.swift +++ b/AnimeGen/Buttons func/HeartButton.swift @@ -13,6 +13,7 @@ extension ViewController { @IBAction func heartButtonTapped() { guard let image = imageView.image else { + print("No image found in imageView") return } @@ -21,27 +22,25 @@ extension ViewController { let utType = CGImageSourceGetType(source), UTTypeConformsTo(utType, kUTTypeGIF) { - PHPhotoLibrary.shared().performChanges { + PHPhotoLibrary.shared().performChanges({ let creationRequest = PHAssetCreationRequest.forAsset() creationRequest.addResource(with: .photo, data: data, options: nil) - } completionHandler: { (success, error) in - if success { - print("GIF image saved to Photos library") - self.animateFeedback() - } else { - print("Error saving GIF image: \(error?.localizedDescription ?? "")") - self.showAlert(withTitle: "Error Saving Image!", message: "You didn't allow AnimeGen to access the Photo Library.", viewController: self) + }) { (success, error) in + DispatchQueue.main.async { + if success { + print("GIF image saved to Photos library") + self.animateFeedback() + } else { + self.handleSaveError(error, isGIF: true) + } } } return } if let imageData = image.jpegData(compressionQuality: 1.0), - let uiImage = UIImage(data: imageData) { - - DispatchQueue.main.async { - UIImageWriteToSavedPhotosAlbum(uiImage, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil) - } + let uiImage = UIImage(data: imageData) { + UIImageWriteToSavedPhotosAlbum(uiImage, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil) } else { print("Error converting image to JPEG format") } @@ -50,10 +49,17 @@ extension ViewController { @objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) { if let error = error { print("Error saving image: \(error.localizedDescription)") - self.showAlert(withTitle: "Error Saving Image!", message: "You didn't allow AnimeGen to access the Photo Library.", viewController: self) + showAlert(withTitle: "Error Saving Image!", message: "You didn't allow AnimeGen to access the Photo Library.", viewController: self) } else { - self.animateFeedback() + animateFeedback() print("Image saved successfully") } } + + private func handleSaveError(_ error: Error?, isGIF: Bool) { + let imageType = isGIF ? "GIF" : "image" + let errorMessage = error?.localizedDescription ?? "Unknown error" + print("Error saving \(imageType): \(errorMessage)") + showAlert(withTitle: "Error Saving \(imageType.capitalized)!", message: "You didn't allow AnimeGen to access the Photo Library.", viewController: self) + } } diff --git a/AnimeGen/Settings/APIsValid.swift b/AnimeGen/Settings/APIsValid.swift index a7198903..364e1aa1 100644 --- a/AnimeGen/Settings/APIsValid.swift +++ b/AnimeGen/Settings/APIsValid.swift @@ -11,64 +11,119 @@ struct APIData: Codable { var supported: [String: Bool] } -struct APIsSuppport: View { +struct APIsSupport: View { @State private var apiData: APIData? = nil + @State private var isLoading: Bool = false + @State private var fetchError: Bool = false var body: some View { - VStack { - List { - ForEach(apiData?.supported.sorted(by: { $0.key < $1.key }) ?? [], id: \.key) { (key, value) in - HStack { - Text(key) - Spacer() - if value { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.green) - } else { - Image(systemName: "xmark.circle.fill") - .foregroundColor(.red) + NavigationView { + VStack { + if isLoading { + if #available(iOS 14.0, *) { + ProgressView("Loading...") + .progressViewStyle(CircularProgressViewStyle()) + .padding() + } else { + ActivityIndicator(isAnimating: $isLoading, style: .large) + .padding() + } + } else if fetchError { + Text("Failed to load data. Please try again.") + .foregroundColor(.red) + .padding() + } else { + if #available(iOS 14.0, *) { + LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) { + ForEach(apiData?.supported.sorted(by: { $0.key < $1.key }) ?? [], id: \.key) { (key, value) in + VStack { + Text(key) + Spacer() + if value { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + } else { + Image(systemName: "xmark.circle.fill") + .foregroundColor(.red) + } + } + .padding() + .background(Color(.systemGray6)) + .cornerRadius(12) + .shadow(radius: 4) + } + } + .padding() + } else { + ScrollView { + VStack(spacing: 16) { + ForEach(0..<(apiData?.supported.count ?? 0) / 2 + 1, id: \.self) { rowIndex in + HStack(spacing: 16) { + ForEach(0..<2) { columnIndex in + let index = rowIndex * 2 + columnIndex + if index < (apiData?.supported.count ?? 0) { + let api = apiData!.supported.sorted(by: { $0.key < $1.key })[index] + VStack { + Text(api.key) + Spacer() + if api.value { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + } else { + Image(systemName: "xmark.circle.fill") + .foregroundColor(.red) + } + } + .padding() + .background(Color(.systemGray6)) + .cornerRadius(12) + .shadow(radius: 4) + } else { + Spacer() + } + } + } + } + } + .padding() } } } - } - .overlay( - Text("Support status will not be updated instantly, but rather promptly after the APIs resume operation.") - .font(.footnote) - .foregroundColor(.gray) - .multilineTextAlignment(.center) - .padding(.all, 18) - .frame(maxWidth: .infinity, alignment: .center) - .background(Color.secondary.opacity(0.1)), - alignment: .bottom - ) - Button(action: { - withAnimation { - fetchData() - } - }) { - HStack { - Image(systemName: "arrow.clockwise.circle.fill") - .foregroundColor(.white) - .font(.title) - Text("Refresh") - .foregroundColor(.white) - .font(.headline) + + Button(action: { + withAnimation { + fetchData() + } + }) { + HStack { + Image(systemName: "arrow.clockwise.circle.fill") + .foregroundColor(.white) + .font(.title) + Text("Refresh") + .foregroundColor(.white) + .font(.headline) + } + .padding() + .background(Color("AccentColor")) + .cornerRadius(10) } .padding() - .background(Color("AccentColor")) - .cornerRadius(10) } - .padding() - } - .onAppear { - fetchData() + .onAppear { + fetchData() + } + .navigationBarTitle("APIs Status", displayMode: .inline) } - .navigationBarTitle("APIs Status") } private func fetchData() { + isLoading = true + fetchError = false + guard let url = URL(string: "https://raw.githubusercontent.com/cranci1/cranci.xyz-Astro/master/public/ValidAPI.json") else { print("Invalid URL") + fetchError = true + isLoading = false return } @@ -77,6 +132,10 @@ struct APIsSuppport: View { if let error = error { print("Error fetching data: \(error)") } + DispatchQueue.main.async { + self.fetchError = true + self.isLoading = false + } return } @@ -91,18 +150,36 @@ struct APIsSuppport: View { DispatchQueue.main.async { self.apiData = decodedData + self.isLoading = false } } catch { print("Error decoding JSON: \(error)") + DispatchQueue.main.async { + self.fetchError = true + self.isLoading = false + } } }.resume() } } +struct ActivityIndicator: UIViewRepresentable { + @Binding var isAnimating: Bool + let style: UIActivityIndicatorView.Style + + func makeUIView(context: UIViewRepresentableContext) -> UIActivityIndicatorView { + return UIActivityIndicatorView(style: style) + } + + func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext) { + isAnimating ? uiView.startAnimating() : uiView.stopAnimating() + } +} + #if DEBUG struct ContentView_Previews: PreviewProvider { static var previews: some View { - APIsSuppport() + APIsSupport() } } #endif diff --git a/AnimeGen/Settings/SettingsMain.swift b/AnimeGen/Settings/SettingsMain.swift index 01dd7213..50122625 100644 --- a/AnimeGen/Settings/SettingsMain.swift +++ b/AnimeGen/Settings/SettingsMain.swift @@ -17,8 +17,8 @@ class SettingsMain: UITableViewController { @IBAction func APIsStatus(_ sender: UITapGestureRecognizer) { DispatchQueue.main.async { [weak self] in guard let self = self, let navigationController = self.navigationController else { return } - if !(navigationController.topViewController is UIHostingController) { - let swiftUIView = APIsSuppport() + if !(navigationController.topViewController is UIHostingController) { + let swiftUIView = APIsSupport() let hostingController = UIHostingController(rootView: swiftUIView) navigationController.pushViewController(hostingController, animated: true) }