Skip to content

Commit

Permalink
🚧 Mobile App Foundations (#282)
Browse files Browse the repository at this point in the history
* adds expo project

* adds ts

* changes name

* adds tailwindcss using nativewind

* tsconfig fix

* fixes monorepo paths

* adds nativewind + connect screen + auth wip

* connect + login wip

* Adds redirect to home or connect depending on user state

* formatting

* connecting and logging into stump

* pnpm magic

* removes debug console logs

* final poc

* fix cyclic requires

* add fixme comment

* adds expo project

* adds ts

* changes name

* adds tailwindcss using nativewind

* tsconfig fix

* fixes monorepo paths

* adds nativewind + connect screen + auth wip

* connect + login wip

* Adds redirect to home or connect depending on user state

* formatting

* connecting and logging into stump

* removes debug console logs

* final poc

* prettified a bunch of files

* rebase on mobile-app

* rebase

* adds nativewind + connect screen + auth wip

* connect + login wip

* chore(dev): loosen pre-commit hook

* completed migration to primitive components

* 128 appropriate routing setup eg tabs and stack navigators (#130)

* log in goes back to connect. Home does NOT go back to log in.

* Fixes login screen push path

* from settings can go back to home

* Adds logout in the settings

* Miscellaneous UI fixes on Phones and Tablets

* UI Fixes + media support

* WIP: get mobile app working

* almost have something usable

* AH

* kicking screaming crying sobbing pooping

* very basic stack navigation, framework for building first app release

* WIP local storage alternative

* Revert "WIP local storage alternative"

This reverts commit 4b2d422.

* Try (and fail) to fix zustand for RN

* fix zustand? 🙏

* Created basic tab navigation with icons for mobile app

* messing around

* created navigation from libraries -> series -> books

* Add wrapper Link and example hook usage

* Add some supporting components

* Generalizing some components and routes

* added support for comic reader on android + started supporting landscape mode

* continued development on explore/library pages

* added barebones appearance settings

* fix lints

---------

Co-authored-by: Daniele Cambi <dancam.dev@gmail.com>
Co-authored-by: Daniele Cambi <43640732+dancamdev@users.noreply.github.com>
  • Loading branch information
3 people committed Feb 23, 2024
1 parent b3f7d16 commit d61ab76
Show file tree
Hide file tree
Showing 147 changed files with 5,781 additions and 421 deletions.
3 changes: 2 additions & 1 deletion .npmrc
@@ -1 +1,2 @@
strict-peer-dependencies=false
strict-peer-dependencies=false
node-linker=hoisted
5 changes: 4 additions & 1 deletion .vscode/settings.json
Expand Up @@ -8,7 +8,10 @@
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
},
"tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]],
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
],
"tailwindCSS.classAttributes": ["class", "className", ".*CLASSES", ".*VARIANTS"],
"typescript.tsdk": "node_modules/typescript/lib"
}
14 changes: 14 additions & 0 deletions apps/expo/.gitignore
@@ -0,0 +1,14 @@
node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/

# macOS
.DS_Store
52 changes: 52 additions & 0 deletions apps/expo/README.md
@@ -0,0 +1,52 @@
# Stump Mobile Application

This is the mobile application for the Stump. It is built with [Expo](https://expo.io/), and is currently in the very early stages of development.

## Getting Started 🚀

> Note: You need to have the Expo CLI installed, as well as the mobile SDKs (depending on where you want to run the app). See the [Expo docs](https://docs.expo.io/get-started/installation/) for more info.
1. Clone the repo
2. Follow the [developer guide](https://github.com/aaronleopold/stump#developer-guide-) at the root of the Stump monorepo
3. Start the mobile app and the server:

```bash
moon run server:start mobile:dev # or server:dev if you want to run the server in dev mode
```

And that's it!

## Contributing 🤝

Be sure to review the [CONTRIBUTING.md](https://github.com/aaronleopold/stump/tree/develop/.github/CONTRIBUTING.md) before getting started, as it overviews the guidelines and general process for contributing to the project. Once you've done that, it's as simple as:

1. Fork the repo
2. Create a new branch
3. Make your changes
4. Open a PR (following the title and description guidelines)
5. Wait for review
6. Merge 😍

## Roadmap 🗺️

With the `v0.1.0` release of the Stump server (very) slowly approaching, the mobile app has the following items targeted for an _initial POC_:

- [ ] Various initial expo-related project configuration:
- [ ] Appropriate routing setup (e.g. tabs and stack navigators)
- [x] Configure and connect to a Stump server instance
- [x] Login or claim an unclaimed server instance
- [ ] A home screen that shows the server at a glance:
- [x] Various server statistics
- [ ] In progress media
- [ ] Newly added series and books
- [ ] A library screen that shows a paginated list of series within a library
- [ ] A series screen that shows a pagination list of media within a series
- [ ] A very basic book overview screen
- [ ] Support barebones readers for:
- [ ] Epub
- [ ] CBZ/CBR
- [ ] Dark theme support

## Acknowledgements 🙏

- Thanks to [@dancamdev](https://github.com/dancamdev) for bootstrapping this Expo project 🙌
1 change: 1 addition & 0 deletions apps/expo/app.d.ts
@@ -0,0 +1 @@
/// <reference types="nativewind/types" />
8 changes: 8 additions & 0 deletions apps/expo/app.json
@@ -0,0 +1,8 @@
{
"expo": {
"scheme": "stump",
"name": "stump",
"slug": "stump",
"userInterfaceStyle": "automatic"
}
}
Binary file added apps/expo/assets/fonts/SpaceMono-Regular.ttf
Binary file not shown.
Binary file added apps/expo/assets/images/adaptive-icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/expo/assets/images/favicon.ico
Binary file not shown.
Binary file added apps/expo/assets/images/favicon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/expo/assets/images/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/expo/assets/images/splash.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions apps/expo/babel.config.js
@@ -0,0 +1,7 @@
module.exports = function (api) {
api.cache(true)
return {
plugins: ['nativewind/babel', 'react-native-reanimated/plugin'],
presets: ['babel-preset-expo'],
}
}
5 changes: 5 additions & 0 deletions apps/expo/index.js
@@ -0,0 +1,5 @@
import { registerRootComponent } from 'expo'

import { AppEntry } from './src/AppEntry'

registerRootComponent(AppEntry)
22 changes: 22 additions & 0 deletions apps/expo/metro.config.js
@@ -0,0 +1,22 @@
// Learn more https://docs.expo.dev/guides/monorepos
const { getDefaultConfig } = require('expo/metro-config')
const path = require('path')

// Find the project and workspace directories
const projectRoot = __dirname
const workspaceRoot = path.resolve(projectRoot, '../..')

const config = getDefaultConfig(projectRoot)

// 1. Watch all files within the monorepo
config.watchFolders = [workspaceRoot]
// 2. Let Metro know where to resolve packages and in what order
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules'),
]
// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
// TODO: Needs fix - Although not ideal, this must be set to `false` in order to avoid a dependency collision on uuid
config.resolver.disableHierarchicalLookup = false

module.exports = config
53 changes: 53 additions & 0 deletions apps/expo/package.json
@@ -0,0 +1,53 @@
{
"main": "index.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"@hookform/resolvers": "^3.3.2",
"@react-native-async-storage/async-storage": "1.21.0",
"@react-navigation/bottom-tabs": "^6.5.12",
"@react-navigation/native": "^6.1.10",
"@react-navigation/native-stack": "^6.9.18",
"@stump/api": "*",
"@tanstack/react-query": "^4.20.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"expo": "^50.0.6",
"expo-constants": "~15.4.5",
"expo-image": "^1.10.6",
"expo-splash-screen": "~0.26.4",
"expo-status-bar": "~1.11.1",
"lucide-react-native": "^0.330.0",
"nativewind": "^2.0.11",
"phosphor-react-native": "^1.1.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.50.1",
"react-native": "0.73.4",
"react-native-gesture-handler": "~2.14.1",
"react-native-reanimated": "~3.6.2",
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
"react-native-svg": "14.1.0",
"react-native-web": "~0.19.10",
"tailwind-merge": "^1.14.0",
"zod": "^3.22.4",
"zustand": "^4.5.0"
},
"devDependencies": {
"@babel/core": "^7.19.3",
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
"babel-preset-expo": "^10.0.1",
"metro": "^0.80.5",
"metro-core": "^0.80.5",
"react-native-url-polyfill": "^1.3.0",
"tailwindcss": "3.3.2"
},
"name": "@stump/mobile",
"version": "0.0.1",
"private": true
}
130 changes: 130 additions & 0 deletions apps/expo/src/App.tsx
@@ -0,0 +1,130 @@
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { checkUrl, initializeApi, isAxiosError, isUrl } from '@stump/api'
import { useAuthQuery } from '@stump/client'
import * as SplashScreen from 'expo-splash-screen'
import { useEffect, useState } from 'react'
import { SafeAreaProvider } from 'react-native-safe-area-context'

import { AuthenticatedNavigator } from './screens/authenticated'
import LoginOrClaim from './screens/LoginOrClaim'
import ServerNotAccessible from './screens/ServerNotAccessible'
import { useAppStore, useUserStore } from './stores'

const Stack = createNativeStackNavigator()

export default function AppWrapper() {
const { baseUrl, setBaseUrl, setPlatform } = useAppStore((store) => ({
baseUrl: store.baseUrl,
setBaseUrl: store.setBaseUrl,
setPlatform: store.setPlatform,
}))

const { storeUser, setStoreUser } = useUserStore((state) => ({
setStoreUser: state.setUser,
storeUser: state.user,
}))
const { isConnectedToServer, setIsConnectedToServer } = useAppStore((store) => ({
isConnectedToServer: store.isConnectedWithServer,
setIsConnectedToServer: store.setIsConnectedWithServer,
}))

const [isReady, setIsReady] = useState(false)

const { error } = useAuthQuery({
enabled: !storeUser && !!baseUrl && isConnectedToServer,
onSuccess: setStoreUser,
})

// TODO: This might not be needed anymore after refactoring the client, check this
useEffect(() => {
if (!error) return

const axiosError = isAxiosError(error) ? error : null
const isNetworkError = axiosError?.code === 'ERR_NETWORK'
if (isNetworkError) {
setIsConnectedToServer(false)
}
}, [error, setIsConnectedToServer])

useEffect(() => {
// TODO: ios vs androind?
setPlatform('mobile')
}, [setPlatform])

// TODO: remove, just debugging stuff
useEffect(() => {
setBaseUrl('https://demo.stumpapp.dev')
// setBaseUrl('http://localhost:10801')
}, [setBaseUrl])

useEffect(() => {
if (isReady) {
SplashScreen.hideAsync()
}
}, [isReady])

// console.log({ baseUrl, isConnectedToServer, isReady, storeUser })

/**
* An effect that will verify the baseUrl is accessible to the app.
*/
useEffect(() => {
// TODO: handle errors!
async function handleVerifyConnection() {
if (!isUrl(baseUrl)) {
console.error('Invalid URL')
} else {
const isValid = await checkUrl(baseUrl)

if (!isValid) {
console.error(`Failed to connect to ${baseUrl}`)
} else {
initializeApi(baseUrl, 'v1')
setIsConnectedToServer(true)
}
}

setIsReady(true)
}

if (baseUrl) {
handleVerifyConnection()
} else {
setIsReady(true)
}
}, [baseUrl, setIsConnectedToServer])

// TODO: An offline-only stack which allows for reading of downloaded content
const renderApp = () => {
if (!isConnectedToServer) {
return (
<Stack.Screen
name="Server SOS"
component={ServerNotAccessible}
options={{ headerShown: false }}
/>
)
} else if (!storeUser) {
return <Stack.Screen name="Login" component={LoginOrClaim} options={{ headerShown: false }} />
} else {
return (
<Stack.Screen
name="Authenticated"
component={AuthenticatedNavigator}
options={{ headerShown: false }}
/>
)
}
}

if (!isReady) return null

return (
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator>{renderApp()}</Stack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
)
}
41 changes: 41 additions & 0 deletions apps/expo/src/AppEntry.tsx
@@ -0,0 +1,41 @@
import { StumpClientContextProvider } from '@stump/client'
import { useFonts } from 'expo-font'
import * as SplashScreen from 'expo-splash-screen'
import { useEffect } from 'react'

import App from './App'
import { useAppStore, useUserStore } from './stores'

// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync()

export function AppEntry() {
const [loaded, error] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
})

const setUser = useUserStore((store) => store.setUser)
const setIsConnectedWithServer = useAppStore((store) => store.setIsConnectedWithServer)

// Expo Router uses Error Boundaries to catch errors in the navigation tree.
useEffect(() => {
if (error) throw error
}, [error])

const handleUnauthenticatedResponse = () => setUser(null)
const handleConnectionWithServerChanged = (isConnected: boolean) =>
setIsConnectedWithServer(isConnected)

if (!loaded) {
return null
}

return (
<StumpClientContextProvider
onUnauthenticatedResponse={handleUnauthenticatedResponse}
onConnectionWithServerChanged={handleConnectionWithServerChanged}
>
<App />
</StumpClientContextProvider>
)
}

0 comments on commit d61ab76

Please sign in to comment.