diff --git a/.gitignore b/.gitignore index eb0c8957..6dacb2f8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ web-build/ # macOS .DS_Store + +.env \ No newline at end of file diff --git a/App.jsx b/App.jsx index f46bb2bb..fdd93fb5 100644 --- a/App.jsx +++ b/App.jsx @@ -1,25 +1,6 @@ -import 'react-native-gesture-handler' -import { NavigationContainer } from '@react-navigation/native' -import { createStackNavigator } from '@react-navigation/stack' - import React from 'react' -import store from './app/store' -import { Provider } from 'react-redux' -import { StatusBar } from 'expo-status-bar' +import Main from './src/Main' -import ScreenTimeline from './screens/Timeline' +const App = () =>
-const Stack = createStackNavigator() - -export default function App () { - return ( - - - - - - - - - ) -} +export default App diff --git a/api/client.js b/api/client.js deleted file mode 100644 index bc2b7318..00000000 --- a/api/client.js +++ /dev/null @@ -1,21 +0,0 @@ -export async function client(endpoint, { body, ...customConfig } = {}) { - let data - try { - const response = await window.fetch(endpoint, config) - data = await response.json() - if (response.ok) { - return data - } - throw new Error(response.statusText) - } catch (err) { - return Promise.reject(err.message ? err.message : data) - } -} - -client.get = function (endpoint, customConfig = {}) { - return client(endpoint, { ...customConfig, method: 'GET' }) -} - -client.post = function (endpoint, body, customConfig = {}) { - return client(endpoint, { ...customConfig, body }) -} diff --git a/app.json b/app.json index d6edcc16..7c197d2a 100644 --- a/app.json +++ b/app.json @@ -2,6 +2,7 @@ "expo": { "name": "mastodon-app", "slug": "mastodon-app", + "scheme": "mastodonct", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", @@ -17,10 +18,7 @@ "**/*" ], "ios": { - "supportsTablet": true - }, - "web": { - "favicon": "./assets/favicon.png" + "supportsTablet": false } } -} +} \ No newline at end of file diff --git a/babel.config.js b/babel.config.js index 2900afe9..5cc1c280 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,6 +1,16 @@ -module.exports = function(api) { - api.cache(true); +module.exports = function (api) { + api.cache(true) return { presets: ['babel-preset-expo'], - }; -}; + plugins: [ + [ + 'module-resolver', + { + alias: { + src: './src' + } + } + ] + ] + } +} diff --git a/components/TootTimeline.jsx b/components/TootTimeline.jsx deleted file mode 100644 index 8e644993..00000000 --- a/components/TootTimeline.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react' -import { Image, StyleSheet, Text, View } from 'react-native' -import HTML from 'react-native-render-html' - -import relative_time from '../utils/relative-time' - -export default function TootTimeline ({ item }) { - return ( - - - - - - {item.account.display_name} - {item.account.acct} - - - {relative_time(item.created_at)} - {item.application && item.application.name !== 'Web' && ( - Linking.openURL(item.application.website)}> - {item.application.name} - - )} - - - - {item.content ? : <>} - - ) -} - -const styles = StyleSheet.create({ - tootTimeline: { - flex: 1, - padding: 15 - }, - header: { - flexDirection: 'row' - }, - avatar: { - width: 40, - height: 40 - }, - name: { - flexDirection: 'row' - } -}) diff --git a/package-lock.json b/package-lock.json index 710173de..f637934a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1347,6 +1347,14 @@ } } }, + "@react-native-async-storage/async-storage": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.13.0.tgz", + "integrity": "sha512-c+pKuUe54sysxnqsfG17kaAcd9xJQyTNYDQhZhYf3Ej5khAQPPM85eN2nc1sj1qEnxDde4mcfi3slrOd/KtoSw==", + "requires": { + "deep-assign": "^3.0.0" + } + }, "@react-native-community/cli-debugger-ui": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-4.9.0.tgz", @@ -1679,6 +1687,15 @@ "resolved": "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.10.tgz", "integrity": "sha512-rk4sWFsmtOw8oyx8SD3KSvawwaK7gRBSEIy2TAwURyGt+3TizssXP1r8nx3zY+R7v2vYYHXZ+k2/GULAT/bcaQ==" }, + "@react-navigation/bottom-tabs": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-5.9.2.tgz", + "integrity": "sha512-nSaAXFNbRukhMGq4U3m1rqbFZEPT64/gPTwUwQ5YQom5Q0a6xZ0AnOmtFIXvDBbK5VJiokGdBLJ/TMDchqcujQ==", + "requires": { + "color": "^3.1.2", + "react-native-iphone-x-helper": "^1.2.1" + } + }, "@react-navigation/core": { "version": "5.12.5", "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-5.12.5.tgz", @@ -2021,15 +2038,16 @@ } }, "babel-plugin-module-resolver": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-3.2.0.tgz", - "integrity": "sha512-tjR0GvSndzPew/Iayf4uICWZqjBwnlMWjSx6brryfQ81F9rxBVqwDJtFCV8oOs0+vJeefK9TmdZtkIFdFe1UnA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.0.0.tgz", + "integrity": "sha512-3pdEq3PXALilSJ6dnC4wMWr0AZixHRM4utpdpBR9g5QG7B7JwWyukQv7a9hVxkbGFl+nQbrHDqqQOIBtTXTP/Q==", + "dev": true, "requires": { - "find-babel-config": "^1.1.0", - "glob": "^7.1.2", - "pkg-up": "^2.0.0", - "reselect": "^3.0.1", - "resolve": "^1.4.0" + "find-babel-config": "^1.2.0", + "glob": "^7.1.6", + "pkg-up": "^3.1.0", + "reselect": "^4.0.0", + "resolve": "^1.13.1" } }, "babel-plugin-react-native-web": { @@ -2052,6 +2070,71 @@ "babel-plugin-module-resolver": "^3.2.0", "babel-plugin-react-native-web": "~0.13.6", "metro-react-native-babel-preset": "~0.59.0" + }, + "dependencies": { + "babel-plugin-module-resolver": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-3.2.0.tgz", + "integrity": "sha512-tjR0GvSndzPew/Iayf4uICWZqjBwnlMWjSx6brryfQ81F9rxBVqwDJtFCV8oOs0+vJeefK9TmdZtkIFdFe1UnA==", + "requires": { + "find-babel-config": "^1.1.0", + "glob": "^7.1.2", + "pkg-up": "^2.0.0", + "reselect": "^3.0.1", + "resolve": "^1.4.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "requires": { + "find-up": "^2.1.0" + } + }, + "reselect": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz", + "integrity": "sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=" + } } }, "babel-preset-fbjs": { @@ -3128,6 +3211,14 @@ "uuid": "^3.4.0" } }, + "expo-app-auth": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/expo-app-auth/-/expo-app-auth-9.2.0.tgz", + "integrity": "sha512-aRsRTfCfpuR8n8nO22WwwwTB7JhhJv7T9ZB5f68u6NN/FTF5CnhI3m5t0lawmirNSdmK+mGL0L+gvRyVyt1WLQ==", + "requires": { + "invariant": "^2.2.4" + } + }, "expo-asset": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-8.2.0.tgz", @@ -3536,11 +3627,12 @@ } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "^3.0.0" } }, "fontfaceobserver": { @@ -4494,11 +4586,12 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, @@ -5556,9 +5649,9 @@ "optional": true }, "nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==" + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.13.tgz", + "integrity": "sha512-oYL7jWZUdScASxYOrcwE8EvISFGzO3/1g+t56vCyR0s2nrpmBcOc7hTAFJaVf6HMyEPJrnNelnjRnMN6KZnCPA==" }, "nanomatch": { "version": "1.2.13", @@ -5798,25 +5891,28 @@ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.0.0" } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "parse-json": { "version": "4.0.0", @@ -5929,11 +6025,12 @@ } }, "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dev": true, "requires": { - "find-up": "^2.1.0" + "find-up": "^3.0.0" } }, "plist": { @@ -6773,9 +6870,10 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "reselect": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz", - "integrity": "sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz", + "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==", + "dev": true }, "resolve": { "version": "1.18.1", diff --git a/package.json b/package.json index d434c108..83bc88a3 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,16 @@ "eject": "expo eject" }, "dependencies": { + "@react-native-async-storage/async-storage": "^1.13.0", "@react-native-community/masked-view": "0.1.10", + "@react-navigation/bottom-tabs": "^5.9.2", "@react-navigation/native": "^5.7.6", "@react-navigation/stack": "^5.9.3", "@reduxjs/toolkit": "^1.4.0", "expo": "~39.0.2", + "expo-app-auth": "~9.2.0", "expo-status-bar": "~1.0.2", + "prop-types": "^15.7.2", "react": "16.13.1", "react-dom": "16.13.1", "react-native": "https://github.com/expo/react-native/archive/sdk-39.0.3.tar.gz", @@ -27,7 +31,8 @@ "react-redux": "^7.2.1" }, "devDependencies": { - "@babel/core": "~7.9.0" + "@babel/core": "~7.9.0", + "babel-plugin-module-resolver": "^4.0.0" }, "private": true } diff --git a/screens/Timeline.jsx b/screens/Timeline.jsx deleted file mode 100644 index 683edaff..00000000 --- a/screens/Timeline.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useEffect } from 'react' -import { ActivityIndicator, FlatList, View } from 'react-native' -import { useSelector, useDispatch } from 'react-redux' - -import TootTimeline from '../components/TootTimeline' - -import { allToots, fetchNewer, fetchOlder } from './timelineSlice' - -export default function ScreenTimeline () { - const dispatch = useDispatch() - const toots = useSelector(allToots) - - const fetchNewerStatus = useSelector(state => state.timeline.status) - const fetchNewerError = useSelector(state => state.timeline.error) - const fetchOlderStatus = useSelector(state => state.timeline.status) - const fetchOlderError = useSelector(state => state.timeline.error) - - useEffect(() => { - if (fetchOlderStatus === 'idle') { - dispatch(fetchOlder()) - } - }, [fetchOlderStatus, dispatch]) - - let content - - if (fetchOlderStatus === 'error') { - content = {fetchOlderError} - } else { - content = ( - <> - id} - renderItem={TootTimeline} - onRefresh={() => dispatch(fetchNewer(toots[0].id))} - refreshing={fetchNewerStatus === 'loading'} - onEndReached={() => dispatch(fetchOlder(toots[toots.length - 1].id))} - onEndReachedThreshold={0.2} - style={{ height: '100%', width: '100%' }} - /> - {fetchOlderStatus === 'loading' && } - - ) - } - - return {content} -} diff --git a/screens/timelineSlice.js b/screens/timelineSlice.js deleted file mode 100644 index db5646a6..00000000 --- a/screens/timelineSlice.js +++ /dev/null @@ -1,72 +0,0 @@ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' - -export const fetchNewer = createAsyncThunk('timeline/fetchNewer', async id => { - let data - try { - const response = await fetch( - `https://m.cmx.im/api/v1/timelines/public?since_id=${id}` - ) - data = await response.json() - if (response.ok) { - return data - } - throw new Error(response.statusText) - } catch (err) { - return Promise.reject(err.message ? err.message : data) - } -}) - -export const fetchOlder = createAsyncThunk('timeline/fetchOlder', async id => { - let data - try { - const response = await fetch( - `https://m.cmx.im/api/v1/timelines/public${id ? `?max_id=${id}` : ''}` - ) - data = await response.json() - if (response.ok) { - return data - } - throw new Error(response.statusText) - } catch (err) { - return Promise.reject(err.message ? err.message : data) - } -}) - -export const timelineSlice = createSlice({ - name: 'timeline', - initialState: { - toots: [], - status: 'idle', - error: null - }, - extraReducers: { - [fetchNewer.pending]: (state, action) => { - state.status = 'loading' - }, - [fetchNewer.fulfilled]: (state, action) => { - state.status = 'succeeded' - state.toots.unshift(...action.payload) - }, - [fetchNewer.rejected]: (state, action) => { - state.status = 'failed' - state.error = action.payload - }, - [fetchOlder.pending]: (state, action) => { - state.status = 'loading' - }, - [fetchOlder.fulfilled]: (state, action) => { - state.status = 'succeeded' - state.toots.push(...action.payload) - }, - [fetchOlder.rejected]: (state, action) => { - state.status = 'failed' - state.error = action.payload - } - } -}) - -// export const { update } = timelineSlice.actions - -export const allToots = state => state.timeline.toots - -export default timelineSlice.reducer diff --git a/src/Main.jsx b/src/Main.jsx new file mode 100644 index 00000000..0fba7890 --- /dev/null +++ b/src/Main.jsx @@ -0,0 +1,35 @@ +import 'react-native-gesture-handler' +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' +import { NavigationContainer } from '@react-navigation/native' +import { enableScreens } from 'react-native-screens' +enableScreens() + +import React from 'react' +import store from './app/store' +import { Provider } from 'react-redux' +import { StatusBar } from 'expo-status-bar' + +import MainTimeline from 'src/stacks/MainTimeline' +import PublicTimeline from 'src/stacks/PublicTimeline' +import Notifications from 'src/stacks/Notifications' +import Me from 'src/stacks/Me' + +const Tab = createBottomTabNavigator() + +export default function Main () { + return ( + <> + + + + + + + {/* */} + + + + + + ) +} diff --git a/src/api/client.js b/src/api/client.js new file mode 100644 index 00000000..bd744518 --- /dev/null +++ b/src/api/client.js @@ -0,0 +1,53 @@ +export async function client ( + instance, + endpoint, + query, + { body, ...customConfig } = {} +) { + if (!instance || !endpoint) { + console.error('Missing instance or endpoint.') + return Promise.reject('Missing instance or endpoint.') + } + const headers = { 'Content-Type': 'application/json' } + + const config = { + method: body ? 'POST' : 'GET', + ...customConfig, + headers: { + ...headers, + ...customConfig.headers + } + } + + if (body) { + config.body = JSON.stringify(body) + } + let data + try { + const response = await fetch( + `https://${instance}/api/v1/${endpoint}${ + query + ? `?${Object.keys(query) + .map(key => `${key}=${query[key]}`) + .join('&')}` + : '' + }`, + config + ) + data = await response.json() + if (response.ok) { + return data + } + throw new Error(response.statusText) + } catch (err) { + return Promise.reject(err.message ? err.message : data) + } +} + +client.get = function (instance, endpoint, query, customConfig = {}) { + return client(instance, endpoint, query, { ...customConfig, method: 'GET' }) +} + +client.post = function (instance, endpoint, query, body, customConfig = {}) { + return client(instance, endpoint, query, { ...customConfig, body }) +} diff --git a/app/store.js b/src/app/store.js similarity index 51% rename from app/store.js rename to src/app/store.js index f163eb98..c5335808 100644 --- a/app/store.js +++ b/src/app/store.js @@ -1,10 +1,12 @@ import { configureStore } from '@reduxjs/toolkit' -import timelineReducer from '../screens/timelineSlice' +import genericTimelineSlice from 'src/stacks/common/timelineSlice' export default configureStore({ reducer: { - timeline: timelineReducer + 'social.xmflsct.com': genericTimelineSlice('social.xmflsct.com').slice + .reducer, + 'm.cmx.im': genericTimelineSlice('m.cmx.im').slice.reducer }, middleware: getDefaultMiddleware => getDefaultMiddleware({ diff --git a/src/components/TootTimeline.jsx b/src/components/TootTimeline.jsx new file mode 100644 index 00000000..4e15c8e5 --- /dev/null +++ b/src/components/TootTimeline.jsx @@ -0,0 +1,75 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Image, StyleSheet, Text, View } from 'react-native' +import HTML from 'react-native-render-html' + +import relativeTime from 'src/utils/relativeTime' + +export default function TootTimeline ({ item }) { + return ( + + + + + + + {item.reblog + ? item.reblog.account.display_name + : item.account.display_name} + + + {item.reblog ? item.reblog.account.acct : item.account.acct} + + + + {relativeTime(item.created_at)} + {item.application && item.application.name !== 'Web' && ( + Linking.openURL(item.application.website)}> + {item.application.name} + + )} + + + + {item.content ? : <>} + + ) +} + +TootTimeline.propTypes = { + item: PropTypes.shape({ + account: PropTypes.shape({ + avatar: PropTypes.string.isRequired, + display_name: PropTypes.string.isRequired, + acct: PropTypes.string.isRequired + }).isRequired, + created_at: PropTypes.string.isRequired, + application: PropTypes.exact({ + name: PropTypes.string.isRequired, + website: PropTypes.string + }), + content: PropTypes.string + }).isRequired +} + +const styles = StyleSheet.create({ + tootTimeline: { + flex: 1, + padding: 15 + }, + header: { + flexDirection: 'row' + }, + avatar: { + width: 40, + height: 40 + }, + name: { + flexDirection: 'row' + } +}) diff --git a/src/stacks/MainTimeline.jsx b/src/stacks/MainTimeline.jsx new file mode 100644 index 00000000..f35015a6 --- /dev/null +++ b/src/stacks/MainTimeline.jsx @@ -0,0 +1,18 @@ +import React from 'react' +import { createNativeStackNavigator } from 'react-native-screens/native-stack' + +import Timeline from 'src/stacks/common/Timeline' + +const MainTimelineStack = createNativeStackNavigator() + +function Base () { + return +} + +export default function MainTimeline () { + return ( + + + + ) +} diff --git a/src/stacks/Me.jsx b/src/stacks/Me.jsx new file mode 100644 index 00000000..63a2ddab --- /dev/null +++ b/src/stacks/Me.jsx @@ -0,0 +1,22 @@ +import React from 'react' +import { createNativeStackNavigator } from 'react-native-screens/native-stack' + +import Base from './Me/Base' +import Authentication from 'src/stacks/Me/Authentication' + +const Stack = createNativeStackNavigator() + +export default function Me () { + return ( + + + + + ) +} diff --git a/src/stacks/Me/Authentication.jsx b/src/stacks/Me/Authentication.jsx new file mode 100644 index 00000000..3685ae11 --- /dev/null +++ b/src/stacks/Me/Authentication.jsx @@ -0,0 +1,16 @@ +import React from 'react' +import { createNativeStackNavigator } from 'react-native-screens/native-stack' + +import Instance from './Authentication/Instance' +import Webview from './Authentication/Webview' + +const Stack = createNativeStackNavigator() + +export default function Base () { + return ( + + + + + ) +} diff --git a/src/stacks/Me/Authentication/Instance.jsx b/src/stacks/Me/Authentication/Instance.jsx new file mode 100644 index 00000000..e4c653a3 --- /dev/null +++ b/src/stacks/Me/Authentication/Instance.jsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react' +import { Button, TextInput, View } from 'react-native' + +export default function Instance ({ navigation }) { + const [instance, onChangeInstance] = useState() + + return ( + + onChangeInstance(text)} + value={instance} + autoCapitalize='none' + autoCorrect={false} + clearButtonMode='unless-editing' + keyboardType='url' + textContentType='URL' + placeholder='输入服务器' + placeholderTextColor='#888888' + /> +