Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion .github/workflows/react-native-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,48 @@ jobs:

- name: Build macOS
working-directory: react-native
run: yarn build:macos
run: yarn build:macos

build-windows:
name: Build Windows
runs-on: windows-2022
needs: lint
timeout-minutes: 40

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
cache-dependency-path: react-native/yarn.lock

- name: Install dependencies
working-directory: react-native
run: yarn install --frozen-lockfile

- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v2
with:
msbuild-architecture: x64

- name: Setup Visual Studio
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64

- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: |
~\.nuget\packages
react-native\windows\packages
key: nuget-${{ runner.os }}-${{ hashFiles('react-native/windows/**/*.csproj', 'react-native/windows/**/*.sln') }}
restore-keys: |
nuget-${{ runner.os }}-

- name: Build Windows
working-directory: react-native
run: yarn build:windows
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ xcuserdata
bazel-*
build-cmake
node_modules/
.dart_tool/
.dart_tool/

# Claude settings
**/.claude/settings.local.json
9 changes: 9 additions & 0 deletions react-native/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,12 @@ yarn-error.log
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Ditto
ditto

# MSBuild logs
*.binlog
msbuild_*.binlog
msbuild_*.err
msbuild_*.wrn
110 changes: 79 additions & 31 deletions react-native/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
DITTO_PLAYGROUND_TOKEN,
DITTO_AUTH_URL,
} from '@env';
import { getDittoInstance, setDittoInstance } from './dittoSingleton';

import Fab from './components/Fab';
import NewTaskModal from './components/NewTaskModal';
Expand Down Expand Up @@ -118,6 +119,32 @@ const App = () => {
};

const initDitto = async () => {
// Check for existing global instance first
const existingInstance = getDittoInstance();
if (existingInstance) {
ditto.current = existingInstance;

// Re-register observers for this component
taskObserver.current = ditto.current.store.registerObserver(
'SELECT * FROM tasks WHERE NOT deleted',
response => {
const fetchedTasks: Task[] = response.items.map(doc => ({
id: doc.value._id,
title: doc.value.title as string,
done: doc.value.done,
deleted: doc.value.deleted,
}));
setTasks(fetchedTasks);
},
);
return;
}

// Prevent multiple Ditto instances
if (ditto.current) {
return;
}

try {
// https://docs.ditto.live/sdk/latest/install-guides/react-native#onlineplayground
const databaseId = DITTO_APP_ID;
Expand All @@ -131,6 +158,7 @@ const App = () => {
const config = new DittoConfig(databaseId, connectConfig, 'custom-folder');

ditto.current = await Ditto.open(config);
setDittoInstance(ditto.current);

if (connectConfig.mode === 'server') {
await ditto.current.auth.setExpirationHandler(async (dittoInstance, timeUntilExpiration) => {
Expand Down Expand Up @@ -183,18 +211,32 @@ const App = () => {
};

useEffect(() => {
let mounted = true;

(async () => {
const granted =
Platform.OS === 'android' ? await requestPermissions() : true;
if (granted) {
if (granted && mounted) {
initDitto();
} else {
} else if (!granted) {
Alert.alert(
'Permission Denied',
'You need to grant all permissions to use this app.',
);
}
})();

// Cleanup function
return () => {
mounted = false;
if (ditto.current) {
console.log('Cleaning up Ditto instance');
ditto.current.stopSync();
taskObserver.current?.cancel();
taskSubscription.current?.cancel();
// Note: We don't set ditto.current to null here to prevent re-initialization
}
};
}, []);

const renderItem = ({item}: {item: Task}) => (
Expand All @@ -215,43 +257,49 @@ const App = () => {

return (
<SafeAreaView style={styles.container}>
<DittoInfo appId={DITTO_APP_ID} token={DITTO_PLAYGROUND_TOKEN} />
<DittoSync value={syncEnabled} onChange={toggleSync} />
<Fab onPress={() => setModalVisible(true)} />
<NewTaskModal
visible={modalVisible}
onRequestClose={() => setModalVisible(false)}
onSubmit={task => {
createTask(task);
setModalVisible(false);
}}
onClose={() => setModalVisible(false)}
/>
<EditTaskModal
visible={editingTask !== null}
task={editingTask}
onSubmit={(taskId, newTitle) => {
updateTaskTitle(taskId, newTitle);
setEditingTask(null);
}}
onClose={() => setEditingTask(null)}
/>
<FlatList
contentContainerStyle={styles.listContainer}
data={tasks}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
<View style={styles.appContainer}>
<DittoInfo appId={DITTO_APP_ID} token={DITTO_PLAYGROUND_TOKEN} />
<DittoSync value={syncEnabled} onChange={toggleSync} />
<Fab onPress={() => setModalVisible(true)} />
<FlatList
contentContainerStyle={styles.listContainer}
data={tasks}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
<NewTaskModal
visible={modalVisible}
onSubmit={task => {
createTask(task);
setModalVisible(false);
}}
onClose={() => setModalVisible(false)}
/>
<EditTaskModal
visible={editingTask !== null}
task={editingTask}
onRequestClose={() => setEditingTask(null)}
onSubmit={(taskId, newTitle) => {
updateTaskTitle(taskId, newTitle);
setEditingTask(null);
}}
onClose={() => setEditingTask(null)}
/>
</View>
</SafeAreaView>
);
};

const styles = StyleSheet.create({
container: {
height: '100%',
padding: 20,
flex: 1,
backgroundColor: '#fff',
},
appContainer: {
flex: 1,
padding: 20,
position: 'relative',
},
listContainer: {
gap: 5,
},
Expand Down
11 changes: 11 additions & 0 deletions react-native/NuGet.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="react-native" value="https://pkgs.dev.azure.com/ms/react-native/_packaging/react-native-public/nuget/v3/index.json" />
<add key="Nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<disabledPackageSources>
<clear />
</disabledPackageSources>
</configuration>
27 changes: 25 additions & 2 deletions react-native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ A sample React Native application that lets you create tasks and sync them with
- **Ditto Portal Account**: Ensure you have a Ditto account. Sign up [here](https://portal.ditto.live/signup).
- **App Credentials**: After registration, create an application within the Ditto Portal to obtain your `AppID`, `Online Playground Token`, `Auth URL`, and `Websocket URL`. Visit the [Ditto Portal](https://portal.ditto.live/) to manage your applications.

### Windows-specific Prerequisites

- **Windows 10 or 11** (version 10.0.19041.0 or higher)
- **Visual Studio 2022** with the following workloads:
- Universal Windows Platform development
- Desktop development with C++
- Node.js development (under Individual Components)
- **Developer Mode**: Enabled in Windows Settings > Update & Security > For developers

## Getting Started

### Install Dependencies
Expand Down Expand Up @@ -49,6 +58,12 @@ For Android:
yarn react-native run-android
```

For Windows:

```bash
yarn windows
```

For macOS:

```bash
Expand All @@ -59,11 +74,19 @@ yarn react-native run-macos

- **Task Creation**: Users can add new tasks to their list.
- **Real-time Sync**: Tasks are synchronized in real-time across all devices using the same Ditto application.
- **Cross-Platform**: Supports iOS, Android, and macOS platforms.
- **Cross-Platform**: Supports iOS, Android, Windows, and macOS platforms.

## Additional Information

- Limitation: React Native's Fast Refresh must be disabled and it's something we're working on fixing.
### Windows-specific Notes

- **NuGet.config**: Required for Windows to resolve React Native Windows packages from the correct sources.
- **Ditto Singleton Pattern**: The app uses a singleton pattern (`dittoSingleton.ts`) to prevent multiple Ditto instances, particularly important for Windows which may remount components more frequently than other platforms.
- **Custom Modal Implementation**: Windows uses a custom modal overlay instead of the native Modal component to ensure proper rendering within window bounds.

### Known Limitations

- React Native's Fast Refresh may cause file lock issues with Ditto on Windows due to component remounting. The singleton pattern mitigates this issue.

## iOS Installation

Expand Down
Loading
Loading