Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
119
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a876aae44ea07494ebea9727350aa060b81f317b 153 lines 3.8 kB view raw
1import {useCallback, useEffect, useState} from 'react' 2import {MMKV} from 'react-native-mmkv' 3 4import {type Account, type Device} from '#/storage/schema' 5 6export * from '#/storage/schema' 7 8/** 9 * Generic storage class. DO NOT use this directly. Instead, use the exported 10 * storage instances below. 11 */ 12export class Storage<Scopes extends unknown[], Schema> { 13 protected sep = ':' 14 protected store: MMKV 15 16 constructor({id}: {id: string}) { 17 this.store = new MMKV({id}) 18 } 19 20 /** 21 * Store a value in storage based on scopes and/or keys 22 * 23 * `set([key], value)` 24 * `set([scope, key], value)` 25 */ 26 set<Key extends keyof Schema>( 27 scopes: [...Scopes, Key], 28 data: Schema[Key], 29 ): void { 30 // stored as `{ data: <value> }` structure to ease stringification 31 this.store.set(scopes.join(this.sep), JSON.stringify({data})) 32 } 33 34 /** 35 * Get a value from storage based on scopes and/or keys 36 * 37 * `get([key])` 38 * `get([scope, key])` 39 */ 40 get<Key extends keyof Schema>( 41 scopes: [...Scopes, Key], 42 ): Schema[Key] | undefined { 43 const res = this.store.getString(scopes.join(this.sep)) 44 if (!res) return undefined 45 // parsed from storage structure `{ data: <value> }` 46 return JSON.parse(res).data 47 } 48 49 /** 50 * Remove a value from storage based on scopes and/or keys 51 * 52 * `remove([key])` 53 * `remove([scope, key])` 54 */ 55 remove<Key extends keyof Schema>(scopes: [...Scopes, Key]) { 56 this.store.delete(scopes.join(this.sep)) 57 } 58 59 /** 60 * Remove many values from the same storage scope by keys 61 * 62 * `removeMany([], [key])` 63 * `removeMany([scope], [key])` 64 */ 65 removeMany<Key extends keyof Schema>(scopes: [...Scopes], keys: Key[]) { 66 keys.forEach(key => this.remove([...scopes, key])) 67 } 68 69 /** 70 * For debugging purposes 71 */ 72 removeAll() { 73 this.store.clearAll() 74 } 75 76 /** 77 * Fires a callback when the storage associated with a given key changes 78 * 79 * @returns Listener - call `remove()` to stop listening 80 */ 81 addOnValueChangedListener<Key extends keyof Schema>( 82 scopes: [...Scopes, Key], 83 callback: () => void, 84 ) { 85 return this.store.addOnValueChangedListener(key => { 86 if (key === scopes.join(this.sep)) { 87 callback() 88 } 89 }) 90 } 91} 92 93type StorageSchema<T extends Storage<any, any>> = 94 T extends Storage<any, infer U> ? U : never 95type StorageScopes<T extends Storage<any, any>> = 96 T extends Storage<infer S, any> ? S : never 97 98/** 99 * Hook to use a storage instance. Acts like a useState hook, but persists the 100 * value in storage. 101 */ 102export function useStorage< 103 Store extends Storage<any, any>, 104 Key extends keyof StorageSchema<Store>, 105>( 106 storage: Store, 107 scopes: [...StorageScopes<Store>, Key], 108): [ 109 StorageSchema<Store>[Key] | undefined, 110 (data: StorageSchema<Store>[Key]) => void, 111] { 112 type Schema = StorageSchema<Store> 113 const [value, setValue] = useState<Schema[Key] | undefined>(() => 114 storage.get(scopes), 115 ) 116 117 useEffect(() => { 118 const sub = storage.addOnValueChangedListener(scopes, () => { 119 setValue(storage.get(scopes)) 120 }) 121 return () => sub.remove() 122 }, [storage, scopes]) 123 124 const setter = useCallback( 125 (data: Schema[Key]) => { 126 setValue(data) 127 storage.set(scopes, data) 128 }, 129 [storage, scopes], 130 ) 131 132 return [value, setter] as const 133} 134 135/** 136 * Device data that's specific to the device and does not vary based on account 137 * 138 * `device.set([key], true)` 139 */ 140export const device = new Storage<[], Device>({id: 'bsky_device'}) 141 142/** 143 * Account data that's specific to the account on this device 144 */ 145export const account = new Storage<[string], Account>({id: 'bsky_account'}) 146 147if (__DEV__ && typeof window !== 'undefined') { 148 // @ts-expect-error - dev global 149 window.bsky_storage = { 150 device, 151 account, 152 } 153}