mirror of
https://github.com/cranci1/Sora.git
synced 2026-04-21 08:32:00 +00:00
This commit is contained in:
parent
748cc4e999
commit
59b5f84077
3 changed files with 201 additions and 0 deletions
|
|
@ -0,0 +1,194 @@
|
||||||
|
//
|
||||||
|
// SettingsViewTrackers.swift
|
||||||
|
// Sora
|
||||||
|
//
|
||||||
|
// Created by Francesco on 23/03/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Security
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct SettingsViewTrackers: View {
|
||||||
|
@State private var status: String = "You are not logged in"
|
||||||
|
@State private var isLoggedIn: Bool = false
|
||||||
|
@State private var username: String = ""
|
||||||
|
@State private var isLoading: Bool = false
|
||||||
|
@State private var profileColor: Color = .primary
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Form {
|
||||||
|
Section(header: Text("AniList"), footer: Text("Sora and cranci1 are not affiliated with AniList in any way.")) {
|
||||||
|
HStack() {
|
||||||
|
KFImage(URL(string: "https://raw.githubusercontent.com/cranci1/Ryu/2f10226aa087154974a70c1ec78aa83a47daced9/Ryu/Assets.xcassets/Listing/Anilist.imageset/anilist.png"))
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 80, height: 80)
|
||||||
|
.clipShape(Rectangle())
|
||||||
|
Text("AniList.co")
|
||||||
|
.font(.title2)
|
||||||
|
}
|
||||||
|
if isLoading {
|
||||||
|
ProgressView()
|
||||||
|
} else {
|
||||||
|
if isLoggedIn {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text("Logged in as ")
|
||||||
|
Text(username)
|
||||||
|
.foregroundColor(profileColor)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(status)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button(isLoggedIn ? "Log Out from AniList.co" : "Log In with AniList.co") {
|
||||||
|
if isLoggedIn {
|
||||||
|
logout()
|
||||||
|
} else {
|
||||||
|
login()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Trackers")
|
||||||
|
.onAppear {
|
||||||
|
updateStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func login() {
|
||||||
|
status = "Starting authentication..."
|
||||||
|
AniListLogin.authenticate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func logout() {
|
||||||
|
removeTokenFromKeychain()
|
||||||
|
status = "You are not logged in"
|
||||||
|
isLoggedIn = false
|
||||||
|
username = ""
|
||||||
|
profileColor = .primary
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStatus() {
|
||||||
|
if let token = getTokenFromKeychain() {
|
||||||
|
isLoggedIn = true
|
||||||
|
fetchUserInfo(token: token)
|
||||||
|
} else {
|
||||||
|
isLoggedIn = false
|
||||||
|
status = "You are not logged in"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchUserInfo(token: String) {
|
||||||
|
isLoading = true
|
||||||
|
let userInfoURL = URL(string: "https://graphql.anilist.co")!
|
||||||
|
var request = URLRequest(url: userInfoURL)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||||
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
|
||||||
|
let query = """
|
||||||
|
{
|
||||||
|
Viewer {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
options {
|
||||||
|
profileColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
let body: [String: Any] = ["query": query]
|
||||||
|
|
||||||
|
do {
|
||||||
|
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
|
||||||
|
} catch {
|
||||||
|
status = "Failed to serialize request"
|
||||||
|
Logger.shared.log("Failed to serialize request", type: "Error")
|
||||||
|
isLoading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
isLoading = false
|
||||||
|
if let error = error {
|
||||||
|
status = "Error: \(error.localizedDescription)"
|
||||||
|
Logger.shared.log("Error: \(error.localizedDescription)", type: "Error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let data = data else {
|
||||||
|
status = "No data received"
|
||||||
|
Logger.shared.log("No data received", type: "Error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
||||||
|
let dataDict = json["data"] as? [String: Any],
|
||||||
|
let viewer = dataDict["Viewer"] as? [String: Any],
|
||||||
|
let name = viewer["name"] as? String,
|
||||||
|
let options = viewer["options"] as? [String: Any],
|
||||||
|
let colorName = options["profileColor"] as? String {
|
||||||
|
|
||||||
|
username = name
|
||||||
|
profileColor = colorFromName(colorName)
|
||||||
|
status = "Logged in as \(name)"
|
||||||
|
} else {
|
||||||
|
status = "Unexpected response format!"
|
||||||
|
Logger.shared.log("Unexpected response format!", type: "Error")
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
status = "Failed to parse response: \(error.localizedDescription)"
|
||||||
|
Logger.shared.log("Failed to parse response: \(error.localizedDescription)", type: "Error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorFromName(_ name: String) -> Color {
|
||||||
|
switch name.lowercased() {
|
||||||
|
case "blue":
|
||||||
|
return .blue
|
||||||
|
case "purple":
|
||||||
|
return .purple
|
||||||
|
case "green":
|
||||||
|
return .green
|
||||||
|
case "orange":
|
||||||
|
return .orange
|
||||||
|
case "red":
|
||||||
|
return .red
|
||||||
|
case "pink":
|
||||||
|
return .pink
|
||||||
|
case "gray":
|
||||||
|
return .gray
|
||||||
|
default:
|
||||||
|
return .accentColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTokenFromKeychain() -> String? {
|
||||||
|
let query: [String: Any] = [
|
||||||
|
kSecClass as String: kSecClassGenericPassword,
|
||||||
|
kSecAttrService as String: "me.cranci.sora.AniListToken",
|
||||||
|
kSecAttrAccount as String: "AniListAccessToken",
|
||||||
|
kSecReturnData as String: kCFBooleanTrue!,
|
||||||
|
kSecMatchLimit as String: kSecMatchLimitOne
|
||||||
|
]
|
||||||
|
var item: CFTypeRef?
|
||||||
|
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||||
|
guard status == errSecSuccess, let tokenData = item as? Data else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return String(data: tokenData, encoding: .utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTokenFromKeychain() {
|
||||||
|
let deleteQuery: [String: Any] = [
|
||||||
|
kSecClass as String: kSecClassGenericPassword,
|
||||||
|
kSecAttrService as String: "me.cranci.sora.AniListToken",
|
||||||
|
kSecAttrAccount as String: "AniListAccessToken"
|
||||||
|
]
|
||||||
|
SecItemDelete(deleteQuery as CFDictionary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,9 @@ struct SettingsView: View {
|
||||||
NavigationLink(destination: SettingsViewModule()) {
|
NavigationLink(destination: SettingsViewModule()) {
|
||||||
Text("Modules")
|
Text("Modules")
|
||||||
}
|
}
|
||||||
|
NavigationLink(destination: SettingsViewTrackers()) {
|
||||||
|
Text("Trackers")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Info")) {
|
Section(header: Text("Info")) {
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@
|
||||||
13DB468D2D90093A008CBC03 /* Anilist-Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB468B2D900939008CBC03 /* Anilist-Login.swift */; };
|
13DB468D2D90093A008CBC03 /* Anilist-Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB468B2D900939008CBC03 /* Anilist-Login.swift */; };
|
||||||
13DB468E2D90093A008CBC03 /* Anilist-Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB468C2D90093A008CBC03 /* Anilist-Token.swift */; };
|
13DB468E2D90093A008CBC03 /* Anilist-Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB468C2D90093A008CBC03 /* Anilist-Token.swift */; };
|
||||||
13DB46902D900A38008CBC03 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB468F2D900A38008CBC03 /* URL.swift */; };
|
13DB46902D900A38008CBC03 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB468F2D900A38008CBC03 /* URL.swift */; };
|
||||||
|
13DB46922D900BCE008CBC03 /* SettingsViewTrackers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB46912D900BCE008CBC03 /* SettingsViewTrackers.swift */; };
|
||||||
13DB7CC32D7D99C0004371D3 /* SubtitleSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB7CC22D7D99C0004371D3 /* SubtitleSettingsManager.swift */; };
|
13DB7CC32D7D99C0004371D3 /* SubtitleSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB7CC22D7D99C0004371D3 /* SubtitleSettingsManager.swift */; };
|
||||||
13DB7CC62D7DC7D2004371D3 /* FFmpeg-iOS-Lame in Frameworks */ = {isa = PBXBuildFile; productRef = 13DB7CC52D7DC7D2004371D3 /* FFmpeg-iOS-Lame */; };
|
13DB7CC62D7DC7D2004371D3 /* FFmpeg-iOS-Lame in Frameworks */ = {isa = PBXBuildFile; productRef = 13DB7CC52D7DC7D2004371D3 /* FFmpeg-iOS-Lame */; };
|
||||||
13DB7CEC2D7DED5D004371D3 /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB7CEB2D7DED5D004371D3 /* DownloadManager.swift */; };
|
13DB7CEC2D7DED5D004371D3 /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13DB7CEB2D7DED5D004371D3 /* DownloadManager.swift */; };
|
||||||
|
|
@ -108,6 +109,7 @@
|
||||||
13DB468B2D900939008CBC03 /* Anilist-Login.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Anilist-Login.swift"; sourceTree = "<group>"; };
|
13DB468B2D900939008CBC03 /* Anilist-Login.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Anilist-Login.swift"; sourceTree = "<group>"; };
|
||||||
13DB468C2D90093A008CBC03 /* Anilist-Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Anilist-Token.swift"; sourceTree = "<group>"; };
|
13DB468C2D90093A008CBC03 /* Anilist-Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Anilist-Token.swift"; sourceTree = "<group>"; };
|
||||||
13DB468F2D900A38008CBC03 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
|
13DB468F2D900A38008CBC03 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
|
||||||
|
13DB46912D900BCE008CBC03 /* SettingsViewTrackers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewTrackers.swift; sourceTree = "<group>"; };
|
||||||
13DB7CC22D7D99C0004371D3 /* SubtitleSettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSettingsManager.swift; sourceTree = "<group>"; };
|
13DB7CC22D7D99C0004371D3 /* SubtitleSettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSettingsManager.swift; sourceTree = "<group>"; };
|
||||||
13DB7CEB2D7DED5D004371D3 /* DownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = "<group>"; };
|
13DB7CEB2D7DED5D004371D3 /* DownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = "<group>"; };
|
||||||
13DC0C412D2EC9BA00D0F966 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
13DC0C412D2EC9BA00D0F966 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
|
@ -274,6 +276,7 @@
|
||||||
131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */,
|
131845F82D47C62D00CA7A54 /* SettingsViewGeneral.swift */,
|
||||||
135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */,
|
135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */,
|
||||||
130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */,
|
130C6BF92D53AB1F00DC1432 /* SettingsViewData.swift */,
|
||||||
|
13DB46912D900BCE008CBC03 /* SettingsViewTrackers.swift */,
|
||||||
);
|
);
|
||||||
path = SettingsSubViews;
|
path = SettingsSubViews;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -561,6 +564,7 @@
|
||||||
73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */,
|
73D164D52D8B5B470011A360 /* JavaScriptCore+Extensions.swift in Sources */,
|
||||||
13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */,
|
13EA2BD52D32D97400C1EBD7 /* CustomPlayer.swift in Sources */,
|
||||||
1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */,
|
1399FAD42D3AB38C00E97C31 /* SettingsViewLogger.swift in Sources */,
|
||||||
|
13DB46922D900BCE008CBC03 /* SettingsViewTrackers.swift in Sources */,
|
||||||
13C0E5EA2D5F85EA00E7F619 /* ContinueWatchingManager.swift in Sources */,
|
13C0E5EA2D5F85EA00E7F619 /* ContinueWatchingManager.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue