Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
7 changes: 3 additions & 4 deletions app/components/CourseCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ContentType, RouteName } from "app/constants"

import { AutoImage } from "./AutoImage"
import { Button } from "./Button"
import { Progress } from "./Progress"
import CircularProgress from "./Progress"

export interface CourseCardProps {
/** an optional style override useful for padding & margin. */
Expand Down Expand Up @@ -41,10 +41,9 @@ export const CourseCard = observer(function CourseCard(props: CourseCardProps) {
style={$image}
/>
<View style={$subContainer}>
<View>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove inline styling and use object-based styling as the other styles

<Text style={$title} text="Cohort 3.0 | Web Dev" />
{/* TODO: Change this to a circular progress */}
<Progress progress={50} />
<CircularProgress progress={50} size={50} />
</View>
<Button onPress={handleViewContentPress} text="View Content" />
<TouchableOpacity activeOpacity={0.8} onPress={handleJoinDiscordPress} style={$footer}>
Expand Down
174 changes: 132 additions & 42 deletions app/components/Progress.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,140 @@
import * as React from "react"
import { StyleProp, TextStyle, View, ViewStyle } from "react-native"
import { observer } from "mobx-react-lite"
import { spacing } from "app/theme"
import { Text } from "app/components/Text"

export interface ProgressProps {
/** a required prop to specify the progress. */
progress: number
/** an optional style override useful for padding & margin. */
style?: StyleProp<ViewStyle>
// Packages Imports
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make a different component for circular progress and keep this component intact, alternatively, you can make a preset in this component itself. Whatever suits best to you.

import { useState } from "react";
import { StyleProp, StyleSheet, TextStyle, View, ViewStyle } from "react-native";
import Animated, {
Extrapolate,
interpolate,
runOnJS,
useAnimatedProps,
useDerivedValue,
withTiming,
} from "react-native-reanimated";
import { Svg, Circle } from "react-native-svg";

// Create an Animated Component for the Circle
const AnimatedCircle = Animated.createAnimatedComponent(Circle);

// interface for the component
export interface CircularProgressProps {
progress: number;
size?: number;
strokeWidth?: number;
showLabel?: boolean;
outerCircleColor?: string;
progressCircleColor?: string;
labelColor?: string;
labelStyle?: StyleProp<TextStyle>;
labelSize?: number;
}

export const Progress = observer(function Progress(props: ProgressProps) {
const { progress, style } = props
const $styles = [$container, style]
// function component for CircularProgress
function CircularProgress(props: CircularProgressProps) {
// Destructuring props
const {
size = 80,
strokeWidth = (5 * size) / 100,
progress = 0,
showLabel = true,
labelSize = (20 * size) / 100,
...otherProps
} = props;

// get other props
const {
labelColor = "white",
labelStyle,
outerCircleColor = "white",
progressCircleColor = "dodgerblue",
} = otherProps;

// Constants
const radius = (size - strokeWidth) / 2;
const circum = radius * 2 * Math.PI;

// Local States
const [LabelText, SetLabelText] = useState(0);

// Derive the progress value from props
const derivedProgressValue = useDerivedValue(() => {
if (showLabel) runOnJS(SetLabelText)(Math.min(progress, 100));

return withTiming(progress);
}, [progress]);

// AnimatedProps for the circle
const circleAnimatedProps = useAnimatedProps(() => {
const SVG_Progress = interpolate(
derivedProgressValue.value,
[0, 100],
[100, 0],
Extrapolate.CLAMP
);

// This dash offset is the inner circle progress
return {
strokeDashoffset: radius * Math.PI * 2 * (SVG_Progress / 100),
};
});

// Style for the Label View
const labelViewContainerStyle: StyleProp<ViewStyle> = [
styles.labelView,
{
width: size,
height: size,
},
];

// const style for the label text
const labelTextStyles: StyleProp<TextStyle> = [
{ color: labelColor, fontSize: labelSize },
labelStyle,
];

// render
return (
<View style={$styles}>
<View style={$bar}>
<View style={[$fill, { width: `${progress}%` }]} />
</View>
<Text style={$text}>{progress}%</Text>
</View>
)
})

const $container: ViewStyle = {
flexDirection: "row",
alignItems: "center",
}
<Svg width={size} height={size}>
<Circle
stroke={outerCircleColor}
fill="none"
cx={size / 2}
cy={size / 2}
r={radius}
strokeWidth={strokeWidth}
/>

const $bar: ViewStyle = {
backgroundColor: "#0e1829",
borderRadius: spacing.xxs,
flex: 1,
height: spacing.xs,
}
<AnimatedCircle
stroke={progressCircleColor}
fill="none"
cx={size / 2}
cy={size / 2}
r={radius}
strokeDasharray={`${circum} ${circum}`}
strokeLinecap="round"
transform={`rotate(-90, ${size / 2}, ${size / 2})`}
strokeWidth={strokeWidth}
animatedProps={circleAnimatedProps}
/>

const $fill: ViewStyle = {
backgroundColor: "#1d3255",
borderRadius: spacing.xxxs,
height: "100%",
{showLabel ? (
<View style={labelViewContainerStyle}>
<Animated.Text style={labelTextStyles}>{`${LabelText}%`}</Animated.Text>
</View>
) : null}
</Svg>
);
}

const $text: TextStyle = {
color: "#9CA3AF",
fontSize: spacing.md,
marginLeft: spacing.sm,
}
// exports
export default CircularProgress;

// styles
const styles = StyleSheet.create({
labelView: {
position: "absolute",
top: 0,
left: 0,
justifyContent: "center",
alignItems: "center",
},
});