mirror of
https://github.com/tooot-app/app
synced 2025-04-26 07:58:48 +02:00
Transform into TypeScript
This commit is contained in:
parent
698b54868e
commit
d2cc643b9c
6
App.jsx
6
App.jsx
@ -1,6 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import Index from './src/Index'
|
|
||||||
|
|
||||||
const App = () => <Index />
|
|
||||||
|
|
||||||
export default App
|
|
6
App.tsx
Normal file
6
App.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Index } from 'src/Index'
|
||||||
|
|
||||||
|
const App: React.FC = () => <Index />
|
||||||
|
|
||||||
|
export default App
|
@ -4,6 +4,7 @@ module.exports = function (api) {
|
|||||||
presets: ['babel-preset-expo'],
|
presets: ['babel-preset-expo'],
|
||||||
plugins: [
|
plugins: [
|
||||||
['@babel/plugin-proposal-optional-chaining'],
|
['@babel/plugin-proposal-optional-chaining'],
|
||||||
|
['babel-plugin-typescript-to-proptypes'],
|
||||||
[
|
[
|
||||||
'module-resolver',
|
'module-resolver',
|
||||||
{
|
{
|
||||||
|
121
package-lock.json
generated
121
package-lock.json
generated
@ -1762,6 +1762,27 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz",
|
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz",
|
||||||
"integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ=="
|
"integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ=="
|
||||||
},
|
},
|
||||||
|
"@types/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/istanbul-lib-coverage": {
|
"@types/istanbul-lib-coverage": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||||
@ -1784,6 +1805,82 @@
|
|||||||
"@types/istanbul-lib-report": "*"
|
"@types/istanbul-lib-report": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/prop-types": {
|
||||||
|
"version": "15.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
|
||||||
|
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/react": {
|
||||||
|
"version": "16.9.55",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.55.tgz",
|
||||||
|
"integrity": "sha512-6KLe6lkILeRwyyy7yG9rULKJ0sXplUsl98MGoCfpteXf9sPWFWWMknDcsvubcpaTdBuxtsLF6HDUwdApZL/xIg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/prop-types": "*",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"version": "16.9.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.9.tgz",
|
||||||
|
"integrity": "sha512-jE16FNWO3Logq/Lf+yvEAjKzhpST/Eac8EMd1i4dgZdMczfgqC8EjpxwNgEe3SExHYLliabXDh9DEhhqnlXJhg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/react-native": {
|
||||||
|
"version": "0.63.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.63.30.tgz",
|
||||||
|
"integrity": "sha512-8/PrOjuUaPTCfMeW12ubseZPUGdbRhxYDa/aT+0D0KWVTe60b4H/gJrcfJmBXC6EcCFcimuTzQCv8/S03slYqA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/react-native-htmlview": {
|
||||||
|
"version": "0.12.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-native-htmlview/-/react-native-htmlview-0.12.2.tgz",
|
||||||
|
"integrity": "sha512-r5lWdZcZmcxLrfhIAAzBCEpDUuDFRiB5V9d0QvCqhTRh9vorlEjXgyZ5K8/HzbIOuvGb9/mQJPK0rItEAQk0dw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/react-navigation": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-navigation/-/react-navigation-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-Y7F5zU8BTBK8tEOvUqgvwvPZ7+9vnc2UI1vHwJ/9ZJG98TntNv04GWa6lrn4MA4149pqw6cyNw/V49Yd2osAFQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"react-navigation": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/react-redux": {
|
||||||
|
"version": "7.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.9.tgz",
|
||||||
|
"integrity": "sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.0",
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0",
|
||||||
|
"redux": "^4.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/stack-utils": {
|
"@types/stack-utils": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||||
@ -2111,6 +2208,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz",
|
||||||
"integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ=="
|
"integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ=="
|
||||||
},
|
},
|
||||||
|
"babel-plugin-typescript-to-proptypes": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-typescript-to-proptypes/-/babel-plugin-typescript-to-proptypes-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-CxTjlgiB/qjcC8lMTnmgt/6FsviyDbK/m08ImhnY+M45ZFnDL37zU68n5Kaxh1YWmjNex5R3HMzz0cPwthzHgQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-module-imports": "^7.10.4",
|
||||||
|
"@babel/helper-plugin-utils": "^7.10.4",
|
||||||
|
"@babel/plugin-syntax-typescript": "^7.10.4",
|
||||||
|
"@babel/types": "^7.11.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"babel-preset-expo": {
|
"babel-preset-expo": {
|
||||||
"version": "8.3.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-8.3.0.tgz",
|
||||||
@ -2767,6 +2876,12 @@
|
|||||||
"isobject": "^3.0.1"
|
"isobject": "^3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"csstype": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"dayjs": {
|
"dayjs": {
|
||||||
"version": "1.9.4",
|
"version": "1.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.4.tgz",
|
||||||
@ -7743,6 +7858,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||||
},
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"ua-parser-js": {
|
"ua-parser-js": {
|
||||||
"version": "0.7.22",
|
"version": "0.7.22",
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
||||||
|
11
package.json
11
package.json
@ -24,7 +24,6 @@
|
|||||||
"expo-splash-screen": "~0.6.1",
|
"expo-splash-screen": "~0.6.1",
|
||||||
"expo-status-bar": "~1.0.2",
|
"expo-status-bar": "~1.0.2",
|
||||||
"ky": "^0.24.0",
|
"ky": "^0.24.0",
|
||||||
"prop-types": "^15.7.2",
|
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
"react-native": "https://github.com/expo/react-native/archive/sdk-39.0.3.tar.gz",
|
"react-native": "https://github.com/expo/react-native/archive/sdk-39.0.3.tar.gz",
|
||||||
@ -44,7 +43,15 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "~7.9.0",
|
"@babel/core": "~7.9.0",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
|
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
|
||||||
"babel-plugin-module-resolver": "^4.0.0"
|
"@types/react": "^16.9.55",
|
||||||
|
"@types/react-dom": "^16.9.9",
|
||||||
|
"@types/react-native": "^0.63.30",
|
||||||
|
"@types/react-native-htmlview": "^0.12.2",
|
||||||
|
"@types/react-navigation": "^3.4.0",
|
||||||
|
"@types/react-redux": "^7.1.9",
|
||||||
|
"babel-plugin-module-resolver": "^4.0.0",
|
||||||
|
"babel-plugin-typescript-to-proptypes": "^1.4.1",
|
||||||
|
"typescript": "^4.0.5"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
244
src/@types/mastodon.d.ts
vendored
Normal file
244
src/@types/mastodon.d.ts
vendored
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
declare namespace mastodon {
|
||||||
|
type Account = {
|
||||||
|
// Base
|
||||||
|
id: string
|
||||||
|
username: string
|
||||||
|
acct: string
|
||||||
|
url: string
|
||||||
|
|
||||||
|
// Attributes
|
||||||
|
display_name: string
|
||||||
|
note?: string
|
||||||
|
avatar: string
|
||||||
|
avatar_static: string
|
||||||
|
header: string
|
||||||
|
header_static: string
|
||||||
|
locked: boolean
|
||||||
|
emojis?: Emoji[]
|
||||||
|
discoverable: boolean
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
created_at: string
|
||||||
|
last_status_at: string
|
||||||
|
statuses_count: number
|
||||||
|
followers_count: number
|
||||||
|
following_count: number
|
||||||
|
|
||||||
|
// Others
|
||||||
|
moved?: Status
|
||||||
|
fields: Field[]
|
||||||
|
bot: boolean
|
||||||
|
source: Source
|
||||||
|
}
|
||||||
|
|
||||||
|
type Application = {
|
||||||
|
// Base
|
||||||
|
name: string
|
||||||
|
website?: string
|
||||||
|
vapid_key?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attachment = {
|
||||||
|
// Base
|
||||||
|
id: string
|
||||||
|
type: 'unknown' | 'image' | 'gifv' | 'video' | 'audio'
|
||||||
|
url: string
|
||||||
|
preview_url?: string
|
||||||
|
|
||||||
|
// Others
|
||||||
|
remote_url?: string
|
||||||
|
text_url?: string
|
||||||
|
meta: {
|
||||||
|
original: { width: number; height: number; size: string; aspect: number }
|
||||||
|
small: { width: number; height: number; size: string; aspect: number }
|
||||||
|
focus:
|
||||||
|
| { x: number; y: number }
|
||||||
|
| {
|
||||||
|
length: string
|
||||||
|
duration: number
|
||||||
|
fps: number
|
||||||
|
size: string
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
aspect: number
|
||||||
|
audio_encode: string
|
||||||
|
audio_bitrate: string
|
||||||
|
audio_channels: string
|
||||||
|
original: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
frame_rate: string
|
||||||
|
duration: number
|
||||||
|
bitrate: number
|
||||||
|
}
|
||||||
|
small: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
size: string
|
||||||
|
aspect: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
length: string
|
||||||
|
duration: number
|
||||||
|
fps: number
|
||||||
|
size: string
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
aspect: number
|
||||||
|
original: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
frame_rate: string
|
||||||
|
duration: number
|
||||||
|
bitrate: number
|
||||||
|
}
|
||||||
|
small: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
size: string
|
||||||
|
aspect: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
length: string
|
||||||
|
duration: number
|
||||||
|
audio_encode: string
|
||||||
|
audio_bitrate: string
|
||||||
|
audio_channels: string
|
||||||
|
original: {
|
||||||
|
duration: number
|
||||||
|
bitrate: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
description?: string
|
||||||
|
blurhash?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Card = {
|
||||||
|
// Base
|
||||||
|
url: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
type: 'link' | 'photo' | 'video' | 'rich'
|
||||||
|
|
||||||
|
// Attributes
|
||||||
|
author_name: string
|
||||||
|
author_url: string
|
||||||
|
provider_name: string
|
||||||
|
provider_url: string
|
||||||
|
html: string
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
image: string
|
||||||
|
embed_url: string
|
||||||
|
blurhash: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Emoji = {
|
||||||
|
// Base
|
||||||
|
shortcode: string
|
||||||
|
url: string
|
||||||
|
static_url: string
|
||||||
|
visible_in_picker: boolean
|
||||||
|
category?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Field = {
|
||||||
|
name: string
|
||||||
|
value: string
|
||||||
|
verified_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mention = {
|
||||||
|
// Base
|
||||||
|
id: string
|
||||||
|
username: string
|
||||||
|
acct: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notification = {
|
||||||
|
// Base
|
||||||
|
id: string
|
||||||
|
type: 'follow' | 'mention' | 'reblog' | 'favourite' | 'poll'
|
||||||
|
created_at: string
|
||||||
|
account: Account
|
||||||
|
|
||||||
|
// Others
|
||||||
|
status?: Status
|
||||||
|
}
|
||||||
|
|
||||||
|
type Poll = {
|
||||||
|
// Base
|
||||||
|
id: string
|
||||||
|
expires_at: string
|
||||||
|
expired: boolean
|
||||||
|
multiple: bool
|
||||||
|
votes_count: number
|
||||||
|
voters_count: number
|
||||||
|
voted?: boolean
|
||||||
|
own_votes?: number[]
|
||||||
|
options: { title: string; votes_count: number }[]
|
||||||
|
emojis: Emoji[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Status = {
|
||||||
|
// Base
|
||||||
|
id: string
|
||||||
|
urk: string
|
||||||
|
created_at: string
|
||||||
|
account: Account
|
||||||
|
content: string
|
||||||
|
visibility: 'public' | 'unlisted' | 'private' | 'direct'
|
||||||
|
sensitive: boolean
|
||||||
|
spoiler_text?: string
|
||||||
|
media_attachments: Attachment[]
|
||||||
|
application: Application
|
||||||
|
|
||||||
|
// Attributes
|
||||||
|
mentions: Mention[]
|
||||||
|
tags: Tag[]
|
||||||
|
emojis: Emoji[]
|
||||||
|
|
||||||
|
// Interaction
|
||||||
|
reblogs_count: number
|
||||||
|
favourites_count: number
|
||||||
|
replies_count: number
|
||||||
|
favourited: boolean
|
||||||
|
reblogged: boolean
|
||||||
|
muted: boolean
|
||||||
|
bookmarked: boolean
|
||||||
|
pinned: boolean
|
||||||
|
|
||||||
|
// Others
|
||||||
|
url?: string
|
||||||
|
in_reply_to_id?: string
|
||||||
|
in_reply_to_account_id?: string
|
||||||
|
reblog: Status
|
||||||
|
poll: Poll
|
||||||
|
card: Card
|
||||||
|
language?: string
|
||||||
|
text?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Source = {
|
||||||
|
// Base
|
||||||
|
note: string
|
||||||
|
fields: Field[]
|
||||||
|
|
||||||
|
// Others
|
||||||
|
privacy?: 'public' | 'unlisted' | 'private' | 'direct'
|
||||||
|
sensitive?: boolean
|
||||||
|
language?: string
|
||||||
|
follow_requests_count?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tag = {
|
||||||
|
// Base
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
// history: types
|
||||||
|
}
|
||||||
|
}
|
47
src/@types/store.d.ts
vendored
Normal file
47
src/@types/store.d.ts
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
declare namespace store {
|
||||||
|
type AsyncStatus = 'idle' | 'loading' | 'succeeded' | 'failed'
|
||||||
|
|
||||||
|
type InstanceInfoState = {
|
||||||
|
local: string
|
||||||
|
localToken: string
|
||||||
|
remote: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimelinePage =
|
||||||
|
| 'Following'
|
||||||
|
| 'Local'
|
||||||
|
| 'LocalPublic'
|
||||||
|
| 'RemotePublic'
|
||||||
|
| 'Notifications'
|
||||||
|
| 'Hashtag'
|
||||||
|
| 'List'
|
||||||
|
| 'Toot'
|
||||||
|
| 'Account_Default'
|
||||||
|
| 'Account_All'
|
||||||
|
| 'Account_Media'
|
||||||
|
|
||||||
|
type TimelineState = {
|
||||||
|
toots: mastodon.Status[] | []
|
||||||
|
pointer?: string
|
||||||
|
status: AsyncStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimelinesState = {
|
||||||
|
Following: TimelineState
|
||||||
|
Local: TimelineState
|
||||||
|
LocalPublic: TimelineState
|
||||||
|
RemotePublic: TimelineState
|
||||||
|
Notifications: TimelineState
|
||||||
|
Hashtag: TimelineState
|
||||||
|
List: TimelineState
|
||||||
|
Toot: TimelineState
|
||||||
|
Account_Default: TimelineState
|
||||||
|
Account_All: TimelineState
|
||||||
|
Account_Media: TimelineState
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountState = {
|
||||||
|
account: mastodon.Account | {}
|
||||||
|
status: AsyncStatus
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@ import Me from 'src/stacks/Me'
|
|||||||
enableScreens()
|
enableScreens()
|
||||||
const Tab = createBottomTabNavigator()
|
const Tab = createBottomTabNavigator()
|
||||||
|
|
||||||
export default function Index () {
|
export const Index: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<StatusBar style='auto' />
|
<StatusBar style='auto' />
|
||||||
@ -26,7 +26,7 @@ export default function Index () {
|
|||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
screenOptions={({ route }) => ({
|
screenOptions={({ route }) => ({
|
||||||
tabBarIcon: ({ focused, color, size }) => {
|
tabBarIcon: ({ focused, color, size }) => {
|
||||||
let name
|
let name: string
|
||||||
switch (route.name) {
|
switch (route.name) {
|
||||||
case 'Local':
|
case 'Local':
|
||||||
name = 'home'
|
name = 'home'
|
||||||
@ -43,6 +43,9 @@ export default function Index () {
|
|||||||
case 'Me':
|
case 'Me':
|
||||||
name = focused ? 'smile' : 'meh'
|
name = focused ? 'smile' : 'meh'
|
||||||
break
|
break
|
||||||
|
default:
|
||||||
|
name = 'alert-octagon'
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return <Feather name={name} size={size} color={color} />
|
return <Feather name={name} size={size} color={color} />
|
||||||
}
|
}
|
@ -1,61 +0,0 @@
|
|||||||
import store from 'src/stacks/common/store'
|
|
||||||
|
|
||||||
export async function client (instance, query, { body, ...customConfig } = {}) {
|
|
||||||
const state = store.getState().instanceInfo
|
|
||||||
|
|
||||||
let url
|
|
||||||
let authHeader
|
|
||||||
switch (instance.type) {
|
|
||||||
case 'local':
|
|
||||||
url = `https://${state.local}/${instance.endpoint}`
|
|
||||||
authHeader = {
|
|
||||||
Authorization: `Bearer ${state.localToken}`
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'remote':
|
|
||||||
url = `https://${state.remote}/${instance.endpoint}`
|
|
||||||
authHeader = {}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return Promise.reject('Instance type is not defined.')
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = { 'Content-Type': 'application/json', ...authHeader }
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
method: body ? 'POST' : 'GET',
|
|
||||||
...customConfig,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
...customConfig.headers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryString = query
|
|
||||||
? `?${query.map(({ key, value }) => `${key}=${value}`).join('&')}`
|
|
||||||
: ''
|
|
||||||
|
|
||||||
if (body) {
|
|
||||||
config.body = JSON.stringify(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
let data
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${url}${queryString}`, config)
|
|
||||||
data = await response.json()
|
|
||||||
if (response.ok) {
|
|
||||||
return { headers: response.headers, body: 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 })
|
|
||||||
}
|
|
@ -1,14 +1,22 @@
|
|||||||
import store from 'src/stacks/common/store'
|
import store, { RootState } from 'src/stacks/common/store'
|
||||||
import ky from 'ky'
|
import ky from 'ky'
|
||||||
|
|
||||||
export default async function client ({
|
const client = async ({
|
||||||
method, // * get / post
|
method,
|
||||||
instance, // * local / remote
|
instance,
|
||||||
endpoint, // * if url is empty
|
endpoint,
|
||||||
query, // object
|
query,
|
||||||
body // object
|
body
|
||||||
}) {
|
}: {
|
||||||
const state = store.getState().instanceInfo
|
method: 'get' | 'post'
|
||||||
|
instance: 'local' | 'remote'
|
||||||
|
endpoint: string
|
||||||
|
query?: {
|
||||||
|
[key: string]: string | number | boolean
|
||||||
|
}
|
||||||
|
body?: object
|
||||||
|
}): Promise<any> => {
|
||||||
|
const state: RootState['instanceInfo'] = store.getState().instanceInfo
|
||||||
|
|
||||||
let response
|
let response
|
||||||
try {
|
try {
|
||||||
@ -38,3 +46,5 @@ export default async function client ({
|
|||||||
return Promise.reject({ body: response.error_message })
|
return Promise.reject({ body: response.error_message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default client
|
@ -1,14 +1,23 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import propTypesEmoji from 'src/prop-types/emoji'
|
|
||||||
import propTypesMention from 'src/prop-types/mention'
|
|
||||||
import { StyleSheet, Text } from 'react-native'
|
import { StyleSheet, Text } from 'react-native'
|
||||||
import HTMLView from 'react-native-htmlview'
|
import HTMLView, { HTMLViewNode } from 'react-native-htmlview'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
import Emojis from 'src/components/Toot/Emojis'
|
import Emojis from 'src/components/Toot/Emojis'
|
||||||
|
|
||||||
function renderNode ({ node, index, navigation, mentions, showFullLink }) {
|
const renderNode = ({
|
||||||
|
node,
|
||||||
|
index,
|
||||||
|
navigation,
|
||||||
|
mentions,
|
||||||
|
showFullLink
|
||||||
|
}: {
|
||||||
|
node: HTMLViewNode
|
||||||
|
index: number
|
||||||
|
navigation: object
|
||||||
|
mentions?: mastodon.Mention[]
|
||||||
|
showFullLink: boolean
|
||||||
|
}) => {
|
||||||
if (node.name == 'a') {
|
if (node.name == 'a') {
|
||||||
const classes = node.attribs.class
|
const classes = node.attribs.class
|
||||||
const href = node.attribs.href
|
const href = node.attribs.href
|
||||||
@ -69,27 +78,40 @@ function renderNode ({ node, index, navigation, mentions, showFullLink }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ParseContent ({
|
export interface Props {
|
||||||
|
content: string
|
||||||
|
emojis?: mastodon.Emoji[]
|
||||||
|
emojiSize?: number
|
||||||
|
mentions?: mastodon.Mention[]
|
||||||
|
showFullLink?: boolean
|
||||||
|
linesTruncated?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const ParseContent: React.FC<Props> = ({
|
||||||
content,
|
content,
|
||||||
emojis,
|
emojis,
|
||||||
emojiSize = 14,
|
emojiSize = 14,
|
||||||
mentions,
|
mentions,
|
||||||
showFullLink = false,
|
showFullLink = false,
|
||||||
linesTruncated = 10
|
linesTruncated = 10
|
||||||
}) {
|
}) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HTMLView
|
<HTMLView
|
||||||
value={content}
|
value={content}
|
||||||
stylesheet={HTMLstyles}
|
stylesheet={HTMLstyles}
|
||||||
paragraphBreak={null}
|
paragraphBreak=''
|
||||||
renderNode={(node, index) =>
|
renderNode={(node, index) =>
|
||||||
renderNode({ node, index, navigation, mentions, showFullLink })
|
renderNode({ node, index, navigation, mentions, showFullLink })
|
||||||
}
|
}
|
||||||
TextComponent={({ children }) => (
|
TextComponent={({ children }) =>
|
||||||
|
emojis ? (
|
||||||
<Emojis content={children} emojis={emojis} dimension={emojiSize} />
|
<Emojis content={children} emojis={emojis} dimension={emojiSize} />
|
||||||
)}
|
) : (
|
||||||
|
<Text>{children}</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
RootComponent={({ children }) => {
|
RootComponent={({ children }) => {
|
||||||
return <Text numberOfLines={linesTruncated}>{children}</Text>
|
return <Text numberOfLines={linesTruncated}>{children}</Text>
|
||||||
}}
|
}}
|
||||||
@ -109,11 +131,4 @@ const HTMLstyles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ParseContent.propTypes = {
|
export default ParseContent
|
||||||
content: PropTypes.string.isRequired,
|
|
||||||
emojis: PropTypes.arrayOf(propTypesEmoji),
|
|
||||||
emojiSize: PropTypes.number,
|
|
||||||
mentions: PropTypes.arrayOf(propTypesMention),
|
|
||||||
showFullLink: PropTypes.bool,
|
|
||||||
linesTruncated: PropTypes.number
|
|
||||||
}
|
|
@ -1,17 +1,22 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import propTypesEmoji from 'src/prop-types/emoji'
|
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
|
||||||
import Emojis from './Emojis'
|
import Emojis from './Emojis'
|
||||||
|
|
||||||
export default function Actioned ({
|
export interface Props {
|
||||||
|
action: 'favourite' | 'follow' | 'mention' | 'poll' | 'reblog'
|
||||||
|
name?: string
|
||||||
|
emojis?: mastodon.Emoji[]
|
||||||
|
notification?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Actioned: React.FC<Props> = ({
|
||||||
action,
|
action,
|
||||||
name,
|
name,
|
||||||
emojis,
|
emojis,
|
||||||
notification = false
|
notification = false
|
||||||
}) {
|
}) => {
|
||||||
let icon
|
let icon
|
||||||
let content
|
let content
|
||||||
switch (action) {
|
switch (action) {
|
||||||
@ -51,7 +56,11 @@ export default function Actioned ({
|
|||||||
{icon}
|
{icon}
|
||||||
{content ? (
|
{content ? (
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
|
{emojis ? (
|
||||||
<Emojis content={content} emojis={emojis} dimension={12} />
|
<Emojis content={content} emojis={emojis} dimension={12} />
|
||||||
|
) : (
|
||||||
|
<Text>{content}</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@ -74,10 +83,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Actioned.propTypes = {
|
export default Actioned
|
||||||
action: PropTypes.oneOf(['favourite', 'follow', 'mention', 'poll', 'reblog'])
|
|
||||||
.isRequired,
|
|
||||||
name: PropTypes.string,
|
|
||||||
emojis: PropTypes.arrayOf(propTypesEmoji),
|
|
||||||
notification: PropTypes.bool
|
|
||||||
}
|
|
@ -1,18 +1,26 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
|
||||||
import action from 'src/components/action'
|
import action from 'src/components/action'
|
||||||
|
|
||||||
export default function Actions ({
|
export interface Props {
|
||||||
|
id: string
|
||||||
|
replies_count: number
|
||||||
|
reblogs_count: number
|
||||||
|
reblogged?: boolean
|
||||||
|
favourites_count: number
|
||||||
|
favourited?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Actions: React.FC<Props> = ({
|
||||||
id,
|
id,
|
||||||
replies_count,
|
replies_count,
|
||||||
reblogs_count,
|
reblogs_count,
|
||||||
reblogged,
|
reblogged,
|
||||||
favourites_count,
|
favourites_count,
|
||||||
favourited
|
favourited
|
||||||
}) {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.actions}>
|
<View style={styles.actions}>
|
||||||
<Pressable style={styles.action}>
|
<Pressable style={styles.action}>
|
||||||
@ -23,7 +31,17 @@ export default function Actions ({
|
|||||||
<Feather name='repeat' />
|
<Feather name='repeat' />
|
||||||
<Text>{reblogs_count}</Text>
|
<Text>{reblogs_count}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Pressable style={styles.action} onPress={() => action('favourite', id)}>
|
<Pressable
|
||||||
|
style={styles.action}
|
||||||
|
onPress={() =>
|
||||||
|
action({
|
||||||
|
id,
|
||||||
|
type: 'favourite',
|
||||||
|
stateKey: 'favourited',
|
||||||
|
statePrev: favourited || false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
<Feather name='heart' />
|
<Feather name='heart' />
|
||||||
<Text>{favourites_count}</Text>
|
<Text>{favourites_count}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
@ -49,11 +67,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Actions.propTypes = {
|
export default Actions
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
replies_count: PropTypes.number.isRequired,
|
|
||||||
reblogs_count: PropTypes.number.isRequired,
|
|
||||||
reblogged: PropTypes.bool.isRequired,
|
|
||||||
favourites_count: PropTypes.number.isRequired,
|
|
||||||
favourited: PropTypes.bool.isRequired
|
|
||||||
}
|
|
@ -1,12 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import propTypesAttachment from 'src/prop-types/attachment'
|
|
||||||
import { Text, View } from 'react-native'
|
import { Text, View } from 'react-native'
|
||||||
|
|
||||||
import AttachmentImage from './Attachment/AttachmentImage'
|
import AttachmentImage from './Attachment/AttachmentImage'
|
||||||
import AttachmentVideo from './Attachment/AttachmentVideo'
|
import AttachmentVideo from './Attachment/AttachmentVideo'
|
||||||
|
|
||||||
export default function Attachment ({ media_attachments, sensitive, width }) {
|
export interface Props {
|
||||||
|
media_attachments: mastodon.Attachment[]
|
||||||
|
sensitive: boolean
|
||||||
|
width: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const Attachment: React.FC<Props> = ({
|
||||||
|
media_attachments,
|
||||||
|
sensitive,
|
||||||
|
width
|
||||||
|
}) => {
|
||||||
let attachment
|
let attachment
|
||||||
let attachmentHeight
|
let attachmentHeight
|
||||||
// if (width) {}
|
// if (width) {}
|
||||||
@ -74,8 +82,4 @@ export default function Attachment ({ media_attachments, sensitive, width }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Attachment.propTypes = {
|
export default Attachment
|
||||||
media_attachments: PropTypes.arrayOf(propTypesAttachment),
|
|
||||||
sensitive: PropTypes.bool.isRequired,
|
|
||||||
width: PropTypes.number.isRequired
|
|
||||||
}
|
|
@ -1,10 +1,18 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import propTypesAttachment from 'src/prop-types/attachment'
|
|
||||||
import { Button, Image, Modal, StyleSheet, Pressable, View } from 'react-native'
|
import { Button, Image, Modal, StyleSheet, Pressable, View } from 'react-native'
|
||||||
import ImageViewer from 'react-native-image-zoom-viewer'
|
import ImageViewer from 'react-native-image-zoom-viewer'
|
||||||
|
|
||||||
export default function AttachmentImage ({ media_attachments, sensitive, width }) {
|
export interface Props {
|
||||||
|
media_attachments: mastodon.Attachment[]
|
||||||
|
sensitive: boolean
|
||||||
|
width: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const AttachmentImage: React.FC<Props> = ({
|
||||||
|
media_attachments,
|
||||||
|
sensitive,
|
||||||
|
width
|
||||||
|
}) => {
|
||||||
const [mediaSensitive, setMediaSensitive] = useState(sensitive)
|
const [mediaSensitive, setMediaSensitive] = useState(sensitive)
|
||||||
const [imageModalVisible, setImageModalVisible] = useState(false)
|
const [imageModalVisible, setImageModalVisible] = useState(false)
|
||||||
const [imageModalIndex, setImageModalIndex] = useState(0)
|
const [imageModalIndex, setImageModalIndex] = useState(0)
|
||||||
@ -16,8 +24,8 @@ export default function AttachmentImage ({ media_attachments, sensitive, width }
|
|||||||
}
|
}
|
||||||
}, [mediaSensitive])
|
}, [mediaSensitive])
|
||||||
|
|
||||||
let images = []
|
let images: { url: string; width: number; height: number }[] = []
|
||||||
media_attachments = media_attachments.map((m, i) => {
|
const imagesNode = media_attachments.map((m, i) => {
|
||||||
images.push({
|
images.push({
|
||||||
url: m.url,
|
url: m.url,
|
||||||
width: m.meta.original.width,
|
width: m.meta.original.width,
|
||||||
@ -44,7 +52,7 @@ export default function AttachmentImage ({ media_attachments, sensitive, width }
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={styles.media}>
|
<View style={styles.media}>
|
||||||
{media_attachments}
|
{imagesNode}
|
||||||
{mediaSensitive && (
|
{mediaSensitive && (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@ -95,8 +103,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
AttachmentImage.propTypes = {
|
export default AttachmentImage
|
||||||
media_attachments: PropTypes.arrayOf(propTypesAttachment),
|
|
||||||
sensitive: PropTypes.bool.isRequired,
|
|
||||||
width: PropTypes.number.isRequired
|
|
||||||
}
|
|
@ -1,15 +1,19 @@
|
|||||||
import React, { useRef, useState } from 'react'
|
import React, { useRef, useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import propTypesAttachment from 'src/prop-types/attachment'
|
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import { Video } from 'expo-av'
|
import { Video } from 'expo-av'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
|
||||||
export default function AttachmentVideo ({
|
export interface Props {
|
||||||
|
media_attachments: mastodon.Attachment[]
|
||||||
|
sensitive: boolean
|
||||||
|
width: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const AttachmentVideo: React.FC<Props> = ({
|
||||||
media_attachments,
|
media_attachments,
|
||||||
sensitive,
|
sensitive,
|
||||||
width
|
width
|
||||||
}) {
|
}) => {
|
||||||
const videoPlayer = useRef()
|
const videoPlayer = useRef()
|
||||||
const [mediaSensitive, setMediaSensitive] = useState(sensitive)
|
const [mediaSensitive, setMediaSensitive] = useState(sensitive)
|
||||||
const [videoPlay, setVideoPlay] = useState(false)
|
const [videoPlay, setVideoPlay] = useState(false)
|
||||||
@ -28,7 +32,7 @@ export default function AttachmentVideo ({
|
|||||||
>
|
>
|
||||||
<Video
|
<Video
|
||||||
ref={videoPlayer}
|
ref={videoPlayer}
|
||||||
source={{ uri: video.remote_url }}
|
source={{ uri: video.remote_url || video.url }}
|
||||||
style={{
|
style={{
|
||||||
width: videoWidth,
|
width: videoWidth,
|
||||||
height: videoHeight
|
height: videoHeight
|
||||||
@ -65,8 +69,4 @@ export default function AttachmentVideo ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachmentVideo.propTypes = {
|
export default AttachmentVideo
|
||||||
media_attachments: PropTypes.arrayOf(propTypesAttachment),
|
|
||||||
sensitive: PropTypes.bool.isRequired,
|
|
||||||
width: PropTypes.number.isRequired
|
|
||||||
}
|
|
@ -1,9 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { Image, Pressable, StyleSheet } from 'react-native'
|
import { Image, Pressable, StyleSheet } from 'react-native'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
export default function Avatar ({ uri, id }) {
|
export interface Props {
|
||||||
|
uri: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Avatar: React.FC<Props> = ({ uri, id }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
// Need to fix go back root
|
// Need to fix go back root
|
||||||
return (
|
return (
|
||||||
@ -32,7 +36,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Avatar.propTypes = {
|
export default Avatar
|
||||||
uri: PropTypes.string.isRequired,
|
|
||||||
id: PropTypes.string.isRequired
|
|
||||||
}
|
|
@ -1,9 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import propTypesCard from 'src/prop-types/card'
|
|
||||||
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
|
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
export default function Card ({ card }) {
|
export interface Props {
|
||||||
|
card: mastodon.Card
|
||||||
|
}
|
||||||
|
|
||||||
|
const Card: React.FC<Props> = ({ card }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
return (
|
return (
|
||||||
card && (
|
card && (
|
||||||
@ -53,6 +56,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Card.propTypes = {
|
export default Card
|
||||||
card: propTypesCard
|
|
||||||
}
|
|
@ -1,11 +1,22 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { Text } from 'react-native'
|
import { Text } from 'react-native'
|
||||||
import Collapsible from 'react-native-collapsible'
|
import Collapsible from 'react-native-collapsible'
|
||||||
|
|
||||||
import ParseContent from 'src/components/ParseContent'
|
import ParseContent from 'src/components/ParseContent'
|
||||||
|
|
||||||
export default function Content ({ content, emojis, mentions, spoiler_text }) {
|
export interface Props {
|
||||||
|
content: string
|
||||||
|
emojis: mastodon.Emoji[]
|
||||||
|
mentions: mastodon.Mention[]
|
||||||
|
spoiler_text?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Content: React.FC<Props> = ({
|
||||||
|
content,
|
||||||
|
emojis,
|
||||||
|
mentions,
|
||||||
|
spoiler_text
|
||||||
|
}) => {
|
||||||
const [spoilerCollapsed, setSpoilerCollapsed] = useState(true)
|
const [spoilerCollapsed, setSpoilerCollapsed] = useState(true)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -40,9 +51,4 @@ export default function Content ({ content, emojis, mentions, spoiler_text }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Content.propTypes = {
|
export default Content
|
||||||
content: ParseContent.propTypes.content,
|
|
||||||
emojis: ParseContent.propTypes.emojis,
|
|
||||||
mentions: ParseContent.propTypes.mentions,
|
|
||||||
spoiler_text: PropTypes.string
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import propTypesEmoji from 'src/prop-types/emoji'
|
|
||||||
import { Image, Text } from 'react-native'
|
|
||||||
|
|
||||||
const regexEmoji = new RegExp(/(:[a-z0-9_]+:)/)
|
|
||||||
|
|
||||||
export default function Emojis ({ content, emojis, dimension }) {
|
|
||||||
const hasEmojis = content.match(regexEmoji)
|
|
||||||
return hasEmojis ? (
|
|
||||||
content.split(regexEmoji).map((str, i) => {
|
|
||||||
if (str.match(regexEmoji)) {
|
|
||||||
const emojiShortcode = str.split(regexEmoji)[1]
|
|
||||||
const emojiIndex = emojis.findIndex(emoji => {
|
|
||||||
return emojiShortcode === `:${emoji.shortcode}:`
|
|
||||||
})
|
|
||||||
return emojiIndex === -1 ? (
|
|
||||||
<Text key={i} style={{ color: 'red' }}>
|
|
||||||
Something wrong with emoji!
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<Image
|
|
||||||
key={i}
|
|
||||||
source={{ uri: emojis[emojiIndex].url }}
|
|
||||||
style={{ width: dimension, height: dimension }}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Text
|
|
||||||
key={i}
|
|
||||||
style={{ fontSize: dimension, lineHeight: dimension + 1 }}
|
|
||||||
>
|
|
||||||
{str}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<Text style={{ fontSize: dimension, lineHeight: dimension + 1 }}>
|
|
||||||
{content}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Emojis.propTypes = {
|
|
||||||
content: PropTypes.string.isRequired,
|
|
||||||
emojis: PropTypes.arrayOf(propTypesEmoji)
|
|
||||||
}
|
|
52
src/components/Toot/Emojis.tsx
Normal file
52
src/components/Toot/Emojis.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Image, Text } from 'react-native'
|
||||||
|
|
||||||
|
const regexEmoji = new RegExp(/(:[a-z0-9_]+:)/)
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
content: string
|
||||||
|
emojis: mastodon.Emoji[]
|
||||||
|
dimension: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const Emojis: React.FC<Props> = ({ content, emojis, dimension }) => {
|
||||||
|
const hasEmojis = content.match(regexEmoji)
|
||||||
|
return hasEmojis ? (
|
||||||
|
<>
|
||||||
|
{content.split(regexEmoji).map((str, i) => {
|
||||||
|
if (str.match(regexEmoji)) {
|
||||||
|
const emojiShortcode = str.split(regexEmoji)[1]
|
||||||
|
const emojiIndex = emojis.findIndex(emoji => {
|
||||||
|
return emojiShortcode === `:${emoji.shortcode}:`
|
||||||
|
})
|
||||||
|
return emojiIndex === -1 ? (
|
||||||
|
<Text key={i} style={{ color: 'red' }}>
|
||||||
|
Something wrong with emoji!
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Image
|
||||||
|
key={i}
|
||||||
|
source={{ uri: emojis[emojiIndex].url }}
|
||||||
|
style={{ width: dimension, height: dimension }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key={i}
|
||||||
|
style={{ fontSize: dimension, lineHeight: dimension + 1 }}
|
||||||
|
>
|
||||||
|
{str}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Text style={{ fontSize: dimension, lineHeight: dimension + 1 }}>
|
||||||
|
{content}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Emojis
|
@ -1,17 +1,26 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
import Emojis from './Emojis'
|
import Emojis from './Emojis'
|
||||||
import relativeTime from 'src/utils/relativeTime'
|
import relativeTime from 'src/utils/relativeTime'
|
||||||
|
|
||||||
export default function Header ({
|
export interface Props {
|
||||||
|
name: string
|
||||||
|
emojis?: mastodon.Emoji[]
|
||||||
|
account: string
|
||||||
|
created_at: string
|
||||||
|
application?: mastodon.Application
|
||||||
|
}
|
||||||
|
|
||||||
|
const Header: React.FC<Props> = ({
|
||||||
name,
|
name,
|
||||||
emojis,
|
emojis,
|
||||||
account,
|
account,
|
||||||
created_at,
|
created_at,
|
||||||
application
|
application
|
||||||
}) {
|
}) => {
|
||||||
|
const navigation = useNavigation()
|
||||||
const [since, setSince] = useState(relativeTime(created_at))
|
const [since, setSince] = useState(relativeTime(created_at))
|
||||||
|
|
||||||
// causing full re-render
|
// causing full re-render
|
||||||
@ -25,7 +34,11 @@ export default function Header ({
|
|||||||
<View>
|
<View>
|
||||||
<View style={styles.names}>
|
<View style={styles.names}>
|
||||||
<View style={styles.name}>
|
<View style={styles.name}>
|
||||||
|
{emojis ? (
|
||||||
<Emojis content={name} emojis={emojis} dimension={14} />
|
<Emojis content={name} emojis={emojis} dimension={14} />
|
||||||
|
) : (
|
||||||
|
<Text>{name}</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.account} numberOfLines={1}>
|
<Text style={styles.account} numberOfLines={1}>
|
||||||
@{account}
|
@{account}
|
||||||
@ -38,7 +51,11 @@ export default function Header ({
|
|||||||
{application && application.name !== 'Web' && (
|
{application && application.name !== 'Web' && (
|
||||||
<View>
|
<View>
|
||||||
<Text
|
<Text
|
||||||
onPress={() => Linking.openURL(application.website)}
|
onPress={() => {
|
||||||
|
navigation.navigate('Webview', {
|
||||||
|
uri: application.website
|
||||||
|
})
|
||||||
|
}}
|
||||||
style={styles.application}
|
style={styles.application}
|
||||||
>
|
>
|
||||||
{application.name}
|
{application.name}
|
||||||
@ -78,13 +95,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Header.propTypes = {
|
export default Header
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
emojis: Emojis.propTypes.emojis,
|
|
||||||
account: PropTypes.string.isRequired,
|
|
||||||
created_at: PropTypes.string.isRequired,
|
|
||||||
application: PropTypes.exact({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
website: PropTypes.string
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,10 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import propTypesPoll from 'src/prop-types/poll'
|
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
|
||||||
import Emojis from './Emojis'
|
import Emojis from './Emojis'
|
||||||
|
|
||||||
export default function Poll ({ poll }) {
|
export interface Props {
|
||||||
|
poll: mastodon.Poll
|
||||||
|
}
|
||||||
|
|
||||||
|
const Poll: React.FC<Props> = ({ poll }) => {
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
{poll.options.map((option, index) => (
|
{poll.options.map((option, index) => (
|
||||||
@ -13,7 +16,11 @@ export default function Poll ({ poll }) {
|
|||||||
<Text>
|
<Text>
|
||||||
{Math.round((option.votes_count / poll.votes_count) * 100)}%
|
{Math.round((option.votes_count / poll.votes_count) * 100)}%
|
||||||
</Text>
|
</Text>
|
||||||
<Text>{option.title}</Text>
|
<Emojis
|
||||||
|
content={option.title}
|
||||||
|
emojis={poll.emojis}
|
||||||
|
dimension={14}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@ -42,6 +49,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Poll.propTypes = {
|
export default Poll
|
||||||
poll: propTypesPoll
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import propTypesNotification from 'src/prop-types/notification'
|
|
||||||
import { Dimensions, Pressable, StyleSheet, Text, View } from 'react-native'
|
import { Dimensions, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
@ -12,7 +11,11 @@ import Attachment from './Toot/Attachment'
|
|||||||
import Card from './Toot/Card'
|
import Card from './Toot/Card'
|
||||||
import Actions from './Toot/Actions'
|
import Actions from './Toot/Actions'
|
||||||
|
|
||||||
export default function TootNotification ({ toot }) {
|
export interface Props {
|
||||||
|
toot: mastodon.Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
const TootNotification: React.FC<Props> = ({ toot }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const actualAccount = toot.status ? toot.status.account : toot.account
|
const actualAccount = toot.status ? toot.status.account : toot.account
|
||||||
|
|
||||||
@ -46,8 +49,8 @@ export default function TootNotification ({ toot }) {
|
|||||||
emojis={toot.status.emojis}
|
emojis={toot.status.emojis}
|
||||||
mentions={toot.status.mentions}
|
mentions={toot.status.mentions}
|
||||||
spoiler_text={toot.status.spoiler_text}
|
spoiler_text={toot.status.spoiler_text}
|
||||||
tags={toot.status.tags}
|
// tags={toot.status.tags}
|
||||||
style={{ flex: 1 }}
|
// style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{toot.status.poll && <Poll poll={toot.status.poll} />}
|
{toot.status.poll && <Poll poll={toot.status.poll} />}
|
||||||
@ -99,6 +102,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
TootNotification.propTypes = {
|
export default TootNotification
|
||||||
toot: propTypesNotification
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import propTypesStatus from 'src/prop-types/status'
|
|
||||||
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
|
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
@ -12,15 +11,14 @@ import Attachment from './Toot/Attachment'
|
|||||||
import Card from './Toot/Card'
|
import Card from './Toot/Card'
|
||||||
import Actions from './Toot/Actions'
|
import Actions from './Toot/Actions'
|
||||||
|
|
||||||
export default function TootTimeline ({ toot }) {
|
export interface Props {
|
||||||
|
toot: mastodon.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
const TootTimeline: React.FC<Props> = ({ toot }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
let actualContent
|
let actualContent = toot.reblog ? toot.reblog : toot
|
||||||
if (toot.reblog) {
|
|
||||||
actualContent = toot.reblog
|
|
||||||
} else {
|
|
||||||
actualContent = toot
|
|
||||||
}
|
|
||||||
|
|
||||||
const tootView = useMemo(() => {
|
const tootView = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@ -60,8 +58,8 @@ export default function TootTimeline ({ toot }) {
|
|||||||
emojis={actualContent.emojis}
|
emojis={actualContent.emojis}
|
||||||
mentions={actualContent.mentions}
|
mentions={actualContent.mentions}
|
||||||
spoiler_text={actualContent.spoiler_text}
|
spoiler_text={actualContent.spoiler_text}
|
||||||
tags={actualContent.tags}
|
// tags={actualContent.tags}
|
||||||
style={{ flex: 1 }}
|
// style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@ -88,7 +86,7 @@ export default function TootTimeline ({ toot }) {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
}, [toot])
|
||||||
|
|
||||||
return tootView
|
return tootView
|
||||||
}
|
}
|
||||||
@ -109,6 +107,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
TootTimeline.propTypes = {
|
export default TootTimeline
|
||||||
toot: propTypesStatus
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
import { Alert } from 'react-native'
|
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
import { client } from 'src/api/client'
|
|
||||||
|
|
||||||
export default async function action (type, id) {
|
|
||||||
// If header if needed for remote server
|
|
||||||
const header = {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${useSelector(
|
|
||||||
state => state.instanceInfo.localToken
|
|
||||||
)}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const instance = `https://${useSelector(
|
|
||||||
state => state.instanceInfo.local
|
|
||||||
)}/api/v1/`
|
|
||||||
|
|
||||||
let endpoint
|
|
||||||
switch (type) {
|
|
||||||
case 'favourite':
|
|
||||||
endpoint = `${instance}statuses/${id}/favourite`
|
|
||||||
break
|
|
||||||
case 'unfavourite':
|
|
||||||
endpoint = `${instance}statuses/${id}/unfavourite`
|
|
||||||
break
|
|
||||||
case 'reblog':
|
|
||||||
endpoint = `${instance}statuses/${id}/reblog`
|
|
||||||
break
|
|
||||||
case 'unreblog':
|
|
||||||
endpoint = `${instance}statuses/${id}/unreblog`
|
|
||||||
break
|
|
||||||
case 'bookmark':
|
|
||||||
endpoint = `${instance}statuses/${id}/bookmark`
|
|
||||||
break
|
|
||||||
case 'unbookmark':
|
|
||||||
endpoint = `${instance}statuses/${id}/unbookmark`
|
|
||||||
break
|
|
||||||
case 'mute':
|
|
||||||
endpoint = `${instance}statuses/${id}/mute`
|
|
||||||
break
|
|
||||||
case 'unmute':
|
|
||||||
endpoint = `${instance}statuses/${id}/unmute`
|
|
||||||
break
|
|
||||||
case 'pin':
|
|
||||||
endpoint = `${instance}statuses/${id}/pin`
|
|
||||||
break
|
|
||||||
case 'unpin':
|
|
||||||
endpoint = `${instance}statuses/${id}/unpin`
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await client.post(endpoint, [], header)
|
|
||||||
console.log(res)
|
|
||||||
|
|
||||||
const alert = {
|
|
||||||
title: 'This is a title',
|
|
||||||
message: 'This is a message'
|
|
||||||
}
|
|
||||||
Alert.alert(alert.title, alert.message, [
|
|
||||||
{ text: 'OK', onPress: () => console.log('OK Pressed') }
|
|
||||||
])
|
|
||||||
}
|
|
41
src/components/action.ts
Normal file
41
src/components/action.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Alert } from 'react-native'
|
||||||
|
|
||||||
|
import client from 'src/api/client'
|
||||||
|
|
||||||
|
export interface params {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = async ({
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
stateKey,
|
||||||
|
statePrev
|
||||||
|
}: {
|
||||||
|
id: string
|
||||||
|
type: 'favourite' | 'reblog' | 'bookmark' | 'mute' | 'pin'
|
||||||
|
stateKey: 'favourited' | 'reblogged' | 'bookmarked' | 'muted' | 'pinned'
|
||||||
|
statePrev: boolean
|
||||||
|
}): Promise<void> => {
|
||||||
|
const alert = {
|
||||||
|
title: 'This is a title',
|
||||||
|
message: 'This is a message'
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await client({
|
||||||
|
method: 'post',
|
||||||
|
instance: 'local',
|
||||||
|
endpoint: `statuses/${id}/${statePrev ? 'un' : ''}${type}`
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.body[stateKey] === statePrev) {
|
||||||
|
// Update redux
|
||||||
|
console.log('OK!!!')
|
||||||
|
} else {
|
||||||
|
Alert.alert(alert.title, alert.message, [
|
||||||
|
{ text: 'OK', onPress: () => console.log('OK Pressed') }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default action
|
@ -1,37 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
import propTypesEmoji from './emoji'
|
|
||||||
import propTypesStatus from './status'
|
|
||||||
|
|
||||||
const propTypesAccount = PropTypes.shape({
|
|
||||||
// Base
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
username: PropTypes.string.isRequired,
|
|
||||||
acct: PropTypes.string.isRequired,
|
|
||||||
url: PropTypes.string.isRequired,
|
|
||||||
|
|
||||||
// Attributes
|
|
||||||
display_name: PropTypes.string.isRequired,
|
|
||||||
note: PropTypes.string,
|
|
||||||
avatar: PropTypes.string.isRequired,
|
|
||||||
avatar_static: PropTypes.string.isRequired,
|
|
||||||
header: PropTypes.string.isRequired,
|
|
||||||
header_static: PropTypes.string.isRequired,
|
|
||||||
locked: PropTypes.bool.isRequired,
|
|
||||||
emojis: PropTypes.arrayOf(propTypesEmoji),
|
|
||||||
discoverable: PropTypes.bool.isRequired,
|
|
||||||
|
|
||||||
// Statistics
|
|
||||||
created_at: PropTypes.string.isRequired,
|
|
||||||
last_status_at: PropTypes.string.isRequired,
|
|
||||||
statuses_count: PropTypes.number.isRequired,
|
|
||||||
followers_count: PropTypes.number.isRequired,
|
|
||||||
following_count: PropTypes.number.isRequired,
|
|
||||||
|
|
||||||
// Others
|
|
||||||
moved: propTypesStatus,
|
|
||||||
// fields prop-types
|
|
||||||
bot: PropTypes.bool.isRequired
|
|
||||||
// source prop-types
|
|
||||||
})
|
|
||||||
|
|
||||||
export default propTypesAccount
|
|
@ -1,10 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
const propTypesApplication = PropTypes.shape({
|
|
||||||
// Base
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
website: PropTypes.string,
|
|
||||||
vapid_key: PropTypes.string
|
|
||||||
})
|
|
||||||
|
|
||||||
export default propTypesApplication
|
|
@ -1,19 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
const propTypesAttachment = PropTypes.shape({
|
|
||||||
// Base
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
type: PropTypes.oneOf(['unknown', 'image', 'gifv', 'video', 'audio'])
|
|
||||||
.isRequired,
|
|
||||||
url: PropTypes.string.isRequired,
|
|
||||||
preview_url: PropTypes.string,
|
|
||||||
|
|
||||||
// Others
|
|
||||||
remote_url: PropTypes.string,
|
|
||||||
text_url: PropTypes.string,
|
|
||||||
meta: PropTypes.object,
|
|
||||||
description: PropTypes.string,
|
|
||||||
blurhash: PropTypes.string
|
|
||||||
})
|
|
||||||
|
|
||||||
export default propTypesAttachment
|
|
@ -1,23 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
const propTypesCard = PropTypes.shape({
|
|
||||||
// Base
|
|
||||||
url: PropTypes.string.isRequired,
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
description: PropTypes.string.isRequired,
|
|
||||||
type: PropTypes.oneOf(['link', 'photo', 'video', 'rich']).isRequired,
|
|
||||||
|
|
||||||
// Attributes
|
|
||||||
author_name: PropTypes.string,
|
|
||||||
author_url: PropTypes.string,
|
|
||||||
provider_name: PropTypes.string,
|
|
||||||
provider_url: PropTypes.string,
|
|
||||||
html: PropTypes.string,
|
|
||||||
width: PropTypes.number,
|
|
||||||
height: PropTypes.number,
|
|
||||||
image: PropTypes.string,
|
|
||||||
embed_url: PropTypes.string,
|
|
||||||
blurhash: PropTypes.string
|
|
||||||
})
|
|
||||||
|
|
||||||
export default propTypesCard
|
|
@ -1,12 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
const propTypesEmoji = PropTypes.shape({
|
|
||||||
// Base
|
|
||||||
shortcode: PropTypes.string.isRequired,
|
|
||||||
url: PropTypes.string.isRequired,
|
|
||||||
static_url: PropTypes.string.isRequired,
|
|
||||||
visible_in_picker: PropTypes.bool.isRequired,
|
|
||||||
category: PropTypes.string
|
|
||||||
})
|
|
||||||
|
|
||||||
export default propTypesEmoji
|
|
@ -1,11 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
const propTypesMention = PropTypes.shape({
|
|
||||||
// Base
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
username: PropTypes.string.isRequired,
|
|
||||||
acct: PropTypes.string.isRequired,
|
|
||||||
url: PropTypes.string.isRequired
|
|
||||||
})
|
|
||||||
|
|
||||||
export default propTypesMention
|
|
@ -1,17 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
import propTypesAccount from './account'
|
|
||||||
import propTypesStatus from './status'
|
|
||||||
|
|
||||||
const propTypesNotification = PropTypes.shape({
|
|
||||||
// Base
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
type: PropTypes.oneOf(['follow', 'mention', 'reblog', 'favourite', 'poll'])
|
|
||||||
.isRequired,
|
|
||||||
created_at: PropTypes.string.isRequired,
|
|
||||||
account: propTypesAccount,
|
|
||||||
|
|
||||||
// Others
|
|
||||||
status: propTypesStatus
|
|
||||||
})
|
|
||||||
|
|
||||||
export default propTypesNotification
|
|
@ -1,23 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
import propTypesEmoji from './emoji'
|
|
||||||
|
|
||||||
const propTypesPoll = PropTypes.shape({
|
|
||||||
// Base
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
expires_at: PropTypes.string.isRequired,
|
|
||||||
expired: PropTypes.bool.isRequired,
|
|
||||||
multiple: PropTypes.bool.isRequired,
|
|
||||||
votes_count: PropTypes.number.isRequired,
|
|
||||||
voters_count: PropTypes.number.isRequired,
|
|
||||||
voted: PropTypes.bool,
|
|
||||||
own_votes: PropTypes.arrayOf(PropTypes.number),
|
|
||||||
options: PropTypes.arrayOf(
|
|
||||||
PropTypes.exact({
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
votes_count: PropTypes.number.isRequired
|
|
||||||
})
|
|
||||||
).isRequired,
|
|
||||||
emojis: PropTypes.arrayOf(propTypesEmoji)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default propTypesPoll
|
|
@ -1,51 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
import propTypesAccount from './account'
|
|
||||||
import propTypesAttachment from './attachment'
|
|
||||||
import propTypesApplication from './application'
|
|
||||||
import propTypesMention from './mention'
|
|
||||||
import propTypesTag from './tag'
|
|
||||||
import propTypesEmoji from './emoji'
|
|
||||||
import propTypesPoll from './poll'
|
|
||||||
import propTypesCard from './card'
|
|
||||||
|
|
||||||
const propTypesStatus = PropTypes.shape({
|
|
||||||
// Base
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
uri: PropTypes.string.isRequired,
|
|
||||||
created_at: PropTypes.string.isRequired,
|
|
||||||
account: propTypesAccount,
|
|
||||||
content: PropTypes.string.isRequired, // Might not be required
|
|
||||||
visibility: PropTypes.oneOf(['public', 'unlisted', 'private', 'direct'])
|
|
||||||
.isRequired,
|
|
||||||
sensitive: PropTypes.bool.isRequired,
|
|
||||||
spoiler_text: PropTypes.string,
|
|
||||||
media_attachments: PropTypes.arrayOf(propTypesAttachment),
|
|
||||||
application: propTypesApplication,
|
|
||||||
|
|
||||||
// Attributes
|
|
||||||
mentions: PropTypes.arrayOf(propTypesMention),
|
|
||||||
tags: PropTypes.arrayOf(propTypesTag),
|
|
||||||
emojis: PropTypes.arrayOf(propTypesEmoji),
|
|
||||||
|
|
||||||
// Interaction
|
|
||||||
reblogs_count: PropTypes.number.isRequired,
|
|
||||||
favourites_count: PropTypes.number.isRequired,
|
|
||||||
replies_count: PropTypes.number.isRequired,
|
|
||||||
favourited: PropTypes.bool,
|
|
||||||
reblogged: PropTypes.bool,
|
|
||||||
muted: PropTypes.bool,
|
|
||||||
bookmarked: PropTypes.bool,
|
|
||||||
pinned: PropTypes.bool,
|
|
||||||
|
|
||||||
// Others
|
|
||||||
url: PropTypes.string,
|
|
||||||
in_reply_to_id: PropTypes.string,
|
|
||||||
in_reply_to_account_id: PropTypes.string,
|
|
||||||
reblog: propTypesStatus,
|
|
||||||
poll: propTypesPoll,
|
|
||||||
card: propTypesCard,
|
|
||||||
language: PropTypes.string,
|
|
||||||
text: PropTypes.string
|
|
||||||
})
|
|
||||||
|
|
||||||
export default propTypesStatus
|
|
@ -1,10 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
const propTypesTag = PropTypes.shape({
|
|
||||||
// Base
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
url: PropTypes.string.isRequired
|
|
||||||
// history prop-types
|
|
||||||
})
|
|
||||||
|
|
||||||
export default propTypesTag
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
|
|
||||||
import TimelinesCombined from 'src/stacks/common/TimelinesCombined'
|
import TimelinesCombined from 'src/stacks/common/TimelinesCombined'
|
||||||
|
|
||||||
export default function Local () {
|
const Local: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<TimelinesCombined
|
<TimelinesCombined
|
||||||
name='Local'
|
name='Local'
|
||||||
@ -13,3 +13,5 @@ export default function Local () {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Local
|
@ -6,7 +6,7 @@ import Authentication from 'src/stacks/Me/Authentication'
|
|||||||
|
|
||||||
const Stack = createNativeStackNavigator()
|
const Stack = createNativeStackNavigator()
|
||||||
|
|
||||||
export default function Me () {
|
const Me: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator>
|
<Stack.Navigator>
|
||||||
<Stack.Screen name='Me-Base' component={Base} />
|
<Stack.Screen name='Me-Base' component={Base} />
|
||||||
@ -20,3 +20,5 @@ export default function Me () {
|
|||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Me
|
@ -7,7 +7,7 @@ import sharedScreens from 'src/stacks/Shared/sharedScreens'
|
|||||||
|
|
||||||
const Stack = createNativeStackNavigator()
|
const Stack = createNativeStackNavigator()
|
||||||
|
|
||||||
export default function Notifications () {
|
const Notifications: React.FC = () => {
|
||||||
const [renderHeader, setRenderHeader] = useState(false)
|
const [renderHeader, setRenderHeader] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -18,7 +18,6 @@ export default function Notifications () {
|
|||||||
return (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
statusBarAnimation: 'none',
|
|
||||||
headerRight: () =>
|
headerRight: () =>
|
||||||
renderHeader ? (
|
renderHeader ? (
|
||||||
<Feather name='search' size={24} color='black' />
|
<Feather name='search' size={24} color='black' />
|
||||||
@ -34,3 +33,5 @@ export default function Notifications () {
|
|||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Notifications
|
@ -2,12 +2,9 @@ import React from 'react'
|
|||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
|
|
||||||
import Base from './Me/Base'
|
|
||||||
import Authentication from 'src/stacks/Me/Authentication'
|
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator()
|
const Stack = createNativeStackNavigator()
|
||||||
|
|
||||||
export default function Post () {
|
const Post: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
// <Stack.Navigator>
|
// <Stack.Navigator>
|
||||||
// <Stack.Screen name='Me-Base' component={Base} />
|
// <Stack.Screen name='Me-Base' component={Base} />
|
||||||
@ -22,3 +19,5 @@ export default function Post () {
|
|||||||
<View></View>
|
<View></View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Post
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
|
|
||||||
import TimelinesCombined from 'src/stacks/common/TimelinesCombined'
|
import TimelinesCombined from 'src/stacks/common/TimelinesCombined'
|
||||||
|
|
||||||
export default function Public () {
|
const Public: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<TimelinesCombined
|
<TimelinesCombined
|
||||||
name='Public'
|
name='Public'
|
||||||
@ -13,3 +13,5 @@ export default function Public () {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Public
|
@ -22,7 +22,13 @@ import Timeline from 'src/stacks/common/Timeline'
|
|||||||
|
|
||||||
// Moved account example: https://m.cmx.im/web/accounts/27812
|
// Moved account example: https://m.cmx.im/web/accounts/27812
|
||||||
|
|
||||||
function Header ({ uri, size }) {
|
const Header = ({
|
||||||
|
uri,
|
||||||
|
size
|
||||||
|
}: {
|
||||||
|
uri: string
|
||||||
|
size: { width: number; height: number }
|
||||||
|
}) => {
|
||||||
if (uri) {
|
if (uri) {
|
||||||
return (
|
return (
|
||||||
<Image
|
<Image
|
||||||
@ -35,7 +41,13 @@ function Header ({ uri, size }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Information ({ account, emojis }) {
|
const Information = ({
|
||||||
|
account,
|
||||||
|
emojis
|
||||||
|
}: {
|
||||||
|
account: mastodon.Account
|
||||||
|
emojis: mastodon.Emoji[]
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.information}>
|
<View style={styles.information}>
|
||||||
{/* <Text>Moved or not: {account.moved}</Text> */}
|
{/* <Text>Moved or not: {account.moved}</Text> */}
|
||||||
@ -85,7 +97,7 @@ function Information ({ account, emojis }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Toots ({ account }) {
|
const Toots = ({ account }: { account: string }) => {
|
||||||
const [segment, setSegment] = useState(0)
|
const [segment, setSegment] = useState(0)
|
||||||
const [segmentManuallyTriggered, setSegmentManuallyTriggered] = useState(
|
const [segmentManuallyTriggered, setSegmentManuallyTriggered] = useState(
|
||||||
false
|
false
|
||||||
@ -161,11 +173,19 @@ function Toots ({ account }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Account ({
|
export interface Props {
|
||||||
|
route: {
|
||||||
|
params: {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Account: React.FC<Props> = ({
|
||||||
route: {
|
route: {
|
||||||
params: { id }
|
params: { id }
|
||||||
}
|
}
|
||||||
}) {
|
}) => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const accountState = useSelector(state => state.account)
|
const accountState = useSelector(state => state.account)
|
||||||
// const stateRelationships = useSelector(relationshipsState)
|
// const stateRelationships = useSelector(relationshipsState)
|
||||||
@ -241,3 +261,5 @@ const styles = StyleSheet.create({
|
|||||||
marginTop: 4
|
marginTop: 4
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export default Account
|
@ -7,11 +7,19 @@ import { reset } from 'src/stacks/common/timelineSlice'
|
|||||||
|
|
||||||
// Show remote hashtag? Only when private, show local version?
|
// Show remote hashtag? Only when private, show local version?
|
||||||
|
|
||||||
export default function Hashtag ({
|
export interface Props {
|
||||||
|
route: {
|
||||||
|
params: {
|
||||||
|
hashtag: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Hashtag: React.FC<Props> = ({
|
||||||
route: {
|
route: {
|
||||||
params: { hashtag }
|
params: { hashtag }
|
||||||
}
|
}
|
||||||
}) {
|
}) => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
@ -26,3 +34,5 @@ export default function Hashtag ({
|
|||||||
|
|
||||||
return <Timeline page='Hashtag' hashtag={hashtag} />
|
return <Timeline page='Hashtag' hashtag={hashtag} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Hashtag
|
@ -7,11 +7,19 @@ import { reset } from 'src/stacks/common/timelineSlice'
|
|||||||
|
|
||||||
// Show remote hashtag? Only when private, show local version?
|
// Show remote hashtag? Only when private, show local version?
|
||||||
|
|
||||||
export default function Toot ({
|
export interface Props {
|
||||||
|
route: {
|
||||||
|
params: {
|
||||||
|
toot: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Toot: React.FC<Props> = ({
|
||||||
route: {
|
route: {
|
||||||
params: { toot }
|
params: { toot }
|
||||||
}
|
}
|
||||||
}) {
|
}) => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
@ -26,3 +34,5 @@ export default function Toot ({
|
|||||||
|
|
||||||
return <Timeline page='Toot' toot={toot} disableRefresh />
|
return <Timeline page='Toot' toot={toot} disableRefresh />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Toot
|
@ -1,17 +1,22 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { WebView } from 'react-native-webview'
|
import { WebView } from 'react-native-webview'
|
||||||
|
|
||||||
// Update page title
|
// Update page title
|
||||||
|
|
||||||
export default function Webview ({
|
export interface Props {
|
||||||
|
route: {
|
||||||
|
params: {
|
||||||
|
uri: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Webview: React.FC<Props> = ({
|
||||||
route: {
|
route: {
|
||||||
params: { uri }
|
params: { uri }
|
||||||
}
|
}
|
||||||
}) {
|
}) => {
|
||||||
return <WebView source={{ uri: uri }} />
|
return <WebView source={{ uri: uri }} />
|
||||||
}
|
}
|
||||||
|
|
||||||
Webview.propTypes = {
|
export default Webview
|
||||||
uri: PropTypes.string.isRequired
|
|
||||||
}
|
|
@ -5,7 +5,11 @@ import Hashtag from 'src/stacks/Shared/Hashtag'
|
|||||||
import Toot from 'src/stacks/Shared/Toot'
|
import Toot from 'src/stacks/Shared/Toot'
|
||||||
import Webview from 'src/stacks/Shared/Webview'
|
import Webview from 'src/stacks/Shared/Webview'
|
||||||
|
|
||||||
export default function sharedScreens (Stack) {
|
export interface Props {
|
||||||
|
Stack: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedScreens = Stack => {
|
||||||
return [
|
return [
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
key='Account'
|
key='Account'
|
||||||
@ -43,3 +47,5 @@ export default function sharedScreens (Stack) {
|
|||||||
/>
|
/>
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default sharedScreens
|
@ -1,24 +1,24 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { ActivityIndicator, FlatList, Text, View } from 'react-native'
|
import { ActivityIndicator, FlatList, Text, View } from 'react-native'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
|
||||||
import TootNotification from 'src/components/TootNotification'
|
import TootNotification from 'src/components/TootNotification'
|
||||||
import TootTimeline from 'src/components/TootTimeline'
|
import TootTimeline from 'src/components/TootTimeline'
|
||||||
|
import { RootState } from 'src/stacks/common/store'
|
||||||
import { fetch } from './timelineSlice'
|
import { fetch } from './timelineSlice'
|
||||||
|
|
||||||
// Opening nesting hashtag pages
|
// Opening nesting hashtag pages
|
||||||
|
|
||||||
export default function Timeline ({
|
const Timeline: React.FC<{
|
||||||
page,
|
page: store.TimelinePage
|
||||||
hashtag,
|
hashtag?: string
|
||||||
list,
|
list?: string
|
||||||
toot,
|
toot?: string
|
||||||
account,
|
account?: string
|
||||||
disableRefresh = false
|
disableRefresh?: boolean
|
||||||
}) {
|
}> = ({ page, hashtag, list, toot, account, disableRefresh = false }) => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const state = useSelector(state => state.timelines[page])
|
const state = useSelector((state: RootState) => state.timelines[page])
|
||||||
const [timelineReady, setTimelineReady] = useState(false)
|
const [timelineReady, setTimelineReady] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -27,7 +27,9 @@ export default function Timeline ({
|
|||||||
dispatch(fetch({ page, hashtag, list, toot, account }))
|
dispatch(fetch({ page, hashtag, list, toot, account }))
|
||||||
setTimelineReady(true)
|
setTimelineReady(true)
|
||||||
}
|
}
|
||||||
return () => (mounted = false)
|
return () => {
|
||||||
|
mounted = false
|
||||||
|
}
|
||||||
}, [state, dispatch])
|
}, [state, dispatch])
|
||||||
|
|
||||||
let content
|
let content
|
||||||
@ -42,12 +44,12 @@ export default function Timeline ({
|
|||||||
keyExtractor={({ id }) => id}
|
keyExtractor={({ id }) => id}
|
||||||
renderItem={({ item, index, separators }) =>
|
renderItem={({ item, index, separators }) =>
|
||||||
page === 'Notifications' ? (
|
page === 'Notifications' ? (
|
||||||
<TootNotification key={item.key} toot={item} />
|
<TootNotification key={index} toot={item} />
|
||||||
) : (
|
) : (
|
||||||
<TootTimeline key={item.key} toot={item} />
|
<TootTimeline key={index} toot={item} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{...(state.pointer && { initialScrollIndex: state.pointer })}
|
// {...(state.pointer && { initialScrollIndex: state.pointer })}
|
||||||
{...(!disableRefresh && {
|
{...(!disableRefresh && {
|
||||||
onRefresh: () =>
|
onRefresh: () =>
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -84,10 +86,4 @@ export default function Timeline ({
|
|||||||
return <View>{content}</View>
|
return <View>{content}</View>
|
||||||
}
|
}
|
||||||
|
|
||||||
Timeline.propTypes = {
|
export default Timeline
|
||||||
page: PropTypes.string.isRequired,
|
|
||||||
hashtag: PropTypes.string,
|
|
||||||
list: PropTypes.string,
|
|
||||||
toot: PropTypes.string,
|
|
||||||
disableRefresh: PropTypes.bool
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { Dimensions, FlatList, View } from 'react-native'
|
import { Dimensions, FlatList, View } from 'react-native'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
import SegmentedControl from '@react-native-community/segmented-control'
|
import SegmentedControl from '@react-native-community/segmented-control'
|
||||||
@ -10,7 +9,7 @@ import sharedScreens from 'src/stacks/Shared/sharedScreens'
|
|||||||
|
|
||||||
const Stack = createNativeStackNavigator()
|
const Stack = createNativeStackNavigator()
|
||||||
|
|
||||||
function Page ({ item: { page } }) {
|
const Page = ({ item: { page } }: { item: { page: store.TimelinePage } }) => {
|
||||||
return (
|
return (
|
||||||
<View style={{ width: Dimensions.get('window').width }}>
|
<View style={{ width: Dimensions.get('window').width }}>
|
||||||
<Timeline page={page} />
|
<Timeline page={page} />
|
||||||
@ -18,7 +17,12 @@ function Page ({ item: { page } }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TimelinesCombined ({ name, content }) {
|
export interface Props {
|
||||||
|
name: string
|
||||||
|
content: { title: string; page: string }[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimelinesCombined: React.FC<Props> = ({ name, content }) => {
|
||||||
const [segment, setSegment] = useState(0)
|
const [segment, setSegment] = useState(0)
|
||||||
const [renderHeader, setRenderHeader] = useState(false)
|
const [renderHeader, setRenderHeader] = useState(false)
|
||||||
const [segmentManuallyTriggered, setSegmentManuallyTriggered] = useState(
|
const [segmentManuallyTriggered, setSegmentManuallyTriggered] = useState(
|
||||||
@ -30,14 +34,13 @@ export default function TimelinesCombined ({ name, content }) {
|
|||||||
return
|
return
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const horizontalPaging = useRef()
|
const horizontalPaging = useRef(null!)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator>
|
<Stack.Navigator>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name={name}
|
name={name}
|
||||||
options={{
|
options={{
|
||||||
statusBarAnimation: 'none',
|
|
||||||
headerRight: () =>
|
headerRight: () =>
|
||||||
renderHeader ? (
|
renderHeader ? (
|
||||||
<Feather name='search' size={24} color='black' />
|
<Feather name='search' size={24} color='black' />
|
||||||
@ -96,13 +99,4 @@ export default function TimelinesCombined ({ name, content }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelinesCombined.propTypes = {
|
export default TimelinesCombined
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
content: PropTypes.arrayOf(
|
|
||||||
PropTypes.exact({
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
page: Timeline.propTypes.page,
|
|
||||||
instance: PropTypes.oneOf(['local', 'remote'])
|
|
||||||
})
|
|
||||||
).isRequired
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import client from 'src/api/client'
|
|||||||
|
|
||||||
export const fetch = createAsyncThunk(
|
export const fetch = createAsyncThunk(
|
||||||
'account/fetch',
|
'account/fetch',
|
||||||
async ({ id }, { getState }) => {
|
async ({ id }: { id: string }) => {
|
||||||
const res = await client({
|
const res = await client({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
instance: 'local',
|
instance: 'local',
|
||||||
@ -25,18 +25,20 @@ export const accountSlice = createSlice({
|
|||||||
reducers: {
|
reducers: {
|
||||||
reset: () => accountInitState
|
reset: () => accountInitState
|
||||||
},
|
},
|
||||||
extraReducers: {
|
extraReducers: builder => {
|
||||||
[fetch.pending]: state => {
|
builder.addCase(fetch.pending, state => {
|
||||||
state.status = 'loading'
|
state.status = 'loading'
|
||||||
},
|
})
|
||||||
[fetch.fulfilled]: (state, action) => {
|
|
||||||
|
builder.addCase(fetch.fulfilled, (state, action) => {
|
||||||
state.status = 'succeeded'
|
state.status = 'succeeded'
|
||||||
state.account = action.payload
|
state.account = action.payload
|
||||||
},
|
})
|
||||||
[fetch.rejected]: (state, action) => {
|
|
||||||
|
builder.addCase(fetch.rejected, (state, action) => {
|
||||||
state.status = 'failed'
|
state.status = 'failed'
|
||||||
console.error(action.error.message)
|
console.error(action.error.message)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -3,7 +3,7 @@ import { configureStore } from '@reduxjs/toolkit'
|
|||||||
import instanceInfoSlice from 'src/stacks/common/instanceInfoSlice'
|
import instanceInfoSlice from 'src/stacks/common/instanceInfoSlice'
|
||||||
import timelineSlice from 'src/stacks/common/timelineSlice'
|
import timelineSlice from 'src/stacks/common/timelineSlice'
|
||||||
import accountSlice from 'src/stacks/common/accountSlice'
|
import accountSlice from 'src/stacks/common/accountSlice'
|
||||||
import relationshipsSlice from 'src/stacks/common/relationshipsSlice'
|
// import relationshipsSlice from 'src/stacks/common/relationshipsSlice'
|
||||||
|
|
||||||
// get site information from local storage and pass to reducers
|
// get site information from local storage and pass to reducers
|
||||||
const preloadedState = {
|
const preloadedState = {
|
||||||
@ -18,10 +18,14 @@ const reducer = {
|
|||||||
instanceInfo: instanceInfoSlice,
|
instanceInfo: instanceInfoSlice,
|
||||||
timelines: timelineSlice,
|
timelines: timelineSlice,
|
||||||
account: accountSlice,
|
account: accountSlice,
|
||||||
relationships: relationshipsSlice
|
// relationships: relationshipsSlice
|
||||||
}
|
}
|
||||||
|
|
||||||
export default configureStore({
|
const store = configureStore({
|
||||||
preloadedState,
|
preloadedState,
|
||||||
reducer
|
reducer
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export default store
|
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof store.getState>
|
@ -1,25 +1,40 @@
|
|||||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
import {
|
||||||
|
AnyAction,
|
||||||
|
createAsyncThunk,
|
||||||
|
createSlice,
|
||||||
|
PayloadAction
|
||||||
|
} from '@reduxjs/toolkit'
|
||||||
|
|
||||||
import client from 'src/api/client'
|
import client from 'src/api/client'
|
||||||
|
|
||||||
// Naming convention
|
|
||||||
// Following: timelines/home
|
|
||||||
// Local: timelines/public/local
|
|
||||||
// LocalPublic: timelines/public
|
|
||||||
// RemotePublic: remote/timelines/public
|
|
||||||
// Notifications: notifications
|
|
||||||
// Hashtag: hastag
|
|
||||||
// List: list
|
|
||||||
|
|
||||||
export const fetch = createAsyncThunk(
|
export const fetch = createAsyncThunk(
|
||||||
'timeline/fetch',
|
'timeline/fetch',
|
||||||
async (
|
async (
|
||||||
{ page, paginationDirection, query = {}, account, hashtag, list, toot },
|
{
|
||||||
|
page,
|
||||||
|
paginationDirection,
|
||||||
|
query = {},
|
||||||
|
account,
|
||||||
|
hashtag,
|
||||||
|
list,
|
||||||
|
toot
|
||||||
|
}: {
|
||||||
|
page: string
|
||||||
|
paginationDirection?: 'prev' | 'next'
|
||||||
|
query?: {
|
||||||
|
[key: string]: string | number | boolean
|
||||||
|
}
|
||||||
|
account?: string
|
||||||
|
hashtag?: string
|
||||||
|
list?: string
|
||||||
|
toot?: string
|
||||||
|
},
|
||||||
{ getState }
|
{ getState }
|
||||||
) => {
|
) => {
|
||||||
let res
|
let res
|
||||||
|
|
||||||
if (paginationDirection) {
|
if (paginationDirection) {
|
||||||
|
//@ts-ignore
|
||||||
const allToots = getState().timelines[page].toots
|
const allToots = getState().timelines[page].toots
|
||||||
switch (paginationDirection) {
|
switch (paginationDirection) {
|
||||||
case 'prev':
|
case 'prev':
|
||||||
@ -177,7 +192,7 @@ export const fetch = createAsyncThunk(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const timelineInitState = {
|
const timelineInitState: store.TimelineState = {
|
||||||
toots: [],
|
toots: [],
|
||||||
pointer: undefined,
|
pointer: undefined,
|
||||||
status: 'idle'
|
status: 'idle'
|
||||||
@ -199,31 +214,41 @@ export const timelineSlice = createSlice({
|
|||||||
Account_Media: timelineInitState
|
Account_Media: timelineInitState
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
reset (state, action) {
|
reset: (state, action: PayloadAction<store.TimelinePage>) => {
|
||||||
state[action.payload] = timelineInitState
|
state[action.payload] = timelineInitState
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
extraReducers: {
|
extraReducers: builder => {
|
||||||
[fetch.pending]: (state, action) => {
|
builder.addCase(fetch.pending, (state, action: AnyAction) => {
|
||||||
|
//@ts-ignore
|
||||||
state[action.meta.arg.page].status = 'loading'
|
state[action.meta.arg.page].status = 'loading'
|
||||||
},
|
})
|
||||||
[fetch.fulfilled]: (state, action) => {
|
|
||||||
|
builder.addCase(fetch.fulfilled, (state, action) => {
|
||||||
|
//@ts-ignore
|
||||||
state[action.meta.arg.page].status = 'succeeded'
|
state[action.meta.arg.page].status = 'succeeded'
|
||||||
|
|
||||||
|
if (action.payload?.toots) {
|
||||||
if (action.meta.arg.paginationDirection === 'prev') {
|
if (action.meta.arg.paginationDirection === 'prev') {
|
||||||
|
//@ts-ignore
|
||||||
state[action.meta.arg.page].toots.unshift(...action.payload.toots)
|
state[action.meta.arg.page].toots.unshift(...action.payload.toots)
|
||||||
} else {
|
} else {
|
||||||
|
//@ts-ignore
|
||||||
state[action.meta.arg.page].toots.push(...action.payload.toots)
|
state[action.meta.arg.page].toots.push(...action.payload.toots)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (action.payload.pointer) {
|
if (action.payload?.pointer) {
|
||||||
|
//@ts-ignore
|
||||||
state[action.meta.arg.page].pointer = action.payload.pointer
|
state[action.meta.arg.page].pointer = action.payload.pointer
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
[fetch.rejected]: (state, action) => {
|
|
||||||
|
builder.addCase(fetch.rejected, (state, action) => {
|
||||||
|
//@ts-ignore
|
||||||
state[action.meta.arg.page].status = 'failed'
|
state[action.meta.arg.page].status = 'failed'
|
||||||
console.error(action.error.message)
|
console.error(action.error.message)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||||
|
|
||||||
export async function getItem () {
|
const getItem = async () => {
|
||||||
try {
|
try {
|
||||||
const value = await AsyncStorage.getItem('@social.xmflsct.com')
|
const value = await AsyncStorage.getItem('@social.xmflsct.com')
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@ -15,10 +15,12 @@ export async function getItem () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllKeys () {
|
const getAllKeys = async () => {
|
||||||
try {
|
try {
|
||||||
return await AsyncStorage.getAllKeys()
|
return await AsyncStorage.getAllKeys()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Get all keys error')
|
console.error('Get all keys error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default { getItem, getAllKeys }
|
@ -1,7 +1,5 @@
|
|||||||
import PropTypes from 'prop-types'
|
const relativeTime = (date: string) => {
|
||||||
|
let units = {
|
||||||
export default function relativeTime (date) {
|
|
||||||
var units = {
|
|
||||||
year: 24 * 60 * 60 * 1000 * 365,
|
year: 24 * 60 * 60 * 1000 * 365,
|
||||||
month: (24 * 60 * 60 * 1000 * 365) / 12,
|
month: (24 * 60 * 60 * 1000 * 365) / 12,
|
||||||
day: 24 * 60 * 60 * 1000,
|
day: 24 * 60 * 60 * 1000,
|
||||||
@ -10,9 +8,9 @@ export default function relativeTime (date) {
|
|||||||
second: 1000
|
second: 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
var rtf = new Intl.RelativeTimeFormat('zh', { numeric: 'auto' })
|
let rtf = new Intl.RelativeTimeFormat('zh', { numeric: 'auto' })
|
||||||
|
|
||||||
var elapsed = new Date(date) - new Date()
|
let elapsed: number = new Date(date) - new Date()
|
||||||
|
|
||||||
// "Math.abs" accounts for both "past" & "future" scenarios
|
// "Math.abs" accounts for both "past" & "future" scenarios
|
||||||
for (var u in units)
|
for (var u in units)
|
||||||
@ -20,6 +18,4 @@ export default function relativeTime (date) {
|
|||||||
return rtf.format(Math.round(elapsed / units[u]), u)
|
return rtf.format(Math.round(elapsed / units[u]), u)
|
||||||
}
|
}
|
||||||
|
|
||||||
relativeTime.propTypes = {
|
export default relativeTime
|
||||||
date: PropTypes.string.isRequired
|
|
||||||
}
|
|
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"jsx": "react-native",
|
||||||
|
"lib": ["dom", "esnext"],
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noEmit": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"strict": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"src/*": ["src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user