mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-08-03 20:47:22 -04:00
Generate widgets
This commit is contained in:
parent
ca833c31a4
commit
fd3e5daa3f
@ -25,14 +25,23 @@ struct Kiwix: App {
|
|||||||
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
||||||
|
|
||||||
private let fileMonitor: DirectoryMonitor
|
private let fileMonitor: DirectoryMonitor
|
||||||
|
private let activityService: ActivityService?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
fileMonitor = DirectoryMonitor(url: URL.documentDirectory) { LibraryOperations.scanDirectory($0) }
|
fileMonitor = DirectoryMonitor(url: URL.documentDirectory) { LibraryOperations.scanDirectory($0) }
|
||||||
|
// MARK: - live activities
|
||||||
|
switch AppType.current {
|
||||||
|
case .kiwix:
|
||||||
|
activityService = nil //ActivityService()
|
||||||
|
case .custom:
|
||||||
|
activityService = nil
|
||||||
|
}
|
||||||
UNUserNotificationCenter.current().delegate = appDelegate
|
UNUserNotificationCenter.current().delegate = appDelegate
|
||||||
// MARK: - migrations
|
// MARK: - migrations
|
||||||
if !ProcessInfo.processInfo.arguments.contains("testing") {
|
if !ProcessInfo.processInfo.arguments.contains("testing") {
|
||||||
_ = MigrationService().migrateAll()
|
_ = MigrationService().migrateAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
|
59
Common/DownloadActivityAttributes.swift
Normal file
59
Common/DownloadActivityAttributes.swift
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// This file is part of Kiwix for iOS & macOS.
|
||||||
|
//
|
||||||
|
// Kiwix is free software; you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kiwix is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
|
||||||
|
#if os(iOS)
|
||||||
|
|
||||||
|
import ActivityKit
|
||||||
|
|
||||||
|
public struct DownloadActivityAttributes: ActivityAttributes {
|
||||||
|
|
||||||
|
public let title: String
|
||||||
|
|
||||||
|
public init(title: String) {
|
||||||
|
self.title = title
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ContentState: Codable & Hashable {
|
||||||
|
public let items: [DownloadItem]
|
||||||
|
public var totalProgress: Double {
|
||||||
|
let sum = items.reduce(Double(0.0), { partialResult, item in
|
||||||
|
partialResult + item.progress
|
||||||
|
})
|
||||||
|
return sum / Double(items.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(items: [DownloadItem]) {
|
||||||
|
self.items = items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DownloadItem: Codable & Hashable {
|
||||||
|
public let uuid: UUID
|
||||||
|
public let description: String
|
||||||
|
public let progress: Double
|
||||||
|
|
||||||
|
public init(uuid: UUID, description: String, progress: Double) {
|
||||||
|
self.uuid = uuid
|
||||||
|
self.description = description
|
||||||
|
self.progress = progress
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(completedFor uuid: UUID) {
|
||||||
|
self.uuid = uuid
|
||||||
|
self.progress = 1.0
|
||||||
|
self.description = "Completed!" //TODO: update
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
@ -63,5 +63,9 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>NSSupportsLiveActivities</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -12,39 +12,39 @@
|
|||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
|
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
|
||||||
//
|
|
||||||
//#if os(iOS)
|
#if os(iOS)
|
||||||
//
|
|
||||||
import Combine
|
import Combine
|
||||||
import ActivityKit
|
import ActivityKit
|
||||||
import KiwixWidgetsExtension
|
|
||||||
//
|
@MainActor
|
||||||
//@MainActor
|
final class ActivityService {
|
||||||
//final class ActivityService {
|
|
||||||
//
|
private var cancellables = Set<AnyCancellable>()
|
||||||
// private var cancellables = Set<AnyCancellable>()
|
private var activity: Activity<DownloadActivityAttributes>?
|
||||||
// private var activity: Activity<DownloadActivityAttributes>?
|
|
||||||
//
|
init(
|
||||||
// init(
|
publisher: @MainActor () -> CurrentValueSubject<[UUID: DownloadState], Never> = { DownloadService.shared.progress.publisher
|
||||||
// publisher: @MainActor () -> CurrentValueSubject<[UUID: DownloadState], Never> = { DownloadService.shared.progress.publisher
|
}
|
||||||
// }
|
) {
|
||||||
// ) {
|
publisher().sink { [weak self] (state: [UUID : DownloadState]) in
|
||||||
// publisher().sink { [weak self] (state: [UUID : DownloadState]) in
|
guard let self else { return }
|
||||||
// guard let self else { return }
|
if state.isEmpty {
|
||||||
// if state.isEmpty {
|
stop()
|
||||||
// stop()
|
} else {
|
||||||
// } else {
|
update(state: state)
|
||||||
// update(state: state)
|
}
|
||||||
// }
|
}.store(in: &cancellables)
|
||||||
// }.store(in: &cancellables)
|
}
|
||||||
// }
|
|
||||||
//
|
private func start(with state: [UUID: DownloadState]) {
|
||||||
// private func start(with state: [UUID: DownloadState]) {
|
let content = ActivityContent(
|
||||||
// let content = ActivityContent(
|
state: activityState(from: state),
|
||||||
// state: activityState(from: state),
|
staleDate: nil,
|
||||||
// staleDate: nil,
|
relevanceScore: 0.0
|
||||||
// relevanceScore: 0.0
|
)
|
||||||
// )
|
debugPrint("start with: \(state)")
|
||||||
// if let activity = try? Activity
|
// if let activity = try? Activity
|
||||||
// .request(
|
// .request(
|
||||||
// attributes: DownloadActivityAttributes(title: "Downloads"),
|
// attributes: DownloadActivityAttributes(title: "Downloads"),
|
||||||
@ -59,9 +59,10 @@ import KiwixWidgetsExtension
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private func update(state: [UUID: DownloadState]) {
|
private func update(state: [UUID: DownloadState]) {
|
||||||
|
debugPrint("update state: \(state)")
|
||||||
// guard let activity else {
|
// guard let activity else {
|
||||||
// start(with: state)
|
// start(with: state)
|
||||||
// return
|
// return
|
||||||
@ -74,9 +75,10 @@ import KiwixWidgetsExtension
|
|||||||
// )
|
// )
|
||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private func stop() {
|
private func stop() {
|
||||||
|
debugPrint("stop")
|
||||||
// if let activity {
|
// if let activity {
|
||||||
// let previousState = activity.content.state
|
// let previousState = activity.content.state
|
||||||
// Task {
|
// Task {
|
||||||
@ -86,21 +88,21 @@ import KiwixWidgetsExtension
|
|||||||
// self.activity = nil
|
// self.activity = nil
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private func activityState(from state: [UUID: DownloadState]) -> DownloadActivityAttributes.ContentState {
|
private func activityState(from state: [UUID: DownloadState]) -> DownloadActivityAttributes.ContentState {
|
||||||
// DownloadActivityAttributes.ContentState(
|
DownloadActivityAttributes.ContentState(
|
||||||
// items: state.map { (key: UUID, download: DownloadState)-> DownloadActivityAttributes.DownloadItem in
|
items: state.map { (key: UUID, download: DownloadState)-> DownloadActivityAttributes.DownloadItem in
|
||||||
// DownloadActivityAttributes.DownloadItem(uuid: key, description: key.uuidString, progress: Double(download.downloaded/download.total))
|
DownloadActivityAttributes.DownloadItem(uuid: key, description: key.uuidString, progress: Double(download.downloaded/download.total))
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// private func completeState(for previousState: DownloadActivityAttributes.ContentState) -> DownloadActivityAttributes.ContentState {
|
private func completeState(for previousState: DownloadActivityAttributes.ContentState) -> DownloadActivityAttributes.ContentState {
|
||||||
// DownloadActivityAttributes
|
DownloadActivityAttributes
|
||||||
// .ContentState(items: previousState.items.map { item in
|
.ContentState(items: previousState.items.map { item in
|
||||||
// DownloadActivityAttributes.DownloadItem(completedFor: item.uuid)
|
DownloadActivityAttributes.DownloadItem(completedFor: item.uuid)
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//#endif
|
#endif
|
||||||
|
11
Widgets/Assets.xcassets/AccentColor.colorset/Contents.json
Normal file
11
Widgets/Assets.xcassets/AccentColor.colorset/Contents.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
35
Widgets/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
35
Widgets/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "tinted"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
6
Widgets/Assets.xcassets/Contents.json
Normal file
6
Widgets/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
94
Widgets/DownloadsLiveActivity.swift
Normal file
94
Widgets/DownloadsLiveActivity.swift
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// This file is part of Kiwix for iOS & macOS.
|
||||||
|
//
|
||||||
|
// Kiwix is free software; you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kiwix is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
|
||||||
|
|
||||||
|
import ActivityKit
|
||||||
|
import WidgetKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DownloadsLiveActivity: Widget {
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
ActivityConfiguration(for: DownloadActivityAttributes.self) { context in
|
||||||
|
// Lock screen/banner UI goes here
|
||||||
|
VStack {
|
||||||
|
ForEach(context.state.items, id: \.uuid) { item in
|
||||||
|
HStack {
|
||||||
|
Text(item.description)
|
||||||
|
ProgressView(value: item.progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.activityBackgroundTint(Color.cyan)
|
||||||
|
.activitySystemActionForegroundColor(Color.black)
|
||||||
|
|
||||||
|
} dynamicIsland: { context in
|
||||||
|
DynamicIsland {
|
||||||
|
// Expanded UI goes here. Compose the expanded UI through
|
||||||
|
// various regions, like leading/trailing/center/bottom
|
||||||
|
DynamicIslandExpandedRegion(.leading) {
|
||||||
|
VStack {
|
||||||
|
ForEach(context.state.items, id: \.uuid) { item in
|
||||||
|
Text(item.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DynamicIslandExpandedRegion(.trailing) {
|
||||||
|
ProgressView(value: context.state.totalProgress)
|
||||||
|
}
|
||||||
|
DynamicIslandExpandedRegion(.bottom) {
|
||||||
|
ProgressView(value: context.state.totalProgress)
|
||||||
|
}
|
||||||
|
} compactLeading: {
|
||||||
|
ProgressView(value: context.state.totalProgress)
|
||||||
|
} compactTrailing: {
|
||||||
|
ProgressView(value: context.state.totalProgress)
|
||||||
|
} minimal: {
|
||||||
|
ProgressView(value: context.state.totalProgress)
|
||||||
|
}
|
||||||
|
.widgetURL(URL(string: "https://www.kiwix.org"))
|
||||||
|
.keylineTint(Color.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DownloadActivityAttributes {
|
||||||
|
fileprivate static var preview: DownloadActivityAttributes {
|
||||||
|
DownloadActivityAttributes(title: "Downloads")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DownloadActivityAttributes.ContentState {
|
||||||
|
fileprivate static var midProgress: DownloadActivityAttributes.ContentState {
|
||||||
|
DownloadActivityAttributes.ContentState(items: [
|
||||||
|
DownloadActivityAttributes.DownloadItem(uuid: UUID(), description: "First item", progress: 0.5),
|
||||||
|
DownloadActivityAttributes.DownloadItem(uuid: UUID(), description: "2nd item", progress: 0.9)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate static var completed: DownloadActivityAttributes.ContentState {
|
||||||
|
DownloadActivityAttributes.ContentState(items: [
|
||||||
|
DownloadActivityAttributes.DownloadItem(uuid: UUID(), description: "First item", progress: 1.0),
|
||||||
|
DownloadActivityAttributes.DownloadItem(uuid: UUID(), description: "2nd item", progress: 0.8)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 18.0, *)
|
||||||
|
#Preview("Notification", as: .content, using: DownloadActivityAttributes.preview) {
|
||||||
|
DownloadsLiveActivity()
|
||||||
|
} contentStates: {
|
||||||
|
DownloadActivityAttributes.ContentState.midProgress
|
||||||
|
DownloadActivityAttributes.ContentState.completed
|
||||||
|
}
|
11
Widgets/Info.plist
Normal file
11
Widgets/Info.plist
Normal file
@ -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>
|
25
Widgets/WidgetsBundle.swift
Normal file
25
Widgets/WidgetsBundle.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// This file is part of Kiwix for iOS & macOS.
|
||||||
|
//
|
||||||
|
// Kiwix is free software; you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation; either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kiwix is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
|
||||||
|
|
||||||
|
import WidgetKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@available(iOS 16.6, *)
|
||||||
|
@main
|
||||||
|
struct WidgetsBundle: WidgetBundle {
|
||||||
|
var body: some Widget {
|
||||||
|
DownloadsLiveActivity()
|
||||||
|
}
|
||||||
|
}
|
15
project.yml
15
project.yml
@ -2,7 +2,7 @@ name: Kiwix
|
|||||||
options:
|
options:
|
||||||
xcodeVersion: 15.2
|
xcodeVersion: 15.2
|
||||||
deploymentTarget: # the three latest major versions should be supported
|
deploymentTarget: # the three latest major versions should be supported
|
||||||
iOS: 16.0
|
iOS: 16.6
|
||||||
macOS: 13.0
|
macOS: 13.0
|
||||||
generateEmptyDirectories: true
|
generateEmptyDirectories: true
|
||||||
useTabs: false
|
useTabs: false
|
||||||
@ -87,6 +87,9 @@ targetTemplates:
|
|||||||
- path: PrivacyInfo.xcprivacy
|
- path: PrivacyInfo.xcprivacy
|
||||||
destinationFilters:
|
destinationFilters:
|
||||||
- iOS
|
- iOS
|
||||||
|
- path: Common
|
||||||
|
destinationFilters:
|
||||||
|
- iOS
|
||||||
- path: Contents
|
- path: Contents
|
||||||
includes:
|
includes:
|
||||||
- Resources
|
- Resources
|
||||||
@ -136,6 +139,16 @@ targets:
|
|||||||
- path: Tests
|
- path: Tests
|
||||||
dependencies:
|
dependencies:
|
||||||
- target: Kiwix
|
- target: Kiwix
|
||||||
|
Widgets:
|
||||||
|
type: app-extension
|
||||||
|
supportedDestinations: [iOS]
|
||||||
|
settings:
|
||||||
|
base:
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER: self.Kiwix.widgets
|
||||||
|
INFOPLIST_FILE: Widgets/Info.plist
|
||||||
|
sources:
|
||||||
|
- path: Common
|
||||||
|
- path: Widgets
|
||||||
|
|
||||||
schemes:
|
schemes:
|
||||||
Kiwix:
|
Kiwix:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user