2024-03-07 17:22:11 +01:00
// M a d e b y L u m a a
import WidgetKit
import SwiftUI
import SwiftData
struct FollowCountWidgetView : View {
@ Environment ( \ . widgetFamily ) private var family : WidgetFamily
2024-03-07 18:26:53 +01:00
var entry : FollowCountWidget . Provider . Entry
2024-03-07 17:22:11 +01:00
var body : some View {
if let account = entry . configuration . account {
ZStack {
2024-03-08 13:50:48 +01:00
#if os ( iOS )
2024-03-07 17:22:11 +01:00
if family = = WidgetFamily . systemSmall {
small
} else if family = = WidgetFamily . systemMedium {
medium
2024-03-08 13:50:48 +01:00
}
#endif
if family = = WidgetFamily . accessoryRectangular {
2024-03-07 17:22:11 +01:00
rectangular
}
}
. modelContainer ( for : [ LoggedAccount . self ] )
} else {
Text ( " widget.select-account " )
. font ( . caption )
}
}
var small : some View {
VStack ( alignment : . center ) {
Image ( uiImage : entry . pfp )
. resizable ( )
. scaledToFit ( )
. frame ( width : 60 , height : 60 )
. foregroundStyle ( Color . white )
. clipShape ( Circle ( ) )
Spacer ( )
Text ( entry . followers , format : . number . notation ( . compactName ) )
. font ( . title . monospacedDigit ( ) . bold ( ) )
. contentTransition ( . numericText ( ) )
Text ( " widget.followers " )
. font ( . caption )
. foregroundStyle ( Color . gray )
Spacer ( )
Text ( " @ \( entry . configuration . account ! . username ) " )
. redacted ( reason : . privacy )
. font ( . caption . bold ( ) )
}
}
var medium : some View {
HStack {
Spacer ( )
VStack ( alignment : . center , spacing : 10 ) {
Image ( uiImage : entry . pfp )
. resizable ( )
. scaledToFit ( )
. frame ( width : 60 , height : 60 )
. foregroundStyle ( Color . white )
. clipShape ( Circle ( ) )
Text ( " @ \( entry . configuration . account ! . username ) " )
. redacted ( reason : . privacy )
. font ( . caption . bold ( ) )
}
Spacer ( )
VStack {
Text ( entry . followers , format : . number . notation ( . compactName ) )
. font ( . title . monospacedDigit ( ) . bold ( ) )
. contentTransition ( . numericText ( ) )
Text ( " widget.followers " )
. font ( . caption )
. foregroundStyle ( Color . gray )
}
Spacer ( )
}
}
var rectangular : some View {
HStack {
VStack ( alignment : . leading ) {
Text ( " @ \( entry . configuration . account ! . username ) " )
. multilineTextAlignment ( . leading )
. font ( . caption )
. opacity ( 0.7 )
Text ( entry . followers , format : . number . notation ( . compactName ) )
. multilineTextAlignment ( . leading )
. font ( . system ( size : 32 , weight : . bold ) . monospacedDigit ( ) )
. contentTransition ( . numericText ( ) )
2024-03-07 18:26:53 +01:00
. redacted ( reason : . privacy )
2024-03-07 17:22:11 +01:00
}
. padding ( . horizontal , 7.5 )
Spacer ( )
}
}
}
struct FollowCountWidget : Widget {
let kind : String = " FollowCountWidget "
let modelContainer : ModelContainer
init ( ) {
guard let modelContainer : ModelContainer = try ? . init ( for : LoggedAccount . self , configurations : ModelConfiguration ( isStoredInMemoryOnly : true ) ) else { fatalError ( " Couldn't get LoggedAccounts " ) }
self . modelContainer = modelContainer
}
var body : some WidgetConfiguration {
AppIntentConfiguration ( kind : kind , intent : AccountAppIntent . self , provider : Provider ( ) ) { entry in
FollowCountWidgetView ( entry : entry )
. containerBackground ( Color ( " WidgetBackground " ) , for : . widget )
}
. configurationDisplayName ( " widget.follow-count " )
. description ( " widget.follow-count.description " )
2024-03-08 13:50:48 +01:00
#if os ( iOS )
2024-03-07 17:22:11 +01:00
. supportedFamilies ( [ . systemSmall , . systemMedium , . accessoryRectangular ] )
2024-03-08 13:50:48 +01:00
#else
. supportedFamilies ( [ . accessoryRectangular ] )
#endif
2024-03-07 17:22:11 +01:00
}
2024-03-07 18:26:53 +01:00
struct Provider : AppIntentTimelineProvider {
2024-03-08 13:50:48 +01:00
func recommendations ( ) -> [ AppIntentRecommendation < AccountAppIntent > ] {
let intent = AccountAppIntent ( )
return [ . init ( intent : intent , description : intent . account ? . username ? ? String ( " Mastodon " ) ) ]
}
2024-03-07 18:26:53 +01:00
func placeholder ( in context : Context ) -> SimpleEntry {
let placeholder : UIImage = UIImage ( systemName : " person.crop.circle " ) ? ? UIImage ( )
2024-03-08 13:50:48 +01:00
placeholder . withTintColor ( UIColor . actualLabel )
2024-03-07 18:26:53 +01:00
return SimpleEntry ( date : Date ( ) , pfp : placeholder , followers : 38 , configuration : AccountAppIntent ( ) )
}
func snapshot ( for configuration : AccountAppIntent , in context : Context ) async -> SimpleEntry {
let data = await getData ( configuration : configuration )
return SimpleEntry ( date : Date ( ) , pfp : data . 0 , followers : data . 1 , configuration : configuration )
}
func timeline ( for configuration : AccountAppIntent , in context : Context ) async -> Timeline < SimpleEntry > {
var entries : [ SimpleEntry ] = [ ]
let data = await getData ( configuration : configuration )
// G e n e r a t e a t i m e l i n e c o n s i s t i n g o f t w o e n t r i e s a n h o u r a p a r t , s t a r t i n g f r o m t h e c u r r e n t d a t e .
let currentDate = Date ( )
for hourOffset in 0 . . < 2 {
let entryDate = Calendar . current . date ( byAdding : . hour , value : hourOffset , to : currentDate ) !
let entry = SimpleEntry ( date : entryDate , pfp : data . 0 , followers : data . 1 , configuration : configuration )
entries . append ( entry )
}
return Timeline ( entries : entries , policy : . atEnd )
}
private func getData ( configuration : AccountAppIntent ) async -> ( UIImage , Int ) {
var pfp : UIImage = UIImage ( systemName : " person.crop.circle " ) ? ? UIImage ( )
2024-03-08 13:50:48 +01:00
pfp . withTintColor ( UIColor . actualLabel )
2024-03-07 18:26:53 +01:00
if let account = configuration . account {
do {
let acc = try await account . client . getString ( endpoint : Accounts . verifyCredentials , forceVersion : . v1 )
if let serialized : [ String : Any ] = try JSONSerialization . jsonObject ( with : acc . data ( using : String . Encoding . utf8 ) ? ? Data ( ) ) as ? [ String : Any ] {
let avatar : String = serialized [ " avatar " ] as ! String
let task = try await URLSession . shared . data ( from : URL ( string : avatar ) ! )
pfp = UIImage ( data : task . 0 ) ? ? UIImage ( )
let followers : Int = serialized [ " followers_count " ] as ! Int
return ( pfp , followers )
}
} catch {
print ( error )
}
}
return ( pfp , 0 )
}
}
struct SimpleEntry : TimelineEntry {
let date : Date
let pfp : UIImage
let followers : Int
let configuration : AccountAppIntent
}
2024-03-07 17:22:11 +01:00
}
2024-03-08 13:50:48 +01:00
private extension Color {
private static var label : Color {
#if os ( iOS )
return Color ( uiColor : UIColor . label )
#else
return Color . white
#endif
}
}
private extension UIColor {
static var actualLabel : UIColor {
#if os ( iOS )
return UIColor . label
#else
return UIColor . white
#endif
}
}