expo template
12
index.ts
@@ -28,6 +28,15 @@ const project = await p.group(
|
||||
]
|
||||
})
|
||||
,
|
||||
mobile: async () =>
|
||||
await p.select({
|
||||
message: 'Pick a mobile framework.',
|
||||
options: [
|
||||
{ value: "expo", label: "ReactNative + Expo" },
|
||||
{ value: "none", label: "None" }
|
||||
]
|
||||
})
|
||||
,
|
||||
goprefix: () =>
|
||||
p.text({
|
||||
message: "What would you like to use as a go package prefix?",
|
||||
@@ -65,6 +74,9 @@ await Bun.$`bun install`.cwd(path.join(destDir,'packages','rpc')).quiet();
|
||||
if (project.frontend !== "none") {
|
||||
await Bun.$`bun install`.cwd(path.join(destDir, 'apps', 'web')).quiet();
|
||||
}
|
||||
if (project.mobile !== "none") {
|
||||
await Bun.$`bun install`.cwd(path.join(destDir, 'apps', 'mobile')).quiet();
|
||||
}
|
||||
s.stop("Dependencies installed.");
|
||||
|
||||
p.outro("You're all set!");
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"expo@claude-plugins-official": true
|
||||
}
|
||||
}
|
||||
43
template/apps/<@if(eq(context.project.mobile,"expo"))>/.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
expo-env.d.ts
|
||||
|
||||
# Native
|
||||
.kotlin/
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
example
|
||||
|
||||
# generated native folders
|
||||
/ios
|
||||
/android
|
||||
@@ -0,0 +1,4 @@
|
||||
.expo
|
||||
android
|
||||
node_modules
|
||||
assets
|
||||
1
template/apps/<@if(eq(context.project.mobile,"expo"))>/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{ "recommendations": ["expo.vscode-expo-tools"] }
|
||||
7
template/apps/<@if(eq(context.project.mobile,"expo"))>/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit",
|
||||
"source.organizeImports": "explicit",
|
||||
"source.sortMembers": "explicit"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Expo HAS CHANGED
|
||||
|
||||
Read the exact versioned docs at https://docs.expo.dev/versions/v56.0.0/ before writing any code.
|
||||
@@ -0,0 +1 @@
|
||||
@AGENTS.md
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "mobile",
|
||||
"slug": "mobile",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "mobile",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"ios": {
|
||||
"icon": "./assets/expo.icon"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"backgroundColor": "#E6F4FE",
|
||||
"foregroundImage": "./assets/images/android-icon-foreground.png",
|
||||
"backgroundImage": "./assets/images/android-icon-background.png",
|
||||
"monochromeImage": "./assets/images/android-icon-monochrome.png"
|
||||
},
|
||||
"predictiveBackGestureEnabled": false,
|
||||
"package": "com.gregorl.mobile"
|
||||
},
|
||||
"web": {
|
||||
"output": "static",
|
||||
"favicon": "./assets/images/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
[
|
||||
"expo-splash-screen",
|
||||
{
|
||||
"backgroundColor": "#208AEF",
|
||||
"android": {
|
||||
"image": "./assets/images/splash-icon.png",
|
||||
"imageWidth": 76
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true,
|
||||
"reactCompiler": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="652" height="606" viewBox="0 0 652 606" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M353.554 0H298.446C273.006 0 249.684 14.6347 237.962 37.9539L4.37994 502.646C-1.04325 513.435 -1.45067 526.178 3.2716 537.313L22.6123 582.918C34.6475 611.297 72.5404 614.156 88.4414 587.885L309.863 222.063C313.34 216.317 319.439 212.826 326 212.826C332.561 212.826 338.659 216.317 342.137 222.063L563.559 587.885C579.46 614.156 617.352 611.297 629.388 582.918L648.728 537.313C653.451 526.178 653.043 513.435 647.62 502.646L414.038 37.9539C402.316 14.6347 378.994 0 353.554 0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 608 B |
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fill" : {
|
||||
"automatic-gradient" : "extended-srgb:0.00000,0.47843,1.00000,1.00000"
|
||||
},
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"image-name" : "expo-symbol 2.svg",
|
||||
"name" : "expo-symbol 2",
|
||||
"position" : {
|
||||
"scale" : 1,
|
||||
"translation-in-points" : [
|
||||
1.1008400065293245e-05,
|
||||
-16.046875
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"image-name" : "grid.png",
|
||||
"name" : "grid"
|
||||
}
|
||||
],
|
||||
"shadow" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"translucency" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"circles" : [
|
||||
"watchOS"
|
||||
],
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 780 KiB |
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 215 B |
|
After Width: | Height: | Size: 347 B |
|
After Width: | Height: | Size: 468 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 343 B |
|
After Width: | Height: | Size: 479 B |
|
After Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,19 @@
|
||||
// Learn more: https://docs.expo.dev/guides/monorepos/
|
||||
const { getDefaultConfig } = require('expo/metro-config');
|
||||
const path = require('path');
|
||||
|
||||
const projectRoot = __dirname;
|
||||
const monorepoRoot = path.resolve(projectRoot, '../..');
|
||||
|
||||
const config = getDefaultConfig(projectRoot);
|
||||
|
||||
// Watch the whole monorepo so changes in packages/* trigger rebuilds.
|
||||
config.watchFolders = [monorepoRoot];
|
||||
|
||||
// Resolve modules from the app's node_modules first, then the workspace root.
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, 'node_modules'),
|
||||
path.resolve(monorepoRoot, 'node_modules'),
|
||||
];
|
||||
|
||||
module.exports = config;
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "mobile",
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@expo/ui": "~56.0.14",
|
||||
"@<@var(context.project.name)>/rpc": "workspace:*",
|
||||
"expo": "~56.0.5",
|
||||
"expo-constants": "~56.0.16",
|
||||
"expo-crypto": "~56.0.4",
|
||||
"expo-dev-client": "~56.0.18",
|
||||
"expo-device": "~56.0.4",
|
||||
"expo-font": "~56.0.5",
|
||||
"expo-glass-effect": "~56.0.4",
|
||||
"expo-image": "~56.0.9",
|
||||
"expo-linking": "~56.0.12",
|
||||
"expo-router": "~56.2.7",
|
||||
"expo-splash-screen": "~56.0.10",
|
||||
"expo-status-bar": "~56.0.4",
|
||||
"expo-symbols": "~56.0.5",
|
||||
"expo-system-ui": "~56.0.5",
|
||||
"expo-web-browser": "~56.0.5",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-native": "0.85.3",
|
||||
"react-native-gesture-handler": "~2.31.1",
|
||||
"react-native-reanimated": "4.3.1",
|
||||
"react-native-safe-area-context": "~5.7.0",
|
||||
"react-native-screens": "4.25.2",
|
||||
"react-native-web": "~0.21.0",
|
||||
"react-native-worklets": "0.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "~19.2.2",
|
||||
"typescript": "~6.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"reset-project": "node ./scripts/reset-project.js",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"lint": "expo lint"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Stack } from "expo-router";
|
||||
|
||||
export default function RootLayout() {
|
||||
return <Stack />;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { createRouter, Todo } from "@<@var(context.project.name)>/rpc"
|
||||
import { useEffect, useState } from "react"
|
||||
import { ScrollView, Button, Checkbox, Host, Column, TextInput, Text, useNativeState, Row, Icon } from "@expo/ui";
|
||||
import * as Crypto from "expo-crypto"
|
||||
import { colorInvert, controlSize } from "@expo/ui/swift-ui/modifiers";
|
||||
import { fillMaxWidth, padding, width } from "@expo/ui/jetpack-compose/modifiers";
|
||||
export default function Index() {
|
||||
const router = createRouter("http://10.0.2.2:8080")
|
||||
const [todos, setTodos] = useState<Array<Todo>>([])
|
||||
const todoToCreateTask = useNativeState<string>("")
|
||||
const fetchTodos = () => {
|
||||
router.todos.listTodos({}).then((r) => {
|
||||
setTodos(r.todos)
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
fetchTodos();
|
||||
})
|
||||
|
||||
const setTodoDone = (id: string, task: string) => {
|
||||
return (done: boolean) => {
|
||||
router.todos.updateTodo({ todo: { id, task, done } })
|
||||
fetchTodos()
|
||||
}
|
||||
}
|
||||
|
||||
const deleteTodo = (id: string) => {
|
||||
return () => {
|
||||
router.todos.deleteTodo({ todo: { id } })
|
||||
fetchTodos()
|
||||
}
|
||||
}
|
||||
|
||||
const createTodo = () => {
|
||||
router.todos.createTodo({ todo: { id: Crypto.randomUUID(), task: todoToCreateTask.value } }).then((r) => {
|
||||
console.log(r)
|
||||
fetchTodos()
|
||||
}).catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
}
|
||||
|
||||
const updateTodoToCreateTask = (task: string) => {
|
||||
todoToCreateTask.value = task
|
||||
}
|
||||
|
||||
return (
|
||||
<Host style={{ flex: 1 }}>
|
||||
<Column alignment="center" modifiers={[fillMaxWidth()]}>
|
||||
<Text> create todo </Text>
|
||||
<TextInput value={todoToCreateTask} onChangeText={updateTodoToCreateTask} />
|
||||
<Button modifiers={[controlSize('regular')]} label="create Todo" onPress={createTodo} />
|
||||
<ScrollView modifiers={[fillMaxWidth()]}>
|
||||
{
|
||||
todos.map((todo) => (
|
||||
<Row alignment="center" modifiers={[fillMaxWidth(),padding(20,0,20,0)]}>
|
||||
<Column alignment="start" modifiers={[width(200)]}>
|
||||
<Text> {todo.task} </Text>
|
||||
</Column>
|
||||
<Column alignment="center">
|
||||
<Checkbox value={todo.done || false} onValueChange={setTodoDone(todo.id || "", todo.task)} />
|
||||
</Column>
|
||||
<Column alignment="end" modifiers={[fillMaxWidth()]}>
|
||||
<Button modifiers={[controlSize('regular')]} label="delete" onPress={deleteTodo(todo.id || "")}/>
|
||||
</Column>
|
||||
</Row>
|
||||
))
|
||||
}
|
||||
</ScrollView>
|
||||
</Column>
|
||||
</Host>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"@/assets/*": [
|
||||
"./assets/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".expo/types/**/*.ts",
|
||||
"expo-env.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
{ pkgs, lib, config, inputs, ... }:
|
||||
<@if(neq(context.project.frontend,"none"))>
|
||||
bundev = {
|
||||
exec = "bun dev";
|
||||
cwd = "./apps/web";
|
||||
after= ["devenv:processes:air@started"];
|
||||
};
|
||||
<@endif>{ pkgs, lib, config, inputs, ... }:
|
||||
|
||||
{
|
||||
packages = [
|
||||
@@ -13,8 +19,27 @@
|
||||
pkgs.protoc-gen-connect-go
|
||||
pkgs.protoc-gen-es
|
||||
pkgs.cobra-cli
|
||||
<@if(eq(context.project.mobile,"expo"))>
|
||||
pkgs.glib
|
||||
<@endif>
|
||||
];
|
||||
|
||||
<@if(eq(context.project.mobile,"expo"))>
|
||||
android = {
|
||||
enable = true;
|
||||
emulator = {
|
||||
enable = true;
|
||||
};
|
||||
buildTools.version = ["34.0.0" "35.0.0" "36.0.0" ]; # add 36.0.0
|
||||
reactNative.enable = true;
|
||||
android-studio = {
|
||||
enable = false;
|
||||
};
|
||||
ndk = {
|
||||
enable = true;
|
||||
version = [ "27.1.12297006" ];
|
||||
};
|
||||
};
|
||||
<@endif>
|
||||
languages.go.enable = true;
|
||||
languages.typescript.enable = true;
|
||||
services.postgres = {
|
||||
@@ -28,6 +53,21 @@
|
||||
}
|
||||
];
|
||||
};
|
||||
<@if(eq(context.project.mobile,"expo"))>
|
||||
enterShell = ''
|
||||
export LD_LIBRARY_PATH=$ANDROID_HOME/emulator/lib64:$LD_LIBRARY_PATH
|
||||
'';
|
||||
tasks.'<@var(context.project.name)>:create-avd'.exec = ''
|
||||
avdmanager create avd
|
||||
-n "Pixel_5_API34"
|
||||
-k "system-images;android-34;google_apis_playstore;x86_64"
|
||||
-d "pixel_5"
|
||||
'';
|
||||
tasks.'<@var(context.project.name)>:run-emulator' = {
|
||||
exec = "emulator -avd Pixel_5_API34";
|
||||
after = [ "<@var(context.project.name)>:create-avd" ]
|
||||
};
|
||||
<@endif>
|
||||
processes = {
|
||||
air = {
|
||||
exec = "air";
|
||||
@@ -49,12 +89,11 @@
|
||||
cwd = "./services/api";
|
||||
after= ["devenv:processes:air@started"];
|
||||
};
|
||||
<@if(neq(context.project.frontend,"none"))>
|
||||
bundev = {
|
||||
exec = "bun dev";
|
||||
cwd = "./apps/web";
|
||||
after= ["devenv:processes:air@started"];
|
||||
};
|
||||
<@if(eq(context.project.mobile,"expo"))>
|
||||
emulator = {
|
||||
exec
|
||||
}
|
||||
<@endif>
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
|
||||
<@if(context.project.mobile.expo)>
|
||||
nixpkgs:
|
||||
allowUnfree: true
|
||||
<@endif>
|
||||
inputs:
|
||||
nixpkgs:
|
||||
url: github:cachix/devenv-nixpkgs/rolling
|
||||
|
||||