feat: update habit progress boolean

This commit is contained in:
Théo LUDWIG 2024-04-11 23:03:45 +02:00
parent d75a8ab2cd
commit 867667f4c7
Signed by: theoludwig
GPG Key ID: ADFE5A563D718F3B
13 changed files with 5729 additions and 248 deletions

View File

@ -1,6 +1,7 @@
import type { GoalFrequency } from "./Goal" import type { GoalFrequency } from "./Goal"
import type { Habit } from "./Habit" import type { Habit } from "./Habit"
import { HabitHistory } from "./HabitHistory" import { HabitHistory } from "./HabitHistory"
import type { HabitProgress } from "./HabitProgress"
export interface HabitsTrackerData { export interface HabitsTrackerData {
habitsHistory: { habitsHistory: {
@ -35,24 +36,6 @@ export class HabitsTracker implements HabitsTrackerData {
) )
} }
// public setHabitProgress(options: SetHabitProgressOptions): void {
// const { date, goalProgress, habitHistory } = options
// if (goalProgress.isBoolean()) {
// const currentHabitProgress = habitHistory.getProgressesByDate(date)[0]
// if (currentHabitProgress == null) {
// habitHistory.progressHistory = [
// ...habitHistory.progressHistory,
// new HabitProgress({
// date,
// goalProgress,
// habitId,
// id,
// }),
// ]
// }
// }
// }
public editHabit(habit: Habit): void { public editHabit(habit: Habit): void {
const habitHistory = this.getHabitHistoryById(habit.id) const habitHistory = this.getHabitHistoryById(habit.id)
if (habitHistory == null) { if (habitHistory == null) {
@ -61,6 +44,25 @@ export class HabitsTracker implements HabitsTrackerData {
habitHistory.habit = habit habitHistory.habit = habit
} }
public updateHabitProgress(habitProgress: HabitProgress): void {
const habitHistory = this.getHabitHistoryById(habitProgress.habitId)
if (habitHistory == null) {
return
}
const habitProgressSaved = habitHistory.progressHistory.find((progress) => {
return progress.id === habitProgress.id
})
if (habitProgressSaved == null) {
habitHistory.progressHistory = [
...habitHistory.progressHistory,
habitProgress,
]
return
}
habitProgressSaved.goalProgress = habitProgress.goalProgress
habitProgressSaved.date = habitProgress.date
}
public getAllHabitsHistory(): HabitHistory[] { public getAllHabitsHistory(): HabitHistory[] {
return [ return [
...this.habitsHistory.daily, ...this.habitsHistory.daily,

View File

@ -4,7 +4,7 @@ import type {
} from "../entities/HabitProgress" } from "../entities/HabitProgress"
export interface HabitProgressUpdateOptions { export interface HabitProgressUpdateOptions {
habitProgressData: HabitProgressData habitProgressData: Omit<HabitProgressData, "habitId">
} }
export interface HabitProgressUpdateRepository { export interface HabitProgressUpdateRepository {

View File

@ -1,28 +1,56 @@
import type { GoalProgress } from "../entities/Goal" import type { GoalProgress } from "../entities/Goal"
import type { HabitHistory } from "../entities/HabitHistory" import type { HabitHistory } from "../entities/HabitHistory"
import type { HabitProgress } from "../entities/HabitProgress"
import type { HabitProgressCreateRepository } from "../repositories/HabitProgressCreate" import type { HabitProgressCreateRepository } from "../repositories/HabitProgressCreate"
import type { HabitProgressUpdateRepository } from "../repositories/HabitProgressUpdate" import type { HabitProgressUpdateRepository } from "../repositories/HabitProgressUpdate"
export interface HabitGoalProgressUpdateOptions { export interface HabitGoalProgressUpdateUseCaseOptions {
date: Date date: Date
goalProgress: GoalProgress goalProgress: GoalProgress
habitHistory: HabitHistory habitHistory: HabitHistory
} }
export class HabitGoalProgressUpdateUseCase export interface HabitGoalProgressUpdateUseCaseDependencyOptions {
implements HabitGoalProgressUpdateOptions habitProgressCreateRepository: HabitProgressCreateRepository
{ habitProgressUpdateRepository: HabitProgressUpdateRepository
public date: Date }
public goalProgress: GoalProgress
public habitHistory: HabitHistory
public constructor(option: HabitGoalProgressUpdateOptions) { export class HabitGoalProgressUpdateUseCase
this.date = option.date implements HabitGoalProgressUpdateUseCaseDependencyOptions
this.goalProgress = option.goalProgress {
this.habitHistory = option.habitHistory public habitProgressCreateRepository: HabitProgressCreateRepository
public habitProgressUpdateRepository: HabitProgressUpdateRepository
public constructor(options: HabitGoalProgressUpdateUseCaseDependencyOptions) {
this.habitProgressCreateRepository = options.habitProgressCreateRepository
this.habitProgressUpdateRepository = options.habitProgressUpdateRepository
} }
public async execute(data: unknown): Promise<HabitHistory> { public async execute(
// options: HabitGoalProgressUpdateUseCaseOptions,
): Promise<HabitProgress> {
const { date, goalProgress, habitHistory } = options
if (goalProgress.isBoolean()) {
const currentHabitProgress = habitHistory.getProgressesByDate(date)[0]
if (currentHabitProgress == null) {
return await this.habitProgressCreateRepository.execute({
habitProgressData: {
date,
goalProgress,
habitId: habitHistory.habit.id,
},
})
}
return await this.habitProgressUpdateRepository.execute({
habitProgressData: {
date,
goalProgress,
id: currentHabitProgress.id,
},
})
}
throw new Error("Not implemented")
} }
} }

View File

@ -10,6 +10,9 @@ import { HabitCreateSupabaseRepository } from "./supabase/repositories/HabitCrea
import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate" import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
import { HabitEditSupabaseRepository } from "./supabase/repositories/HabitEdit" import { HabitEditSupabaseRepository } from "./supabase/repositories/HabitEdit"
import { HabitEditUseCase } from "@/domain/use-cases/HabitEdit" import { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
import { HabitProgressCreateSupabaseRepository } from "./supabase/repositories/HabitProgressCreate"
import { HabitProgressUpdateSupabaseRepository } from "./supabase/repositories/HabitProgressUpdate"
import { HabitGoalProgressUpdateUseCase } from "@/domain/use-cases/HabitGoalProgressUpdate"
/** /**
* Repositories * Repositories
@ -30,6 +33,16 @@ const habitCreateRepository = new HabitCreateSupabaseRepository({
const habitEditRepository = new HabitEditSupabaseRepository({ const habitEditRepository = new HabitEditSupabaseRepository({
supabaseClient, supabaseClient,
}) })
const habitProgressCreateRepository = new HabitProgressCreateSupabaseRepository(
{
supabaseClient,
},
)
const habitProgressUpdateRepository = new HabitProgressUpdateSupabaseRepository(
{
supabaseClient,
},
)
/** /**
* Use Cases * Use Cases
@ -47,6 +60,10 @@ const retrieveHabitsTrackerUseCase = new RetrieveHabitsTrackerUseCase({
const habitEditUseCase = new HabitEditUseCase({ const habitEditUseCase = new HabitEditUseCase({
habitEditRepository, habitEditRepository,
}) })
const habitGoalProgressUpdateUseCase = new HabitGoalProgressUpdateUseCase({
habitProgressCreateRepository,
habitProgressUpdateRepository,
})
/** /**
* Presenters * Presenters
@ -58,4 +75,5 @@ export const habitsTrackerPresenter = new HabitsTrackerPresenter({
retrieveHabitsTrackerUseCase, retrieveHabitsTrackerUseCase,
habitCreateUseCase, habitCreateUseCase,
habitEditUseCase, habitEditUseCase,
habitGoalProgressUpdateUseCase,
}) })

View File

@ -0,0 +1,38 @@
import type { HabitProgressCreateRepository } from "@/domain/repositories/HabitProgressCreate"
import { SupabaseRepository } from "./_SupabaseRepository"
import { HabitProgress } from "@/domain/entities/HabitProgress"
export class HabitProgressCreateSupabaseRepository
extends SupabaseRepository
implements HabitProgressCreateRepository
{
public execute: HabitProgressCreateRepository["execute"] = async (
options,
) => {
const { habitProgressData } = options
const { goalProgress, date, habitId } = habitProgressData
let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
if (goalProgress.isNumeric()) {
goalProgressValue = goalProgress.progress
}
const { data, error } = await this.supabaseClient
.from("habits_progresses")
.insert({
habit_id: Number(habitId),
date: date.toISOString(),
goal_progress: goalProgressValue,
})
.select("*")
const insertedProgress = data?.[0]
if (error != null || insertedProgress == null) {
throw new Error(error?.message ?? "Failed to create habit progress.")
}
const habitProgress = new HabitProgress({
id: insertedProgress.id.toString(),
habitId: insertedProgress.habit_id.toString(),
date: new Date(insertedProgress.date),
goalProgress,
})
return habitProgress
}
}

View File

@ -0,0 +1,38 @@
import type { HabitProgressUpdateRepository } from "@/domain/repositories/HabitProgressUpdate"
import { SupabaseRepository } from "./_SupabaseRepository"
import { HabitProgress } from "@/domain/entities/HabitProgress"
export class HabitProgressUpdateSupabaseRepository
extends SupabaseRepository
implements HabitProgressUpdateRepository
{
public execute: HabitProgressUpdateRepository["execute"] = async (
options,
) => {
const { habitProgressData } = options
const { id, goalProgress, date } = habitProgressData
let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
if (goalProgress.isNumeric()) {
goalProgressValue = goalProgress.progress
}
const { data, error } = await this.supabaseClient
.from("habits_progresses")
.update({
date: date.toISOString(),
goal_progress: goalProgressValue,
})
.eq("id", id)
.select("*")
const insertedProgress = data?.[0]
if (error != null || insertedProgress == null) {
throw new Error(error?.message ?? "Failed to update habit progress.")
}
const habitProgress = new HabitProgress({
id: insertedProgress.id.toString(),
habitId: insertedProgress.habit_id.toString(),
date: new Date(insertedProgress.date),
goalProgress,
})
return habitProgress
}
}

24
package-lock.json generated
View File

@ -23,6 +23,7 @@
"expo-system-ui": "2.9.3", "expo-system-ui": "2.9.3",
"expo-web-browser": "12.8.2", "expo-web-browser": "12.8.2",
"immer": "10.0.4", "immer": "10.0.4",
"lottie-react-native": "6.7.2",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "7.51.2", "react-hook-form": "7.51.2",
@ -17868,6 +17869,29 @@
"loose-envify": "cli.js" "loose-envify": "cli.js"
} }
}, },
"node_modules/lottie-react-native": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-6.7.2.tgz",
"integrity": "sha512-MZVx6N1EeO/EaSx8T44mJ0aHc5Mqee+xIfWwszni0oz8U2wlHdaWGjES44dHxaxgAp/0dRaFt3PkpZ6egTzcBg==",
"peerDependencies": {
"@dotlottie/react-player": "^1.6.1",
"@lottiefiles/react-lottie-player": "^3.5.3",
"react": "*",
"react-native": ">=0.46",
"react-native-windows": ">=0.63.x"
},
"peerDependenciesMeta": {
"@dotlottie/react-player": {
"optional": true
},
"@lottiefiles/react-lottie-player": {
"optional": true
},
"react-native-windows": {
"optional": true
}
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",

View File

@ -33,6 +33,7 @@
"expo-system-ui": "2.9.3", "expo-system-ui": "2.9.3",
"expo-web-browser": "12.8.2", "expo-web-browser": "12.8.2",
"immer": "10.0.4", "immer": "10.0.4",
"lottie-react-native": "6.7.2",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "7.51.2", "react-hook-form": "7.51.2",

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,10 @@ import type { HabitCreateData, HabitEditData } from "@/domain/entities/Habit"
import { getErrorsFieldsFromZodError } from "../../utils/zod" import { getErrorsFieldsFromZodError } from "../../utils/zod"
import type { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate" import type { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
import type { HabitEditUseCase } from "@/domain/use-cases/HabitEdit" import type { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
import type {
HabitGoalProgressUpdateUseCase,
HabitGoalProgressUpdateUseCaseOptions,
} from "@/domain/use-cases/HabitGoalProgressUpdate"
export interface HabitsTrackerPresenterState { export interface HabitsTrackerPresenterState {
habitsTracker: HabitsTracker habitsTracker: HabitsTracker
@ -34,12 +38,17 @@ export interface HabitsTrackerPresenterState {
global: ErrorGlobal global: ErrorGlobal
} }
} }
habitGoalProgressUpdate: {
state: FetchState
}
} }
export interface HabitsTrackerPresenterOptions { export interface HabitsTrackerPresenterOptions {
retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase
habitCreateUseCase: HabitCreateUseCase habitCreateUseCase: HabitCreateUseCase
habitEditUseCase: HabitEditUseCase habitEditUseCase: HabitEditUseCase
habitGoalProgressUpdateUseCase: HabitGoalProgressUpdateUseCase
} }
export class HabitsTrackerPresenter export class HabitsTrackerPresenter
@ -49,12 +58,14 @@ export class HabitsTrackerPresenter
public retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase public retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase
public habitCreateUseCase: HabitCreateUseCase public habitCreateUseCase: HabitCreateUseCase
public habitEditUseCase: HabitEditUseCase public habitEditUseCase: HabitEditUseCase
public habitGoalProgressUpdateUseCase: HabitGoalProgressUpdateUseCase
public constructor(options: HabitsTrackerPresenterOptions) { public constructor(options: HabitsTrackerPresenterOptions) {
const { const {
retrieveHabitsTrackerUseCase, retrieveHabitsTrackerUseCase,
habitCreateUseCase, habitCreateUseCase,
habitEditUseCase, habitEditUseCase,
habitGoalProgressUpdateUseCase,
} = options } = options
const habitsTracker = HabitsTracker.default() const habitsTracker = HabitsTracker.default()
super({ super({
@ -74,10 +85,14 @@ export class HabitsTrackerPresenter
global: null, global: null,
}, },
}, },
habitGoalProgressUpdate: {
state: "idle",
},
}) })
this.retrieveHabitsTrackerUseCase = retrieveHabitsTrackerUseCase this.retrieveHabitsTrackerUseCase = retrieveHabitsTrackerUseCase
this.habitCreateUseCase = habitCreateUseCase this.habitCreateUseCase = habitCreateUseCase
this.habitEditUseCase = habitEditUseCase this.habitEditUseCase = habitEditUseCase
this.habitGoalProgressUpdateUseCase = habitGoalProgressUpdateUseCase
} }
public async habitCreate(data: unknown): Promise<FetchState> { public async habitCreate(data: unknown): Promise<FetchState> {
@ -158,4 +173,26 @@ export class HabitsTrackerPresenter
}) })
} }
} }
public async habitUpdateProgress(
options: HabitGoalProgressUpdateUseCaseOptions,
): Promise<FetchState> {
try {
this.setState((state) => {
state.habitGoalProgressUpdate.state = "loading"
})
const habitProgress =
await this.habitGoalProgressUpdateUseCase.execute(options)
this.setState((state) => {
state.habitsTracker.updateHabitProgress(habitProgress)
state.habitGoalProgressUpdate.state = "success"
})
return "success"
} catch (error) {
this.setState((state) => {
state.habitGoalProgressUpdate.state = "error"
})
return "error"
}
}
} }

View File

@ -1,143 +0,0 @@
import { useState } from "react"
import { Calendar } from "react-native-calendars"
import { Text } from "react-native-paper"
import { SafeAreaView } from "react-native-safe-area-context"
export const HabitHistory: React.FC = () => {
const [selected, setSelected] = useState("")
return (
<SafeAreaView
style={[
{
flex: 1,
alignItems: "center",
justifyContent: "center",
},
]}
>
<Calendar
onDayPress={(day) => {
setSelected(day.dateString)
}}
markedDates={{
"2023-03-01": { selected: true, marked: true, selectedColor: "blue" },
"2023-03-02": { marked: true },
"2023-03-03": { selected: true, marked: true, selectedColor: "blue" },
[selected]: {
selected: true,
disableTouchEvent: true,
selectedColor: "orange",
},
}}
theme={{
backgroundColor: "#000000",
calendarBackground: "#000000",
textSectionTitleColor: "#b6c1cd",
selectedDayBackgroundColor: "#00adf5",
selectedDayTextColor: "#ffffff",
todayTextColor: "#00adf5",
dayTextColor: "#2d4150",
textDisabledColor: "#d9efff",
}}
/>
<Text>{selected}</Text>
</SafeAreaView>
)
}
/*
<Agenda
// The list of items that have to be displayed in agenda. If you want to render item as empty date
// the value of date key has to be an empty array []. If there exists no value for date key it is
// considered that the date in question is not yet loaded
items={{
'2012-05-22': [{name: 'item 1 - any js object'}],
'2012-05-23': [{name: 'item 2 - any js object', height: 80}],
'2012-05-24': [],
'2012-05-25': [{name: 'item 3 - any js object'}, {name: 'any js object'}]
}}
// Callback that gets called when items for a certain month should be loaded (month became visible)
loadItemsForMonth={month => {
console.log('trigger items loading');
}}
// Callback that fires when the calendar is opened or closed
onCalendarToggled={calendarOpened => {
console.log(calendarOpened);
}}
// Callback that gets called on day press
onDayPress={day => {
console.log('day pressed');
}}
// Callback that gets called when day changes while scrolling agenda list
onDayChange={day => {
console.log('day changed');
}}
// Initially selected day
selected={'2012-05-16'}
// Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
minDate={'2012-05-10'}
// Maximum date that can be selected, dates after maxDate will be grayed out. Default = undefined
maxDate={'2012-05-30'}
// Max amount of months allowed to scroll to the past. Default = 50
pastScrollRange={50}
// Max amount of months allowed to scroll to the future. Default = 50
futureScrollRange={50}
// Specify how each item should be rendered in agenda
renderItem={(item, firstItemInDay) => {
return <View />;
}}
// Specify how each date should be rendered. day can be undefined if the item is not first in that day
renderDay={(day, item) => {
return <View />;
}}
// Specify how empty date content with no items should be rendered
renderEmptyDate={() => {
return <View />;
}}
// Specify how agenda knob should look like
renderKnob={() => {
return <View />;
}}
// Override inner list with a custom implemented component
renderList={listProps => {
return <MyCustomList {...listProps} />;
}}
// Specify what should be rendered instead of ActivityIndicator
renderEmptyData={() => {
return <View />;
}}
// Specify your item comparison function for increased performance
rowHasChanged={(r1, r2) => {
return r1.text !== r2.text;
}}
// Hide knob button. Default = false
hideKnob={true}
// When `true` and `hideKnob` prop is `false`, the knob will always be visible and the user will be able to drag the knob up and close the calendar. Default = false
showClosingKnob={false}
// By default, agenda dates are marked if they have at least one item, but you can override this if needed
markedDates={{
'2012-05-16': {selected: true, marked: true},
'2012-05-17': {marked: true},
'2012-05-18': {disabled: true}
}}
// If disabledByDefault={true} dates flagged as not disabled will be enabled. Default = false
disabledByDefault={true}
// If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly
onRefresh={() => console.log('refreshing...')}
// Set this true while waiting for new data from a refresh
refreshing={false}
// Add a custom RefreshControl component, used to provide pull-to-refresh functionality for the ScrollView
refreshControl={null}
// Agenda theme
theme={{
...calendarTheme,
agendaDayTextColor: 'yellow',
agendaDayNumColor: 'green',
agendaTodayColor: 'red',
agendaKnobColor: 'blue'
}}
// Agenda container style
style={{}}
/>
*/

View File

@ -1,30 +1,37 @@
import FontAwesome6 from "@expo/vector-icons/FontAwesome6" import FontAwesome6 from "@expo/vector-icons/FontAwesome6"
import { useRouter } from "expo-router" import { useRouter } from "expo-router"
import { View } from "react-native"
import { List, Text, Checkbox } from "react-native-paper"
import { useState } from "react" import { useState } from "react"
import { View } from "react-native"
import { Checkbox, List, Text } from "react-native-paper"
import type LottieView from "lottie-react-native"
import type { GoalProgress } from "@/domain/entities/Goal" import type { GoalBoolean } from "@/domain/entities/Goal"
import type { Habit } from "@/domain/entities/Habit" import { GoalBooleanProgress } from "@/domain/entities/Goal"
import type { HabitHistory } from "@/domain/entities/HabitHistory"
import { getColorRGBAFromHex } from "@/utils/colors" import { getColorRGBAFromHex } from "@/utils/colors"
import { useHabitsTracker } from "../../contexts/HabitsTracker"
export interface HabitCardProps { export interface HabitCardProps {
habit: Habit habitHistory: HabitHistory
goalProgress: GoalProgress selectedDate: Date
confettiRef: React.MutableRefObject<LottieView | null>
} }
export const HabitCard: React.FC<HabitCardProps> = (props) => { export const HabitCard: React.FC<HabitCardProps> = (props) => {
const { habit, goalProgress } = props const { habitHistory, selectedDate, confettiRef } = props
const { habit } = habitHistory
const router = useRouter() const router = useRouter()
const { habitsTrackerPresenter } = useHabitsTracker()
const goalProgress = habitHistory.getGoalProgressByDate(selectedDate)
const [checked, setChecked] = useState(goalProgress.isCompleted())
const habitColor = getColorRGBAFromHex({ const habitColor = getColorRGBAFromHex({
hexColor: habit.color, hexColor: habit.color,
opacity: 0.4, opacity: 0.4,
}) })
const [checked, setChecked] = useState(goalProgress.isCompleted())
return ( return (
<List.Item <List.Item
onPress={() => { onPress={() => {
@ -57,15 +64,17 @@ export const HabitCard: React.FC<HabitCardProps> = (props) => {
]} ]}
left={() => { left={() => {
return ( return (
<FontAwesome6 <View style={{ justifyContent: "center", alignItems: "center" }}>
size={24} <FontAwesome6
name={habit.icon} size={24}
style={[ name={habit.icon}
{ style={[
width: 30, {
}, width: 30,
]} },
/> ]}
/>
</View>
) )
}} }}
right={() => { right={() => {
@ -82,14 +91,25 @@ export const HabitCard: React.FC<HabitCardProps> = (props) => {
} }
return ( return (
<View> <Checkbox
<Checkbox color="black"
status={checked ? "checked" : "unchecked"} status={checked ? "checked" : "unchecked"}
onPress={() => { onPress={async () => {
setChecked(!checked) const isCheckedNew = !checked
}} setChecked(isCheckedNew)
/> if (isCheckedNew) {
</View> confettiRef.current?.play()
}
await habitsTrackerPresenter.habitUpdateProgress({
date: selectedDate,
habitHistory,
goalProgress: new GoalBooleanProgress({
goal: habit.goal as GoalBoolean,
progress: isCheckedNew,
}),
})
}}
/>
) )
}} }}
/> />

View File

@ -1,9 +1,11 @@
import { useState } from "react" import LottieView from "lottie-react-native"
import { Dimensions, ScrollView } from "react-native" import { useRef, useState } from "react"
import { Dimensions, ScrollView, View } from "react-native"
import { Divider, List } from "react-native-paper" import { Divider, List } from "react-native-paper"
import type { GoalFrequency } from "@/domain/entities/Goal" import type { GoalFrequency } from "@/domain/entities/Goal"
import type { HabitsTracker } from "@/domain/entities/HabitsTracker" import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
import confettiJSON from "../../../assets/confetti.json"
import { capitalize } from "@/utils/strings" import { capitalize } from "@/utils/strings"
import { HabitCard } from "./HabitCard" import { HabitCard } from "./HabitCard"
@ -24,51 +26,85 @@ export const HabitsList: React.FC<HabitsListProps> = (props) => {
monthly: true, monthly: true,
}) })
const confettiRef = useRef<LottieView | null>(null)
return ( return (
<ScrollView <>
showsVerticalScrollIndicator={false} <View
style={{ pointerEvents="none"
paddingHorizontal: 20, style={{
width: Dimensions.get("window").width, width: "100%",
backgroundColor: "white", height: "100%",
}} position: "absolute",
> zIndex: 100,
<Divider /> justifyContent: "center",
<List.Section> alignItems: "center",
{frequenciesFiltered.map((frequency) => { }}
return ( >
<List.Accordion <LottieView
expanded={accordionExpanded[frequency]} ref={confettiRef}
onPress={() => { source={confettiJSON}
setAccordionExpanded((old) => { autoPlay={false}
return { loop={false}
...old, style={[
[frequency]: !old[frequency], {
} position: "absolute",
}) top: 0,
}} left: 0,
key={frequency} right: 0,
title={capitalize(frequency)} bottom: 0,
titleStyle={[ },
{ ]}
fontSize: 26, resizeMode="cover"
}, />
]} </View>
>
{habitsTracker.habitsHistory[frequency].map((item) => { <ScrollView
const goalProgress = item.getGoalProgressByDate(selectedDate) showsVerticalScrollIndicator={false}
return ( style={{
<HabitCard paddingHorizontal: 20,
habit={item.habit} width: Dimensions.get("window").width,
goalProgress={goalProgress} backgroundColor: "white",
key={item.habit.id} }}
/> >
) <Divider />
})}
</List.Accordion> <List.Section>
) {frequenciesFiltered.map((frequency) => {
})} return (
</List.Section> <List.Accordion
</ScrollView> expanded={accordionExpanded[frequency]}
onPress={() => {
setAccordionExpanded((old) => {
return {
...old,
[frequency]: !old[frequency],
}
})
}}
key={frequency}
title={capitalize(frequency)}
titleStyle={[
{
fontSize: 26,
},
]}
>
{habitsTracker.habitsHistory[frequency].map((item) => {
return (
<HabitCard
habitHistory={item}
selectedDate={selectedDate}
key={item.habit.id + selectedDate.toISOString()}
confettiRef={confettiRef}
/>
)
})}
</List.Accordion>
)
})}
</List.Section>
</ScrollView>
</>
) )
} }