Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Create shared preferences API (#4654)

authored by

Hailey and committed by
GitHub
83e8522e 2397104a

+722 -81
+31
__e2e__/flows/shared-prefs.yml
··· 1 + appId: xyz.blueskyweb.app 2 + --- 3 + - runScript: 4 + file: ../setupServer.js 5 + env: 6 + SERVER_PATH: "?users&posts&feeds" 7 + - runFlow: 8 + file: ../setupApp.yml 9 + - tapOn: 10 + id: "e2eSignInAlice" 11 + - tapOn: "/sys/debug" 12 + - tapOn: 13 + id: "sharedPrefsTestOpenBtn" 14 + - tapOn: 15 + id: "setStringBtn" 16 + - assertVisible: "Hello" 17 + - tapOn: 18 + id: "removeStringBtn" 19 + - assertVisible: "null" 20 + - tapOn: 21 + id: "setBoolBtn" 22 + - assertVisible: "true" 23 + - tapOn: 24 + id: "setNumberBtn" 25 + - assertVisible: "123" 26 + - tapOn: 27 + id: "addToSetBtn" 28 + - assertVisible: "true" 29 + - tapOn: 30 + id: "removeFromSetBtn" 31 + - assertVisible: "false"
-11
modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/deviceprefs/ExpoBlueskyDevicePrefsModule.kt
··· 1 - package expo.modules.blueskyswissarmy.deviceprefs 2 - 3 - import expo.modules.kotlin.modules.Module 4 - import expo.modules.kotlin.modules.ModuleDefinition 5 - 6 - class ExpoBlueskyDevicePrefsModule : Module() { 7 - override fun definition() = 8 - ModuleDefinition { 9 - Name("ExpoBlueskyDevicePrefs") 10 - } 11 - }
+69
modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/sharedprefs/ExpoBlueskySharedPrefsModule.kt
··· 1 + package expo.modules.blueskyswissarmy.sharedprefs 2 + 3 + import android.content.Context 4 + import android.util.Log 5 + import expo.modules.kotlin.jni.JavaScriptValue 6 + import expo.modules.kotlin.modules.Module 7 + import expo.modules.kotlin.modules.ModuleDefinition 8 + 9 + class ExpoBlueskySharedPrefsModule : Module() { 10 + private fun getContext(): Context { 11 + val context = appContext.reactContext ?: throw Error("Context is null") 12 + return context 13 + } 14 + 15 + override fun definition() = 16 + ModuleDefinition { 17 + Name("ExpoBlueskySharedPrefs") 18 + 19 + Function("setString") { key: String, value: String -> 20 + return@Function SharedPrefs(getContext()).setValue(key, value) 21 + } 22 + 23 + Function("setValue") { key: String, value: JavaScriptValue -> 24 + val context = getContext() 25 + Log.d("ExpoBlueskySharedPrefs", "Setting value for key: $key") 26 + try { 27 + if (value.isNumber()) { 28 + SharedPrefs(context).setValue(key, value.getFloat()) 29 + } else if (value.isBool()) { 30 + SharedPrefs(context).setValue(key, value.getBool()) 31 + } else if (value.isNull() || value.isUndefined()) { 32 + SharedPrefs(context).removeValue(key) 33 + } else { 34 + Log.d(NAME, "Unsupported type: ${value.kind()}") 35 + } 36 + } catch (e: Error) { 37 + Log.d(NAME, "Error setting value: $e") 38 + } 39 + } 40 + 41 + Function("removeValue") { key: String -> 42 + return@Function SharedPrefs(getContext()).removeValue(key) 43 + } 44 + 45 + Function("getString") { key: String -> 46 + return@Function SharedPrefs(getContext()).getString(key) 47 + } 48 + 49 + Function("getNumber") { key: String -> 50 + return@Function SharedPrefs(getContext()).getFloat(key) 51 + } 52 + 53 + Function("getBool") { key: String -> 54 + return@Function SharedPrefs(getContext()).getBoolean(key) 55 + } 56 + 57 + Function("addToSet") { key: String, value: String -> 58 + return@Function SharedPrefs(getContext()).addToSet(key, value) 59 + } 60 + 61 + Function("removeFromSet") { key: String, value: String -> 62 + return@Function SharedPrefs(getContext()).removeFromSet(key, value) 63 + } 64 + 65 + Function("setContains") { key: String, value: String -> 66 + return@Function SharedPrefs(getContext()).setContains(key, value) 67 + } 68 + } 69 + }
+241
modules/expo-bluesky-swiss-army/android/src/main/java/expo/modules/blueskyswissarmy/sharedprefs/SharedPrefs.kt
··· 1 + package expo.modules.blueskyswissarmy.sharedprefs 2 + 3 + import android.content.Context 4 + import android.content.SharedPreferences 5 + import android.util.Log 6 + 7 + val DEFAULTS = 8 + mapOf<String, Any>( 9 + "playSoundChat" to true, 10 + "playSoundFollow" to false, 11 + "playSoundLike" to false, 12 + "playSoundMention" to false, 13 + "playSoundQuote" to false, 14 + "playSoundReply" to false, 15 + "playSoundRepost" to false, 16 + "badgeCount" to 0, 17 + ) 18 + 19 + const val NAME = "SharedPrefs" 20 + 21 + class SharedPrefs( 22 + private val context: Context, 23 + ) { 24 + companion object { 25 + private var hasInitialized = false 26 + 27 + private var instance: SharedPreferences? = null 28 + 29 + fun getInstance( 30 + context: Context, 31 + info: String? = "(no info)", 32 + ): SharedPreferences { 33 + if (instance == null) { 34 + Log.d(NAME, "No preferences instance found, creating one.") 35 + instance = context.getSharedPreferences("xyz.blueskyweb.app", Context.MODE_PRIVATE) 36 + } 37 + 38 + val safeInstance = instance ?: throw Error("Preferences is null: $info") 39 + 40 + if (!hasInitialized) { 41 + Log.d(NAME, "Preferences instance has not been initialized yet.") 42 + initialize(safeInstance) 43 + hasInitialized = true 44 + Log.d(NAME, "Preferences instance has been initialized.") 45 + } 46 + 47 + return safeInstance 48 + } 49 + 50 + private fun initialize(instance: SharedPreferences) { 51 + instance 52 + .edit() 53 + .apply { 54 + DEFAULTS.forEach { (key, value) -> 55 + if (instance.contains(key)) { 56 + return@forEach 57 + } 58 + 59 + when (value) { 60 + is Boolean -> { 61 + putBoolean(key, value) 62 + } 63 + 64 + is String -> { 65 + putString(key, value) 66 + } 67 + 68 + is Array<*> -> { 69 + putStringSet(key, value.map { it.toString() }.toSet()) 70 + } 71 + 72 + is Map<*, *> -> { 73 + putStringSet(key, value.map { it.toString() }.toSet()) 74 + } 75 + } 76 + } 77 + }.apply() 78 + } 79 + } 80 + 81 + fun setValue( 82 + key: String, 83 + value: String, 84 + ) { 85 + val safeInstance = getInstance(context) 86 + safeInstance 87 + .edit() 88 + .apply { 89 + putString(key, value) 90 + }.apply() 91 + } 92 + 93 + fun setValue( 94 + key: String, 95 + value: Float, 96 + ) { 97 + val safeInstance = getInstance(context) 98 + safeInstance 99 + .edit() 100 + .apply { 101 + putFloat(key, value) 102 + }.apply() 103 + } 104 + 105 + fun setValue( 106 + key: String, 107 + value: Boolean, 108 + ) { 109 + val safeInstance = getInstance(context) 110 + safeInstance 111 + .edit() 112 + .apply { 113 + putBoolean(key, value) 114 + }.apply() 115 + } 116 + 117 + fun setValue( 118 + key: String, 119 + value: Set<String>, 120 + ) { 121 + val safeInstance = getInstance(context) 122 + safeInstance 123 + .edit() 124 + .apply { 125 + putStringSet(key, value) 126 + }.apply() 127 + } 128 + 129 + fun removeValue(key: String) { 130 + val safeInstance = getInstance(context) 131 + safeInstance 132 + .edit() 133 + .apply { 134 + remove(key) 135 + }.apply() 136 + } 137 + 138 + fun getString(key: String): String? { 139 + val safeInstance = getInstance(context) 140 + return safeInstance.getString(key, null) 141 + } 142 + 143 + fun getFloat(key: String): Float? { 144 + val safeInstance = getInstance(context) 145 + if (!safeInstance.contains(key)) { 146 + return null 147 + } 148 + return safeInstance.getFloat(key, 0.0f) 149 + } 150 + 151 + @Suppress("ktlint:standard:function-naming") 152 + fun _setAnyValue( 153 + key: String, 154 + value: Any, 155 + ) { 156 + val safeInstance = getInstance(context) 157 + safeInstance 158 + .edit() 159 + .apply { 160 + when (value) { 161 + is String -> putString(key, value) 162 + is Float -> putFloat(key, value) 163 + is Boolean -> putBoolean(key, value) 164 + is Set<*> -> putStringSet(key, value.map { it.toString() }.toSet()) 165 + else -> throw Error("Unsupported type: ${value::class.java}") 166 + } 167 + }.apply() 168 + } 169 + 170 + fun getBoolean(key: String): Boolean? { 171 + val safeInstance = getInstance(context) 172 + if (!safeInstance.contains(key)) { 173 + return null 174 + } 175 + Log.d(NAME, "Getting boolean for key: $key") 176 + val res = safeInstance.getBoolean(key, false) 177 + Log.d(NAME, "Got boolean for key: $key, value: $res") 178 + return res 179 + } 180 + 181 + fun addToSet( 182 + key: String, 183 + value: String, 184 + ) { 185 + val safeInstance = getInstance(context) 186 + val set = safeInstance.getStringSet(key, setOf()) ?: setOf() 187 + val newSet = 188 + set.toMutableSet().apply { 189 + add(value) 190 + } 191 + safeInstance 192 + .edit() 193 + .apply { 194 + putStringSet(key, newSet) 195 + }.apply() 196 + } 197 + 198 + fun removeFromSet( 199 + key: String, 200 + value: String, 201 + ) { 202 + val safeInstance = getInstance(context) 203 + val set = safeInstance.getStringSet(key, setOf()) ?: setOf() 204 + val newSet = 205 + set.toMutableSet().apply { 206 + remove(value) 207 + } 208 + safeInstance 209 + .edit() 210 + .apply { 211 + putStringSet(key, newSet) 212 + }.apply() 213 + } 214 + 215 + fun setContains( 216 + key: String, 217 + value: String, 218 + ): Boolean { 219 + val safeInstance = getInstance(context) 220 + val set = safeInstance.getStringSet(key, setOf()) ?: setOf() 221 + return set.contains(value) 222 + } 223 + 224 + fun hasValue(key: String): Boolean { 225 + val safeInstance = getInstance(context) 226 + return safeInstance.contains(key) 227 + } 228 + 229 + fun getValues(keys: Set<String>): Map<String, Any?> { 230 + val safeInstance = getInstance(context) 231 + return keys.associateWith { key -> 232 + when (val value = safeInstance.all[key]) { 233 + is String -> value 234 + is Float -> value 235 + is Boolean -> value 236 + is Set<*> -> value 237 + else -> null 238 + } 239 + } 240 + } 241 + }
+2 -2
modules/expo-bluesky-swiss-army/expo-module.config.json
··· 1 1 { 2 2 "platforms": ["ios", "tvos", "android", "web"], 3 3 "ios": { 4 - "modules": ["ExpoBlueskyDevicePrefsModule", "ExpoBlueskyReferrerModule"] 4 + "modules": ["ExpoBlueskySharedPrefsModule", "ExpoBlueskyReferrerModule"] 5 5 }, 6 6 "android": { 7 7 "modules": [ 8 - "expo.modules.blueskyswissarmy.deviceprefs.ExpoBlueskyDevicePrefsModule", 8 + "expo.modules.blueskyswissarmy.sharedprefs.ExpoBlueskySharedPrefsModule", 9 9 "expo.modules.blueskyswissarmy.referrer.ExpoBlueskyReferrerModule" 10 10 ] 11 11 }
+2 -2
modules/expo-bluesky-swiss-army/index.ts
··· 1 - import * as DevicePrefs from './src/DevicePrefs' 2 1 import * as Referrer from './src/Referrer' 2 + import * as SharedPrefs from './src/SharedPrefs' 3 3 4 - export {DevicePrefs, Referrer} 4 + export {Referrer, SharedPrefs}
-23
modules/expo-bluesky-swiss-army/ios/DevicePrefs/ExpoBlueskyDevicePrefsModule.swift
··· 1 - import ExpoModulesCore 2 - 3 - public class ExpoBlueskyDevicePrefsModule: Module { 4 - func getDefaults(_ useAppGroup: Bool) -> UserDefaults? { 5 - if useAppGroup { 6 - return UserDefaults(suiteName: "group.app.bsky") 7 - } else { 8 - return UserDefaults.standard 9 - } 10 - } 11 - 12 - public func definition() -> ModuleDefinition { 13 - Name("ExpoBlueskyDevicePrefs") 14 - 15 - AsyncFunction("getStringValueAsync") { (key: String, useAppGroup: Bool) in 16 - return self.getDefaults(useAppGroup)?.string(forKey: key) 17 - } 18 - 19 - AsyncFunction("setStringValueAsync") { (key: String, value: String?, useAppGroup: Bool) in 20 - self.getDefaults(useAppGroup)?.setValue(value, forKey: key) 21 - } 22 - } 23 - }
+62
modules/expo-bluesky-swiss-army/ios/SharedPrefs/ExpoBlueskySharedPrefsModule.swift
··· 1 + import Foundation 2 + import ExpoModulesCore 3 + 4 + public class ExpoBlueskySharedPrefsModule: Module { 5 + let defaults = UserDefaults(suiteName: "group.app.bsky") 6 + 7 + func getDefaults(_ info: String = "(no info)") -> UserDefaults? { 8 + guard let defaults = self.defaults else { 9 + NSLog("Failed to get defaults for app group: \(info)") 10 + return nil 11 + } 12 + return defaults 13 + } 14 + 15 + public func definition() -> ModuleDefinition { 16 + Name("ExpoBlueskySharedPrefs") 17 + 18 + // JavaScripValue causes a crash when trying to check `isString()`. Let's 19 + // explicitly define setString instead. 20 + Function("setString") { (key: String, value: String?) in 21 + SharedPrefs.shared.setValue(key, value) 22 + } 23 + 24 + Function("setValue") { (key: String, value: JavaScriptValue) in 25 + if value.isNumber() { 26 + SharedPrefs.shared.setValue(key, value.getDouble()) 27 + } else if value.isBool() { 28 + SharedPrefs.shared.setValue(key, value.getBool()) 29 + } else if value.isNull() || value.isUndefined() { 30 + SharedPrefs.shared.removeValue(key) 31 + } 32 + } 33 + 34 + Function("removeValue") { (key: String) in 35 + SharedPrefs.shared.removeValue(key) 36 + } 37 + 38 + Function("getString") { (key: String) in 39 + return SharedPrefs.shared.getString(key) 40 + } 41 + 42 + Function("getBool") { (key: String) in 43 + return SharedPrefs.shared.getBool(key) 44 + } 45 + 46 + Function("getNumber") { (key: String) in 47 + return SharedPrefs.shared.getNumber(key) 48 + } 49 + 50 + Function("addToSet") { (key: String, value: String) in 51 + SharedPrefs.shared.addToSet(key, value) 52 + } 53 + 54 + Function("removeFromSet") { (key: String, value: String) in 55 + SharedPrefs.shared.removeFromSet(key, value) 56 + } 57 + 58 + Function("setContains") { (key: String, value: String) in 59 + return SharedPrefs.shared.setContains(key, value) 60 + } 61 + } 62 + }
+89
modules/expo-bluesky-swiss-army/ios/SharedPrefs/SharedPrefs.swift
··· 1 + import Foundation 2 + 3 + public class SharedPrefs { 4 + public static let shared = SharedPrefs() 5 + 6 + private let defaults = UserDefaults(suiteName: "group.app.bsky") 7 + 8 + init() { 9 + if defaults == nil { 10 + NSLog("Failed to get user defaults for app group.") 11 + } 12 + } 13 + 14 + private func getDefaults(_ info: String = "(no info)") -> UserDefaults? { 15 + guard let defaults = self.defaults else { 16 + NSLog("Failed to get defaults for app group: \(info)") 17 + return nil 18 + } 19 + return defaults 20 + } 21 + 22 + public func setValue(_ key: String, _ value: String?) { 23 + getDefaults(key)?.setValue(value, forKey: key) 24 + } 25 + 26 + public func setValue(_ key: String, _ value: Double?) { 27 + getDefaults(key)?.setValue(value, forKey: key) 28 + } 29 + 30 + public func setValue(_ key: String, _ value: Bool?) { 31 + getDefaults(key)?.setValue(value, forKey: key) 32 + } 33 + 34 + public func _setAnyValue(_ key: String, _ value: Any?) { 35 + getDefaults(key)?.setValue(value, forKey: key) 36 + } 37 + 38 + public func removeValue(_ key: String) { 39 + getDefaults(key)?.removeObject(forKey: key) 40 + } 41 + 42 + public func getString(_ key: String) -> String? { 43 + return getDefaults(key)?.string(forKey: key) 44 + } 45 + 46 + public func getNumber(_ key: String) -> Double? { 47 + return getDefaults(key)?.double(forKey: key) 48 + } 49 + 50 + public func getBool(_ key: String) -> Bool? { 51 + return getDefaults(key)?.bool(forKey: key) 52 + } 53 + 54 + public func addToSet(_ key: String, _ value: String) { 55 + var dict: [String: Bool]? 56 + if var currDict = getDefaults(key)?.dictionary(forKey: key) as? [String: Bool] { 57 + currDict[value] = true 58 + dict = currDict 59 + } else { 60 + dict = [ 61 + value: true 62 + ] 63 + } 64 + getDefaults(key)?.setValue(dict, forKey: key) 65 + } 66 + 67 + public func removeFromSet(_ key: String, _ value: String) { 68 + guard var dict = getDefaults(key)?.dictionary(forKey: key) as? [String: Bool] else { 69 + return 70 + } 71 + dict.removeValue(forKey: value) 72 + getDefaults(key)?.setValue(dict, forKey: key) 73 + } 74 + 75 + public func setContains(_ key: String, _ value: String) -> Bool { 76 + guard let dict = getDefaults(key)?.dictionary(forKey: key) as? [String: Bool] else { 77 + return false 78 + } 79 + return dict[value] == true 80 + } 81 + 82 + public func hasValue(_ key: String) -> Bool { 83 + return getDefaults(key)?.value(forKey: key) != nil 84 + } 85 + 86 + public func getValues(_ keys: [String]) -> [String: Any?]? { 87 + return getDefaults("keys:\(keys)")?.dictionaryWithValues(forKeys: keys) 88 + } 89 + }
-18
modules/expo-bluesky-swiss-army/src/DevicePrefs/index.ios.ts
··· 1 - import {requireNativeModule} from 'expo-modules-core' 2 - 3 - const NativeModule = requireNativeModule('ExpoBlueskyDevicePrefs') 4 - 5 - export function getStringValueAsync( 6 - key: string, 7 - useAppGroup?: boolean, 8 - ): Promise<string | null> { 9 - return NativeModule.getStringValueAsync(key, useAppGroup) 10 - } 11 - 12 - export function setStringValueAsync( 13 - key: string, 14 - value: string | null, 15 - useAppGroup?: boolean, 16 - ): Promise<void> { 17 - return NativeModule.setStringValueAsync(key, value, useAppGroup) 18 - }
-16
modules/expo-bluesky-swiss-army/src/DevicePrefs/index.ts
··· 1 - import {NotImplementedError} from '../NotImplemented' 2 - 3 - export function getStringValueAsync( 4 - key: string, 5 - useAppGroup?: boolean, 6 - ): Promise<string | null> { 7 - throw new NotImplementedError({key, useAppGroup}) 8 - } 9 - 10 - export function setStringValueAsync( 11 - key: string, 12 - value: string | null, 13 - useAppGroup?: boolean, 14 - ): Promise<string | null> { 15 - throw new NotImplementedError({key, value, useAppGroup}) 16 - }
+51
modules/expo-bluesky-swiss-army/src/SharedPrefs/index.native.ts
··· 1 + import {requireNativeModule} from 'expo-modules-core' 2 + 3 + const NativeModule = requireNativeModule('ExpoBlueskySharedPrefs') 4 + 5 + export function setValue( 6 + key: string, 7 + value: string | number | boolean | null | undefined, 8 + ): void { 9 + // A bug on Android causes `JavaScripValue.isString()` to cause a crash on some occasions, seemingly because of a 10 + // memory violation. Instead, we will use a specific function to set strings on this platform. 11 + if (typeof value === 'string') { 12 + return NativeModule.setString(key, value) 13 + } 14 + return NativeModule.setValue(key, value) 15 + } 16 + 17 + export function removeValue(key: string): void { 18 + return NativeModule.removeValue(key) 19 + } 20 + 21 + export function getString(key: string): string | undefined { 22 + return nullToUndefined(NativeModule.getString(key)) 23 + } 24 + 25 + export function getNumber(key: string): number | undefined { 26 + return nullToUndefined(NativeModule.getNumber(key)) 27 + } 28 + 29 + export function getBool(key: string): boolean | undefined { 30 + return nullToUndefined(NativeModule.getBool(key)) 31 + } 32 + 33 + export function addToSet(key: string, value: string): void { 34 + return NativeModule.addToSet(key, value) 35 + } 36 + 37 + export function removeFromSet(key: string, value: string): void { 38 + return NativeModule.removeFromSet(key, value) 39 + } 40 + 41 + export function setContains(key: string, value: string): boolean { 42 + return NativeModule.setContains(key, value) 43 + } 44 + 45 + // iOS returns `null` if a value does not exist, and Android returns `undefined. Normalize these here for JS types 46 + function nullToUndefined(value: any) { 47 + if (value == null) { 48 + return undefined 49 + } 50 + return value 51 + }
+36
modules/expo-bluesky-swiss-army/src/SharedPrefs/index.ts
··· 1 + import {NotImplementedError} from '../NotImplemented' 2 + 3 + export function setValue( 4 + key: string, 5 + value: string | number | boolean | null | undefined, 6 + ): void { 7 + throw new NotImplementedError({key, value}) 8 + } 9 + 10 + export function removeValue(key: string): void { 11 + throw new NotImplementedError({key}) 12 + } 13 + 14 + export function getString(key: string): string | null { 15 + throw new NotImplementedError({key}) 16 + } 17 + 18 + export function getNumber(key: string): number | null { 19 + throw new NotImplementedError({key}) 20 + } 21 + 22 + export function getBool(key: string): boolean | null { 23 + throw new NotImplementedError({key}) 24 + } 25 + 26 + export function addToSet(key: string, value: string): void { 27 + throw new NotImplementedError({key, value}) 28 + } 29 + 30 + export function removeFromSet(key: string, value: string): void { 31 + throw new NotImplementedError({key, value}) 32 + } 33 + 34 + export function setContains(key: string, value: string): boolean { 35 + throw new NotImplementedError({key, value}) 36 + }
+1
package.json
··· 34 34 "typecheck": "tsc --project ./tsconfig.check.json", 35 35 "e2e:mock-server": "./jest/dev-infra/with-test-redis-and-db.sh ts-node --project tsconfig.e2e.json __e2e__/mock-server.ts", 36 36 "e2e:metro": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios", 37 + "e2e:metro-android": "EXPO_PUBLIC_ENV=e2e NODE_ENV=test RN_SRC_EXT=e2e.ts,e2e.tsx expo run:android", 37 38 "e2e:run": "maestro test __e2e__", 38 39 "perf:test": "NODE_ENV=test maestro test", 39 40 "perf:test:run": "NODE_ENV=test maestro test __e2e__/perf-test.yml",
+6
src/Navigation.tsx
··· 39 39 import {PreferencesFollowingFeed} from 'view/screens/PreferencesFollowingFeed' 40 40 import {PreferencesThreads} from 'view/screens/PreferencesThreads' 41 41 import {SavedFeeds} from 'view/screens/SavedFeeds' 42 + import {SharedPreferencesTesterScreen} from '#/screens/E2E/SharedPreferencesTesterScreen' 42 43 import HashtagScreen from '#/screens/Hashtag' 43 44 import {ModerationScreen} from '#/screens/Moderation' 44 45 import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers' ··· 232 233 name="DebugMod" 233 234 getComponent={() => DebugModScreen} 234 235 options={{title: title(msg`Moderation states`), requireAuth: true}} 236 + /> 237 + <Stack.Screen 238 + name="SharedPreferencesTester" 239 + getComponent={() => SharedPreferencesTesterScreen} 240 + options={{title: title(msg`Shared Preferences Tester`)}} 235 241 /> 236 242 <Stack.Screen 237 243 name="Log"
+5 -9
src/components/hooks/useStarterPackEntry.native.ts
··· 7 7 import {isAndroid} from 'platform/detection' 8 8 import {useHasCheckedForStarterPack} from 'state/preferences/used-starter-packs' 9 9 import {useSetActiveStarterPack} from 'state/shell/starter-pack' 10 - import {DevicePrefs, Referrer} from '../../../modules/expo-bluesky-swiss-army' 10 + import {Referrer, SharedPrefs} from '../../../modules/expo-bluesky-swiss-army' 11 11 12 12 export function useStarterPackEntry() { 13 13 const [ready, setReady] = React.useState(false) ··· 39 39 uri = createStarterPackLinkFromAndroidReferrer(res.installReferrer) 40 40 } 41 41 } else { 42 - const res = await DevicePrefs.getStringValueAsync( 43 - 'starterPackUri', 44 - true, 45 - ) 46 - 47 - if (res) { 48 - uri = httpStarterPackUriToAtUri(res) 49 - DevicePrefs.setStringValueAsync('starterPackUri', null, true) 42 + const starterPackUri = SharedPrefs.getString('starterPackUri') 43 + if (starterPackUri) { 44 + uri = httpStarterPackUriToAtUri(starterPackUri) 45 + SharedPrefs.setValue('starterPackUri', null) 50 46 } 51 47 } 52 48
+1
src/lib/routes/types.ts
··· 25 25 ProfileLabelerLikedBy: {name: string} 26 26 Debug: undefined 27 27 DebugMod: undefined 28 + SharedPreferencesTester: undefined 28 29 Log: undefined 29 30 Support: undefined 30 31 PrivacyPolicy: undefined
+113
src/screens/E2E/SharedPreferencesTesterScreen.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + 4 + import {ScrollView} from 'view/com/util/Views' 5 + import {atoms as a} from '#/alf' 6 + import {Button, ButtonText} from '#/components/Button' 7 + import {Text} from '#/components/Typography' 8 + import {SharedPrefs} from '../../../modules/expo-bluesky-swiss-army' 9 + 10 + export function SharedPreferencesTesterScreen() { 11 + const [currentTestOutput, setCurrentTestOutput] = React.useState<string>('') 12 + 13 + return ( 14 + <ScrollView contentContainerStyle={{backgroundColor: 'red'}}> 15 + <View style={[a.flex_1]}> 16 + <View> 17 + <Text testID="testOutput">{currentTestOutput}</Text> 18 + </View> 19 + <View style={[a.flex_wrap]}> 20 + <Button 21 + label="btn" 22 + testID="setStringBtn" 23 + style={[a.self_center]} 24 + variant="solid" 25 + color="primary" 26 + size="xsmall" 27 + onPress={async () => { 28 + SharedPrefs.removeValue('testerString') 29 + SharedPrefs.setValue('testerString', 'Hello') 30 + const str = SharedPrefs.getString('testerString') 31 + console.log(JSON.stringify(str)) 32 + setCurrentTestOutput(`${str}`) 33 + }}> 34 + <ButtonText>Set String</ButtonText> 35 + </Button> 36 + <Button 37 + label="btn" 38 + testID="removeStringBtn" 39 + style={[a.self_center]} 40 + variant="solid" 41 + color="primary" 42 + size="xsmall" 43 + onPress={async () => { 44 + SharedPrefs.removeValue('testerString') 45 + const str = SharedPrefs.getString('testerString') 46 + setCurrentTestOutput(`${str}`) 47 + }}> 48 + <ButtonText>Remove String</ButtonText> 49 + </Button> 50 + <Button 51 + label="btn" 52 + testID="setBoolBtn" 53 + style={[a.self_center]} 54 + variant="solid" 55 + color="primary" 56 + size="xsmall" 57 + onPress={async () => { 58 + SharedPrefs.removeValue('testerBool') 59 + SharedPrefs.setValue('testerBool', true) 60 + const bool = SharedPrefs.getBool('testerBool') 61 + setCurrentTestOutput(`${bool}`) 62 + }}> 63 + <ButtonText>Set Bool</ButtonText> 64 + </Button> 65 + <Button 66 + label="btn" 67 + testID="setNumberBtn" 68 + style={[a.self_center]} 69 + variant="solid" 70 + color="primary" 71 + size="xsmall" 72 + onPress={async () => { 73 + SharedPrefs.removeValue('testerNumber') 74 + SharedPrefs.setValue('testerNumber', 123) 75 + const num = SharedPrefs.getNumber('testerNumber') 76 + setCurrentTestOutput(`${num}`) 77 + }}> 78 + <ButtonText>Set Number</ButtonText> 79 + </Button> 80 + <Button 81 + label="btn" 82 + testID="addToSetBtn" 83 + style={[a.self_center]} 84 + variant="solid" 85 + color="primary" 86 + size="xsmall" 87 + onPress={async () => { 88 + SharedPrefs.removeFromSet('testerSet', 'Hello!') 89 + SharedPrefs.addToSet('testerSet', 'Hello!') 90 + const contains = SharedPrefs.setContains('testerSet', 'Hello!') 91 + setCurrentTestOutput(`${contains}`) 92 + }}> 93 + <ButtonText>Add to Set</ButtonText> 94 + </Button> 95 + <Button 96 + label="btn" 97 + testID="removeFromSetBtn" 98 + style={[a.self_center]} 99 + variant="solid" 100 + color="primary" 101 + size="xsmall" 102 + onPress={async () => { 103 + SharedPrefs.removeFromSet('testerSet', 'Hello!') 104 + const contains = SharedPrefs.setContains('testerSet', 'Hello!') 105 + setCurrentTestOutput(`${contains}`) 106 + }}> 107 + <ButtonText>Remove from Set</ButtonText> 108 + </Button> 109 + </View> 110 + </View> 111 + </ScrollView> 112 + ) 113 + }
+13
src/view/screens/Storybook/Dialogs.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 + import {useNavigation} from '@react-navigation/native' 3 4 4 5 import {useDialogStateControlContext} from '#/state/dialogs' 6 + import {NavigationProp} from 'lib/routes/types' 5 7 import {atoms as a} from '#/alf' 6 8 import {Button, ButtonText} from '#/components/Button' 7 9 import * as Dialog from '#/components/Dialog' ··· 18 20 const [shouldRenderUnmountTest, setShouldRenderUnmountTest] = 19 21 React.useState(false) 20 22 const unmountTestInterval = React.useRef<number>() 23 + const navigation = useNavigation<NavigationProp>() 21 24 22 25 const onUnmountTestStartPressWithClose = () => { 23 26 setShouldRenderUnmountTest(true) ··· 132 135 onPress={onUnmountTestEndPress} 133 136 label="two"> 134 137 <ButtonText>End Unmount Test</ButtonText> 138 + </Button> 139 + 140 + <Button 141 + variant="solid" 142 + color="primary" 143 + size="small" 144 + onPress={() => navigation.navigate('SharedPreferencesTester')} 145 + label="two" 146 + testID="sharedPrefsTestOpenBtn"> 147 + <ButtonText>Open Shared Prefs Tester</ButtonText> 135 148 </Button> 136 149 137 150 <Prompt.Outer control={prompt}>