feat: update habit progress boolean
This commit is contained in:
parent
d75a8ab2cd
commit
867667f4c7
@ -1,6 +1,7 @@
|
||||
import type { GoalFrequency } from "./Goal"
|
||||
import type { Habit } from "./Habit"
|
||||
import { HabitHistory } from "./HabitHistory"
|
||||
import type { HabitProgress } from "./HabitProgress"
|
||||
|
||||
export interface HabitsTrackerData {
|
||||
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 {
|
||||
const habitHistory = this.getHabitHistoryById(habit.id)
|
||||
if (habitHistory == null) {
|
||||
@ -61,6 +44,25 @@ export class HabitsTracker implements HabitsTrackerData {
|
||||
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[] {
|
||||
return [
|
||||
...this.habitsHistory.daily,
|
||||
|
@ -4,7 +4,7 @@ import type {
|
||||
} from "../entities/HabitProgress"
|
||||
|
||||
export interface HabitProgressUpdateOptions {
|
||||
habitProgressData: HabitProgressData
|
||||
habitProgressData: Omit<HabitProgressData, "habitId">
|
||||
}
|
||||
|
||||
export interface HabitProgressUpdateRepository {
|
||||
|
@ -1,28 +1,56 @@
|
||||
import type { GoalProgress } from "../entities/Goal"
|
||||
import type { HabitHistory } from "../entities/HabitHistory"
|
||||
import type { HabitProgress } from "../entities/HabitProgress"
|
||||
import type { HabitProgressCreateRepository } from "../repositories/HabitProgressCreate"
|
||||
import type { HabitProgressUpdateRepository } from "../repositories/HabitProgressUpdate"
|
||||
|
||||
export interface HabitGoalProgressUpdateOptions {
|
||||
export interface HabitGoalProgressUpdateUseCaseOptions {
|
||||
date: Date
|
||||
goalProgress: GoalProgress
|
||||
habitHistory: HabitHistory
|
||||
}
|
||||
|
||||
export class HabitGoalProgressUpdateUseCase
|
||||
implements HabitGoalProgressUpdateOptions
|
||||
{
|
||||
public date: Date
|
||||
public goalProgress: GoalProgress
|
||||
public habitHistory: HabitHistory
|
||||
export interface HabitGoalProgressUpdateUseCaseDependencyOptions {
|
||||
habitProgressCreateRepository: HabitProgressCreateRepository
|
||||
habitProgressUpdateRepository: HabitProgressUpdateRepository
|
||||
}
|
||||
|
||||
public constructor(option: HabitGoalProgressUpdateOptions) {
|
||||
this.date = option.date
|
||||
this.goalProgress = option.goalProgress
|
||||
this.habitHistory = option.habitHistory
|
||||
export class HabitGoalProgressUpdateUseCase
|
||||
implements HabitGoalProgressUpdateUseCaseDependencyOptions
|
||||
{
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,9 @@ import { HabitCreateSupabaseRepository } from "./supabase/repositories/HabitCrea
|
||||
import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
|
||||
import { HabitEditSupabaseRepository } from "./supabase/repositories/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
|
||||
@ -30,6 +33,16 @@ const habitCreateRepository = new HabitCreateSupabaseRepository({
|
||||
const habitEditRepository = new HabitEditSupabaseRepository({
|
||||
supabaseClient,
|
||||
})
|
||||
const habitProgressCreateRepository = new HabitProgressCreateSupabaseRepository(
|
||||
{
|
||||
supabaseClient,
|
||||
},
|
||||
)
|
||||
const habitProgressUpdateRepository = new HabitProgressUpdateSupabaseRepository(
|
||||
{
|
||||
supabaseClient,
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Use Cases
|
||||
@ -47,6 +60,10 @@ const retrieveHabitsTrackerUseCase = new RetrieveHabitsTrackerUseCase({
|
||||
const habitEditUseCase = new HabitEditUseCase({
|
||||
habitEditRepository,
|
||||
})
|
||||
const habitGoalProgressUpdateUseCase = new HabitGoalProgressUpdateUseCase({
|
||||
habitProgressCreateRepository,
|
||||
habitProgressUpdateRepository,
|
||||
})
|
||||
|
||||
/**
|
||||
* Presenters
|
||||
@ -58,4 +75,5 @@ export const habitsTrackerPresenter = new HabitsTrackerPresenter({
|
||||
retrieveHabitsTrackerUseCase,
|
||||
habitCreateUseCase,
|
||||
habitEditUseCase,
|
||||
habitGoalProgressUpdateUseCase,
|
||||
})
|
||||
|
38
infrastructure/supabase/repositories/HabitProgressCreate.ts
Normal file
38
infrastructure/supabase/repositories/HabitProgressCreate.ts
Normal 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
|
||||
}
|
||||
}
|
38
infrastructure/supabase/repositories/HabitProgressUpdate.ts
Normal file
38
infrastructure/supabase/repositories/HabitProgressUpdate.ts
Normal 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
24
package-lock.json
generated
@ -23,6 +23,7 @@
|
||||
"expo-system-ui": "2.9.3",
|
||||
"expo-web-browser": "12.8.2",
|
||||
"immer": "10.0.4",
|
||||
"lottie-react-native": "6.7.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "7.51.2",
|
||||
@ -17868,6 +17869,29 @@
|
||||
"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": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
|
@ -33,6 +33,7 @@
|
||||
"expo-system-ui": "2.9.3",
|
||||
"expo-web-browser": "12.8.2",
|
||||
"immer": "10.0.4",
|
||||
"lottie-react-native": "6.7.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "7.51.2",
|
||||
|
5382
presentation/assets/confetti.json
Normal file
5382
presentation/assets/confetti.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,10 @@ import type { HabitCreateData, HabitEditData } from "@/domain/entities/Habit"
|
||||
import { getErrorsFieldsFromZodError } from "../../utils/zod"
|
||||
import type { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
|
||||
import type { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
|
||||
import type {
|
||||
HabitGoalProgressUpdateUseCase,
|
||||
HabitGoalProgressUpdateUseCaseOptions,
|
||||
} from "@/domain/use-cases/HabitGoalProgressUpdate"
|
||||
|
||||
export interface HabitsTrackerPresenterState {
|
||||
habitsTracker: HabitsTracker
|
||||
@ -34,12 +38,17 @@ export interface HabitsTrackerPresenterState {
|
||||
global: ErrorGlobal
|
||||
}
|
||||
}
|
||||
|
||||
habitGoalProgressUpdate: {
|
||||
state: FetchState
|
||||
}
|
||||
}
|
||||
|
||||
export interface HabitsTrackerPresenterOptions {
|
||||
retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase
|
||||
habitCreateUseCase: HabitCreateUseCase
|
||||
habitEditUseCase: HabitEditUseCase
|
||||
habitGoalProgressUpdateUseCase: HabitGoalProgressUpdateUseCase
|
||||
}
|
||||
|
||||
export class HabitsTrackerPresenter
|
||||
@ -49,12 +58,14 @@ export class HabitsTrackerPresenter
|
||||
public retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase
|
||||
public habitCreateUseCase: HabitCreateUseCase
|
||||
public habitEditUseCase: HabitEditUseCase
|
||||
public habitGoalProgressUpdateUseCase: HabitGoalProgressUpdateUseCase
|
||||
|
||||
public constructor(options: HabitsTrackerPresenterOptions) {
|
||||
const {
|
||||
retrieveHabitsTrackerUseCase,
|
||||
habitCreateUseCase,
|
||||
habitEditUseCase,
|
||||
habitGoalProgressUpdateUseCase,
|
||||
} = options
|
||||
const habitsTracker = HabitsTracker.default()
|
||||
super({
|
||||
@ -74,10 +85,14 @@ export class HabitsTrackerPresenter
|
||||
global: null,
|
||||
},
|
||||
},
|
||||
habitGoalProgressUpdate: {
|
||||
state: "idle",
|
||||
},
|
||||
})
|
||||
this.retrieveHabitsTrackerUseCase = retrieveHabitsTrackerUseCase
|
||||
this.habitCreateUseCase = habitCreateUseCase
|
||||
this.habitEditUseCase = habitEditUseCase
|
||||
this.habitGoalProgressUpdateUseCase = habitGoalProgressUpdateUseCase
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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={{}}
|
||||
/>
|
||||
*/
|
@ -1,30 +1,37 @@
|
||||
import FontAwesome6 from "@expo/vector-icons/FontAwesome6"
|
||||
import { useRouter } from "expo-router"
|
||||
import { View } from "react-native"
|
||||
import { List, Text, Checkbox } from "react-native-paper"
|
||||
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 { Habit } from "@/domain/entities/Habit"
|
||||
import type { GoalBoolean } from "@/domain/entities/Goal"
|
||||
import { GoalBooleanProgress } from "@/domain/entities/Goal"
|
||||
import type { HabitHistory } from "@/domain/entities/HabitHistory"
|
||||
import { getColorRGBAFromHex } from "@/utils/colors"
|
||||
import { useHabitsTracker } from "../../contexts/HabitsTracker"
|
||||
|
||||
export interface HabitCardProps {
|
||||
habit: Habit
|
||||
goalProgress: GoalProgress
|
||||
habitHistory: HabitHistory
|
||||
selectedDate: Date
|
||||
confettiRef: React.MutableRefObject<LottieView | null>
|
||||
}
|
||||
|
||||
export const HabitCard: React.FC<HabitCardProps> = (props) => {
|
||||
const { habit, goalProgress } = props
|
||||
const { habitHistory, selectedDate, confettiRef } = props
|
||||
const { habit } = habitHistory
|
||||
|
||||
const router = useRouter()
|
||||
const { habitsTrackerPresenter } = useHabitsTracker()
|
||||
|
||||
const goalProgress = habitHistory.getGoalProgressByDate(selectedDate)
|
||||
const [checked, setChecked] = useState(goalProgress.isCompleted())
|
||||
|
||||
const habitColor = getColorRGBAFromHex({
|
||||
hexColor: habit.color,
|
||||
opacity: 0.4,
|
||||
})
|
||||
|
||||
const [checked, setChecked] = useState(goalProgress.isCompleted())
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
onPress={() => {
|
||||
@ -57,15 +64,17 @@ export const HabitCard: React.FC<HabitCardProps> = (props) => {
|
||||
]}
|
||||
left={() => {
|
||||
return (
|
||||
<FontAwesome6
|
||||
size={24}
|
||||
name={habit.icon}
|
||||
style={[
|
||||
{
|
||||
width: 30,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<View style={{ justifyContent: "center", alignItems: "center" }}>
|
||||
<FontAwesome6
|
||||
size={24}
|
||||
name={habit.icon}
|
||||
style={[
|
||||
{
|
||||
width: 30,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}}
|
||||
right={() => {
|
||||
@ -82,14 +91,25 @@ export const HabitCard: React.FC<HabitCardProps> = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Checkbox
|
||||
status={checked ? "checked" : "unchecked"}
|
||||
onPress={() => {
|
||||
setChecked(!checked)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Checkbox
|
||||
color="black"
|
||||
status={checked ? "checked" : "unchecked"}
|
||||
onPress={async () => {
|
||||
const isCheckedNew = !checked
|
||||
setChecked(isCheckedNew)
|
||||
if (isCheckedNew) {
|
||||
confettiRef.current?.play()
|
||||
}
|
||||
await habitsTrackerPresenter.habitUpdateProgress({
|
||||
date: selectedDate,
|
||||
habitHistory,
|
||||
goalProgress: new GoalBooleanProgress({
|
||||
goal: habit.goal as GoalBoolean,
|
||||
progress: isCheckedNew,
|
||||
}),
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { useState } from "react"
|
||||
import { Dimensions, ScrollView } from "react-native"
|
||||
import LottieView from "lottie-react-native"
|
||||
import { useRef, useState } from "react"
|
||||
import { Dimensions, ScrollView, View } from "react-native"
|
||||
import { Divider, List } from "react-native-paper"
|
||||
|
||||
import type { GoalFrequency } from "@/domain/entities/Goal"
|
||||
import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
|
||||
import confettiJSON from "../../../assets/confetti.json"
|
||||
import { capitalize } from "@/utils/strings"
|
||||
import { HabitCard } from "./HabitCard"
|
||||
|
||||
@ -24,51 +26,85 @@ export const HabitsList: React.FC<HabitsListProps> = (props) => {
|
||||
monthly: true,
|
||||
})
|
||||
|
||||
const confettiRef = useRef<LottieView | null>(null)
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={{
|
||||
paddingHorizontal: 20,
|
||||
width: Dimensions.get("window").width,
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
>
|
||||
<Divider />
|
||||
<List.Section>
|
||||
{frequenciesFiltered.map((frequency) => {
|
||||
return (
|
||||
<List.Accordion
|
||||
expanded={accordionExpanded[frequency]}
|
||||
onPress={() => {
|
||||
setAccordionExpanded((old) => {
|
||||
return {
|
||||
...old,
|
||||
[frequency]: !old[frequency],
|
||||
}
|
||||
})
|
||||
}}
|
||||
key={frequency}
|
||||
title={capitalize(frequency)}
|
||||
titleStyle={[
|
||||
{
|
||||
fontSize: 26,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{habitsTracker.habitsHistory[frequency].map((item) => {
|
||||
const goalProgress = item.getGoalProgressByDate(selectedDate)
|
||||
return (
|
||||
<HabitCard
|
||||
habit={item.habit}
|
||||
goalProgress={goalProgress}
|
||||
key={item.habit.id}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</List.Accordion>
|
||||
)
|
||||
})}
|
||||
</List.Section>
|
||||
</ScrollView>
|
||||
<>
|
||||
<View
|
||||
pointerEvents="none"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
zIndex: 100,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<LottieView
|
||||
ref={confettiRef}
|
||||
source={confettiJSON}
|
||||
autoPlay={false}
|
||||
loop={false}
|
||||
style={[
|
||||
{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={{
|
||||
paddingHorizontal: 20,
|
||||
width: Dimensions.get("window").width,
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
>
|
||||
<Divider />
|
||||
|
||||
<List.Section>
|
||||
{frequenciesFiltered.map((frequency) => {
|
||||
return (
|
||||
<List.Accordion
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user