From 6bc14d88b3294cfc4dbf71b550905be3a4097021 Mon Sep 17 00:00:00 2001 From: WindowsMEMZ Date: Tue, 13 Feb 2024 19:26:18 +0800 Subject: [PATCH] Updated MeowBili features, added danmaku sending feature --- DarockBili.xcodeproj/project.pbxproj | 16 +- Localizable.xcstrings | 497 ++++++++++++++----------- MeowBili/Errors/FeedbackView.swift | 14 +- MeowBili/Extension/UIExt.swift | 50 +++ MeowBili/GlobalView/CommentsView.swift | 259 ++++++------- MeowBili/InMain/SearchView.swift | 1 + MeowBili/Video/VideoDetailView.swift | 63 +++- MeowBili/Video/VideoPlayerView.swift | 14 +- 8 files changed, 531 insertions(+), 383 deletions(-) diff --git a/DarockBili.xcodeproj/project.pbxproj b/DarockBili.xcodeproj/project.pbxproj index 8e7cca04f..07ea3a60a 100644 --- a/DarockBili.xcodeproj/project.pbxproj +++ b/DarockBili.xcodeproj/project.pbxproj @@ -1371,7 +1371,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBili.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 500; + CURRENT_PROJECT_VERSION = 521; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1416,7 +1416,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = MeowBili/MeowBili.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 500; + CURRENT_PROJECT_VERSION = 521; DEVELOPMENT_ASSET_PATHS = "\"MeowBili/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1458,7 +1458,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 500; + CURRENT_PROJECT_VERSION = 521; DEVELOPMENT_ASSET_PATHS = "\"DarockBili Watch App/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1490,7 +1490,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 500; + CURRENT_PROJECT_VERSION = 521; DEVELOPMENT_ASSET_PATHS = "\"DarockBili Watch App/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1643,7 +1643,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 500; + CURRENT_PROJECT_VERSION = 521; DEVELOPMENT_ASSET_PATHS = "\"DarockBili Watch App/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1675,7 +1675,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 500; + CURRENT_PROJECT_VERSION = 521; DEVELOPMENT_ASSET_PATHS = "\"DarockBili Watch App/Preview Content\""; DEVELOPMENT_TEAM = B57D8PP775; ENABLE_PREVIEWS = YES; @@ -1707,7 +1707,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 500; + CURRENT_PROJECT_VERSION = 521; DEVELOPMENT_TEAM = B57D8PP775; INFOPLIST_KEY_CFBundleDisplayName = DarockBili; MARKETING_VERSION = 1.0.0; @@ -1723,7 +1723,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 500; + CURRENT_PROJECT_VERSION = 521; DEVELOPMENT_TEAM = B57D8PP775; INFOPLIST_KEY_CFBundleDisplayName = DarockBili; MARKETING_VERSION = 1.0.0; diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 67762cbca..cd896dca3 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -2163,6 +2163,154 @@ } } } + }, + "login.captchasucc" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Captcha Completed" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "人机验证已完成" + } + } + } + }, + "login.code" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verification Code" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "验证码" + } + } + } + }, + "login.codesent" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verification Code Sent" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "验证码已发送" + } + } + } + }, + "login.first" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "First: Phone Info" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "第一步: 手机号信息" + } + } + } + }, + "login.getcode" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Get Code" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "获取验证码" + } + } + } + }, + "login.goonver" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start Captcha" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "进行人机验证" + } + } + } + }, + "login.login" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Login" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "登录" + } + } + } + }, + "login.phonearea" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Phone Area Code" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "国际冠码" + } + } + } + }, + "login.phonemunber" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Phone Number" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "手机号" + } + } + } + }, + "login.phonenumber" : { + }, "Login.scan" : { "localizations" : { @@ -2196,6 +2344,38 @@ } } }, + "login.second" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Second: Captcha" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "第二步: 人机验证" + } + } + } + }, + "login.third" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Third: Verification Code" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "第三步: 验证码" + } + } + } + }, "LongUIDUserTest" : { "localizations" : { "en" : { @@ -2422,6 +2602,54 @@ } } }, + "navbar.dynamic" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Moments" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "动态" + } + } + } + }, + "navbar.my" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "My" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "我的" + } + } + } + }, + "navbar.suggest" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Home" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "推荐" + } + } + } + }, "Player.analyzying-source" : { "localizations" : { "en" : { @@ -4739,23 +4967,6 @@ } } }, - "login.captchasucc" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Captcha Completed" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "人机验证已完成" - } - } - } - - }, "令枫" : { "localizations" : { "en" : { @@ -4772,23 +4983,6 @@ } } }, - "navbar.dynamic" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Moments" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "动态" - } - } - } - - }, "动态内容" : { }, @@ -4807,24 +5001,10 @@ "发送动态" : { }, - "可选, 最多9个" : { + "发送弹幕" : { }, - "login.phonearea" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Phone Area Code" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "国际冠码" - } - } - } + "可选, 最多9个" : { }, "在使用本 App 前,您需要先知晓以下信息:\n· 本 App 由第三方开发者以及部分社区用户贡献,与哔哩哔哩无合作关系,哔哩哔哩是上海宽娱数码科技有限公司的商标。\n· 本 App 并不是哔哩哔哩的替代品,我们建议您在能够使用官方客户端时尽量使用官方客户端。\n· 本 App 均使用来源于网络的公开信息进行开发。\n· 本 App 中和B站相关的功能完全免费\n· 本 App 中所呈现的B站内容来自哔哩哔哩官方。\n· 本 App 的开发者、负责人和实际责任人是%@\n 联系QQ:3245146430" : { @@ -4842,9 +5022,21 @@ } } } + }, + "复制" : { + + }, + "大" : { + + }, + "字号" : { + }, "将作为动态主体" : { + }, + "小" : { + }, "小尾巴内容" : { @@ -4854,6 +5046,9 @@ }, "应用" : { + }, + "底部" : { + }, "开启“屏幕使用时间”" : { @@ -4864,82 +5059,30 @@ "您可以更改默认的小尾巴内容, 如果不想添加小尾巴, 请清空上方文本框内容. 您可以随时在设置中更改此内容" : { }, - "navbar.my" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "My" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "我的" - } - } - } + "我的收藏" : { }, - "login.phonemunber" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Phone Number" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "手机号" - } - } - } + "推荐" : { + }, - "我的收藏" : { + "普通" : { }, + "极大" : { - "navbar.suggest" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Home" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "推荐" - } - } - } + }, + "极小" : { }, + "标准" : { - "login.login" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Login" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "登录" - } - } - } }, + "模式" : { + }, "清除所有已观看视频" : { }, - "离线缓存" : { }, @@ -4949,78 +5092,9 @@ "稿件" : { }, - "login.first" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "First: Phone Info" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "第一步: 手机号信息" - } - } - } - - }, - "login.third" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Third: Verification Code" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "第三步: 验证码" - } - } - } - - }, - "login.second" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Second: Captcha" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "第二步: 人机验证" - } - } - } - - }, - "login.getcode" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Get Code" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "获取验证码" - } - } - } - }, - "经验" : { }, - "要使用动态小尾巴吗?" : { }, @@ -5029,64 +5103,33 @@ }, "评论" : { + }, + "评论回复" : { + }, "详情" : { }, - "login.goonver" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Start Captcha" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "进行人机验证" - } - } - } + "超大" : { + + }, + "超小" : { }, "选择图片" : { + }, + "选择文本" : { + }, "重新载入" : { }, - "login.code" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verification Code" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "验证码" - } - } - } + "顶部" : { + }, - "login.codesent" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verification Code Sent" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "验证码已发送" - } - } - } + "颜色" : { + }, "" : { @@ -5114,4 +5157,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/MeowBili/Errors/FeedbackView.swift b/MeowBili/Errors/FeedbackView.swift index e1739510b..a40a90a66 100644 --- a/MeowBili/Errors/FeedbackView.swift +++ b/MeowBili/Errors/FeedbackView.swift @@ -80,21 +80,9 @@ import EFQRCode //} struct FeedbackView: View { - @State var qrImage: CGImage? var body: some View { VStack { - if qrImage != nil { - Image(uiImage: UIImage(cgImage: qrImage!)) - .resizable() - .frame(width: 140, height: 140) - Text("Feedback.continue-on-other-device") - .bold() - } - } - .onAppear { - if let image = EFQRCode.generate(for: "https://github.com/Darock-Studio/Darock-Bili/issues") { - qrImage = image - } + WebView(url: URL(string: "https://github.com/Darock-Studio/Darock-Bili/issues")!) } } } diff --git a/MeowBili/Extension/UIExt.swift b/MeowBili/Extension/UIExt.swift index d54dc27cc..5cd36b3ac 100644 --- a/MeowBili/Extension/UIExt.swift +++ b/MeowBili/Extension/UIExt.swift @@ -19,6 +19,7 @@ import UIKit import WebKit import SwiftUI +import DarockKit import Foundation import SDWebImageSwiftUI import MobileCoreServices @@ -260,3 +261,52 @@ struct WebView: UIViewRepresentable { func updateUIView(_ uiView: WKWebView, context: Context) {} } + +@usableFromInline +struct TextSelectView: View { + @usableFromInline + var text: String + + @usableFromInline + init(text: String) { + self.text = text + } + + @usableFromInline + var body: some View { + VStack { + TextEditor(text: .constant(text)) + .padding() + } + } +} + +struct CopyableView: View { + var content: String + var view: () -> V + @State var present = false + init(_ content: String, view: @escaping () -> V) { + self.content = content + self.view = view + } + var body: some View { + view() + .contextMenu { + Button(action: { + UIPasteboard.general.string = content + AlertKitAPI.present(title: "已复制", subtitle: "简介内容已复制到剪贴板", icon: .done, style: .iOS17AppleMusic, haptic: .success) + }, label: { + Label("复制", systemImage: "doc.on.doc") + }) + Button(action: { + present = true + }, label: { + Label("选择文本", systemImage: "selection.pin.in.out") + }) + } + .sheet(isPresented: $present, content: { + TextSelectView(text: content) + }) + } +} + diff --git a/MeowBili/GlobalView/CommentsView.swift b/MeowBili/GlobalView/CommentsView.swift index 127cb5960..77946be55 100644 --- a/MeowBili/GlobalView/CommentsView.swift +++ b/MeowBili/GlobalView/CommentsView.swift @@ -69,26 +69,20 @@ struct CommentsView: View { @State var commentOffsets = [CGFloat]() @State var isLoaded = false @State var isSendCommentPresented = false + @State var presentRepliesGoto = "" + @State var presentRepliesRootData = [String: String]() + @State var isCommentRepliesPresented = false var body: some View { ScrollView { LazyVStack { - if #unavailable(watchOS 10) { - Button(action: { - isSendCommentPresented = true - }, label: { - HStack { - Image(systemName: "square.and.pencil") - Text("Comment.send") - } - }) - .sheet(isPresented: $isSendCommentPresented, content: {CommentSendView(oid: oid, type: type)}) - } if comments.count != 0 { ForEach(0...comments.count - 1, id: \.self) { i in VStack { HStack { - WebImage(url: URL(string: comments[i]["SenderPic"]! + "@30w"), options: [.progressiveLoad]) - .cornerRadius(100) + WebImage(url: URL(string: comments[i]["SenderPic"]!)) + .resizable() + .frame(width: 35, height: 35) + .clipShape(Circle()) VStack { NavigationLink("", isActive: $isSenderDetailsPresented[i], destination: {UserDetailView(uid: comments[i]["SenderID"]!)}) .frame(width: 0, height: 0) @@ -108,34 +102,24 @@ struct CommentsView: View { isSenderDetailsPresented[i] = true } HStack { -// if isEmoted[i] { -// ForEach(Array(zip(0..= replies.count { - ForEach(0...replies.count - 1, id: \.self) { i in - VStack { - HStack { - WebImage(url: URL(string: replies[i]["SenderPic"]! + "@30w"), options: [.progressiveLoad]) - .cornerRadius(100) - VStack { - NavigationLink("", isActive: $isSenderDetailsPresented[i], destination: {UserDetailView(uid: replies[i]["SenderID"]!)}) - .frame(width: 0, height: 0) - HStack { - Text(replies[i]["Sender"]!) - .font(.system(size: 14, weight: .bold)) + ScrollView { + VStack { + if replies.count != 0 { + ForEach(0...replies.count - 1, id: \.self) { i in + VStack { + HStack { + WebImage(url: URL(string: replies[i]["SenderPic"]!)) + .resizable() + .frame(width: 35, height: 35) + .clipShape(Circle()) + VStack { + NavigationLink("", isActive: $isSenderDetailsPresented[i], destination: {UserDetailView(uid: replies[i]["SenderID"]!)}) + .frame(width: 0, height: 0) + HStack { + Text(replies[i]["Sender"]!) + .font(.system(size: 14, weight: .bold)) + .lineLimit(1) + } + Text(replies[i]["IP"]!) + .font(.system(size: 10)) + .foregroundColor(.gray) .lineLimit(1) } - Text(replies[i]["IP"]!) - .font(.system(size: 10)) - .foregroundColor(.gray) - .lineLimit(1) + Spacer() } - Spacer() - } - .onTapGesture { - isSenderDetailsPresented[i] = true - } - HStack { - // Text({ () -> String in - // var showText = comments[i]["Text"]! - // if comments[i]["Text"]!.contains("[") && comments[i]["Text"]!.contains("]") { - // var emojiNames = [String]() - // let textSpd = comments[i]["Text"]!.split(separator: "[") - // for text in textSpd { - // emojiNames.append(String(text.split(separator: "]")[0])) - // } - // var emojiLinks = [String]() - // for name in emojiNames { - // emojiLinks.append(biliEmojiDictionary[name] ?? name) - // } - // for i in 0...emojiLinks.count - 1 { - // showText = showText.replacingOccurrences(of: "[\(emojiNames[i])]", with: "\(AsyncImage(url: URL(string: emojiLinks[i])))") - // } - // } - // return showText - // }()) - Text(replies[i]["Text"]!) - .font(.system(size: 16, weight: .bold)) - .lineLimit((replies[i]["isFull"] ?? "false") == "true" ? 1000 : 8) - .onTapGesture { - replies[i].updateValue((replies[i]["isFull"] ?? "false") == "true" ? "false" : "true", forKey: "isFull") - } - Spacer() - } - HStack { - Label(replies[i]["Like"]!, systemImage: replies[i]["UserAction"]! == "1" ? "hand.thumbsup.fill" : "hand.thumbsup") - .font(.system(size: 14)) - .foregroundColor(replies[i]["UserAction"]! == "1" ? Color(hex: 0xF889BA) : .white) - .lineLimit(1) - .opacity(replies[i]["UserAction"]! == "1" ? 1 : 0.6) - .onTapGesture { - let headers: HTTPHeaders = [ - "cookie": "SESSDATA=\(sessdata)", - "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" - ] - AF.request("https://api.bilibili.com/x/v2/reply/action", method: .post, parameters: BiliCommentLike(type: type, oid: avid, rpid: Int(replies[i]["Rpid"]!)!, action: replies[i]["UserAction"]! == "1" ? 0 : 1, csrf: biliJct), headers: headers).response { response in - debugPrint(response) - replies[i]["UserAction"]! = replies[i]["UserAction"]! == "1" ? "0" : "1" + .onTapGesture { + isSenderDetailsPresented[i] = true + currentPresentationDetent = .large + } + HStack { + // Text({ () -> String in + // var showText = comments[i]["Text"]! + // if comments[i]["Text"]!.contains("[") && comments[i]["Text"]!.contains("]") { + // var emojiNames = [String]() + // let textSpd = comments[i]["Text"]!.split(separator: "[") + // for text in textSpd { + // emojiNames.append(String(text.split(separator: "]")[0])) + // } + // var emojiLinks = [String]() + // for name in emojiNames { + // emojiLinks.append(biliEmojiDictionary[name] ?? name) + // } + // for i in 0...emojiLinks.count - 1 { + // showText = showText.replacingOccurrences(of: "[\(emojiNames[i])]", with: "\(AsyncImage(url: URL(string: emojiLinks[i])))") + // } + // } + // return showText + // }()) + Text(replies[i]["Text"]!) + .font(.system(size: 16, weight: .bold)) + .lineLimit((replies[i]["isFull"] ?? "false") == "true" ? 1000 : 8) + .onTapGesture { + replies[i].updateValue((replies[i]["isFull"] ?? "false") == "true" ? "false" : "true", forKey: "isFull") } - } - Label("", systemImage: replies[i]["UserAction"]! == "2" ? "hand.thumbsdown.fill" : "hand.thumbsdown") - .font(.system(size: 14)) - .foregroundColor(replies[i]["UserAction"]! == "2" ? Color(hex: 0xF889BA) : .white) - .lineLimit(1) - .opacity(replies[i]["UserAction"]! == "2" ? 1 : 0.6) - .onTapGesture { - let headers: HTTPHeaders = [ - "cookie": "SESSDATA=\(sessdata)", - "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" - ] - AF.request("https://api.bilibili.com/x/v2/reply/hate", method: .post, parameters: BiliCommentLike(type: type, oid: avid, rpid: Int(replies[i]["Rpid"]!)!, action: replies[i]["UserAction"]! == "2" ? 0 : 1, csrf: biliJct), headers: headers).response { response in - debugPrint(response) - replies[i]["UserAction"]! = replies[i]["UserAction"]! == "2" ? "0" : "2" + Spacer() + } + HStack { + Label(replies[i]["Like"]!, systemImage: replies[i]["UserAction"]! == "1" ? "hand.thumbsup.fill" : "hand.thumbsup") + .font(.system(size: 14)) + .foregroundColor(replies[i]["UserAction"]! == "1" ? Color(hex: 0xF889BA) : .white) + .lineLimit(1) + .opacity(replies[i]["UserAction"]! == "1" ? 1 : 0.6) + .onTapGesture { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/v2/reply/action", method: .post, parameters: BiliCommentLike(type: type, oid: avid, rpid: Int(replies[i]["Rpid"]!)!, action: replies[i]["UserAction"]! == "1" ? 0 : 1, csrf: biliJct), headers: headers).response { response in + debugPrint(response) + replies[i]["UserAction"]! = replies[i]["UserAction"]! == "1" ? "0" : "1" + } } - } - Spacer() + Label("", systemImage: replies[i]["UserAction"]! == "2" ? "hand.thumbsdown.fill" : "hand.thumbsdown") + .font(.system(size: 14)) + .foregroundColor(replies[i]["UserAction"]! == "2" ? Color(hex: 0xF889BA) : .white) + .lineLimit(1) + .opacity(replies[i]["UserAction"]! == "2" ? 1 : 0.6) + .onTapGesture { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/v2/reply/hate", method: .post, parameters: BiliCommentLike(type: type, oid: avid, rpid: Int(replies[i]["Rpid"]!)!, action: replies[i]["UserAction"]! == "2" ? 0 : 1, csrf: biliJct), headers: headers).response { response in + debugPrint(response) + replies[i]["UserAction"]! = replies[i]["UserAction"]! == "2" ? "0" : "2" + } + } + Spacer() + } + Divider() + } + .id(replies[i]["Rpid"]!) + .padding(5) + .offset(y: commentOffsets[i]) + .animation(.easeOut(duration: 0.4), value: commentOffsets[i]) + .onAppear { + commentOffsets[i] = 0 } - Divider() - } - .tag(replies[i]["Rpid"]!) - .padding(5) - .offset(y: commentOffsets[i]) - .animation(.easeOut(duration: 0.4), value: commentOffsets[i]) - .onAppear { - commentOffsets[i] = 0 } + } else { + ProgressView() } } } .onAppear { - if goto != nil { - proxy.scrollTo(goto!, anchor: .top) - } - for _ in replies { - commentOffsets.append(20) - isSenderDetailsPresented.append(false) + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata);", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + DarockKit.Network.shared.requestJSON("https://api.bilibili.com/x/v2/reply/reply?type=\(type)&oid=\(avid)&root=\(rootData["Rpid"]!)", headers: headers) { respJson, isSuccess in + if isSuccess { + if !CheckBApiError(from: respJson) { return } + for reply in respJson["data"]["replies"] { + commentOffsets.append(20) + isSenderDetailsPresented.append(false) + replies.append(["Text": reply.1["content"]["message"].string ?? "[加载失败]", "Sender": reply.1["member"]["uname"].string ?? "[加载失败]", "SenderPic": reply.1["member"]["avatar"].string ?? "E", "SenderID": reply.1["member"]["mid"].string ?? "E", "IP": reply.1["reply_control"]["location"].string ?? "", "UserAction": String(reply.1["action"].int ?? 0), "Rpid": String(reply.1["rpid"].int ?? -1), "Like": String(reply.1["like"].int ?? -1)]) + } + } } + + proxy.scrollTo(goto, anchor: .top) } } + .navigationTitle("评论回复") } + .presentationDetents([.medium, .large], selection: $currentPresentationDetent) } } } diff --git a/MeowBili/InMain/SearchView.swift b/MeowBili/InMain/SearchView.swift index 6dfc3f559..0548e0a8d 100644 --- a/MeowBili/InMain/SearchView.swift +++ b/MeowBili/InMain/SearchView.swift @@ -36,6 +36,7 @@ struct SearchMainView: View { .frame(width: 0, height: 0) .hidden() TextField("Search.\(Image(systemName: "magnifyingglass"))", text: $searchText) + .submitLabel(.search) .onSubmit { isSearchPresented = true if searchText != (searchHistory.first ?? "") { diff --git a/MeowBili/Video/VideoDetailView.swift b/MeowBili/Video/VideoDetailView.swift index a588a96f7..ac0e6be61 100644 --- a/MeowBili/Video/VideoDetailView.swift +++ b/MeowBili/Video/VideoDetailView.swift @@ -69,10 +69,17 @@ struct VideoDetailView: View { @State var videoBvid = "" @State var videoCID: Int64 = 0 @State var shouldPausePlayer = false + @State var isDescSelectPresented = false + @State var danmakuSendCache = "" + @State var danmakuSendColor = Color(hex: 0xFFFFFF) + @State var currentPlayTime = 0.0 + @State var danmakuSendFontSize = 25 + @State var danmakuSendMode = 1 + @FocusState var isEditingDanmaku: Bool var body: some View { VStack { if isDecoded { - VideoPlayerView(isDanmakuEnabled: $isDanmakuEnabled, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID, shouldPause: $shouldPausePlayer) + VideoPlayerView(isDanmakuEnabled: $isDanmakuEnabled, videoLink: $videoLink, videoBvid: $videoBvid, videoCID: $videoCID, shouldPause: $shouldPausePlayer, currentPlayTime: $currentPlayTime) .frame(height: 240) } else { Rectangle() @@ -81,6 +88,48 @@ struct VideoDetailView: View { } HStack { Spacer() + TextField("发送弹幕", text: $danmakuSendCache) + .textFieldStyle(.roundedBorder) + .submitLabel(.send) + .onSubmit { + let headers: HTTPHeaders = [ + "cookie": "SESSDATA=\(sessdata)", + "User-Agent": "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ] + AF.request("https://api.bilibili.com/x/v2/dm/post", method: .post, parameters: ["type": 1, "oid": videoCID, "msg": danmakuSendCache, "bvid": videoDetails["BV"]!, "progress": Int(currentPlayTime * 1000), "color": { () -> Int in + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + UIColor(danmakuSendColor).getRed(&red, green: &green, blue: &blue, alpha: &alpha) + return (Int(red * 255) << 16) + (Int(green * 255) << 8) + Int(blue * 255) + }(), "fontsize": danmakuSendFontSize, "pool": 0, "mode": danmakuSendMode, "rnd": Date.now.timeStamp * 1000000, "csrf": biliJct], headers: headers).response { response in + debugPrint(response) + danmakuSendCache = "" + } + } + .focused($isEditingDanmaku) + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + ColorPicker("颜色", selection: $danmakuSendColor) + Picker("字号", selection: $danmakuSendFontSize) { + Text("极小").tag(12) + Text("超小").tag(16) + Text("小").tag(18) + Text("标准").tag(25) + Text("大").tag(36) + Text("超大").tag(45) + Text("极大").tag(64) + } + Picker("模式", selection: $danmakuSendMode) { + Text("普通").tag(1) + Text("顶部").tag(5) + Text("底部").tag(4) + } + } + } + .frame(width: isEditingDanmaku ? nil : 90) + .animation(.easeOut(duration: 0.2), value: isEditingDanmaku) Button(action: { isDanmakuEnabled.toggle() }, label: { @@ -337,16 +386,15 @@ struct VideoDetailView: View { .opacity(0.6) Spacer() .frame(height: 5) - HStack { - VStack { + CopyableView(videoDesc) { + HStack { Image(systemName: "info.circle") + Text(videoDesc) Spacer() } - Text(videoDesc) - Spacer() + .font(.system(size: 12)) + .opacity(0.65) } - .font(.system(size: 12)) - .opacity(0.65) HStack { VStack { Image(systemName: "tag") @@ -391,6 +439,7 @@ struct VideoDetailView: View { } .navigationTitle("Video") .navigationBarTitleDisplayMode(.inline) + .scrollDismissesKeyboard(.immediately) .onAppear { let headers: HTTPHeaders = [ "cookie": "SESSDATA=\(sessdata)", diff --git a/MeowBili/Video/VideoPlayerView.swift b/MeowBili/Video/VideoPlayerView.swift index c2d912d9c..56dd65f40 100644 --- a/MeowBili/Video/VideoPlayerView.swift +++ b/MeowBili/Video/VideoPlayerView.swift @@ -29,6 +29,7 @@ struct VideoPlayerView: View { @Binding var videoBvid: String @Binding var videoCID: Int64 @Binding var shouldPause: Bool + @Binding var currentPlayTime: Double @AppStorage("DedeUserID") var dedeUserID = "" @AppStorage("DedeUserID__ckMd5") var dedeUserID__ckMd5 = "" @AppStorage("SESSDATA") var sessdata = "" @@ -117,10 +118,15 @@ struct VideoPlayerView: View { danmakuTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in danmakuOffset = player.currentTime().seconds * 50 + currentPlayTime = player.currentTime().seconds } playProgressTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in - UserDefaults.standard.set(player.currentTime().seconds, forKey: "\(videoBvid)\(videoCID)PlayTime") + if (player.currentItem?.duration.seconds ?? 0) - player.currentTime().seconds > 10 { + UserDefaults.standard.set(player.currentTime().seconds, forKey: "\(videoBvid)\(videoCID)PlayTime") + } else { + UserDefaults.standard.removeObject(forKey: "\(videoBvid)\(videoCID)PlayTime") + } } } } @@ -133,7 +139,11 @@ struct VideoPlayerView: View { danmakuTimer?.invalidate() playProgressTimer?.invalidate() player?.pause() - UserDefaults.standard.set(player.currentTime().seconds, forKey: "\(videoBvid)\(videoCID)PlayTime") + if (player.currentItem?.duration.seconds ?? 0) - player.currentTime().seconds > 10 { + UserDefaults.standard.set(player.currentTime().seconds, forKey: "\(videoBvid)\(videoCID)PlayTime") + } else { + UserDefaults.standard.removeObject(forKey: "\(videoBvid)\(videoCID)PlayTime") + } } .onChange(of: videoLink) { value in let asset = AVURLAsset(url: URL(string: value)!, options: ["AVURLAssetHTTPHeaderFieldsKey": ["User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "Referer": "https://www.bilibili.com"]])