Skip to content

Commit

Permalink
初步实现Widgets
Browse files Browse the repository at this point in the history
实现了Widgets,但是预想中的AppIntents和URL Scheme没有实现

这两个完善之后Widgets会更有用,所以希望至少能够参考一下,或是在这个基础上继续Coding

原本准备全部完善再提交的,但是我明天就要开学了(初三

真的对不起!!!!!
  • Loading branch information
FengzihangCode committed Sep 8, 2024
1 parent b468814 commit cda30eb
Show file tree
Hide file tree
Showing 9 changed files with 572 additions and 2 deletions.
223 changes: 221 additions & 2 deletions DarockBili.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions MeowBili/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>meowbili</string>
<string>drkbili</string>
</array>
</dict>
Expand Down
68 changes: 68 additions & 0 deletions MeowBili/Others/BiliBiliAPIService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
//
// BiliBiliAPIService.swift
// DarockBili
//
// Created by feng on 9/8/24.
//
//===----------------------------------------------------------------------===//
//
// This source file is part of the MeowBili open source project
//
// Copyright (c) 2024 Darock Studio and the MeowBili project authors
// Licensed under GNU General Public License v3
//
// See https://darock.top/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

import Foundation

class BiliBiliAPIService {

enum ContentType {
case trending, recommendations
}

/// 获取BiliBili数据
/// - Parameters:
/// - type: 内容类型(热门或推荐)
/// - limit: 限制返回的视频数量,默认是5
func fetchBiliBiliData(for type: ContentType, limit: Int = 5) async -> Result<[(title: String, description: String, author: String, views: String)], Error> {
do {
let urlString: String
switch type {
case .trending:
urlString = "https://api.bilibili.com/x/web-interface/popular?ps=\(limit)"
case .recommendations:
urlString = "https://api.bilibili.com/x/web-interface/index/top/rcmd?ps=1"
}

guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}

let (data, _) = try await URLSession.shared.data(from: url)

if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let dataDict = json["data"] as? [String: Any],
let list = dataDict["list"] as? [[String: Any]] {

let videos = list.prefix(limit).map { video -> (String, String, String, String) in
let videoTitle = video["title"] as? String ?? "无标题"
let videoDesc = video["desc"] as? String ?? "无描述"
let videoAuthor = video["author"] as? String ?? "未知作者"
let videoViews = video["view"] as? String ?? "未知播放量"
return (title: videoTitle, description: videoDesc, author: videoAuthor, views: videoViews)
}

return .success(videos)
} else {
return .failure(NSError(domain: "BiliBiliAPIService", code: -1, userInfo: [NSLocalizedDescriptionKey: "数据格式错误"]))
}

} catch {
return .failure(error)
}
}
}
6 changes: 6 additions & 0 deletions MeowWidget/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "255",
"green" : "255",
"red" : "255"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "26",
"green" : "24",
"red" : "23"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
38 changes: 38 additions & 0 deletions MeowWidget/Assets.xcassets/WidgetTitleColor.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "153",
"green" : "103",
"red" : "255"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "126",
"green" : "79",
"red" : "212"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
11 changes: 11 additions & 0 deletions MeowWidget/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>
162 changes: 162 additions & 0 deletions MeowWidget/MeowWidget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
//
//
// MeowWidget.swift
// MeowWidgetExtension
//
// Created by feng on 9/8/24.
//
//===----------------------------------------------------------------------===//
//
// This source file is part of the MeowBili open source project
//
// Copyright (c) 2024 Darock Studio and the MeowBili project authors
// Licensed under GNU General Public License v3
//
// See https://darock.top/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

import WidgetKit
import SwiftUI
import Intents

struct MeowWidgetEntry: TimelineEntry {
let date: Date
let videoTitle: String
let videoDescription: String
let videoAuthor: String
let videoViews: String
}

struct Provider: TimelineProvider {
func placeholder(in context: Context) -> MeowWidgetEntry {
MeowWidgetEntry(date: Date(), videoTitle: "视频名称", videoDescription: "描述", videoAuthor: "作者", videoViews: "播放量")
}

func getSnapshot(in context: Context, completion: @escaping (MeowWidgetEntry) -> ()) {
let entry = MeowWidgetEntry(date: Date(), videoTitle: "视频名称", videoDescription: "描述", videoAuthor: "作者", videoViews: "播放量")
completion(entry)
}

func getTimeline(in context: Context, completion: @escaping (Timeline<MeowWidgetEntry>) -> ()) {
Task {
let trendingResult = await BiliBiliAPIService().fetchBiliBiliData(for: .trending, limit: 5)
let recommendationsResult = await BiliBiliAPIService().fetchBiliBiliData(for: .recommendations, limit: 5)

var allVideos: [(title: String, description: String, author: String, views: String)] = []
if case .success(let trendingVideos) = trendingResult {
allVideos.append(contentsOf: trendingVideos)
}
if case .success(let recommendationsVideos) = recommendationsResult {
allVideos.append(contentsOf: recommendationsVideos)
}

if !allVideos.isEmpty {
var entries: [MeowWidgetEntry] = []
let currentDate = Date()

// 每隔 10 分钟轮播一个视频标题
for (index, video) in allVideos.enumerated() {
let entryDate = Calendar.current.date(byAdding: .minute, value: index * 10, to: currentDate) ?? currentDate
let entry = MeowWidgetEntry(
date: entryDate,
videoTitle: video.title,
videoDescription: video.description,
videoAuthor: video.author,
videoViews: video.views
)
entries.append(entry)
}

let nextUpdateDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate) ?? currentDate
let timeline = Timeline(entries: entries, policy: .after(nextUpdateDate))
completion(timeline)
} else {
let entry = MeowWidgetEntry(date: Date(), videoTitle: "暂无数据", videoDescription: "", videoAuthor: "", videoViews: "")
completion(Timeline(entries: [entry], policy: .atEnd))
}
}
}
}

struct MeowWidgetEntryView : View {
var entry: Provider.Entry
@Environment(\.widgetFamily) var family

var body: some View {
VStack(alignment: .leading) {
HStack {
Image("MeowBili")
.resizable()
.frame(width: 24, height: 24)
Text("喵哩喵哩")
.font(.headline)
.foregroundColor(Color("WidgetTitleColor"))
}

Spacer().frame(height: 10)

switch family {
case .systemSmall:
Text(entry.videoTitle)
.font(.headline)

case .systemMedium:
Text(entry.videoTitle)
.font(.headline)
Text(entry.videoDescription)
.font(.subheadline)
Spacer()
Text("在喵哩喵哩查看更多内容")
.font(.footnote)
.foregroundColor(.gray)

case .systemLarge:
Text(entry.videoTitle)
.font(.headline)
Text(entry.videoDescription)
.font(.subheadline)
Text("作者: \(entry.videoAuthor)")
.font(.footnote)
Text("播放量: \(entry.videoViews)")
.font(.footnote)
Spacer()
Text("在喵哩喵哩查看更多内容")
.font(.footnote)
.foregroundColor(.gray)

case .accessoryCircular, .accessoryRectangular:
if entry.videoTitle == "打开喵哩喵哩" {
Text("打开喵哩喵哩")
.font(.headline)
} else {
Text(entry.videoTitle)
.font(.headline)
}

default:
Text(entry.videoTitle)
.font(.headline)
Text(entry.videoDescription)
.font(.subheadline)
}
}
.padding()
.background(Color("WidgetBackgroundColor"))
.widgetURL(URL(string: "meowbili://")!)
}
}


struct MeowWidget: Widget {
let kind: String = "MeowWidget"

var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
MeowWidgetEntryView(entry: entry)
}
.configurationDisplayName("喵哩喵哩Widget")
.description("热门或推荐的视频内容")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .accessoryCircular, .accessoryRectangular])
}
}
27 changes: 27 additions & 0 deletions MeowWidget/MeowWidgetBundle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
//
// MeowWidgetBundle.swift
// MeowWidgetExtension
//
// Created by feng on 9/8/24.
//
//===----------------------------------------------------------------------===//
//
// This source file is part of the MeowBili open source project
//
// Copyright (c) 2024 Darock Studio and the MeowBili project authors
// Licensed under GNU General Public License v3
//
// See https://darock.top/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

import WidgetKit
import SwiftUI

@main
struct MeowWidgetBundle: WidgetBundle {
var body: some Widget {
MeowWidget()
}
}

0 comments on commit cda30eb

Please sign in to comment.