2023-05-20 09:24:09 -07:00
//
// U n s t a b l e F e a t u r e s . s w i f t
// S i d e S t o r e
//
// C r e a t e d b y n a t u r e c o d e v o i d o n 5 / 2 0 / 2 3 .
// C o p y r i g h t © 2 0 2 3 S i d e S t o r e . A l l r i g h t s r e s e r v e d .
//
2023-05-27 21:53:04 -07:00
import Foundation
2023-05-20 14:23:25 -07:00
2023-05-27 21:53:04 -07:00
class UnstableFeatures {
2023-06-13 20:40:35 -07:00
#if UNSTABLE
2023-05-27 21:53:04 -07:00
fileprivate struct Metadata {
2023-06-13 20:40:35 -07:00
// / I f t r u e , t h i s U n s t a b l e F e a t u r e w i l l b e a v a i l a b l e i n A d v a n c e d S e t t i n g s i n s t e a d o f b e i n g e x c l u s i v e t o D e v e l o p e r M o d e .
2023-05-27 21:53:04 -07:00
var availableOutsideDevMode = false
2023-06-13 20:40:35 -07:00
// / R u n w h e n t h e f e a t u r e i s e n a b l e d .
2023-05-27 21:53:04 -07:00
var onEnable = { }
2023-06-13 20:40:35 -07:00
// / R u n w h e n t h e f e a t u r e i s d i s a b l e d
2023-05-27 21:53:04 -07:00
var onDisable = { }
}
2023-06-13 20:40:35 -07:00
#endif
2023-05-20 09:24:09 -07:00
2023-05-27 21:53:04 -07:00
enum Feature : String , CaseIterable {
// T h e v a l u e w i l l b e t h e G i t H u b I s s u e n u m b e r . F o r e x a m p l e , " 1 2 3 " w o u l d c o r r e s p o n d t o h t t p s : / / g i t h u b . c o m / S i d e S t o r e / S i d e S t o r e / i s s u e s / 1 2 3
//
2023-06-13 20:40:35 -07:00
// U n s t a b l e F e a t u r e s m u s t h a v e a G i t H u b I s s u e f o r t r a c k i n g p r o g r e s s , P R s a n d f e e d b a c k / b u g r e p o r t i n g / c o m m e n t i n g .
2023-05-27 21:53:04 -07:00
//
// P l e a s e o r d e r t h e c a s e b y t h e i s s u e n u m b e r . T h e y w i l l b e o r d e r e d b y i s s u e n u m b e r ( a s c e n d i n g ) i n t h e u n s t a b l e f e a t u r e s m e n u , s o p l e a s e o r d e r t h e m t h e s a m e w a y h e r e a n d i n ` m e t a d a t a ` .
2023-06-12 23:27:27 -07:00
case swiftUI = " 386 "
case jitUrlScheme = " 385 "
2023-06-14 19:02:15 -07:00
case onboarding = " 389 "
2023-05-27 21:53:04 -07:00
// / D u m m y v a r i a n t t o e n s u r e t h e r e i s a l w a y s a t l e a s t o n e v a r i a n t . D O N O T U S E !
case dummy = " dummy "
2023-05-20 09:24:09 -07:00
2023-06-13 20:40:35 -07:00
#if UNSTABLE
2023-05-27 21:53:04 -07:00
fileprivate var metadata : Metadata {
switch self {
// I f y o u r u n s t a b l e f e a t u r e i s s t a b l e e n o u g h t o b e u s e d b y n i g h t l y u s e r s w h o a r e n o t a l p h a t e s t e r s o r d e v e l o p e r s ,
// y o u m a y w a n t t o h a v e i t a v a i l a b l e i n t h e U n s t a b l e F e a t u r e s m e n u i n A d v a n c e d S e t t i n g s ( o u t s i d e o f d e v m o d e ) . T o d o s o , a d d t h i s :
// c a s e . y o u r F e a t u r e : r e t u r n M e t a d a t a ( a v a i l a b l e O u t s i d e D e v M o d e : t r u e )
2023-05-27 22:21:36 -07:00
// Y o u c a n a l s o a d d c u s t o m h o o k s f o r w h e n y o u r f e a t u r e i s e n a b l e d o r d i s a b l e d . H o w e v e r , w e s t r o n g l y r e c o m m e n d m o v i n g t h e s e t o a n e w f i l e . E x a m p l e : h t t p s : / / g i t h u b . c o m / S i d e S t o r e / S i d e S t o r e / b l o b / 0 2 6 3 9 2 d b c 7 a 5 4 5 4 a 3 9 b 9 2 8 7 f 4 6 9 d 3 2 b 5 e 6 7 6 8 b b 8 / A l t S t o r e / U n s t a b l e % 2 0 F e a t u r e s / U n s t a b l e F e a t u r e s % 2 B S w i f t U I . s w i f t
2023-06-13 20:40:35 -07:00
// S e e t h e ` M e t a d a t a ` s t r u c t f o r m o r e t h i n g s y o u c a n d o .
2023-05-27 21:53:04 -07:00
// P l e a s e k e e p t h e o r d e r i n g o f t h e c a s e s i n t h i s s w i t c h s t a t e m e n t t h e s a m e a s t h e o r d e r i n g o f t h e e n u m v a r i a n t s !
case . swiftUI : return Metadata ( availableOutsideDevMode : true , onEnable : SwiftUI . onEnable , onDisable : SwiftUI . onDisable )
case . jitUrlScheme : return Metadata ( availableOutsideDevMode : true )
default : return Metadata ( )
}
2023-05-20 09:24:09 -07:00
}
2023-06-13 20:40:35 -07:00
#endif
2023-05-20 09:24:09 -07:00
}
2023-05-27 21:53:04 -07:00
2023-05-20 09:24:09 -07:00
#if UNSTABLE
2023-05-27 21:53:04 -07:00
private static var features : [ Feature : Bool ] = [ : ]
2023-05-24 21:01:11 -07:00
2023-05-27 21:53:04 -07:00
static func getFeatures ( _ inDevMode : Bool ) -> [ ( key : Feature , value : Bool ) ] {
// E n s u r e e v e r y f e a t u r e i s i n t h e d i c t i o n a r y
for feature in Feature . allCases {
if features [ feature ] = = nil {
features [ feature ] = false
}
}
2023-05-24 21:01:11 -07:00
return features
. filter { feature , _ in
feature != . dummy &&
2023-05-27 21:53:04 -07:00
( inDevMode || feature . metadata . availableOutsideDevMode )
} . sorted { a , b in a . key . rawValue > b . key . rawValue } // C o n v e r t t o a r r a y o f k e y s a n d v a l u e s ( a n d a l s o s o r t t h e m b y i s s u e n u m b e r )
2023-05-24 21:01:11 -07:00
}
2023-05-20 09:24:09 -07:00
static func load ( ) {
2023-05-24 21:01:11 -07:00
if features . count > 0 { return print ( " It seems unstable features have already been loaded, skipping " ) }
2023-05-20 09:24:09 -07:00
if let rawFeatures = UserDefaults . shared . unstableFeatures ,
2023-05-20 10:47:55 -07:00
let rawFeatures = try ? JSONDecoder ( ) . decode ( [ String : Bool ] . self , from : rawFeatures ) {
2023-05-20 09:24:09 -07:00
for rawFeature in rawFeatures {
2023-05-27 21:53:04 -07:00
if let feature = Feature . allCases . first ( where : { feature in String ( describing : feature ) = = rawFeature . key } ) {
2023-05-24 21:01:11 -07:00
features [ feature ] = rawFeature . value
2023-05-20 09:24:09 -07:00
} else {
print ( " Unknown unstable feature: \( rawFeature . key ) = \( rawFeature . value ) " )
}
}
2023-05-24 21:01:11 -07:00
2023-05-20 09:24:09 -07:00
save ( load : true )
} else {
print ( " Setting all unstable features to false since we couldn't load them from UserDefaults (either they were never saved or there was an error decoding JSON) " )
2023-05-27 21:53:04 -07:00
for feature in Feature . allCases {
2023-05-24 21:01:11 -07:00
features [ feature ] = false
2023-05-20 09:24:09 -07:00
}
save ( )
}
}
private static func save ( load : Bool = false ) {
var rawFeatures : [ String : Bool ] = [ : ]
2023-05-24 21:01:11 -07:00
for feature in features {
2023-05-20 09:24:09 -07:00
rawFeatures [ String ( describing : feature . key ) ] = feature . value
}
UserDefaults . shared . unstableFeatures = try ! JSONEncoder ( ) . encode ( rawFeatures )
print ( " \( load ? " Loaded " : " Saved " ) unstable features: \( String ( describing : rawFeatures ) ) " )
}
2023-05-27 21:53:04 -07:00
static func set ( _ feature : Feature , enabled : Bool ) {
2023-05-24 21:01:11 -07:00
features [ feature ] = enabled
2023-05-27 21:53:04 -07:00
// L e t ' s s a v e b e f o r e r u n n i n g t h e h o o k s . . . t h e y m i g h t c r a s h t h e a p p o r s o m e t h i n g
2023-05-20 09:24:09 -07:00
save ( )
2023-05-27 21:53:04 -07:00
// S h o u l d b e n o - o p f o r f e a t u r e s w i t h t h e d e f a u l t h o o k s ( t h e y d o n o t h i n g )
if enabled {
feature . metadata . onEnable ( )
} else {
feature . metadata . onDisable ( )
}
2023-05-20 09:24:09 -07:00
}
2023-05-20 14:23:25 -07:00
#endif
2023-05-20 09:24:09 -07:00
2023-05-20 14:28:33 -07:00
@ inline ( __always ) // h o p e f u l l y t h i s w i l l h e l p t h e c o m p i l e r r e a l i z e t h a t i f s t a t e m e n t s t h a t u s e t h i s f u n c t i o n s h o u l d b e r e m o v e d o n n o n - u n s t a b l e b u i l d s
2023-05-27 21:53:04 -07:00
static func enabled ( _ feature : Feature ) -> Bool {
2023-05-20 09:24:09 -07:00
#if UNSTABLE
2023-05-24 21:01:11 -07:00
features [ feature ] ? ? false
2023-05-20 09:24:09 -07:00
#else
false
#endif
}
}