mirror of
https://github.com/tooot-app/app
synced 2025-04-04 13:42:00 +02:00
Commit
This commit is contained in:
parent
a6e33d8b0a
commit
97e9ceca5a
188
package-lock.json
generated
188
package-lock.json
generated
@ -1348,9 +1348,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@react-native-async-storage/async-storage": {
|
"@react-native-async-storage/async-storage": {
|
||||||
"version": "1.13.0",
|
"version": "1.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.13.1.tgz",
|
||||||
"integrity": "sha512-c+pKuUe54sysxnqsfG17kaAcd9xJQyTNYDQhZhYf3Ej5khAQPPM85eN2nc1sj1qEnxDde4mcfi3slrOd/KtoSw==",
|
"integrity": "sha512-yNTjYah8LuCZDqD+kbPdgyfht1uW2uEf5OKlaAthcmKz9wOf68ccO7PFXyLXlFIEZDWkRcEk4Cu4MSsQYI9pBQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"deep-assign": "^3.0.0"
|
"deep-assign": "^3.0.0"
|
||||||
}
|
}
|
||||||
@ -1692,52 +1692,58 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@react-native-community/segmented-control/-/segmented-control-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native-community/segmented-control/-/segmented-control-2.1.1.tgz",
|
||||||
"integrity": "sha512-vSrg+DIqX0zGeOb1o6oFLoWFFW8l1UEX/f7/9dXXzWHChF3rIqEpNHC4ONmsLJqWePN4E/n+k+q29z+GbqrqsQ=="
|
"integrity": "sha512-vSrg+DIqX0zGeOb1o6oFLoWFFW8l1UEX/f7/9dXXzWHChF3rIqEpNHC4ONmsLJqWePN4E/n+k+q29z+GbqrqsQ=="
|
||||||
},
|
},
|
||||||
|
"@react-native-community/viewpager": {
|
||||||
|
"version": "4.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-community/viewpager/-/viewpager-4.1.6.tgz",
|
||||||
|
"integrity": "sha512-zNPQlgrPSB4JHT7w+kB7tTNsbP8H+Tqg1m+AszSArHCwSK+4dl1mgUt7hDd6XX3TTXpH2CdDsRx2cmJTTAweXw=="
|
||||||
|
},
|
||||||
"@react-navigation/bottom-tabs": {
|
"@react-navigation/bottom-tabs": {
|
||||||
"version": "5.9.2",
|
"version": "5.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-5.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-5.10.0.tgz",
|
||||||
"integrity": "sha512-nSaAXFNbRukhMGq4U3m1rqbFZEPT64/gPTwUwQ5YQom5Q0a6xZ0AnOmtFIXvDBbK5VJiokGdBLJ/TMDchqcujQ==",
|
"integrity": "sha512-nsdGtbFx2jrNJx26EXI4s4Nm+KxirkGCEsxwJ/XQk/EFjVHYFiFe5s1VLbirM1wi32rbUjJSgq7aD+mtgR0rAw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.3",
|
||||||
"react-native-iphone-x-helper": "^1.2.1"
|
"react-native-iphone-x-helper": "^1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@react-navigation/core": {
|
"@react-navigation/core": {
|
||||||
"version": "5.12.5",
|
"version": "5.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-5.12.5.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-5.13.0.tgz",
|
||||||
"integrity": "sha512-+QdQDtC75K1sBfACwCUNFlrCOYf36GYGr9YURHugm3LDEHUwAj7HPJA8FFLw1Rmp5N/P5q9Uct2B/XxJhYzKqA==",
|
"integrity": "sha512-h6Y8daT1Q1ARSS6swjES+lO/ydBHmopIWCcMNs3PPjGZOQizqgvzq+h2lU9w+tZApEDad/mXP0mwYXheR2D91w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@react-navigation/routers": "^5.4.12",
|
"@react-navigation/routers": "^5.5.0",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"nanoid": "^3.1.12",
|
"nanoid": "^3.1.15",
|
||||||
"query-string": "^6.13.5",
|
"query-string": "^6.13.6",
|
||||||
"react-is": "^16.13.0",
|
"react-is": "^16.13.0",
|
||||||
"use-subscription": "^1.4.0"
|
"use-subscription": "^1.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@react-navigation/native": {
|
"@react-navigation/native": {
|
||||||
"version": "5.7.6",
|
"version": "5.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-5.7.6.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-5.8.0.tgz",
|
||||||
"integrity": "sha512-u+o9Ifs4//Ah6UczXiaAV+hiWPL0NyTbErj5WayeQUd6K5IIbA1UwumRVWs2Xp8q/4Q9h6FpPHUcKOyiTxhaqA==",
|
"integrity": "sha512-oIckbpcv4GMyHub8oV+rbVT7CDbUDSaQ0xW1tvDuooHnIZOwuQitA+JDslPW7dQK4U8uJIkdl8upWn0hnotDkQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@react-navigation/core": "^5.12.5",
|
"@react-navigation/core": "^5.13.0",
|
||||||
"nanoid": "^3.1.12"
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"nanoid": "^3.1.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@react-navigation/routers": {
|
"@react-navigation/routers": {
|
||||||
"version": "5.4.12",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-5.4.12.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-5.5.0.tgz",
|
||||||
"integrity": "sha512-IwMmxeb5e6LboljhakmhtrHBXLYFrFDr2c1GjAG538e4MjT4QGi/ZYckAxCh/NqKI0knnzqKppPl2NsOMv/NoQ==",
|
"integrity": "sha512-IUT37OPKu0AHEbECBvhvx8IafkoMVxyY82ldR4BhLYdKYVwgjK111m8WpkezbCeaMBkLHpamQHAMFjLFY4et9w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"nanoid": "^3.1.12"
|
"nanoid": "^3.1.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@react-navigation/stack": {
|
"@react-navigation/stack": {
|
||||||
"version": "5.9.3",
|
"version": "5.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-5.10.0.tgz",
|
||||||
"integrity": "sha512-/CJ5Rsrc9bMI20dD8oC/QSZHARMFuUv8DeoiYE7R2N0M44cFupF1CrzaZBBC/S4Zi1ahZ0A+Hj/gAzAEQrNTvA==",
|
"integrity": "sha512-1+OVWPHTXc0HT4iMedVHWmIi4RnDOCfxmEjmEZfPCV+7BuzAJKAMW0KxJBFj3uAJscS/FxlZ3Qnjo5Z4aSNXqA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.3",
|
||||||
"react-native-iphone-x-helper": "^1.2.1"
|
"react-native-iphone-x-helper": "^1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@reduxjs/toolkit": {
|
"@reduxjs/toolkit": {
|
||||||
@ -2325,6 +2331,23 @@
|
|||||||
"node-int64": "^0.4.0"
|
"node-int64": "^0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"buffer": {
|
||||||
|
"version": "4.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
|
||||||
|
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
|
||||||
|
"requires": {
|
||||||
|
"base64-js": "^1.0.2",
|
||||||
|
"ieee754": "^1.1.4",
|
||||||
|
"isarray": "^1.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"isarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"buffer-alloc": {
|
"buffer-alloc": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
||||||
@ -2402,9 +2425,9 @@
|
|||||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
|
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30001150",
|
"version": "1.0.30001151",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001150.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001151.tgz",
|
||||||
"integrity": "sha512-kiNKvihW0m36UhAFnl7bOAv0i1K1f6wpfVtTF5O5O82XzgtBnb05V0XeV3oZ968vfg2sRNChsHw8ASH2hDfoYQ=="
|
"integrity": "sha512-Zh3sHqskX6mHNrqUerh+fkf0N72cMxrmflzje/JyVImfpknscMnkeJrlFGJcqTmaa0iszdYptGpWMJCRQDkBVw=="
|
||||||
},
|
},
|
||||||
"capture-exit": {
|
"capture-exit": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@ -2918,9 +2941,9 @@
|
|||||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||||
},
|
},
|
||||||
"electron-to-chromium": {
|
"electron-to-chromium": {
|
||||||
"version": "1.3.583",
|
"version": "1.3.584",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.583.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.584.tgz",
|
||||||
"integrity": "sha512-L9BwLwJohjZW9mQESI79HRzhicPk1DFgM+8hOCfGgGCFEcA3Otpv7QK6SGtYoZvfQfE3wKLh0Hd5ptqUFv3gvQ=="
|
"integrity": "sha512-NB3DzrTzJFhWkUp+nl2KtUtoFzrfGXTir2S+BU4tXGyXH9vlluPuFpE3pTKeH7+PY460tHLjKzh6K2+TWwW+Ww=="
|
||||||
},
|
},
|
||||||
"emoji-regex": {
|
"emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
@ -3090,6 +3113,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
||||||
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
||||||
},
|
},
|
||||||
|
"events": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
|
||||||
|
},
|
||||||
"exec-sh": {
|
"exec-sh": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz",
|
||||||
@ -3220,6 +3248,15 @@
|
|||||||
"url-parse": "^1.4.4"
|
"url-parse": "^1.4.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"expo-av": {
|
||||||
|
"version": "8.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-av/-/expo-av-8.6.0.tgz",
|
||||||
|
"integrity": "sha512-ZZJtCLCDigeDMu2fyv76Kyifu2quhTn6ViYjM2ku/HvFSvUxlb5kK9imuLog3kR+LRxeIiVS9F29r3PCOqI+rQ==",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"nullthrows": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"expo-constants": {
|
"expo-constants": {
|
||||||
"version": "9.2.0",
|
"version": "9.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-9.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-9.2.0.tgz",
|
||||||
@ -3265,9 +3302,9 @@
|
|||||||
"integrity": "sha512-zlWGua8vm7+af4otaSpJlzu0SYIr0aWbL0qICySCDUEKkqig6MqfuI69NYHC0w9ocWZuh2xyj6Rbfy01UqcVSg=="
|
"integrity": "sha512-zlWGua8vm7+af4otaSpJlzu0SYIr0aWbL0qICySCDUEKkqig6MqfuI69NYHC0w9ocWZuh2xyj6Rbfy01UqcVSg=="
|
||||||
},
|
},
|
||||||
"expo-linking": {
|
"expo-linking": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-1.0.5.tgz",
|
||||||
"integrity": "sha512-tKZvn3D2t/rJQQbDXZaPl3pEZvyO2coSO1WHtXeOCUaWFjrrHxjW0HAZ2H2iR0zALPq/lXo0Po83RsES3E0DAg==",
|
"integrity": "sha512-LH26/ilFU0rCdsO1SJbNqoii3jTBqHdEfSloXhEb73aKdQT2BG6z5IjFIQXV2RiCmxNJbotdbfXyWSPqPoCEwg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"expo-constants": "~9.2.0",
|
"expo-constants": "~9.2.0",
|
||||||
"qs": "^6.5.0",
|
"qs": "^6.5.0",
|
||||||
@ -3284,6 +3321,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/expo-permissions/-/expo-permissions-9.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/expo-permissions/-/expo-permissions-9.3.0.tgz",
|
||||||
"integrity": "sha512-ylSJZVvEGJVFTKsFrUL2S6gCvFt+/o8TJ3xT4WaMjHe2/2Z7R8ng6NR47Kt54t7XBIV/SZ7DIY9uRiR7TPuNYA=="
|
"integrity": "sha512-ylSJZVvEGJVFTKsFrUL2S6gCvFt+/o8TJ3xT4WaMjHe2/2Z7R8ng6NR47Kt54t7XBIV/SZ7DIY9uRiR7TPuNYA=="
|
||||||
},
|
},
|
||||||
|
"expo-secure-store": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-9.2.0.tgz",
|
||||||
|
"integrity": "sha512-CtoMeuw/BzmLZMxmw30YiAZY51bpuOsBQpt3CrvLqpT2Q4/M18Tc1H4qXzHER3GPfZeG2nEJQkEgHsHXrIhPXg=="
|
||||||
|
},
|
||||||
"expo-splash-screen": {
|
"expo-splash-screen": {
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.6.2.tgz",
|
||||||
@ -3706,9 +3748,9 @@
|
|||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||||
},
|
},
|
||||||
"gensync": {
|
"gensync": {
|
||||||
"version": "1.0.0-beta.1",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
"integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg=="
|
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
|
||||||
},
|
},
|
||||||
"get-caller-file": {
|
"get-caller-file": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
@ -3823,6 +3865,36 @@
|
|||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
||||||
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
|
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
|
||||||
},
|
},
|
||||||
|
"html-entities": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA=="
|
||||||
|
},
|
||||||
|
"htmlparser2": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
|
||||||
|
"requires": {
|
||||||
|
"domelementtype": "^1.3.1",
|
||||||
|
"domhandler": "^2.3.0",
|
||||||
|
"domutils": "^1.5.1",
|
||||||
|
"entities": "^1.1.1",
|
||||||
|
"inherits": "^2.0.1",
|
||||||
|
"readable-stream": "^3.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"readable-stream": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||||
|
"requires": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"htmlparser2-without-node-native": {
|
"htmlparser2-without-node-native": {
|
||||||
"version": "3.9.2",
|
"version": "3.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/htmlparser2-without-node-native/-/htmlparser2-without-node-native-3.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/htmlparser2-without-node-native/-/htmlparser2-without-node-native-3.9.2.tgz",
|
||||||
@ -3862,6 +3934,11 @@
|
|||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ieee754": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||||
|
},
|
||||||
"image-size": {
|
"image-size": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
|
||||||
@ -5632,9 +5709,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"nanoid": {
|
"nanoid": {
|
||||||
"version": "3.1.15",
|
"version": "3.1.16",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.15.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.16.tgz",
|
||||||
"integrity": "sha512-n8rXUZ8UU3lV6+43atPrSizqzh25n1/f00Wx1sCiE7R1sSHytZLTTiQl8DjC4IDLOnEZDlgJhy0yO4VsIpMxow=="
|
"integrity": "sha512-+AK8MN0WHji40lj8AEuwLOvLSbWYApQpre/aFJZD71r43wVRLrOYS4FmJOPQYon1TqB462RzrrxlfA74XRES8w=="
|
||||||
},
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
@ -6624,6 +6701,17 @@
|
|||||||
"fbjs": "^1.0.0"
|
"fbjs": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-render-html": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-render-html/-/react-native-render-html-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-OiLItEzKgS7dzD9XI5bHhjcUEfpWdzH1FgexzjbBdICPfYjmmcefpcRmLZY1+HMfxJ7wL8iF1PzTF48LchGTBA==",
|
||||||
|
"requires": {
|
||||||
|
"buffer": "^4.5.1",
|
||||||
|
"events": "^1.1.0",
|
||||||
|
"html-entities": "^1.2.0",
|
||||||
|
"htmlparser2": "3.10.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-safe-area-context": {
|
"react-native-safe-area-context": {
|
||||||
"version": "3.1.4",
|
"version": "3.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.1.4.tgz",
|
||||||
@ -6667,15 +6755,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-redux": {
|
"react-redux": {
|
||||||
"version": "7.2.1",
|
"version": "7.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.2.tgz",
|
||||||
"integrity": "sha512-T+VfD/bvgGTUA74iW9d2i5THrDQWbweXP0AVNI8tNd1Rk5ch1rnMiJkDD67ejw7YBKM4+REvcvqRuWJb7BLuEg==",
|
"integrity": "sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.12.1",
|
||||||
"hoist-non-react-statics": "^3.3.0",
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react-is": "^16.9.0"
|
"react-is": "^16.13.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hoist-non-react-statics": {
|
"hoist-non-react-statics": {
|
||||||
|
@ -28,11 +28,16 @@
|
|||||||
"react-native-htmlview": "^0.16.0",
|
"react-native-htmlview": "^0.16.0",
|
||||||
"react-native-image-zoom-viewer": "^3.0.1",
|
"react-native-image-zoom-viewer": "^3.0.1",
|
||||||
"react-native-reanimated": "~1.13.0",
|
"react-native-reanimated": "~1.13.0",
|
||||||
|
"react-native-render-html": "^4.2.4",
|
||||||
"react-native-safe-area-context": "3.1.4",
|
"react-native-safe-area-context": "3.1.4",
|
||||||
"react-native-screens": "~2.10.1",
|
"react-native-screens": "~2.10.1",
|
||||||
"react-native-web": "~0.13.7",
|
"react-native-web": "~0.13.7",
|
||||||
"react-native-webview": "10.7.0",
|
"react-native-webview": "10.7.0",
|
||||||
"react-redux": "^7.2.1"
|
"react-redux": "^7.2.1",
|
||||||
|
"expo-av": "~8.6.0",
|
||||||
|
"expo-secure-store": "~9.2.0",
|
||||||
|
"expo-splash-screen": "~0.6.1",
|
||||||
|
"@react-native-community/viewpager": "4.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "~7.9.0",
|
"@babel/core": "~7.9.0",
|
||||||
|
@ -4,7 +4,7 @@ import { StyleSheet, Text } from 'react-native'
|
|||||||
import HTMLView from 'react-native-htmlview'
|
import HTMLView from 'react-native-htmlview'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
import Emojis from 'src/components/TootTimeline/Emojis'
|
import Emojis from 'src/components/Toot/Emojis'
|
||||||
|
|
||||||
function renderNode ({ node, index, navigation, mentions, showFullLink }) {
|
function renderNode ({ node, index, navigation, mentions, showFullLink }) {
|
||||||
if (node.name == 'a') {
|
if (node.name == 'a') {
|
||||||
@ -72,7 +72,8 @@ export default function ParseContent ({
|
|||||||
emojis,
|
emojis,
|
||||||
emojiSize = 14,
|
emojiSize = 14,
|
||||||
mentions,
|
mentions,
|
||||||
showFullLink = false
|
showFullLink = false,
|
||||||
|
linesTruncated = 10
|
||||||
}) {
|
}) {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
@ -80,13 +81,16 @@ export default function ParseContent ({
|
|||||||
<HTMLView
|
<HTMLView
|
||||||
value={content}
|
value={content}
|
||||||
stylesheet={HTMLstyles}
|
stylesheet={HTMLstyles}
|
||||||
addLineBreaks={null}
|
paragraphBreak={null}
|
||||||
renderNode={(node, index) =>
|
renderNode={(node, index) =>
|
||||||
renderNode({ node, index, navigation, mentions, showFullLink })
|
renderNode({ node, index, navigation, mentions, showFullLink })
|
||||||
}
|
}
|
||||||
TextComponent={({ children }) => (
|
TextComponent={({ children }) => (
|
||||||
<Emojis content={children} emojis={emojis} dimension={emojiSize} />
|
<Emojis content={children} emojis={emojis} dimension={emojiSize} />
|
||||||
)}
|
)}
|
||||||
|
RootComponent={({ children }) => {
|
||||||
|
return <Text numberOfLines={linesTruncated}>{children}</Text>
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -115,5 +119,6 @@ ParseContent.propTypes = {
|
|||||||
acct: PropTypes.string.isRequired
|
acct: PropTypes.string.isRequired
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
showFullLink: PropTypes.bool
|
showFullLink: PropTypes.bool,
|
||||||
|
linesTruncated: PropTypes.number
|
||||||
}
|
}
|
||||||
|
129
src/components/Toot.jsx
Normal file
129
src/components/Toot.jsx
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
|
import Reblog from './Toot/Reblog'
|
||||||
|
import Avatar from './Toot/Avatar'
|
||||||
|
import Header from './Toot/Header'
|
||||||
|
import Content from './Toot/Content'
|
||||||
|
import Poll from './Toot/Poll'
|
||||||
|
import Media from './Toot/Media'
|
||||||
|
import Card from './Toot/Card'
|
||||||
|
import Actions from './Toot/Actions'
|
||||||
|
|
||||||
|
// Maybe break away notification types? https://docs.joinmastodon.org/entities/notification/
|
||||||
|
|
||||||
|
export default function Toot ({ item, notification }) {
|
||||||
|
const navigation = useNavigation()
|
||||||
|
|
||||||
|
let actualContent
|
||||||
|
if (notification && item.status) {
|
||||||
|
actualContent = item.status
|
||||||
|
} else if (item.reblog) {
|
||||||
|
actualContent = item.reblog
|
||||||
|
} else {
|
||||||
|
actualContent = item
|
||||||
|
}
|
||||||
|
|
||||||
|
const toot = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<View style={styles.tootTimeline}>
|
||||||
|
{item.reblog && (
|
||||||
|
<Reblog
|
||||||
|
name={item.account.display_name || item.account.username}
|
||||||
|
emojis={item.account.emojis}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<View style={styles.toot}>
|
||||||
|
<Avatar
|
||||||
|
uri={actualContent.account.avatar}
|
||||||
|
id={actualContent.account.id}
|
||||||
|
/>
|
||||||
|
<View style={styles.details}>
|
||||||
|
<Header
|
||||||
|
name={
|
||||||
|
actualContent.account.display_name ||
|
||||||
|
actualContent.account.username
|
||||||
|
}
|
||||||
|
emojis={actualContent.account.emojis}
|
||||||
|
account={actualContent.account.acct}
|
||||||
|
created_at={item.created_at}
|
||||||
|
application={item.application}
|
||||||
|
/>
|
||||||
|
{/* Can pass toot info to next page to speed up performance */}
|
||||||
|
<Pressable
|
||||||
|
onPress={() =>
|
||||||
|
navigation.navigate('Toot', { toot: actualContent.id })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{actualContent.content ? (
|
||||||
|
<Content
|
||||||
|
content={actualContent.content}
|
||||||
|
emojis={actualContent.emojis}
|
||||||
|
mentions={actualContent.mentions}
|
||||||
|
spoiler_text={actualContent.spoiler_text}
|
||||||
|
tags={actualContent.tags}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
{actualContent.poll && <Poll poll={actualContent.poll} />}
|
||||||
|
{actualContent.media_attachments && (
|
||||||
|
<Media
|
||||||
|
media_attachments={actualContent.media_attachments}
|
||||||
|
sensitive={actualContent.sensitive}
|
||||||
|
width={Dimensions.get('window').width - 24 - 50 - 8}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{actualContent.card && <Card card={actualContent.card} />}
|
||||||
|
</Pressable>
|
||||||
|
<Actions
|
||||||
|
replies_count={actualContent.replies_count}
|
||||||
|
reblogs_count={actualContent.reblogs_count}
|
||||||
|
reblogged={actualContent.reblogged}
|
||||||
|
favourites_count={actualContent.favourites_count}
|
||||||
|
favourited={actualContent.favourited}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return toot
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
tootTimeline: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: 12
|
||||||
|
},
|
||||||
|
toot: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
details: {
|
||||||
|
flex: 1,
|
||||||
|
flexGrow: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Toot.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,
|
||||||
|
notification: PropTypes.bool
|
||||||
|
}
|
48
src/components/Toot/Actions.jsx
Normal file
48
src/components/Toot/Actions.jsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
|
||||||
|
export default function Actions ({
|
||||||
|
replies_count,
|
||||||
|
reblogs_count,
|
||||||
|
reblogged,
|
||||||
|
favourites_count,
|
||||||
|
favourited
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<View style={styles.actions}>
|
||||||
|
<Pressable style={styles.action}>
|
||||||
|
<Feather name='message-circle' />
|
||||||
|
<Text>{replies_count}</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable style={styles.action}>
|
||||||
|
<Feather name='repeat' />
|
||||||
|
<Text>{reblogs_count}</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable style={styles.action}>
|
||||||
|
<Feather name='heart' />
|
||||||
|
<Text>{favourites_count}</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable style={styles.action}>
|
||||||
|
<Feather name='share' />
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
actions: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
width: '25%',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Actions.propTypes = {
|
||||||
|
// uri: PropTypes.string
|
||||||
|
// }
|
73
src/components/Toot/Card.jsx
Normal file
73
src/components/Toot/Card.jsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
|
export default function Card ({ card }) {
|
||||||
|
const navigation = useNavigation()
|
||||||
|
return (
|
||||||
|
card && (
|
||||||
|
<Pressable
|
||||||
|
style={styles.card}
|
||||||
|
onPress={() => {
|
||||||
|
navigation.navigate('Webview', {
|
||||||
|
uri: card.url
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{card.image && (
|
||||||
|
<View style={styles.left}>
|
||||||
|
<Image source={{ uri: card.image }} style={styles.image} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View style={styles.right}>
|
||||||
|
<Text numberOfLines={1}>{card.title}</Text>
|
||||||
|
{card.description ? (
|
||||||
|
<Text numberOfLines={2}>{card.description}</Text>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
<Text numberOfLines={1}>{card.url}</Text>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
card: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: 70,
|
||||||
|
marginTop: 12
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
width: 70
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Card.propTypes = {
|
||||||
|
card: PropTypes.exact({
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
description: PropTypes.string,
|
||||||
|
type: PropTypes.oneOf(['link', 'photo', 'video']),
|
||||||
|
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
|
||||||
|
}).isRequired
|
||||||
|
}
|
48
src/components/Toot/Content.jsx
Normal file
48
src/components/Toot/Content.jsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Text } from 'react-native'
|
||||||
|
import Collapsible from 'react-native-collapsible'
|
||||||
|
|
||||||
|
import ParseContent from 'src/components/ParseContent'
|
||||||
|
|
||||||
|
export default function Content ({ content, emojis, mentions, spoiler_text }) {
|
||||||
|
const [spoilerCollapsed, setSpoilerCollapsed] = useState(true)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{content &&
|
||||||
|
(spoiler_text ? (
|
||||||
|
<>
|
||||||
|
<Text>
|
||||||
|
{spoiler_text}{' '}
|
||||||
|
<Text onPress={() => setSpoilerCollapsed(!spoilerCollapsed)}>
|
||||||
|
点击展开
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Collapsible collapsed={spoilerCollapsed}>
|
||||||
|
<ParseContent
|
||||||
|
content={content}
|
||||||
|
emojis={emojis}
|
||||||
|
emojiSize={14}
|
||||||
|
mentions={mentions}
|
||||||
|
/>
|
||||||
|
</Collapsible>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<ParseContent
|
||||||
|
content={content}
|
||||||
|
emojis={emojis}
|
||||||
|
emojiSize={14}
|
||||||
|
mentions={mentions}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Content.propTypes = {
|
||||||
|
content: ParseContent.propTypes.content,
|
||||||
|
emojis: ParseContent.propTypes.emojis,
|
||||||
|
mentions: ParseContent.propTypes.mentions,
|
||||||
|
spoiler_text: PropTypes.string
|
||||||
|
}
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Image, Text } from 'react-native'
|
import { Image, Text } from 'react-native'
|
||||||
|
|
||||||
const regexEmoji = new RegExp(/(:.*?:)/g)
|
const regexEmoji = new RegExp(/(:[a-z0-9_]+:)/)
|
||||||
|
|
||||||
export default function Emojis ({ content, emojis, dimension }) {
|
export default function Emojis ({ content, emojis, dimension }) {
|
||||||
const hasEmojis = content.match(regexEmoji)
|
const hasEmojis = content.match(regexEmoji)
|
||||||
@ -13,7 +13,11 @@ export default function Emojis ({ content, emojis, dimension }) {
|
|||||||
const emojiIndex = emojis.findIndex(emoji => {
|
const emojiIndex = emojis.findIndex(emoji => {
|
||||||
return emojiShortcode === `:${emoji.shortcode}:`
|
return emojiShortcode === `:${emoji.shortcode}:`
|
||||||
})
|
})
|
||||||
return (
|
return emojiIndex === -1 ? (
|
||||||
|
<Text key={i} style={{ color: 'red' }}>
|
||||||
|
Something wrong with emoji!
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
<Image
|
<Image
|
||||||
key={i}
|
key={i}
|
||||||
source={{ uri: emojis[emojiIndex].url }}
|
source={{ uri: emojis[emojiIndex].url }}
|
@ -27,7 +27,9 @@ export default function Header ({
|
|||||||
<View style={styles.name}>
|
<View style={styles.name}>
|
||||||
<Emojis content={name} emojis={emojis} dimension={14} />
|
<Emojis content={name} emojis={emojis} dimension={14} />
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.account}>@{account}</Text>
|
<Text style={styles.account} numberOfLines={1}>
|
||||||
|
@{account}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.meta}>
|
<View style={styles.meta}>
|
||||||
<View>
|
<View>
|
@ -9,12 +9,9 @@ import {
|
|||||||
Pressable,
|
Pressable,
|
||||||
View
|
View
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import Collapsible from 'react-native-collapsible'
|
|
||||||
import ImageViewer from 'react-native-image-zoom-viewer'
|
import ImageViewer from 'react-native-image-zoom-viewer'
|
||||||
|
|
||||||
import ParseContent from 'src/components/ParseContent'
|
export default function Media ({ media_attachments, sensitive, width }) {
|
||||||
|
|
||||||
function Media ({ 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)
|
||||||
@ -26,6 +23,7 @@ function Media ({ media_attachments, sensitive, width }) {
|
|||||||
}
|
}
|
||||||
}, [mediaSensitive])
|
}, [mediaSensitive])
|
||||||
|
|
||||||
|
let media
|
||||||
let images = []
|
let images = []
|
||||||
if (width) {
|
if (width) {
|
||||||
media_attachments = media_attachments.map((m, i) => {
|
media_attachments = media_attachments.map((m, i) => {
|
||||||
@ -41,7 +39,7 @@ function Media ({ media_attachments, sensitive, width }) {
|
|||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
key={i}
|
key={i}
|
||||||
style={{ flexGrow: 1, height: width / 5, margin: 4 }}
|
style={{ flexGrow: 1, height: width / 2, margin: 4 }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setImageModalIndex(i)
|
setImageModalIndex(i)
|
||||||
setImageModalVisible(true)
|
setImageModalVisible(true)
|
||||||
@ -57,7 +55,7 @@ function Media ({ media_attachments, sensitive, width }) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (images) {
|
if (images) {
|
||||||
return (
|
media = (
|
||||||
<>
|
<>
|
||||||
<View style={styles.media}>
|
<View style={styles.media}>
|
||||||
{media_attachments}
|
{media_attachments}
|
||||||
@ -95,53 +93,14 @@ function Media ({ media_attachments, sensitive, width }) {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return <View style={styles.media}>{media_attachments}</View>
|
media = <View style={styles.media}>{media_attachments}</View>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return <></>
|
media = <></>
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export default function Content ({
|
|
||||||
content,
|
|
||||||
emojis,
|
|
||||||
media_attachments,
|
|
||||||
mentions,
|
|
||||||
sensitive,
|
|
||||||
spoiler_text,
|
|
||||||
width
|
|
||||||
}) {
|
|
||||||
const [spoilerCollapsed, setSpoilerCollapsed] = useState(true)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
media_attachments.length > 0 && (
|
||||||
{content &&
|
|
||||||
(spoiler_text ? (
|
|
||||||
<>
|
|
||||||
<Text>
|
|
||||||
{spoiler_text}{' '}
|
|
||||||
<Text onPress={() => setSpoilerCollapsed(!spoilerCollapsed)}>
|
|
||||||
点击展开
|
|
||||||
</Text>
|
|
||||||
</Text>
|
|
||||||
<Collapsible collapsed={spoilerCollapsed}>
|
|
||||||
<ParseContent
|
|
||||||
content={content}
|
|
||||||
emojis={emojis}
|
|
||||||
emojiSize={14}
|
|
||||||
mentions={mentions}
|
|
||||||
/>
|
|
||||||
</Collapsible>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<ParseContent
|
|
||||||
content={content}
|
|
||||||
emojis={emojis}
|
|
||||||
emojiSize={14}
|
|
||||||
mentions={mentions}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{media_attachments.length > 0 && (
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: width + 8,
|
width: width + 8,
|
||||||
@ -150,14 +109,9 @@ export default function Content ({
|
|||||||
marginLeft: -4
|
marginLeft: -4
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Media
|
{media}
|
||||||
media_attachments={media_attachments}
|
|
||||||
sensitive={sensitive}
|
|
||||||
width={width}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,12 +130,8 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Content.propTypes = {
|
Media.propTypes = {
|
||||||
content: ParseContent.propTypes.content,
|
|
||||||
emojis: ParseContent.propTypes.emojis,
|
|
||||||
// media_attachments
|
// media_attachments
|
||||||
mentions: ParseContent.propTypes.mentions,
|
|
||||||
sensitive: PropTypes.bool.isRequired,
|
sensitive: PropTypes.bool.isRequired,
|
||||||
spoiler_text: PropTypes.string,
|
|
||||||
width: PropTypes.number.isRequired
|
width: PropTypes.number.isRequired
|
||||||
}
|
}
|
63
src/components/Toot/Poll.jsx
Normal file
63
src/components/Toot/Poll.jsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
|
||||||
|
import Emojis from './Emojis'
|
||||||
|
|
||||||
|
export default function Poll ({ poll }) {
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{poll.options.map((option, index) => (
|
||||||
|
<View key={index}>
|
||||||
|
<View style={{ flexDirection: 'row' }}>
|
||||||
|
<Text>
|
||||||
|
{Math.round((option.votes_count / poll.votes_count) * 100)}%
|
||||||
|
</Text>
|
||||||
|
<Text>{option.title}</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: `${Math.round(
|
||||||
|
(option.votes_count / poll.votes_count) * 100
|
||||||
|
)}%`,
|
||||||
|
height: 5,
|
||||||
|
backgroundColor: 'blue'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
avatar: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
marginRight: 8
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Poll.propTypes = {
|
||||||
|
poll: PropTypes.exact({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
expires_at: PropTypes.string.isRequired,
|
||||||
|
expired: PropTypes.bool.isRequired,
|
||||||
|
multiple: PropTypes.bool.isRequired,
|
||||||
|
votes_count: PropTypes.number,
|
||||||
|
voters_count: PropTypes.number,
|
||||||
|
voted: PropTypes.bool.isRequired,
|
||||||
|
own_votes: PropTypes.array,
|
||||||
|
options: PropTypes.arrayOf(
|
||||||
|
PropTypes.exact({
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
votes_count: PropTypes.number.isRequired
|
||||||
|
})
|
||||||
|
),
|
||||||
|
emojis: Emojis.propTypes.emojis
|
||||||
|
}).isRequired
|
||||||
|
}
|
@ -1,104 +0,0 @@
|
|||||||
import React, { useState } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
|
||||||
|
|
||||||
import Reblog from './TootTimeline/Reblog'
|
|
||||||
import Avatar from './TootTimeline/Avatar'
|
|
||||||
import Header from './TootTimeline/Header'
|
|
||||||
import Content from './TootTimeline/Content'
|
|
||||||
import Actions from './TootTimeline/Actions'
|
|
||||||
|
|
||||||
// Maybe break away notification types? https://docs.joinmastodon.org/entities/notification/
|
|
||||||
|
|
||||||
export default function TootTimeline ({ item, notification }) {
|
|
||||||
let contentAggregated = {}
|
|
||||||
let actualContent
|
|
||||||
if (notification && item.status) {
|
|
||||||
actualContent = item.status
|
|
||||||
} else if (item.reblog) {
|
|
||||||
actualContent = item.reblog
|
|
||||||
} else {
|
|
||||||
actualContent = item
|
|
||||||
}
|
|
||||||
contentAggregated = {
|
|
||||||
content: actualContent.content,
|
|
||||||
emojis: actualContent.emojis,
|
|
||||||
media_attachments: actualContent.media_attachments,
|
|
||||||
mentions: actualContent.mentions,
|
|
||||||
sensitive: actualContent.sensitive,
|
|
||||||
spoiler_text: actualContent.spoiler_text,
|
|
||||||
tags: actualContent.tags
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.tootTimeline}>
|
|
||||||
{item.reblog && (
|
|
||||||
<Reblog
|
|
||||||
name={item.account.display_name || item.account.username}
|
|
||||||
emojis={item.account.emojis}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<View style={styles.toot}>
|
|
||||||
<Avatar
|
|
||||||
uri={item.reblog?.account.avatar || item.account.avatar}
|
|
||||||
id={item.reblog?.account.id || item.account.id}
|
|
||||||
/>
|
|
||||||
<View style={styles.details}>
|
|
||||||
<Header
|
|
||||||
name={
|
|
||||||
(item.reblog?.account.display_name
|
|
||||||
? item.reblog?.account.display_name
|
|
||||||
: item.reblog?.account.username) ||
|
|
||||||
(item.account.display_name
|
|
||||||
? item.account.display_name
|
|
||||||
: item.account.username)
|
|
||||||
}
|
|
||||||
emojis={item.reblog?.account.emojis || item.account.emojis}
|
|
||||||
account={item.reblog?.account.acct || item.account.acct}
|
|
||||||
created_at={item.created_at}
|
|
||||||
application={item.application || null}
|
|
||||||
/>
|
|
||||||
<Content
|
|
||||||
{...contentAggregated}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
width={Dimensions.get('window').width - 24 - 50 - 8}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<Actions />
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
tootTimeline: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'column',
|
|
||||||
padding: 12
|
|
||||||
},
|
|
||||||
toot: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row'
|
|
||||||
},
|
|
||||||
details: {
|
|
||||||
flex: 1,
|
|
||||||
flexGrow: 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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,
|
|
||||||
notification: PropTypes.bool
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { StyleSheet } from 'react-native'
|
|
||||||
|
|
||||||
export default function Actions () {
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
width: 50,
|
|
||||||
height: 50
|
|
||||||
})
|
|
||||||
|
|
||||||
// Actions.propTypes = {
|
|
||||||
// uri: PropTypes.string
|
|
||||||
// }
|
|
@ -1,21 +1,24 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Dimensions,
|
Dimensions,
|
||||||
|
FlatList,
|
||||||
Image,
|
Image,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
View
|
View
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import HTMLView from 'react-native-htmlview'
|
import SegmentedControl from '@react-native-community/segmented-control'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { useFocusEffect } from '@react-navigation/native'
|
import { useFocusEffect } from '@react-navigation/native'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
|
||||||
import * as accountSlice from 'src/stacks/common/accountSlice'
|
import * as accountSlice from 'src/stacks/common/accountSlice'
|
||||||
import * as relationshipsSlice from 'src/stacks/common/relationshipsSlice'
|
import * as relationshipsSlice from 'src/stacks/common/relationshipsSlice'
|
||||||
|
import * as timelineSlice from 'src/stacks/common/timelineSlice'
|
||||||
|
|
||||||
import ParseContent from 'src/components/ParseContent'
|
import ParseContent from 'src/components/ParseContent'
|
||||||
|
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
|
||||||
|
|
||||||
@ -65,10 +68,7 @@ function Information ({ account, emojis }) {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
{account.note && <ParseContent content={account.note} emojis={emojis} />}
|
||||||
<Text>
|
|
||||||
<ParseContent content={account.note} emojis={emojis} />
|
|
||||||
</Text>
|
|
||||||
<Text>
|
<Text>
|
||||||
加入时间{' '}
|
加入时间{' '}
|
||||||
{new Date(account.created_at).toLocaleDateString('zh-CN', {
|
{new Date(account.created_at).toLocaleDateString('zh-CN', {
|
||||||
@ -85,13 +85,89 @@ function Information ({ account, emojis }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Toots ({ account }) {
|
||||||
|
const [segment, setSegment] = useState(0)
|
||||||
|
const [segmentManuallyTriggered, setSegmentManuallyTriggered] = useState(
|
||||||
|
false
|
||||||
|
)
|
||||||
|
const horizontalPaging = useRef()
|
||||||
|
|
||||||
|
const pages = ['Account_Default', 'Account_All', 'Account_Media']
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SegmentedControl
|
||||||
|
values={['嘟嘟', '嘟嘟和回复', '媒体']}
|
||||||
|
selectedIndex={segment}
|
||||||
|
onChange={({ nativeEvent }) => {
|
||||||
|
setSegmentManuallyTriggered(true)
|
||||||
|
setSegment(nativeEvent.selectedSegmentIndex)
|
||||||
|
horizontalPaging.current.scrollToIndex({
|
||||||
|
index: nativeEvent.selectedSegmentIndex
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
style={{ width: '100%', height: 30 }}
|
||||||
|
/>
|
||||||
|
<FlatList
|
||||||
|
style={{ width: Dimensions.get('window').width, height: '100%' }}
|
||||||
|
data={pages}
|
||||||
|
keyExtractor={page => page}
|
||||||
|
renderItem={({ item, index }) => {
|
||||||
|
return (
|
||||||
|
<View style={{ width: Dimensions.get('window').width }}>
|
||||||
|
<Timeline
|
||||||
|
key={index}
|
||||||
|
page={item}
|
||||||
|
account={account}
|
||||||
|
disableRefresh
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
ref={horizontalPaging}
|
||||||
|
bounces={false}
|
||||||
|
getItemLayout={(data, index) => ({
|
||||||
|
length: Dimensions.get('window').width,
|
||||||
|
offset: Dimensions.get('window').width * index,
|
||||||
|
index
|
||||||
|
})}
|
||||||
|
horizontal
|
||||||
|
ListHeaderComponent={
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: Dimensions.get('window').width,
|
||||||
|
height: 100,
|
||||||
|
position: 'absolute'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>Test</Text>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
onMomentumScrollEnd={() => {
|
||||||
|
setSegmentManuallyTriggered(false)
|
||||||
|
}}
|
||||||
|
onScroll={({ nativeEvent }) =>
|
||||||
|
!segmentManuallyTriggered &&
|
||||||
|
setSegment(
|
||||||
|
nativeEvent.contentOffset.x <= Dimensions.get('window').width / 3
|
||||||
|
? 0
|
||||||
|
: 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pagingEnabled
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function Account ({
|
export default function Account ({
|
||||||
route: {
|
route: {
|
||||||
params: { id }
|
params: { id }
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const accountState = useSelector(accountSlice.retrive)
|
const accountState = useSelector(state => state.account)
|
||||||
// const stateRelationships = useSelector(relationshipsState)
|
// const stateRelationships = useSelector(relationshipsState)
|
||||||
const [loaded, setLoaded] = useState(false)
|
const [loaded, setLoaded] = useState(false)
|
||||||
const [headerImageSize, setHeaderImageSize] = useState()
|
const [headerImageSize, setHeaderImageSize] = useState()
|
||||||
@ -100,7 +176,6 @@ export default function Account ({
|
|||||||
if (accountState.status === 'idle') {
|
if (accountState.status === 'idle') {
|
||||||
dispatch(accountSlice.fetch({ id }))
|
dispatch(accountSlice.fetch({ id }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accountState.account.header) {
|
if (accountState.account.header) {
|
||||||
Image.getSize(accountState.account.header, (width, height) => {
|
Image.getSize(accountState.account.header, (width, height) => {
|
||||||
setHeaderImageSize({ width, height })
|
setHeaderImageSize({ width, height })
|
||||||
@ -117,12 +192,15 @@ export default function Account ({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
dispatch(accountSlice.reset())
|
dispatch(accountSlice.reset())
|
||||||
|
dispatch(timelineSlice.reset('Account_Default'))
|
||||||
|
dispatch(timelineSlice.reset('Account_All'))
|
||||||
|
dispatch(timelineSlice.reset('Account_Media'))
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
)
|
)
|
||||||
// add emoji support
|
// add emoji support
|
||||||
return loaded ? (
|
return loaded ? (
|
||||||
<ScrollView>
|
<View>
|
||||||
<Header
|
<Header
|
||||||
uri={accountState.account.header}
|
uri={accountState.account.header}
|
||||||
size={
|
size={
|
||||||
@ -134,9 +212,10 @@ export default function Account ({
|
|||||||
/>
|
/>
|
||||||
<Information
|
<Information
|
||||||
account={accountState.account}
|
account={accountState.account}
|
||||||
emojis={accountState.emojis}
|
emojis={accountState.account.emojis}
|
||||||
/>
|
/>
|
||||||
</ScrollView>
|
{accountState.account.id && <Toots account={accountState.account.id} />}
|
||||||
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)
|
)
|
||||||
|
28
src/stacks/Shared/Toot.jsx
Normal file
28
src/stacks/Shared/Toot.jsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { useFocusEffect } from '@react-navigation/native'
|
||||||
|
|
||||||
|
import Timeline from 'src/stacks/common/Timeline'
|
||||||
|
import { reset } from 'src/stacks/common/timelineSlice'
|
||||||
|
|
||||||
|
// Show remote hashtag? Only when private, show local version?
|
||||||
|
|
||||||
|
export default function Toot ({
|
||||||
|
route: {
|
||||||
|
params: { toot }
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
React.useCallback(() => {
|
||||||
|
// Do something when the screen is focused
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dispatch(reset('Toot'))
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
)
|
||||||
|
|
||||||
|
return <Timeline page='Toot' toot={toot} disableRefresh />
|
||||||
|
}
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { WebView } from 'react-native-webview'
|
import { WebView } from 'react-native-webview'
|
||||||
|
|
||||||
// Webview preview card
|
// Update page title
|
||||||
|
|
||||||
export default function Webview ({
|
export default function Webview ({
|
||||||
route: {
|
route: {
|
||||||
|
@ -1,47 +1,56 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { ActivityIndicator, FlatList, 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 TootTimeline from 'src/components/TootTimeline'
|
import Toot from 'src/components/Toot'
|
||||||
import { fetch, getState } from './timelineSlice'
|
import { fetch } from './timelineSlice'
|
||||||
|
|
||||||
// Opening nesting hashtag pages
|
// Opening nesting hashtag pages
|
||||||
|
|
||||||
export default function Timeline ({ page, hashtag, list }) {
|
export default function Timeline ({
|
||||||
|
page,
|
||||||
|
hashtag,
|
||||||
|
list,
|
||||||
|
toot,
|
||||||
|
account,
|
||||||
|
disableRefresh
|
||||||
|
}) {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const state = useSelector(state => getState(state)(page))
|
const state = useSelector(state => state.timelines[page])
|
||||||
const [timelineReady, setTimelineReady] = useState(false)
|
const [timelineReady, setTimelineReady] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.status === 'idle') {
|
if (state.status === 'idle') {
|
||||||
dispatch(fetch({ page, hashtag, list }))
|
dispatch(fetch({ page, hashtag, list, toot, account }))
|
||||||
setTimelineReady(true)
|
setTimelineReady(true)
|
||||||
}
|
}
|
||||||
}, [state, dispatch])
|
}, [state, dispatch])
|
||||||
|
|
||||||
let content
|
let content
|
||||||
if (state.status === 'error') {
|
if (state.status === 'failed') {
|
||||||
content = <Text>Error message</Text>
|
content = <Text>Error message</Text>
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
<FlatList
|
<FlatList
|
||||||
|
style={{ minHeight: '100%' }}
|
||||||
data={state.toots}
|
data={state.toots}
|
||||||
keyExtractor={({ id }) => id}
|
keyExtractor={({ id }) => id}
|
||||||
renderItem={({ item, index, separators }) => (
|
renderItem={({ item, index, separators }) => (
|
||||||
<TootTimeline key={item.key} item={item} />
|
<Toot key={item.key} item={item} />
|
||||||
)}
|
)}
|
||||||
onRefresh={() =>
|
{...(state.pointer && { initialScrollIndex: state.pointer })}
|
||||||
|
{...(!disableRefresh && {
|
||||||
|
onRefresh: () =>
|
||||||
dispatch(
|
dispatch(
|
||||||
fetch({
|
fetch({
|
||||||
page,
|
page,
|
||||||
query: [{ key: 'since_id', value: state.toots[0].id }]
|
query: [{ key: 'since_id', value: state.toots[0].id }]
|
||||||
})
|
})
|
||||||
)
|
),
|
||||||
}
|
refreshing: state.status === 'loading',
|
||||||
refreshing={state.status === 'loading'}
|
onEndReached: () => {
|
||||||
onEndReached={() => {
|
|
||||||
if (!timelineReady) {
|
if (!timelineReady) {
|
||||||
dispatch(
|
dispatch(
|
||||||
fetch({
|
fetch({
|
||||||
@ -56,8 +65,9 @@ export default function Timeline ({ page, hashtag, list }) {
|
|||||||
)
|
)
|
||||||
setTimelineReady(true)
|
setTimelineReady(true)
|
||||||
}
|
}
|
||||||
}}
|
},
|
||||||
onEndReachedThreshold={0.5}
|
onEndReachedThreshold: 0.5
|
||||||
|
})}
|
||||||
onMomentumScrollBegin={() => setTimelineReady(false)}
|
onMomentumScrollBegin={() => setTimelineReady(false)}
|
||||||
/>
|
/>
|
||||||
{state.status === 'loading' && <ActivityIndicator />}
|
{state.status === 'loading' && <ActivityIndicator />}
|
||||||
@ -71,5 +81,7 @@ export default function Timeline ({ page, hashtag, list }) {
|
|||||||
Timeline.propTypes = {
|
Timeline.propTypes = {
|
||||||
page: PropTypes.string.isRequired,
|
page: PropTypes.string.isRequired,
|
||||||
hashtag: PropTypes.string,
|
hashtag: PropTypes.string,
|
||||||
list: PropTypes.string
|
list: PropTypes.string,
|
||||||
|
toot: PropTypes.string,
|
||||||
|
disableRefresh: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Animated, Dimensions, Text, 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'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
@ -8,20 +8,32 @@ import { Feather } from '@expo/vector-icons'
|
|||||||
import Timeline from './Timeline'
|
import Timeline from './Timeline'
|
||||||
import Account from 'src/stacks/Shared/Account'
|
import Account from 'src/stacks/Shared/Account'
|
||||||
import Hashtag from 'src/stacks/Shared/Hashtag'
|
import Hashtag from 'src/stacks/Shared/Hashtag'
|
||||||
|
import Toot from 'src/stacks/Shared/Toot'
|
||||||
import Webview from 'src/stacks/Shared/Webview'
|
import Webview from 'src/stacks/Shared/Webview'
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator()
|
const Stack = createNativeStackNavigator()
|
||||||
|
|
||||||
|
function Page ({ item: { page } }) {
|
||||||
|
return (
|
||||||
|
<View style={{ width: Dimensions.get('window').width }}>
|
||||||
|
<Timeline page={page} />
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function TimelinesCombined ({ name, content }) {
|
export default function TimelinesCombined ({ 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(
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const nbr = setTimeout(() => setRenderHeader(true), 50)
|
const nbr = setTimeout(() => setRenderHeader(true), 50)
|
||||||
return
|
return
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const moveAnimation = useRef(new Animated.Value(0)).current
|
const horizontalPaging = useRef()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator>
|
<Stack.Navigator>
|
||||||
@ -38,37 +50,47 @@ export default function TimelinesCombined ({ name, content }) {
|
|||||||
<SegmentedControl
|
<SegmentedControl
|
||||||
values={[content[0].title, content[1].title]}
|
values={[content[0].title, content[1].title]}
|
||||||
selectedIndex={segment}
|
selectedIndex={segment}
|
||||||
onChange={e => {
|
onChange={({ nativeEvent }) => {
|
||||||
setSegment(e.nativeEvent.selectedSegmentIndex)
|
setSegmentManuallyTriggered(true)
|
||||||
Animated.timing(moveAnimation, {
|
setSegment(nativeEvent.selectedSegmentIndex)
|
||||||
toValue:
|
horizontalPaging.current.scrollToIndex({
|
||||||
-e.nativeEvent.selectedSegmentIndex *
|
index: nativeEvent.selectedSegmentIndex
|
||||||
Dimensions.get('window').width,
|
})
|
||||||
duration: 250,
|
|
||||||
useNativeDriver: false
|
|
||||||
}).start()
|
|
||||||
}}
|
}}
|
||||||
style={{ width: 150, height: 30 }}
|
style={{ width: 150, height: 30 }}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props => (
|
{() => (
|
||||||
<Animated.View
|
<FlatList
|
||||||
style={{
|
style={{ width: Dimensions.get('window').width, height: '100%' }}
|
||||||
flexDirection: 'row',
|
data={content}
|
||||||
width: Dimensions.get('window').width * 2,
|
keyExtractor={({ page }) => page}
|
||||||
left: moveAnimation
|
renderItem={({ item, index }) => {
|
||||||
|
return <Page key={index} item={item} />
|
||||||
}}
|
}}
|
||||||
{...props}
|
ref={horizontalPaging}
|
||||||
>
|
bounces={false}
|
||||||
<View style={{ width: Dimensions.get('window').width }}>
|
getItemLayout={(data, index) => ({
|
||||||
<Timeline page={content[0].page} />
|
length: Dimensions.get('window').width,
|
||||||
</View>
|
offset: Dimensions.get('window').width * index,
|
||||||
<View style={{ width: Dimensions.get('window').width }}>
|
index
|
||||||
<Timeline page={content[1].page} />
|
})}
|
||||||
</View>
|
horizontal
|
||||||
</Animated.View>
|
onMomentumScrollEnd={() => setSegmentManuallyTriggered(false)}
|
||||||
|
onScroll={({ nativeEvent }) =>
|
||||||
|
!segmentManuallyTriggered &&
|
||||||
|
setSegment(
|
||||||
|
nativeEvent.contentOffset.x <=
|
||||||
|
Dimensions.get('window').width / 2
|
||||||
|
? 0
|
||||||
|
: 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pagingEnabled
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@ -87,12 +109,19 @@ export default function TimelinesCombined ({ name, content }) {
|
|||||||
title: `#${decodeURIComponent(route.params.hashtag)}`
|
title: `#${decodeURIComponent(route.params.hashtag)}`
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='Toot'
|
||||||
|
component={Toot}
|
||||||
|
options={() => ({
|
||||||
|
title: '对话'
|
||||||
|
})}
|
||||||
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Webview'
|
name='Webview'
|
||||||
component={Webview}
|
component={Webview}
|
||||||
options={({ route }) => ({
|
// options={({ route }) => ({
|
||||||
title: `${route.params.domain}`
|
// title: `${route.params.domain}`
|
||||||
})}
|
// })}
|
||||||
/>
|
/>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
)
|
)
|
||||||
|
@ -16,8 +16,6 @@ const accountInitState = {
|
|||||||
status: 'idle'
|
status: 'idle'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const retrive = state => state.account
|
|
||||||
|
|
||||||
export const accountSlice = createSlice({
|
export const accountSlice = createSlice({
|
||||||
name: 'account',
|
name: 'account',
|
||||||
initialState: accountInitState,
|
initialState: accountInitState,
|
||||||
|
@ -30,8 +30,6 @@ const relationshipsInitState = {
|
|||||||
status: 'idle'
|
status: 'idle'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const retrive = state => state.relationships
|
|
||||||
|
|
||||||
export const relationshipSlice = createSlice({
|
export const relationshipSlice = createSlice({
|
||||||
name: 'relationships',
|
name: 'relationships',
|
||||||
initialState: {
|
initialState: {
|
||||||
|
@ -13,9 +13,10 @@ import { client } from 'src/api/client'
|
|||||||
|
|
||||||
export const fetch = createAsyncThunk(
|
export const fetch = createAsyncThunk(
|
||||||
'timeline/fetch',
|
'timeline/fetch',
|
||||||
async ({ page, query = [], hashtag, list }, { getState }) => {
|
async ({ page, query = [], account, hashtag, list, toot }, { getState }) => {
|
||||||
const instanceLocal = getState().instanceInfo.local + '/api/v1/'
|
const instanceLocal = getState().instanceInfo.local + '/api/v1/'
|
||||||
const instanceRemote = getState().instanceInfo.remote + '/api/v1/'
|
const instanceRemote = getState().instanceInfo.remote + '/api/v1/'
|
||||||
|
// If header if needed for remote server
|
||||||
const header = {
|
const header = {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${getState().instanceInfo.localToken}`
|
Authorization: `Bearer ${getState().instanceInfo.localToken}`
|
||||||
@ -24,36 +25,103 @@ export const fetch = createAsyncThunk(
|
|||||||
|
|
||||||
switch (page) {
|
switch (page) {
|
||||||
case 'Following':
|
case 'Following':
|
||||||
return await client.get(`${instanceLocal}timelines/home`, query, header)
|
return {
|
||||||
|
toots: await client.get(
|
||||||
|
`${instanceLocal}timelines/home`,
|
||||||
|
query,
|
||||||
|
header
|
||||||
|
)
|
||||||
|
}
|
||||||
case 'Local':
|
case 'Local':
|
||||||
query.push({ key: 'local', value: 'true' })
|
query.push({ key: 'local', value: 'true' })
|
||||||
return await client.get(
|
return {
|
||||||
|
toots: await client.get(
|
||||||
`${instanceLocal}timelines/public`,
|
`${instanceLocal}timelines/public`,
|
||||||
query,
|
query,
|
||||||
header
|
header
|
||||||
)
|
)
|
||||||
|
}
|
||||||
case 'LocalPublic':
|
case 'LocalPublic':
|
||||||
return await client.get(
|
return {
|
||||||
|
toots: await client.get(
|
||||||
`${instanceLocal}timelines/public`,
|
`${instanceLocal}timelines/public`,
|
||||||
query,
|
query,
|
||||||
header
|
header
|
||||||
)
|
)
|
||||||
|
}
|
||||||
case 'RemotePublic':
|
case 'RemotePublic':
|
||||||
return await client.get(`${instanceRemote}timelines/public`, query)
|
return {
|
||||||
|
toots: await client.get(`${instanceRemote}timelines/public`, query)
|
||||||
|
}
|
||||||
case 'Notifications':
|
case 'Notifications':
|
||||||
return await client.get(`${instanceLocal}notifications`, query, header)
|
return {
|
||||||
|
toots: await client.get(
|
||||||
|
`${instanceLocal}notifications`,
|
||||||
|
query,
|
||||||
|
header
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 'Account_Default':
|
||||||
|
const toots = await client.get(
|
||||||
|
`${instanceLocal}accounts/${account}/statuses`,
|
||||||
|
[{ key: 'pinned', value: 'true' }],
|
||||||
|
header
|
||||||
|
)
|
||||||
|
toots.push(
|
||||||
|
...(await client.get(
|
||||||
|
`${instanceLocal}accounts/${account}/statuses`,
|
||||||
|
[{ key: 'exclude_replies', value: 'true' }],
|
||||||
|
header
|
||||||
|
))
|
||||||
|
)
|
||||||
|
return { toots: toots }
|
||||||
|
case 'Account_All':
|
||||||
|
return {
|
||||||
|
toots: await client.get(
|
||||||
|
`${instanceLocal}accounts/${account}/statuses`,
|
||||||
|
query,
|
||||||
|
header
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 'Account_Media':
|
||||||
|
return {
|
||||||
|
toots: await client.get(
|
||||||
|
`${instanceLocal}accounts/${account}/statuses`,
|
||||||
|
[{ key: 'only_media', value: 'true' }],
|
||||||
|
header
|
||||||
|
)
|
||||||
|
}
|
||||||
case 'Hashtag':
|
case 'Hashtag':
|
||||||
return await client.get(
|
return {
|
||||||
|
toots: await client.get(
|
||||||
`${instanceLocal}timelines/tag/${hashtag}`,
|
`${instanceLocal}timelines/tag/${hashtag}`,
|
||||||
query,
|
query,
|
||||||
header
|
header
|
||||||
)
|
)
|
||||||
|
}
|
||||||
case 'List':
|
case 'List':
|
||||||
return await client.get(
|
return {
|
||||||
|
toots: await client.get(
|
||||||
`${instanceLocal}timelines/list/${list}`,
|
`${instanceLocal}timelines/list/${list}`,
|
||||||
query,
|
query,
|
||||||
header
|
header
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
case 'Toot':
|
||||||
|
const current = await client.get(
|
||||||
|
`${instanceLocal}statuses/${toot}`,
|
||||||
|
[],
|
||||||
|
header
|
||||||
|
)
|
||||||
|
const context = await client.get(
|
||||||
|
`${instanceLocal}statuses/${toot}/context`,
|
||||||
|
[],
|
||||||
|
header
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
toots: [...context.ancestors, current, ...context.descendants],
|
||||||
|
pointer: context.ancestors.length
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
console.error('Timeline type error')
|
console.error('Timeline type error')
|
||||||
}
|
}
|
||||||
@ -62,11 +130,10 @@ export const fetch = createAsyncThunk(
|
|||||||
|
|
||||||
const timelineInitState = {
|
const timelineInitState = {
|
||||||
toots: [],
|
toots: [],
|
||||||
|
pointer: undefined,
|
||||||
status: 'idle'
|
status: 'idle'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getState = state => page => state.timelines[page]
|
|
||||||
|
|
||||||
export const timelineSlice = createSlice({
|
export const timelineSlice = createSlice({
|
||||||
name: 'timeline',
|
name: 'timeline',
|
||||||
initialState: {
|
initialState: {
|
||||||
@ -76,7 +143,11 @@ export const timelineSlice = createSlice({
|
|||||||
RemotePublic: timelineInitState,
|
RemotePublic: timelineInitState,
|
||||||
Notifications: timelineInitState,
|
Notifications: timelineInitState,
|
||||||
Hashtag: timelineInitState,
|
Hashtag: timelineInitState,
|
||||||
List: timelineInitState
|
List: timelineInitState,
|
||||||
|
Toot: timelineInitState,
|
||||||
|
Account_Default: timelineInitState,
|
||||||
|
Account_All: timelineInitState,
|
||||||
|
Account_Media: timelineInitState
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
reset (state, action) {
|
reset (state, action) {
|
||||||
@ -89,11 +160,15 @@ export const timelineSlice = createSlice({
|
|||||||
},
|
},
|
||||||
[fetch.fulfilled]: (state, action) => {
|
[fetch.fulfilled]: (state, action) => {
|
||||||
state[action.meta.arg.page].status = 'succeeded'
|
state[action.meta.arg.page].status = 'succeeded'
|
||||||
if (action.meta.arg.query && action.meta.arg.query.since_id) {
|
if (
|
||||||
state[action.meta.arg.page].toots.unshift(...action.payload)
|
action.meta.arg.query &&
|
||||||
|
action.meta.arg.query[0].key === 'since_id'
|
||||||
|
) {
|
||||||
|
state[action.meta.arg.page].toots.unshift(...action.payload.toots)
|
||||||
} else {
|
} else {
|
||||||
state[action.meta.arg.page].toots.push(...action.payload)
|
state[action.meta.arg.page].toots.push(...action.payload.toots)
|
||||||
}
|
}
|
||||||
|
state[action.meta.arg.page].pointer = action.payload.pointer
|
||||||
},
|
},
|
||||||
[fetch.rejected]: (state, action) => {
|
[fetch.rejected]: (state, action) => {
|
||||||
state[action.meta.arg.page].status = 'failed'
|
state[action.meta.arg.page].status = 'failed'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user