✨ Post recommendation widget
This commit is contained in:
parent
e920bd954c
commit
8bdaf05223
@ -37,13 +37,6 @@ target 'Runner' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
target 'SolarWidget' do
|
|
||||||
use_frameworks!
|
|
||||||
use_modular_headers!
|
|
||||||
|
|
||||||
pod 'home_widget', :path => '.symlinks/plugins/home_widget/ios'
|
|
||||||
end
|
|
||||||
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
73DA8A022D05C7620024A03E /* Embed Foundation Extensions */ = {
|
73DA8A022D05C7620024A03E /* Embed Foundation Extensions */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 12;
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 13;
|
dstSubfolderSpec = 13;
|
||||||
files = (
|
files = (
|
||||||
@ -113,20 +113,28 @@
|
|||||||
);
|
);
|
||||||
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||||
};
|
};
|
||||||
738C1F502D0D91D000A215F3 /* Exceptions for "Data" folder in "Runner" target */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
|
||||||
membershipExceptions = (
|
|
||||||
User.swift,
|
|
||||||
);
|
|
||||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
|
||||||
};
|
|
||||||
738C1F512D0D91D000A215F3 /* Exceptions for "Data" folder in "SolarWidgetExtension" target */ = {
|
738C1F512D0D91D000A215F3 /* Exceptions for "Data" folder in "SolarWidgetExtension" target */ = {
|
||||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
membershipExceptions = (
|
membershipExceptions = (
|
||||||
|
Post.swift,
|
||||||
User.swift,
|
User.swift,
|
||||||
);
|
);
|
||||||
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||||
};
|
};
|
||||||
|
73BC73712D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarNotifyService" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Attachment.swift,
|
||||||
|
);
|
||||||
|
target = 73DA89F92D05C7620024A03E /* SolarNotifyService */;
|
||||||
|
};
|
||||||
|
73BC73722D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarWidgetExtension" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Attachment.swift,
|
||||||
|
);
|
||||||
|
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||||
|
};
|
||||||
73DA8A062D05C7620024A03E /* Exceptions for "SolarNotifyService" folder in "SolarNotifyService" target */ = {
|
73DA8A062D05C7620024A03E /* Exceptions for "SolarNotifyService" folder in "SolarNotifyService" target */ = {
|
||||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
membershipExceptions = (
|
membershipExceptions = (
|
||||||
@ -148,12 +156,20 @@
|
|||||||
738C1F4F2D0D91CC00A215F3 /* Data */ = {
|
738C1F4F2D0D91CC00A215F3 /* Data */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
exceptions = (
|
exceptions = (
|
||||||
738C1F502D0D91D000A215F3 /* Exceptions for "Data" folder in "Runner" target */,
|
|
||||||
738C1F512D0D91D000A215F3 /* Exceptions for "Data" folder in "SolarWidgetExtension" target */,
|
738C1F512D0D91D000A215F3 /* Exceptions for "Data" folder in "SolarWidgetExtension" target */,
|
||||||
);
|
);
|
||||||
path = Data;
|
path = Data;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
73BC736C2D0DDF5600956BE0 /* Service */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
73BC73712D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarNotifyService" target */,
|
||||||
|
73BC73722D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarWidgetExtension" target */,
|
||||||
|
);
|
||||||
|
path = Service;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
73DA89FB2D05C7620024A03E /* SolarNotifyService */ = {
|
73DA89FB2D05C7620024A03E /* SolarNotifyService */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
exceptions = (
|
exceptions = (
|
||||||
@ -260,6 +276,7 @@
|
|||||||
97C146F01CF9000F007C117D /* Runner */ = {
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
73BC736C2D0DDF5600956BE0 /* Service */,
|
||||||
738C1F4F2D0D91CC00A215F3 /* Data */,
|
738C1F4F2D0D91CC00A215F3 /* Data */,
|
||||||
73111C212CEE3D5E004CF4B3 /* Runner.entitlements */,
|
73111C212CEE3D5E004CF4B3 /* Runner.entitlements */,
|
||||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
@ -378,6 +395,7 @@
|
|||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
738C1F4F2D0D91CC00A215F3 /* Data */,
|
738C1F4F2D0D91CC00A215F3 /* Data */,
|
||||||
|
73BC736C2D0DDF5600956BE0 /* Service */,
|
||||||
);
|
);
|
||||||
name = Runner;
|
name = Runner;
|
||||||
productName = Runner;
|
productName = Runner;
|
||||||
|
38
ios/Runner/Data/Post.swift
Normal file
38
ios/Runner/Data/Post.swift
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// SolarPost.swift
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/14.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SolarPost : Codable {
|
||||||
|
let id: Int
|
||||||
|
let body: SolarPostBody
|
||||||
|
let publisher: SolarPublisher
|
||||||
|
let publisherId: Int
|
||||||
|
let createdAt: Date
|
||||||
|
let updatedAt: Date
|
||||||
|
let editedAt: Date?
|
||||||
|
let publishedAt: Date?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SolarPostBody : Codable {
|
||||||
|
let content: String?
|
||||||
|
let title: String?
|
||||||
|
let description: String?
|
||||||
|
let attachments: [String]?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SolarPublisher : Codable {
|
||||||
|
let id: Int
|
||||||
|
let name: String
|
||||||
|
let nick: String
|
||||||
|
let description: String?
|
||||||
|
let avatar: String?
|
||||||
|
let banner: String?
|
||||||
|
let createdAt: Date
|
||||||
|
let updatedAt: Date
|
||||||
|
}
|
14
ios/Runner/Service/Attachment.swift
Normal file
14
ios/Runner/Service/Attachment.swift
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// Attachment.swift
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/14.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
func getAttachmentUrl(for identifier: String) -> String {
|
||||||
|
let serverBaseUrl = "https://api.sn.solsynth.dev"
|
||||||
|
|
||||||
|
return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/cgi/uc/attachments/\(identifier)"
|
||||||
|
}
|
@ -17,11 +17,6 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
|
|
||||||
private var contentHandler: ((UNNotificationContent) -> Void)?
|
private var contentHandler: ((UNNotificationContent) -> Void)?
|
||||||
private var bestAttemptContent: UNMutableNotificationContent?
|
private var bestAttemptContent: UNMutableNotificationContent?
|
||||||
private let serverBaseUrl = "https://api.sn.solsynth.dev"
|
|
||||||
|
|
||||||
private func getAttachmentUrl(for identifier: String) -> String {
|
|
||||||
identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/cgi/uc/attachments/\(identifier)"
|
|
||||||
}
|
|
||||||
|
|
||||||
private func fetchAvatarImage(from url: String, completion: @escaping (INImage?) -> Void) {
|
private func fetchAvatarImage(from url: String, completion: @escaping (INImage?) -> Void) {
|
||||||
guard let imageURL = URL(string: url) else {
|
guard let imageURL = URL(string: url) else {
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
import WidgetKit
|
import WidgetKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct Provider: TimelineProvider {
|
struct CheckInProvider: TimelineProvider {
|
||||||
func placeholder(in context: Context) -> SimpleEntry {
|
func placeholder(in context: Context) -> CheckInEntry {
|
||||||
SimpleEntry(date: Date(), user: nil, checkIn: nil)
|
CheckInEntry(date: Date(), user: nil, checkIn: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
func getSnapshot(in context: Context, completion: @escaping (CheckInEntry) -> ()) {
|
||||||
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
|
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
|
||||||
|
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = DateFormatter()
|
||||||
@ -35,7 +35,7 @@ struct Provider: TimelineProvider {
|
|||||||
checkIn = try! jsonDecoder.decode(SolarCheckInRecord.self, from: checkInRaw.data(using: .utf8)!)
|
checkIn = try! jsonDecoder.decode(SolarCheckInRecord.self, from: checkInRaw.data(using: .utf8)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = SimpleEntry(
|
let entry = CheckInEntry(
|
||||||
date: Date(),
|
date: Date(),
|
||||||
user: user,
|
user: user,
|
||||||
checkIn: checkIn
|
checkIn: checkIn
|
||||||
@ -51,14 +51,14 @@ struct Provider: TimelineProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SimpleEntry: TimelineEntry {
|
struct CheckInEntry: TimelineEntry {
|
||||||
let date: Date
|
let date: Date
|
||||||
let user: SolarUser?
|
let user: SolarUser?
|
||||||
let checkIn: SolarCheckInRecord?
|
let checkIn: SolarCheckInRecord?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SolarWidgetEntryView : View {
|
struct CheckInWidgetEntryView : View {
|
||||||
var entry: Provider.Entry
|
var entry: CheckInProvider.Entry
|
||||||
|
|
||||||
private let resultTierSymbols: [String] = ["大凶", "凶", "中平", "大吉", "吉"]
|
private let resultTierSymbols: [String] = ["大凶", "凶", "中平", "大吉", "吉"]
|
||||||
|
|
||||||
@ -110,15 +110,15 @@ struct SolarWidgetEntryView : View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct CheckInWidget: Widget {
|
struct CheckInWidget: Widget {
|
||||||
let kind: String = "SolarWidget"
|
let kind: String = "SolarCheckInWidget"
|
||||||
|
|
||||||
var body: some WidgetConfiguration {
|
var body: some WidgetConfiguration {
|
||||||
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
StaticConfiguration(kind: kind, provider: CheckInProvider()) { entry in
|
||||||
if #available(iOS 17.0, *) {
|
if #available(iOS 17.0, *) {
|
||||||
SolarWidgetEntryView(entry: entry)
|
CheckInWidgetEntryView(entry: entry)
|
||||||
.containerBackground(.fill.tertiary, for: .widget)
|
.containerBackground(.fill.tertiary, for: .widget)
|
||||||
} else {
|
} else {
|
||||||
SolarWidgetEntryView(entry: entry)
|
CheckInWidgetEntryView(entry: entry)
|
||||||
.padding()
|
.padding()
|
||||||
.background()
|
.background()
|
||||||
}
|
}
|
||||||
@ -132,8 +132,8 @@ struct CheckInWidget: Widget {
|
|||||||
#Preview(as: .systemSmall) {
|
#Preview(as: .systemSmall) {
|
||||||
CheckInWidget()
|
CheckInWidget()
|
||||||
} timeline: {
|
} timeline: {
|
||||||
SimpleEntry(date: .now, user: nil, checkIn: nil)
|
CheckInEntry(date: .now, user: nil, checkIn: nil)
|
||||||
SimpleEntry(
|
CheckInEntry(
|
||||||
date: .now,
|
date: .now,
|
||||||
user: SolarUser(id: 1, name: "demo", nick: "Deemo"),
|
user: SolarUser(id: 1, name: "demo", nick: "Deemo"),
|
||||||
checkIn: SolarCheckInRecord(id: 1, resultTier: 1, resultExperience: 100, createdAt: Date.now)
|
checkIn: SolarCheckInRecord(id: 1, resultTier: 1, resultExperience: 100, createdAt: Date.now)
|
||||||
|
@ -5,3 +5,237 @@
|
|||||||
// Created by LittleSheep on 2024/12/14.
|
// Created by LittleSheep on 2024/12/14.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct FeaturedPostProvider: TimelineProvider {
|
||||||
|
func placeholder(in context: Context) -> FeaturedPostEntry {
|
||||||
|
FeaturedPostEntry(date: Date(), user: nil, featuredPost: nil, family: .systemMedium)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSnapshot(in context: Context, completion: @escaping (FeaturedPostEntry) -> ()) {
|
||||||
|
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
|
||||||
|
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
|
||||||
|
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
|
||||||
|
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||||
|
|
||||||
|
let userRaw = prefs?.string(forKey: "user")
|
||||||
|
var user: SolarUser?
|
||||||
|
if let userRaw = userRaw {
|
||||||
|
user = try! jsonDecoder.decode(SolarUser.self, from: userRaw.data(using: .utf8)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
let featuredPostRaw = prefs?.string(forKey: "post_featured")
|
||||||
|
var featuredPosts: [SolarPost]?
|
||||||
|
if let featuredPostRaw = featuredPostRaw {
|
||||||
|
featuredPosts = try! jsonDecoder.decode([SolarPost].self, from: featuredPostRaw.data(using: .utf8)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = FeaturedPostEntry(
|
||||||
|
date: Date(),
|
||||||
|
user: user,
|
||||||
|
featuredPost: featuredPosts?.first,
|
||||||
|
family: context.family
|
||||||
|
)
|
||||||
|
completion(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||||
|
getSnapshot(in: context) { (entry) in
|
||||||
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
||||||
|
completion(timeline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FeaturedPostEntry: TimelineEntry {
|
||||||
|
let date: Date
|
||||||
|
let user: SolarUser?
|
||||||
|
let featuredPost: SolarPost?
|
||||||
|
|
||||||
|
let family: WidgetFamily
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FeaturedPostWidgetEntryView : View {
|
||||||
|
var entry: FeaturedPostProvider.Entry
|
||||||
|
|
||||||
|
private let resultTierSymbols: [String] = ["大凶", "凶", "中平", "大吉", "吉"]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
if let featuredPost = entry.featuredPost {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
if let avatar = featuredPost.publisher.avatar {
|
||||||
|
let avatarUrl = getAttachmentUrl(for: avatar)
|
||||||
|
let size: CGFloat = 24
|
||||||
|
|
||||||
|
AsyncImage(url: URL(string: avatarUrl)) { image in
|
||||||
|
image.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.cornerRadius(size / 2)
|
||||||
|
.overlay(
|
||||||
|
Circle()
|
||||||
|
.stroke(Color.white, lineWidth: 4)
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
)
|
||||||
|
.shadow(radius: 10)
|
||||||
|
.frame(width: 24, height: 24, alignment: .center)
|
||||||
|
} placeholder: {
|
||||||
|
ProgressView().frame(width: 24, height: 24, alignment: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("@\(featuredPost.publisher.name)")
|
||||||
|
.font(.system(size: 13, design: .monospaced))
|
||||||
|
.opacity(0.9)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}.frame(maxWidth: .infinity).padding(.bottom, 12)
|
||||||
|
|
||||||
|
if featuredPost.body.title != nil || featuredPost.body.description != nil {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
if let title = featuredPost.body.title {
|
||||||
|
Text(title)
|
||||||
|
.font(.system(size: 17))
|
||||||
|
}
|
||||||
|
if let description = featuredPost.body.description {
|
||||||
|
Text(description)
|
||||||
|
.font(.system(size: 15))
|
||||||
|
}
|
||||||
|
}.padding(.bottom, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let content = featuredPost.body.content {
|
||||||
|
if (featuredPost.body.title == nil && featuredPost.body.description == nil) || entry.family == .systemLarge || entry.family == .systemExtraLarge {
|
||||||
|
Text(
|
||||||
|
(entry.family == .systemLarge || entry.family == .systemExtraLarge) ? content : content.replacingOccurrences(of: "\n", with: " ")
|
||||||
|
)
|
||||||
|
.font(.system(size: 15))
|
||||||
|
} else {
|
||||||
|
Text("\(Image(systemName: "plus")) total \(content.count) characters")
|
||||||
|
.font(.system(size: 11, design: .monospaced))
|
||||||
|
.opacity(0.75)
|
||||||
|
.padding(.top, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let attachment = featuredPost.body.attachments {
|
||||||
|
if attachment.count == 1 {
|
||||||
|
Text("\(Image(systemName: "document.fill")) \(attachment.count) attachment")
|
||||||
|
.font(.system(size: 11, design: .monospaced))
|
||||||
|
.opacity(0.75)
|
||||||
|
.padding(.top, 1)
|
||||||
|
} else if attachment.count > 1 {
|
||||||
|
Text("\(Image(systemName: "document.fill")) \(attachment.count) attachments")
|
||||||
|
.font(.system(size: 11, design: .monospaced))
|
||||||
|
.opacity(0.75)
|
||||||
|
.padding(.top, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text(featuredPost.publishedAt!, format: .dateTime)
|
||||||
|
.font(.system(size: 11))
|
||||||
|
Text("Solar Network Featured Posts")
|
||||||
|
.font(.system(size: 9))
|
||||||
|
} else {
|
||||||
|
VStack(alignment: .center) {
|
||||||
|
Text("No Recommendations").font(.system(size: 19, weight: .bold))
|
||||||
|
Text("Click the widget to open the app to load featured posts")
|
||||||
|
.font(.system(size: 15))
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}.frame(alignment: .center)
|
||||||
|
}
|
||||||
|
}.padding(8).frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FeaturedPostWidget: Widget {
|
||||||
|
let kind: String = "SolarFeaturedPostWidget"
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
StaticConfiguration(kind: kind, provider: FeaturedPostProvider()) { entry in
|
||||||
|
if #available(iOS 17.0, *) {
|
||||||
|
FeaturedPostWidgetEntryView(entry: entry)
|
||||||
|
.containerBackground(.fill.tertiary, for: .widget)
|
||||||
|
} else {
|
||||||
|
FeaturedPostWidgetEntryView(entry: entry)
|
||||||
|
.padding()
|
||||||
|
.background()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Featured Posts")
|
||||||
|
.description("View the featured posts on the Solar Network")
|
||||||
|
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview(as: .systemSmall) {
|
||||||
|
FeaturedPostWidget()
|
||||||
|
} timeline: {
|
||||||
|
FeaturedPostEntry(date: Date.now, user: nil, featuredPost: nil, family: .systemLarge)
|
||||||
|
FeaturedPostEntry(
|
||||||
|
date: .now,
|
||||||
|
user: SolarUser(id: 1, name: "demo", nick: "Deemo"),
|
||||||
|
featuredPost: SolarPost(
|
||||||
|
id: 1,
|
||||||
|
body: SolarPostBody(
|
||||||
|
content: "Hello, World",
|
||||||
|
title: nil,
|
||||||
|
description: nil,
|
||||||
|
attachments: ["zb2hiUEmYcnpHfVN"]
|
||||||
|
),
|
||||||
|
publisher: SolarPublisher(
|
||||||
|
id: 1,
|
||||||
|
name: "demo",
|
||||||
|
nick: "Deemo",
|
||||||
|
description: nil,
|
||||||
|
avatar: "IZxCFkJUPKRijFCx",
|
||||||
|
banner: nil,
|
||||||
|
createdAt: .now,
|
||||||
|
updatedAt: .now
|
||||||
|
),
|
||||||
|
publisherId: 1,
|
||||||
|
createdAt: .now,
|
||||||
|
updatedAt: .now,
|
||||||
|
editedAt: nil,
|
||||||
|
publishedAt: .now
|
||||||
|
),
|
||||||
|
family: .systemSmall
|
||||||
|
)
|
||||||
|
FeaturedPostEntry(
|
||||||
|
date: .now,
|
||||||
|
user: SolarUser(id: 1, name: "demo", nick: "Deemo"),
|
||||||
|
featuredPost: SolarPost(
|
||||||
|
id: 1,
|
||||||
|
body: SolarPostBody(
|
||||||
|
content: "Hello, World\nOh wow",
|
||||||
|
title: "Title",
|
||||||
|
description: "Description",
|
||||||
|
attachments: ["zb2hiUEmYcnpHfVN"]
|
||||||
|
),
|
||||||
|
publisher: SolarPublisher(
|
||||||
|
id: 1,
|
||||||
|
name: "demo",
|
||||||
|
nick: "Deemo",
|
||||||
|
description: nil,
|
||||||
|
avatar: "IZxCFkJUPKRijFCx",
|
||||||
|
banner: nil,
|
||||||
|
createdAt: .now,
|
||||||
|
updatedAt: .now
|
||||||
|
),
|
||||||
|
publisherId: 1,
|
||||||
|
createdAt: .now,
|
||||||
|
updatedAt: .now,
|
||||||
|
editedAt: nil,
|
||||||
|
publishedAt: .now
|
||||||
|
),
|
||||||
|
family: .systemLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@ import SwiftUI
|
|||||||
@main
|
@main
|
||||||
struct SolarWidgetBundle: WidgetBundle {
|
struct SolarWidgetBundle: WidgetBundle {
|
||||||
var body: some Widget {
|
var body: some Widget {
|
||||||
CheckInWidget()
|
// CheckInWidget()
|
||||||
|
FeaturedPostWidget()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,18 +15,22 @@ class HomeWidgetProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveWidgetData(String id, dynamic data) async {
|
Future<void> saveWidgetData(String id, dynamic data, {bool update = true}) async {
|
||||||
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
||||||
await HomeWidget.saveWidgetData(id, jsonEncode(data));
|
await HomeWidget.saveWidgetData(id, jsonEncode(data));
|
||||||
|
if (update) await updateWidget();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateWidget() async {
|
Future<void> updateWidget() async {
|
||||||
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
||||||
|
const widgets = ["SolarFeaturedPostWidget", "SolarCheckInWidget"];
|
||||||
|
for(final widget in widgets) {
|
||||||
await HomeWidget.updateWidget(
|
await HomeWidget.updateWidget(
|
||||||
name: "SolarWidget",
|
name: widget,
|
||||||
iOSName: "SolarWidget",
|
iOSName: widget,
|
||||||
androidName: "com.solsynth.solian.SolarWidget",
|
androidName: "com.solsynth.solian.$widget",
|
||||||
qualifiedAndroidName: "group.solsynth.solian.SolarWidget",
|
qualifiedAndroidName: "group.solsynth.solian.$widget",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:home_widget/home_widget.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@ -428,7 +427,9 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
try {
|
try {
|
||||||
final pt = context.read<SnPostContentProvider>();
|
final pt = context.read<SnPostContentProvider>();
|
||||||
|
final home = context.read<HomeWidgetProvider>();
|
||||||
_posts = await pt.listRecommendations();
|
_posts = await pt.listRecommendations();
|
||||||
|
home.saveWidgetData('post_featured', _posts!.map((e) => e.toJson()).toList());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 2.0.1+25
|
version: 2.0.1+27
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
Loading…
Reference in New Issue
Block a user