Skip to content

Commit 1f67936

Browse files
committed
feat: brownie store
1 parent 6195df1 commit 1f67936

19 files changed

Lines changed: 150016 additions & 736 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ DerivedData
2222
*.xcuserstate
2323
project.xcworkspace
2424
**/.xcode.env.local
25+
example/swift/Generated/**
2526

2627
# Android/IntelliJ
2728
#

.yarn/releases/yarn-1.22.22.cjs

Lines changed: 148049 additions & 0 deletions
Large diffs are not rendered by default.

apps/example/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
!.yarn/releases
55
!.yarn/sdks
66
!.yarn/versions
7+
8+
swift/Generated/**

apps/example/App.tsx

Lines changed: 56 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,83 @@
1-
import React, { useEffect } from 'react';
2-
import { StyleSheet, Text, View, Button } from 'react-native';
1+
import React, { useCallback, useSyncExternalStore } from 'react';
2+
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
33
import {
4-
createNativeStackNavigator,
5-
type NativeStackScreenProps,
6-
} from '@react-navigation/native-stack';
7-
import ReactNativeBrownfield from '@callstack/react-native-brownfield';
8-
import { NavigationContainer } from '@react-navigation/native';
4+
subscribe,
5+
getSnapshot,
6+
setState,
7+
} from '@callstack/react-native-brownfield';
8+
import type { BrownfieldStore } from './brownfield-store.schema';
99

10-
const getRandomValue = () => Math.round(Math.random() * 255);
11-
const getRandomTheme = () => {
12-
const primary = [getRandomValue(), getRandomValue(), getRandomValue()];
13-
const secondary = [
14-
255 - (primary?.[0] || 0),
15-
255 - (primary?.[1] || 0),
16-
255 - (primary?.[2] || 0),
17-
];
10+
const STORE_KEY = 'BrownfieldStore';
1811

19-
return {
20-
primary: `rgb(${primary[0]}, ${primary[1]}, ${primary[2]})`,
21-
secondary: `rgb(${secondary[0]}, ${secondary[1]}, ${secondary[2]})`,
22-
};
23-
};
12+
function useBrownfieldStore<T>(key: string): T {
13+
const sub = useCallback(
14+
(listener: () => void) => subscribe(key, listener),
15+
[key]
16+
);
17+
const snap = useCallback(() => getSnapshot<T>(key), [key]);
18+
return useSyncExternalStore(sub, snap, snap);
19+
}
2420

25-
type Props = NativeStackScreenProps<RootStackParamList, 'Home'>;
21+
function HomeScreen() {
22+
const state = useBrownfieldStore<BrownfieldStore>(STORE_KEY);
2623

27-
function HomeScreen({ navigation, route }: Props) {
28-
const colors = route.params?.theme || getRandomTheme();
24+
const handleIncrement = () => {
25+
setState<BrownfieldStore>(STORE_KEY, { counter: state.counter + 1 });
26+
};
2927

30-
useEffect(() => {
31-
const unsubscribe = navigation.addListener('focus', () => {
32-
const isFirstRoute = !navigation.canGoBack();
33-
ReactNativeBrownfield.setNativeBackGestureAndButtonEnabled(isFirstRoute);
34-
});
35-
return unsubscribe;
36-
}, [navigation]);
28+
const handleSetHasError = () => {
29+
setState<BrownfieldStore>(STORE_KEY, { hasError: !state.hasError });
30+
};
3731

3832
return (
39-
<View style={[styles.container, { backgroundColor: colors.primary }]}>
40-
<Text style={[styles.text, { color: colors.secondary }]}>
41-
React Native Screen
33+
<View style={styles.container}>
34+
<Text style={styles.title}>React Native Side</Text>
35+
<Text style={styles.text}>Count: {state.counter}</Text>
36+
<Text style={styles.text}>
37+
Has error: {state.hasError ? 'true' : 'false'}
4238
</Text>
4339

44-
<Button
45-
onPress={() => {
46-
navigation.push('Home', {
47-
theme: getRandomTheme(),
48-
});
49-
}}
50-
color={colors.secondary}
51-
title="Push next screen"
40+
<TextInput
41+
style={styles.input}
42+
value={state.user}
43+
onChangeText={(text) =>
44+
setState<BrownfieldStore>(STORE_KEY, { user: text })
45+
}
46+
placeholder="User name"
5247
/>
5348

54-
<Button
55-
onPress={() => {
56-
if (navigation.canGoBack()) {
57-
navigation.goBack();
58-
} else {
59-
ReactNativeBrownfield.popToNative(true);
60-
}
61-
}}
62-
color={colors.secondary}
63-
title="Go back"
64-
/>
49+
<Button onPress={handleIncrement} title="Increment" />
50+
<Button onPress={handleSetHasError} title="Toggle Has Error" />
6551
</View>
6652
);
6753
}
68-
type RootStackParamList = {
69-
Home: { theme: { primary: string; secondary: string } };
70-
};
71-
72-
const Stack = createNativeStackNavigator<RootStackParamList>();
73-
7454
export default function App() {
75-
return (
76-
<NavigationContainer>
77-
<Stack.Navigator>
78-
<Stack.Screen name="Home" component={HomeScreen} />
79-
</Stack.Navigator>
80-
</NavigationContainer>
81-
);
55+
return <HomeScreen />;
8256
}
8357

8458
const styles = StyleSheet.create({
8559
container: {
8660
flex: 1,
8761
justifyContent: 'center',
8862
alignItems: 'center',
63+
backgroundColor: '#f0f0f0',
8964
},
90-
text: {
91-
fontSize: 30,
65+
title: {
66+
fontSize: 20,
9267
fontWeight: 'bold',
93-
margin: 10,
68+
marginBottom: 20,
69+
},
70+
text: {
71+
fontSize: 16,
72+
margin: 5,
73+
},
74+
input: {
75+
borderWidth: 1,
76+
borderColor: '#ccc',
77+
borderRadius: 8,
78+
padding: 10,
79+
width: 200,
80+
marginVertical: 10,
81+
backgroundColor: '#fff',
9482
},
9583
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export type BrownfieldStore = {
2+
counter: number;
3+
user: string;
4+
isLoading: boolean;
5+
hasError: boolean;
6+
};

apps/example/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,12 @@
2727
},
2828
"engines": {
2929
"node": ">=20"
30+
},
31+
"brownfield": {
32+
"stores": {
33+
"schema": "./brownfield-store.schema.ts",
34+
"typeName": "BrownfieldStore",
35+
"swift": "./swift/Generated/BrownfieldStore.swift"
36+
}
3037
}
3138
}

apps/example/swift/App.swift

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,73 @@
11
import ReactBrownfield
22
import SwiftUI
33

4+
let initialState = BrownfieldStore(counter: 0, hasError: false, isLoading: false, user: "okwasniewski")
5+
46
@main
57
struct MyApp: App {
6-
init() {
7-
ReactNativeBrownfield.shared.startReactNative {
8-
print("loaded")
9-
}
8+
9+
init() {
10+
ReactNativeBrownfield.shared.startReactNative {
11+
print("loaded")
1012
}
1113

12-
var body: some Scene {
13-
WindowGroup {
14-
ContentView()
15-
}
14+
let state = Store(initialState)
15+
StoreManager.shared.register(
16+
store: state,
17+
for: BrownfieldStore.self
18+
)
19+
}
20+
21+
var body: some Scene {
22+
WindowGroup {
23+
ContentView()
1624
}
17-
}
25+
}
1826

19-
struct ContentView: View {
27+
struct ContentView: View {
2028
var body: some View {
21-
NavigationView {
22-
VStack {
23-
Text("React Native Brownfield App")
24-
.font(.title)
25-
.bold()
26-
.padding()
27-
28-
NavigationLink("Push React Native Screen") {
29-
ReactNativeView(moduleName: "ReactNative")
30-
.navigationBarHidden(true)
31-
}
32-
}
33-
}.navigationViewStyle(StackNavigationViewStyle())
29+
VStack(spacing: 0) {
30+
NativeView()
31+
.frame(maxHeight: .infinity)
32+
33+
Divider()
34+
35+
ReactNativeView(moduleName: "ReactNative")
36+
.frame(maxHeight: .infinity)
37+
}
38+
}
39+
}
40+
41+
struct NativeView: View {
42+
@UseStore<BrownfieldStore, String>(\.user) var user
43+
@UseStore<BrownfieldStore, Double>(\.counter) var counter
44+
45+
var body: some View {
46+
VStack {
47+
Text("Native Side")
48+
.font(.headline)
49+
.padding(.top)
50+
51+
Text("User: \(user)")
52+
Text("Count: \(Int(counter))")
53+
54+
TextField("Name", text: Binding(get: { user }, set: { data in
55+
$user.set { state in state.user = data }
56+
}))
57+
.textFieldStyle(.roundedBorder)
58+
.padding(.horizontal)
59+
60+
Button("Increment") {
61+
$counter.set { state in
62+
state.counter += 1
63+
}
64+
}
65+
.buttonStyle(.borderedProminent)
66+
67+
Spacer()
68+
}
69+
.frame(maxWidth: .infinity)
70+
.background(Color(.systemBackground))
3471
}
72+
}
3573
}

apps/example/swift/Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2438,7 +2438,7 @@ PODS:
24382438
- React-perflogger (= 0.82.1)
24392439
- React-utils (= 0.82.1)
24402440
- SocketRocket
2441-
- RNScreens (4.18.0):
2441+
- RNScreens (4.19.0):
24422442
- boost
24432443
- DoubleConversion
24442444
- fast_float
@@ -2465,10 +2465,10 @@ PODS:
24652465
- ReactCodegen
24662466
- ReactCommon/turbomodule/bridging
24672467
- ReactCommon/turbomodule/core
2468-
- RNScreens/common (= 4.18.0)
2468+
- RNScreens/common (= 4.19.0)
24692469
- SocketRocket
24702470
- Yoga
2471-
- RNScreens/common (4.18.0):
2471+
- RNScreens/common (4.19.0):
24722472
- boost
24732473
- DoubleConversion
24742474
- fast_float
@@ -2814,7 +2814,7 @@ SPEC CHECKSUMS:
28142814
ReactBrownfield: 10f9f7370cd8bd6ef30eb9c736d774f9d17565f7
28152815
ReactCodegen: 878add6c7d8ff8cea87697c44d29c03b79b6f2d9
28162816
ReactCommon: 804dc80944fa90b86800b43c871742ec005ca424
2817-
RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479
2817+
RNScreens: ffbb0296608eb3560de641a711bbdb663ed1f6b4
28182818
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
28192819
Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb
28202820

apps/example/swift/SwiftExample.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
2869186323129ED100458242 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2869186223129ED100458242 /* Assets.xcassets */; };
1212
2869186623129ED100458242 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2869186423129ED100458242 /* LaunchScreen.storyboard */; };
1313
AC7E040409DFEE553311B27E /* libPods-SwiftExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CF9653D1882655DA02862F71 /* libPods-SwiftExample.a */; };
14+
BFSTORE001000000000000001 /* BrownfieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFSTORE001000000000000002 /* BrownfieldStore.swift */; };
1415
ED52AE6E1313AAE3C968412D /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 33BE347ABB4EE17F7F99D32D /* PrivacyInfo.xcprivacy */; };
1516
/* End PBXBuildFile section */
1617

@@ -22,6 +23,7 @@
2223
2869186723129ED100458242 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2324
33BE347ABB4EE17F7F99D32D /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
2425
5BE9069F7C8AF5853F650B68 /* Pods-SwiftExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftExample.debug.xcconfig"; path = "Target Support Files/Pods-SwiftExample/Pods-SwiftExample.debug.xcconfig"; sourceTree = "<group>"; };
26+
BFSTORE001000000000000002 /* BrownfieldStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrownfieldStore.swift; sourceTree = "<group>"; };
2527
CF9653D1882655DA02862F71 /* libPods-SwiftExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SwiftExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
2628
E74DC3AFAC0967529ED1C063 /* Pods-SwiftExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftExample.release.xcconfig"; path = "Target Support Files/Pods-SwiftExample/Pods-SwiftExample.release.xcconfig"; sourceTree = "<group>"; };
2729
/* End PBXFileReference section */
@@ -42,6 +44,7 @@
4244
isa = PBXGroup;
4345
children = (
4446
2869185B23129ECF00458242 /* App.swift */,
47+
BFSTORE001000000000000003 /* Generated */,
4548
2869186223129ED100458242 /* Assets.xcassets */,
4649
2869186423129ED100458242 /* LaunchScreen.storyboard */,
4750
2869186723129ED100458242 /* Info.plist */,
@@ -69,6 +72,14 @@
6972
path = Pods;
7073
sourceTree = "<group>";
7174
};
75+
BFSTORE001000000000000003 /* Generated */ = {
76+
isa = PBXGroup;
77+
children = (
78+
BFSTORE001000000000000002 /* BrownfieldStore.swift */,
79+
);
80+
path = Generated;
81+
sourceTree = "<group>";
82+
};
7283
F4733EE8C5738566111C3C12 /* Frameworks */ = {
7384
isa = PBXGroup;
7485
children = (
@@ -249,6 +260,7 @@
249260
buildActionMask = 2147483647;
250261
files = (
251262
2869185C23129ECF00458242 /* App.swift in Sources */,
263+
BFSTORE001000000000000001 /* BrownfieldStore.swift in Sources */,
252264
);
253265
runOnlyForDeploymentPostprocessing = 0;
254266
};

0 commit comments

Comments
 (0)