mirror of
https://github.com/XITRIX/iTorrent.git
synced 2026-05-30 11:46:50 +00:00
295 lines
11 KiB
Swift
295 lines
11 KiB
Swift
//
|
||
// ProgressWidgetLiveActivity.swift
|
||
// ProgressWidget
|
||
//
|
||
// Created by Даниил Виноградов on 06.04.2024.
|
||
//
|
||
|
||
#if canImport(ActivityKit)
|
||
import ActivityKit
|
||
import AppIntents
|
||
import MarqueeText
|
||
import SwiftUI
|
||
import WidgetKit
|
||
|
||
extension ActivityViewContext<ProgressWidgetAttributes> {
|
||
static var userDefaults: UserDefaults { .itorrentGroup }
|
||
|
||
var tintColor: UIColor {
|
||
if let colorData = state.color,
|
||
let keyColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: colorData)
|
||
{
|
||
return keyColor
|
||
} else {
|
||
return Self.tintColor
|
||
}
|
||
}
|
||
|
||
private static var tintColor: UIColor {
|
||
guard let data = Self.userDefaults.data(forKey: "preferencesTintColor")
|
||
else { return .tintColor }
|
||
return (try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data)) ?? .tintColor
|
||
}
|
||
}
|
||
|
||
struct ProgressWidgetLiveActivity: Widget {
|
||
var body: some WidgetConfiguration {
|
||
let config = ActivityConfiguration(for: ProgressWidgetAttributes.self) { context in
|
||
// Lock screen/banner UI goes here
|
||
|
||
if #available(iOS 18, *) {
|
||
ProgressWidgetLiveActivityWatchSupportContent(context: context)
|
||
.tint(Color(uiColor: context.tintColor))
|
||
.padding()
|
||
} else {
|
||
ProgressWidgetLiveActivityContent(context: context)
|
||
.tint(Color(uiColor: context.tintColor))
|
||
.padding()
|
||
}
|
||
} dynamicIsland: { context in
|
||
DynamicIsland {
|
||
// Expanded UI goes here. Compose the expanded UI through
|
||
// various regions, like leading/trailing/center/bottom
|
||
DynamicIslandExpandedRegion(.leading) {
|
||
LeadingView(context: context)
|
||
}
|
||
DynamicIslandExpandedRegion(.trailing) {
|
||
if context.state.state == .seeding {
|
||
Image(systemName: "arrow.up.to.line")
|
||
} else {
|
||
Text(String("\(String(format: "%.2f", context.state.progress * 100))%")).padding(.top, 2)
|
||
}
|
||
}
|
||
DynamicIslandExpandedRegion(.bottom) {
|
||
VStack {
|
||
HStack {
|
||
Text(context.state.name).padding(.top, 2)
|
||
Spacer()
|
||
if context.state.state == .downloading {
|
||
Text(context.state.timeRemainig)
|
||
}
|
||
|
||
if #available(iOS 17.0, *),
|
||
context.state.state == .seeding
|
||
{
|
||
let intent = {
|
||
let intent = PauseTorrentIntent()
|
||
intent.torrentHash = context.attributes.hash
|
||
return intent
|
||
}()
|
||
PauseButton(intent: intent)
|
||
}
|
||
}
|
||
ProgressView(value: context.state.progress)
|
||
.progressViewStyle(.linear)
|
||
.padding([.leading, .trailing], 8)
|
||
}
|
||
.tint(Color(uiColor: context.tintColor))
|
||
}
|
||
} compactLeading: {
|
||
LeadingView(context: context)
|
||
} compactTrailing: {
|
||
TrailingView(context: context)
|
||
} minimal: {
|
||
TrailingView(context: context)
|
||
}
|
||
.widgetURL(URL(string: "iTorrent:hash:\(context.attributes.hash)"))
|
||
.keylineTint(Color(uiColor: context.tintColor))
|
||
}
|
||
|
||
if #available(iOS 18.0, *) {
|
||
return config.supplementalActivityFamilies([.small])
|
||
} else {
|
||
return config
|
||
}
|
||
}
|
||
}
|
||
|
||
@available(iOS 18.0, *)
|
||
struct ProgressWidgetLiveActivityWatchSupportContent: View {
|
||
@Environment(\.activityFamily) var activityFamily
|
||
@State var context: ActivityViewContext<ProgressWidgetAttributes>
|
||
|
||
var body: some View {
|
||
if activityFamily == .medium {
|
||
ProgressWidgetLiveActivityContent(context: context)
|
||
} else {
|
||
VStack(spacing: 8) {
|
||
HStack {
|
||
MarqueeText(
|
||
text: context.state.name,
|
||
font: UIFont.preferredFont(forTextStyle: .caption1),
|
||
leftFade: 16,
|
||
rightFade: 16,
|
||
startDelay: 3)
|
||
Spacer()
|
||
}
|
||
HStack {
|
||
switch context.state.state {
|
||
case .checkingFiles:
|
||
Text(context.state.state.name)
|
||
Spacer()
|
||
case .downloadingMetadata:
|
||
Text(context.state.state.name)
|
||
Spacer()
|
||
case .downloading:
|
||
Text(String("\(context.state.downSpeed.bitrateToHumanReadable)/s ↓"))
|
||
Spacer()
|
||
Text(String("\(context.state.upSpeed.bitrateToHumanReadable)/s ↑"))
|
||
case .finished:
|
||
Text(context.state.state.name)
|
||
Spacer()
|
||
case .seeding:
|
||
Text(context.state.state.name)
|
||
Spacer()
|
||
Text(String("\(context.state.upSpeed.bitrateToHumanReadable)/s ↑"))
|
||
case .checkingResumeData:
|
||
Text(context.state.state.name)
|
||
Spacer()
|
||
case .paused:
|
||
Text(context.state.state.name)
|
||
Spacer()
|
||
case .storageError:
|
||
EmptyView()
|
||
}
|
||
}
|
||
.font(.caption2)
|
||
.foregroundColor(.secondary)
|
||
|
||
HStack {
|
||
ProgressView(value: context.state.progress)
|
||
.progressViewStyle(.linear)
|
||
Text(String("\(String(format: "%.2f", context.state.progress * 100))%"))
|
||
.font(.caption2)
|
||
.monospaced()
|
||
}
|
||
}
|
||
.widgetURL(URL(string: "iTorrent:hash:\(context.attributes.hash)"))
|
||
}
|
||
}
|
||
}
|
||
|
||
struct ProgressWidgetLiveActivityContent: View {
|
||
@State var context: ActivityViewContext<ProgressWidgetAttributes>
|
||
|
||
var body: some View {
|
||
VStack(spacing: 8) {
|
||
HStack {
|
||
Text(context.state.name)
|
||
Spacer()
|
||
|
||
if #available(iOS 17.0, *),
|
||
context.state.state == .seeding
|
||
{
|
||
let intent = {
|
||
let intent = PauseTorrentIntent()
|
||
intent.torrentHash = context.attributes.hash
|
||
return intent
|
||
}()
|
||
PauseButton(intent: intent)
|
||
}
|
||
}
|
||
HStack {
|
||
switch context.state.state {
|
||
case .checkingFiles:
|
||
Text(context.state.state.name)
|
||
case .downloadingMetadata:
|
||
Text(context.state.state.name)
|
||
case .downloading:
|
||
Text(String("\(context.state.downSpeed.bitrateToHumanReadable)/s ↓"))
|
||
Text(String(" | "))
|
||
Text(String("\(context.state.upSpeed.bitrateToHumanReadable)/s ↑"))
|
||
case .finished:
|
||
Text(context.state.state.name)
|
||
case .seeding:
|
||
Text(context.state.state.name)
|
||
Text(String(" | "))
|
||
Text(String("\(context.state.upSpeed.bitrateToHumanReadable)/s ↑"))
|
||
case .checkingResumeData:
|
||
Text(context.state.state.name)
|
||
case .paused:
|
||
Text(context.state.state.name)
|
||
case .storageError:
|
||
EmptyView()
|
||
}
|
||
|
||
Spacer()
|
||
Text(String("\(String(format: "%.2f", context.state.progress * 100))%"))
|
||
}
|
||
.font(.subheadline)
|
||
.foregroundColor(.secondary)
|
||
|
||
ProgressView(value: context.state.progress)
|
||
.progressViewStyle(.linear)
|
||
}
|
||
.widgetURL(URL(string: "iTorrent:hash:\(context.attributes.hash)"))
|
||
}
|
||
}
|
||
|
||
@available(iOS 17.0, *)
|
||
struct PauseButton: View {
|
||
let intent: any LiveActivityIntent
|
||
|
||
var body: some View {
|
||
Button(intent: intent) {
|
||
Image(systemName: "pause.fill")
|
||
}
|
||
}
|
||
}
|
||
|
||
struct LeadingView: View {
|
||
@State var context: ActivityViewContext<ProgressWidgetAttributes>
|
||
|
||
var body: some View {
|
||
switch context.state.state {
|
||
case .downloading:
|
||
Text(String("\(context.state.downSpeed.bitrateToHumanReadable)/s"))
|
||
case .checkingFiles:
|
||
Image(systemName: "arrow.triangle.2.circlepath")
|
||
case .downloadingMetadata:
|
||
Image(systemName: "arrow.up.arrow.down")
|
||
case .finished:
|
||
EmptyView()
|
||
case .seeding:
|
||
Text(String("\(context.state.upSpeed.bitrateToHumanReadable)/s"))
|
||
case .checkingResumeData:
|
||
Image(systemName: "arrow.triangle.2.circlepath")
|
||
case .paused:
|
||
Image(systemName: "pause.fill")
|
||
case .storageError:
|
||
EmptyView()
|
||
}
|
||
}
|
||
}
|
||
|
||
struct TrailingView: View {
|
||
@State var context: ActivityViewContext<ProgressWidgetAttributes>
|
||
|
||
var body: some View {
|
||
if context.state.state == .seeding {
|
||
Image(systemName: "arrow.up.to.line")
|
||
} else {
|
||
ProgressView(value: context.state.progress)
|
||
.progressViewStyle(.circular)
|
||
.tint(Color(uiColor: context.tintColor))
|
||
}
|
||
}
|
||
}
|
||
|
||
// #Preview("Progress",
|
||
// as: .dynamicIsland(.compact),
|
||
// using: ProgressWidgetAttributes(name: "Test torrent", hash: "")
|
||
// ) {
|
||
// ProgressWidgetLiveActivity()
|
||
// } contentStates: {
|
||
// ProgressWidgetAttributes.ContentState(progress: 0.2, downSpeed: 2000, upSpeed: 1000, timeRemainig: "Осталось САСАТБ", timeStamp: .now)
|
||
// }
|
||
|
||
// #Preview("Notification", as: .content, using: ProgressWidgetAttributes(name: "Test torrent", hash: "")) {
|
||
// ProgressWidgetLiveActivity()
|
||
// } contentStates: {
|
||
// ProgressWidgetAttributes.ContentState(progress: 0.2, downSpeed: 2000, upSpeed: 1000, timeRemainig: "Осталось САСАТБ", timeStamp: .now)
|
||
// ProgressWidgetAttributes.ContentState(progress: 0.7, downSpeed: 12000000, upSpeed: 1000000, timeRemainig: "Осталось САСАТБ", timeStamp: .now)
|
||
// }
|
||
#endif
|