From da28ed009307777078a70c56f6987351deb6e560 Mon Sep 17 00:00:00 2001 From: yssk22 Date: Tue, 25 Jun 2024 17:03:41 +0000 Subject: [PATCH] [expo] implement UPFC tab **Summary** UPFC Tab shows the list of the current applications and remind users that they have payment due for their applications. **Test** - expo **Issue** - N/A --- expo/assets/translations.json | 7 +- expo/features/common/components/card/Card.tsx | 65 +++++++++++ .../common/components/card/CardBody.tsx | 19 +++ .../common/components/card/CardSkelton.tsx | 31 +++++ expo/features/home/Events.tsx | 18 --- expo/features/home/HomeScreen.tsx | 6 +- expo/features/settings/context/theme.tsx | 29 +++-- expo/features/upfc/UPFCApplicationCard.tsx | 109 ++++++++++++++++++ .../upfc/UPFCApplicationStatusLabel.tsx | 42 +++++++ .../upfc/UPFCCurrentApplicationList.tsx | 43 +++++++ expo/features/upfc/UPFCNoCredentials.tsx | 28 +++++ expo/features/upfc/UPFCTab.tsx | 23 ++++ expo/features/upfc/UPFCWebViewScreen.tsx | 43 +++++++ .../upfc/settings/UPFCSettingsForm.tsx | 10 +- .../upfc/settings/UPFCSettingsScreen.tsx | 9 +- expo/features/upfc/useUPFCWebView.tsx | 14 +++ expo/generated/Screens.tsx | 13 ++- expo/package.json | 3 +- expo/scripts/genscreen.js | 3 +- expo/yarn.lock | 13 ++- 20 files changed, 487 insertions(+), 41 deletions(-) create mode 100644 expo/features/common/components/card/Card.tsx create mode 100644 expo/features/common/components/card/CardBody.tsx create mode 100644 expo/features/common/components/card/CardSkelton.tsx delete mode 100644 expo/features/home/Events.tsx create mode 100644 expo/features/upfc/UPFCApplicationCard.tsx create mode 100644 expo/features/upfc/UPFCApplicationStatusLabel.tsx create mode 100644 expo/features/upfc/UPFCCurrentApplicationList.tsx create mode 100644 expo/features/upfc/UPFCNoCredentials.tsx create mode 100644 expo/features/upfc/UPFCTab.tsx create mode 100644 expo/features/upfc/UPFCWebViewScreen.tsx create mode 100644 expo/features/upfc/useUPFCWebView.tsx diff --git a/expo/assets/translations.json b/expo/assets/translations.json index f5c3668f..a70e4f23 100644 --- a/expo/assets/translations.json +++ b/expo/assets/translations.json @@ -38,8 +38,11 @@ "Artists": { "ja": "アーティスト" }, - "Events": { - "ja": "イベント" + "Fan Club": { + "ja": "ファンクラブ" + }, + "Configure fan club settings": { + "ja": "ファンクラブ設定を行う" }, "Goods": { "ja": "グッズ" diff --git a/expo/features/common/components/card/Card.tsx b/expo/features/common/components/card/Card.tsx new file mode 100644 index 00000000..714a1b4b --- /dev/null +++ b/expo/features/common/components/card/Card.tsx @@ -0,0 +1,65 @@ +import Text from '@hpapp/features/common/components/Text'; +import { FontSize, Spacing } from '@hpapp/features/common/constants'; +import { ColorScheme, useColor } from '@hpapp/features/settings/context/theme'; +import React from 'react'; +import { View, StyleSheet } from 'react-native'; + +type StyleProps = React.ComponentProps['style']; + +export type CardProps = { + colorScheme?: ColorScheme; + headerText?: string; + subHeaderText?: string; + footerContent?: JSX.Element; + containerStyle?: StyleProps; + headerStyle?: StyleProps; + children: React.ReactNode; +}; + +export default function Card({ + colorScheme = 'primary', + headerText, + subHeaderText, + containerStyle, + headerStyle, + children +}: CardProps) { + const [color, contrast] = useColor(colorScheme); + const showHeader = headerText !== undefined || subHeaderText !== undefined; + return ( + + {showHeader && ( + + {headerText && ( + + {headerText} + + )} + {subHeaderText && ( + + {subHeaderText} + + )} + + )} + {children} + + ); +} + +const styles = StyleSheet.create({ + container: { + borderRadius: 3, + borderWidth: 1 + }, + header: { + padding: Spacing.Small + }, + headerText: { + fontSize: FontSize.Medium, + fontWeight: 'bold' + }, + subHeaderText: { + fontSize: FontSize.XXSmall + } +}); diff --git a/expo/features/common/components/card/CardBody.tsx b/expo/features/common/components/card/CardBody.tsx new file mode 100644 index 00000000..f3236cf7 --- /dev/null +++ b/expo/features/common/components/card/CardBody.tsx @@ -0,0 +1,19 @@ +import { Spacing } from '@hpapp/features/common/constants'; +import { View, StyleSheet } from 'react-native'; + +type StyleProps = React.ComponentProps['style']; + +export type CardBodyProps = { + style?: StyleProps; + children: React.ReactNode; +}; + +export default function CardBody({ style, children }: CardBodyProps) { + return {children}; +} + +const styles = StyleSheet.create({ + container: { + padding: Spacing.Small + } +}); diff --git a/expo/features/common/components/card/CardSkelton.tsx b/expo/features/common/components/card/CardSkelton.tsx new file mode 100644 index 00000000..c7fabeb0 --- /dev/null +++ b/expo/features/common/components/card/CardSkelton.tsx @@ -0,0 +1,31 @@ +import { Spacing } from '@hpapp/features/common/constants'; +import { ColorScheme, useSkeltonColor } from '@hpapp/features/settings/context/theme'; +import ContentLoader, { Rect } from 'react-content-loader/native'; + +type CardSkeltonProps = { + color?: ColorScheme; +}; + +export default function CardSkelton({ color = 'primary' }: CardSkeltonProps) { + const primary = useSkeltonColor(color); + const innerBarX = Spacing.Small + 3 + Spacing.Medium + 50 + Spacing.Medium; + const innerBarWidth = 400 - (Spacing.Small + 3 + Spacing.Medium + 50 + Spacing.Medium * 3); + return ( + + + + + + + + + + + + + + + + + ); +} diff --git a/expo/features/home/Events.tsx b/expo/features/home/Events.tsx deleted file mode 100644 index 16ef7701..00000000 --- a/expo/features/home/Events.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { View, Text, StyleSheet } from 'react-native'; - -export default function EventsTab() { - return ( - - Events - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - alignItems: 'center', - justifyContent: 'center' - } -}); diff --git a/expo/features/home/HomeScreen.tsx b/expo/features/home/HomeScreen.tsx index 250ba3df..3998ed03 100644 --- a/expo/features/home/HomeScreen.tsx +++ b/expo/features/home/HomeScreen.tsx @@ -1,12 +1,12 @@ import ArtistsTab from '@hpapp/features/artist/ArtistsTab'; import Loading from '@hpapp/features/common/components/Loading'; -import EventsTab from '@hpapp/features/home/Events'; import GoodsTab from '@hpapp/features/home/GoodsTab'; import HomeTab from '@hpapp/features/home/HomeTab'; import AppUpdateBanner from '@hpapp/features/root/banner/AppUpdateBanner'; import { defineScreen, useNavigationOption } from '@hpapp/features/root/protected/stack'; import SettingsTab from '@hpapp/features/settings/SettingsTab'; import { useColor } from '@hpapp/features/settings/context/theme'; +import UPFCTab from '@hpapp/features/upfc/UPFCTab'; import { UPFCProvider } from '@hpapp/features/upfc/context'; import { t } from '@hpapp/system/i18n'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; @@ -35,8 +35,8 @@ const Tabs: TabSpec[] = [ icon: 'people' }, { - name: 'Events', - component: EventsTab, + name: 'Fan Club', + component: UPFCTab, icon: 'calendar' }, { diff --git a/expo/features/settings/context/theme.tsx b/expo/features/settings/context/theme.tsx index cbbe4560..497bf07a 100644 --- a/expo/features/settings/context/theme.tsx +++ b/expo/features/settings/context/theme.tsx @@ -212,16 +212,22 @@ const useColor = (scheme: ColorScheme) => { return [color, yiq > yiqThreshod ? theme.colors.white : theme.colors.black]; }; +const useSkeltonColor = (scheme: ColorScheme, percent = 0.8) => { + const { theme } = useTheme(); + const color = theme.colors[scheme]; + const [r, g, b] = colorToRGB(color); + const r1 = Math.min(255, Math.floor(r + (255 - r) * percent)); + const g1 = Math.min(255, Math.floor(g + (255 - g) * percent)); + const b1 = Math.min(255, Math.floor(b + (255 - b) * percent)); + // convert r1, g1, b1 to hex + return `#${r1.toString(16).padStart(2, '0')}${g1.toString(16).padStart(2, '0')}${b1.toString(16).padStart(2, '0')}`; +}; + const yiqThreshod = 64; // https://en.wikipedia.org/wiki/YIQ const calcYIQ = (color: string) => { // #aabbcc => [R, G, B] as numbers - const [r, g, b] = color - .toUpperCase() - .split('') - .slice(1, 7) - .reduce((acc: string[], curr, n, arr) => (n % 2 ? [...acc, `${arr[n - 1]}${curr}`] : acc), []) - .map((h) => parseInt(h, 16)); + const [r, g, b] = colorToRGB(color); return (r * 299 + g * 587 + b * 114) / 1000; }; @@ -249,4 +255,13 @@ const rgbToColor = (rgb: string): ColorDef | undefined => { return AvailableColorsRGBToColor[rgb]; }; -export { AppThemeProvider, useAppTheme, useColor, ColorScheme, ColorKey, AvailableColors }; +function colorToRGB(color: string) { + return color + .toUpperCase() + .split('') + .slice(1, 7) + .reduce((acc: string[], curr, n, arr) => (n % 2 ? [...acc, `${arr[n - 1]}${curr}`] : acc), []) + .map((h) => parseInt(h, 16)); +} + +export { AppThemeProvider, useAppTheme, useColor, useSkeltonColor, ColorScheme, ColorKey, AvailableColors }; diff --git a/expo/features/upfc/UPFCApplicationCard.tsx b/expo/features/upfc/UPFCApplicationCard.tsx new file mode 100644 index 00000000..5dc87126 --- /dev/null +++ b/expo/features/upfc/UPFCApplicationCard.tsx @@ -0,0 +1,109 @@ +import CalendarDateIcon from '@hpapp/features/common/components/CalendarDateIcon'; +import Text from '@hpapp/features/common/components/Text'; +import Card from '@hpapp/features/common/components/card/Card'; +import ListItem from '@hpapp/features/common/components/list/ListItem'; +import { FontSize, IconSize, Spacing } from '@hpapp/features/common/constants'; +import { useColor } from '@hpapp/features/settings/context/theme'; +import UPFCApplicationStatusLabel from '@hpapp/features/upfc/UPFCApplicationStatusLabel'; +import { EventApplicationTickets } from '@hpapp/features/upfc/scraper'; +import useUPFCWebView from '@hpapp/features/upfc/useUPFCWebView'; +import { toDateString, toTimeString } from '@hpapp/foundation/date'; +import { Button, Icon } from '@rneui/themed'; +import { useMemo } from 'react'; +import { View, StyleSheet } from 'react-native'; + +const styles = StyleSheet.create({ + card: { + margin: Spacing.XSmall + }, + listItem: { + paddingTop: Spacing.XXSmall, + paddingBottom: Spacing.XSmall, + paddingLeft: Spacing.XXSmall, + paddingRight: Spacing.XSmall + }, + listItemCenter: { + justifyContent: 'space-around', + paddingLeft: Spacing.Small, + paddingRight: Spacing.Small, + flexGrow: 1 + }, + listItemCenterText: { + fontSize: FontSize.Small + }, + footer: { + flexGrow: 1, + justifyContent: 'flex-end', + alignItems: 'flex-end', + marginTop: Spacing.Small, + paddingRight: Spacing.Small, + paddingBottom: Spacing.Small + }, + footerButtonIcon: { + marginLeft: Spacing.Small + } +}); + +export type UPFCApplicationCardProps = { + event: EventApplicationTickets; +}; + +export default function UPFCApplicationCard({ event }: UPFCApplicationCardProps) { + const [color] = useColor('primary'); + const openUPFCWeView = useUPFCWebView(); + const paymentWindowString = `${toDateString(event.paymentOpenDate)} ~ ${toDateString(event.paymentDueDate)}`; + const items = useMemo(() => { + return event.tickets.map((t) => { + return ( + } + rightContent={} + > + + + {t.venue} + + {`開場: ${toTimeString(t.openAt)} 開演:${toTimeString( + t.startAt + )}`} + + + ); + }); + }, [event]); + const footer = useMemo(() => { + if (event.paymentDueDate) { + return ( + + + + ); + } + return null; + }, [event.paymentDueDate]); + return ( + + {items} + {footer} + + ); +} diff --git a/expo/features/upfc/UPFCApplicationStatusLabel.tsx b/expo/features/upfc/UPFCApplicationStatusLabel.tsx new file mode 100644 index 00000000..c9767077 --- /dev/null +++ b/expo/features/upfc/UPFCApplicationStatusLabel.tsx @@ -0,0 +1,42 @@ +import Text from '@hpapp/features/common/components/Text'; +import { FontSize, Spacing } from '@hpapp/features/common/constants'; +import { ColorScheme, useColor } from '@hpapp/features/settings/context/theme'; +import { EventTicket } from '@hpapp/features/upfc/scraper'; +import React from 'react'; +import { View, StyleSheet } from 'react-native'; + +const StatusColorScheme: Record = { + 申込済: 'success', + 入金待: 'error', + 入金済: 'primary', + 入金忘: 'disabled', + 落選: 'disabled', + 不明: 'warning' +}; + +export type UPFCApplicationStatusLabelProps = { + ticket: EventTicket; +}; + +export default function UPFCApplicationStatusLabel({ ticket }: UPFCApplicationStatusLabelProps) { + const [color, contrast] = useColor(StatusColorScheme[ticket.status]); + return ( + + {ticket.status} + + ); +} + +const styles = StyleSheet.create({ + container: { + paddingLeft: Spacing.XSmall, + paddingRight: Spacing.XSmall, + paddingTop: Spacing.XXSmall, + paddingBottom: Spacing.XXSmall, + width: 50, + alignItems: 'center' + }, + text: { + fontSize: FontSize.XSmall + } +}); diff --git a/expo/features/upfc/UPFCCurrentApplicationList.tsx b/expo/features/upfc/UPFCCurrentApplicationList.tsx new file mode 100644 index 00000000..af8658ad --- /dev/null +++ b/expo/features/upfc/UPFCCurrentApplicationList.tsx @@ -0,0 +1,43 @@ +import CardSkelton from '@hpapp/features/common/components/card/CardSkelton'; +import UPFCApplicationCard from '@hpapp/features/upfc/UPFCApplicationCard'; +import { useUPFC } from '@hpapp/features/upfc/context'; +import { EventApplicationTickets } from '@hpapp/features/upfc/scraper'; +import { FlatList, ListRenderItemInfo } from 'react-native'; + +function keyExtractor(event: EventApplicationTickets) { + return event.name; +} + +function renderItem(event: ListRenderItemInfo) { + if (event.item.tickets.length === 0) { + return null; + } + return ; +} + +export default function UPFCCurentApplicationList() { + const upfc = useUPFC(); + if (upfc.data === null) { + return ( + <> + + + + + + + ); + } + return ( + + ); +} diff --git a/expo/features/upfc/UPFCNoCredentials.tsx b/expo/features/upfc/UPFCNoCredentials.tsx new file mode 100644 index 00000000..a215d331 --- /dev/null +++ b/expo/features/upfc/UPFCNoCredentials.tsx @@ -0,0 +1,28 @@ +import { useNavigation } from '@hpapp/features/root/protected/stack'; +import UPFCSettingsScreen from '@hpapp/features/upfc/settings/UPFCSettingsScreen'; +import { t } from '@hpapp/system/i18n'; +import { Button } from '@rneui/themed'; +import { View, StyleSheet } from 'react-native'; + +export default function UPFCNoCredentials() { + const navigation = useNavigation(); + return ( + +