Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleaner DealInfo screen #33

Merged
merged 29 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c68781e
fix deal info link buttons gaps and sizing
josh-leyshon Jan 8, 2025
d10d804
Remove unneeded minHeight from dealCard
josh-leyshon Jan 8, 2025
381a2a3
Removed dealCard test because it was really just testing that the inb…
josh-leyshon Jan 8, 2025
936266e
Unwrap DealCard from Pressable so that it can be maybe reused in Deal…
josh-leyshon Jan 8, 2025
9c136d7
DealHeader now just renders a DealCard. will style the card a bit bet…
josh-leyshon Jan 8, 2025
af5df3e
Add base Card component that is basically a minorly styled Flex compo…
josh-leyshon Jan 8, 2025
409a2c2
Refactor DealCard, now only exports the content within and the caller…
josh-leyshon Jan 8, 2025
34347d7
Deleted VoteButtons component, don't see needing it anymore
josh-leyshon Jan 8, 2025
cf2f752
enable eslint fix on save in vscode
josh-leyshon Jan 8, 2025
b72f821
Update base copy colour to black
josh-leyshon Jan 8, 2025
8a6bb69
Redo base Button so now it only accepts primary or secondary colours
josh-leyshon Jan 8, 2025
eff2d50
Delete Description wrapper component and don't justify description text
josh-leyshon Jan 8, 2025
17ed59b
Move global type Deal to the feed-parser
josh-leyshon Jan 8, 2025
80d5074
comment typo fix
josh-leyshon Jan 8, 2025
07cb8bb
Add partText() function as a step in parsing feeds. Can now identify …
josh-leyshon Jan 8, 2025
6e610de
highlight prices in deal card titles
josh-leyshon Jan 8, 2025
5557d09
Distinguish paragraphs in deal description by doubling newlines
josh-leyshon Jan 9, 2025
9be2a0e
change parsed Deal `id` from number to string
josh-leyshon Jan 9, 2025
c7303dc
Start adding partedDescription to Deal type. For now it correctly par…
josh-leyshon Jan 9, 2025
66247c3
Description part parser now strips HTML
josh-leyshon Jan 9, 2025
bfcf845
Render descriptions using PartedText. Links are coloured differently,…
josh-leyshon Jan 9, 2025
2977075
Add base Link component for clickable text links
josh-leyshon Jan 9, 2025
18b3498
Render clickable links in deal descriptions
josh-leyshon Jan 9, 2025
411dd02
Fix Link appearing higher than surrounding text on android
josh-leyshon Jan 9, 2025
e71e0c7
Fix links overlapping following text on Android
josh-leyshon Jan 9, 2025
d095856
Unescape escaped chars in TextParts
josh-leyshon Jan 9, 2025
fa0b70f
fix TextPart metaDiv actually consuming every div in the description.…
josh-leyshon Jan 9, 2025
6ca334a
Fix performance when opening deals with large descriptions by limitin…
josh-leyshon Jan 9, 2025
ba08b1b
Fix link buttons not expanding to content width on android. turns out…
josh-leyshon Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"unstable_ts_config",
],
},
"eslint.codeActionsOnSave.mode": "problems",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
},
"dprint.path": "bin/dprint",
"[typescript]": {
"editor.defaultFormatter": "dprint.dprint",
Expand Down
4 changes: 2 additions & 2 deletions src/base/components/button/__tests__/testHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { JestNativeMatchers } from '@testing-library/jest-native/extend-exp
import { Platform } from 'react-native';
import type { ReactTestInstance } from 'react-test-renderer';
import { assert } from '../../../assert';
import type { ButtonColours } from '../button';
import type { ButtonColour } from '../button';
import { buttonColours } from '../button';

type ToHaveStyleParam = Parameters<JestNativeMatchers<void>['toHaveStyle']>[0];
Expand All @@ -14,7 +14,7 @@ type ToHaveStyleParam = Parameters<JestNativeMatchers<void>['toHaveStyle']>[0];
*/
export function expectButtonColour(
buttonTextElement: ReactTestInstance,
colour: ButtonColours,
colour: ButtonColour,
): void {
const button = Platform.select({
ios: buttonTextElement,
Expand Down
48 changes: 24 additions & 24 deletions src/base/components/button/button.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
import { Button as ReactNativeButton, StyleSheet, View } from 'react-native';
import type { ButtonProps as ReactNativeButtonProps } from 'react-native';
import { Button as ReactNativeButton } from 'react-native';
import { colours } from '../../constants/colours';
import { Column } from '../../layout/flex';

/** Exported for testing. */
export const buttonColours = {
orange: '#ffbd59',
green: '#82f152',
lightGreen: '#a0ff77',
veryLightGreen: '#aaff85',
red: '#f15e5e',
lightRed: '#ffb7b7',
veryLightRed: '#ffcfcf',
primary: colours.primary,
secondary: colours.secondary,
} as const;

export type ButtonColours = keyof typeof buttonColours;
export type ButtonColour = keyof typeof buttonColours;

type ButtonProps = {
title: ReactNativeButtonProps['title'];
onPress: ReactNativeButtonProps['onPress'];
color: ButtonColours;
colour: ButtonColour;
/**
* Whether the button width should grow to fit it's content
* when sharing a flex container with other buttons.
* @default false
*/
fitContent?: boolean;
};

export function Button({ color, title, onPress }: ButtonProps): React.JSX.Element {
const buttonColour = buttonColours[color];
export function Button({ title, onPress, colour, fitContent = false }: ButtonProps): React.JSX.Element {
const buttonColour = buttonColours[colour];
return (
// TODO: Could use a custom Pressable component to add styles, instead of a Button wrapped in a View.
<View style={styles.container}>
// Buttons are wrapped in a flex column so they can always expand horizontally
// to the width of their container.
<Column
{...(fitContent && {
grow: 1,
shrink: 1,
})}
>
<ReactNativeButton title={title} onPress={onPress} color={buttonColour} />
</View>
</Column>
);
}

const styles = StyleSheet.create({
container: {
// Buttons should expand to fill available container space by default.
// Larger buttons are better for mobile touch targets.
flexBasis: 1,
flexGrow: 1,
},
});
30 changes: 30 additions & 0 deletions src/base/components/card/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type React from 'react';
import { StyleSheet } from 'react-native';
import { colours } from '../../constants/colours';
import { sizes } from '../../constants/sizes';
import type { FlexLayoutProps } from '../../layout/flex';
import { Column, Row } from '../../layout/flex';

export type CardProps = {
/**
* Which type of Flex layout to use.
* @default 'column'
*/
direction?: 'row' | 'column';
} & FlexLayoutProps;

/**
* A Card is a styled Flex container.
*/
export function Card({ direction = 'column', style, ...props }: CardProps): React.JSX.Element {
const FlexContainer = direction === 'column' ? Column : Row;
return <FlexContainer {...props} style={[styles.card, style]} />;
}

const styles = StyleSheet.create({
card: {
borderRadius: sizes.medium,
backgroundColor: colours.foreground,
boxShadow: 'rgba(0, 0, 0, 0.16) 0px 1px 4px',
},
});
4 changes: 2 additions & 2 deletions src/base/components/loading/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ type LoadingProps = {
size?: keyof typeof sizeToScale;
};

export function Loading({ size }: LoadingProps): React.JSX.Element {
export function Loading({ size = 'medium' }: LoadingProps): React.JSX.Element {
return (
<Spinner>
<ArcIcon scaleSize={sizeToScale[size ?? 'medium']} />
<ArcIcon scaleSize={sizeToScale[size]} />
</Spinner>
);
}
4 changes: 2 additions & 2 deletions src/base/components/spinner/spinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type SpinnerProps = React.PropsWithChildren<{
*/
export function Spinner({
children,
revsPerSecond,
revsPerSecond = 1,
}: SpinnerProps): React.JSX.Element {
const rotationValue = useRef(new Animated.Value(0)).current;

Expand All @@ -21,7 +21,7 @@ export function Spinner({
Animated.loop(
Animated.timing(rotationValue, {
toValue: 1,
duration: 1000 / (revsPerSecond ?? 1),
duration: 1000 / revsPerSecond,
easing: Easing.linear,
// On web, looping does not work with useNativeDriver enabled.
useNativeDriver: Platform.OS !== 'web',
Expand Down
63 changes: 63 additions & 0 deletions src/base/components/text/link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Icon from '@expo/vector-icons/MaterialIcons';
import type React from 'react';
import { useState } from 'react';
import { Platform, Pressable, StyleSheet } from 'react-native';
import { colours } from '../../constants/colours';
import { Row } from '../../layout/flex';
import type { TextProps } from './text';
import { Text } from './text';

export type LinkProps = Omit<TextProps, 'children' | 'colour'> & {
children: string;
onPress: () => void;
};

/**
* A styled Text element that should open a link when pressed.
* Intended to be nested within a Text element.
*/
export function Link({ onPress, children, ...props }: LinkProps): React.JSX.Element {
const [pressed, setPressed] = useState(false);

// I noticed that only on Android, Text following the View would not have adequate spacing.
// The end of the view and the start of the Text would be touching or overlapping.
// This padding was added to help that. The value was chosen manually after checking what looks good enough.
// I however have no idea _why_ this value is the right one to look good enough.
//
// It also expects that the Text next to this Link will have a space " " first,
// because my TextPart algorithm should do that.
const paddingEnd = children.length * 0.75;

return (
<Pressable
onPressIn={() => setPressed(true)}
onPressOut={() => setPressed(false)}
onPress={onPress}
style={Platform.OS === 'android' ? [styles.pressableAndroid, { paddingEnd }] : undefined}
>
<Row alignItems='center'>
<Text {...props} colour={pressed ? 'primaryLight' : 'primaryDark'}>
{children}
</Text>
<Icon
name='arrow-outward'
color={pressed ? colours.primaryLight : colours.primaryDark}
/>
</Row>
</Pressable>
);
}

const styles = StyleSheet.create({
pressableAndroid: {
// Nesting Views within Text on Android will add extra bottom margin/something to the View,
// which you can't get rid of. This makes the text/items within the View render higher than the surrounding text.
//
// This workaround moves the view down by a fixed amount that roughly aligns the View with the Text.
// It's not perfect and should be replaced when this issue is fixed:
// https://github.com/facebook/react-native/issues/31955
// This workaround was suggested in the same issue:
// https://github.com/facebook/react-native/issues/31955#issuecomment-1586109201
transform: [{ translateY: 2 }],
},
});
14 changes: 12 additions & 2 deletions src/base/components/text/text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import type { FontSize, FontWeight } from '../../constants/text';
import { fontSizes, fontWeights } from '../../constants/text';
import { UnreachableError } from '../../unreachableError';

type TextColour = 'normal' | 'light' | 'veryLight';
type TextColour =
| 'normal'
| 'light'
| 'veryLight'
| keyof Pick<typeof colours, 'primaryDark' | 'primaryLight' | 'secondaryDark'>;

export type TextProps = {
/** Default: medium */
Expand All @@ -27,7 +31,7 @@ export type TextProps = {
>;
} & Omit<RNTextProps, 'style' | 'children'>;

type StatusTextColour = 'success' | 'warning' | 'error';
type StatusTextColour = keyof Pick<typeof colours, 'success' | 'warning' | 'error'>;
type StatusTextProps = Omit<TextProps, 'colour'> & { colour: StatusTextColour };

type InternalTextProps = TextProps | StatusTextProps;
Expand Down Expand Up @@ -95,6 +99,12 @@ function getTextColour(colour: TextColour | StatusTextColour): TextStyle['color'
return colours.copyLight;
case 'veryLight':
return colours.copyLighter;
case 'primaryDark':
return colours.primaryDark;
case 'primaryLight':
return colours.primaryLight;
case 'secondaryDark':
return colours.secondaryDark;
case 'success':
return colours.successContent;
case 'warning':
Expand Down
3 changes: 2 additions & 1 deletion src/base/constants/colours.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// and the secondary colour's hue being at 210 degrees.
// 'foreground' was manually lightened to pure white because it needs to blend in
// with deal image backgrounds, which are white.
// 'copy' was darkened to black for contrast on the new 'foreground'.
// 'success', 'warning' and 'error' were manually softened/lightened a bit from the generated colours.
export const colours = {
primary: 'rgb(237, 188, 89)',
Expand All @@ -19,7 +20,7 @@ export const colours = {
foreground: 'rgb(255, 255, 255)',
border: 'rgb(226, 224, 221)',

copy: 'rgb(41, 39, 35)',
copy: 'rgb(0, 0, 0)',
copyLight: 'rgb(110, 105, 94)',
copyLighter: 'rgb(149, 143, 132)',

Expand Down
2 changes: 1 addition & 1 deletion src/base/layout/flex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { StyleSheet, View } from 'react-native';
import type { StyleProp, ViewStyle } from 'react-native';
import { type Size, sizes } from '../constants/sizes';

type FlexLayoutProps = {
export type FlexLayoutProps = {
children: ReactNode;
/** Default: space-between */
justifyContent?: ViewStyle['justifyContent'];
Expand Down
8 changes: 8 additions & 0 deletions src/base/links/openLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { openURL } from 'expo-linking';

/**
* Open a link, likely in another app.
*/
export async function openLink(url: string): Promise<void> {
await openURL(url).catch(() => console.log('User cancelled dialog'));
}
3 changes: 2 additions & 1 deletion src/feed-parser/__tests__/fixtures/regenValidFixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { join } from 'node:path';
import { convertToOzbargainFeed } from '../../parser';
import { parseRssFeedFromString, validFixtures } from './getFixtures';

const VALID_FIXTURES_DIR = join(import.meta.dirname, 'valid');
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const VALID_FIXTURES_DIR = join(import.meta.dirname ?? __dirname, 'valid');

// Will overwrite any existing file.
async function writeFileToDisk(
Expand Down
Loading
Loading