diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000..b01a8eb
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,6 @@
+# We put the id's of the commits that we want to ignore in this file, one per line.
+# For example: this is a format commit that we don't want to see in the blame output:
+2d222c86c982bef96b232e705dd536f4a7cd853b
+# Because it doesn't change the code, just the formatting:
+# Tell git to use this file for blame by running the following command in your terminal:
+# git config blame.ignoreRevsFile .git-blame-ignore-revs
\ No newline at end of file
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
new file mode 100644
index 0000000..be6b7d8
--- /dev/null
+++ b/.github/workflows/checks.yml
@@ -0,0 +1,21 @@
+name: Checks
+
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - main
+
+jobs:
+ checks:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install dependencies (root)
+ run: npm install
+
+ - name: Run checks (root script)
+ run: npm run checks
diff --git a/App.tsx b/App.tsx
index f93ef5a..33db49d 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1,14 +1,14 @@
// FILE: App.tsx
-import { ThemeProvider } from "@/core/theme/ThemeContext";
-import React, { useEffect } from "react";
-import { StatusBar } from "expo-status-bar";
-import useAppStore from "@/core/store/appStore";
-import { GestureHandlerRootView } from "react-native-gesture-handler";
-import i18n from "@/core/i18n";
-import { useForceUpdate } from "@/core/hooks/useForceUpdate";
-import { I18nManager } from "react-native";
-import { NavigationContainer } from "@react-navigation/native";
-import AppNavigator from "@/navigation/AppNavigator";
+import { ThemeProvider } from '@/core/theme/ThemeContext';
+import React, { useEffect } from 'react';
+import { StatusBar } from 'expo-status-bar';
+import useAppStore from '@/core/store/appStore';
+import { GestureHandlerRootView } from 'react-native-gesture-handler';
+import i18n from '@/core/i18n';
+import { useForceUpdate } from '@/core/hooks/useForceUpdate';
+import { I18nManager } from 'react-native';
+import { NavigationContainer } from '@react-navigation/native';
+import AppNavigator from '@/navigation/AppNavigator';
I18nManager.allowRTL(true);
@@ -33,7 +33,7 @@ export default function App() {
-
+
diff --git a/eslint.config.js b/eslint.config.js
index a726b8a..dc385ba 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,14 +1,14 @@
-const { defineConfig } = require("eslint/config");
-const expoConfig = require("eslint-config-expo/flat");
-const eslintPluginPrettierRecommended = require("eslint-plugin-prettier/recommended");
+const { defineConfig } = require('eslint/config');
+const expoConfig = require('eslint-config-expo/flat');
+const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended');
module.exports = defineConfig([
expoConfig,
eslintPluginPrettierRecommended,
{
rules: {
- "prettier/prettier": "warn",
+ 'prettier/prettier': 'warn',
},
- ignores: ["dist/*", "node_modules/*", "/.expo"],
+ ignores: ['dist/*', 'node_modules/*', '/.expo'],
},
]);
diff --git a/index.ts b/index.ts
index 828b356..1d6e981 100644
--- a/index.ts
+++ b/index.ts
@@ -1,6 +1,6 @@
-import { registerRootComponent } from "expo";
+import { registerRootComponent } from 'expo';
-import App from "./App";
+import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
diff --git a/package.json b/package.json
index 2e08f3a..278a240 100644
--- a/package.json
+++ b/package.json
@@ -10,8 +10,9 @@
"format": "prettier --write .",
"checks:lint": "expo lint --fix",
"checks:tsc": "tsc --noEmit",
+ "checks:export": "expo export",
"checks:format": "prettier --check .",
- "checks": "npm run checks:lint && npm run checks:tsc && npm run checks:format",
+ "checks": "npm run checks:lint && npm run checks:tsc && npm run checks:export && npm run checks:format",
"lint": "expo lint"
},
"dependencies": {
diff --git a/src/components/FAB.tsx b/src/components/FAB.tsx
index a68e7d3..ec0c680 100644
--- a/src/components/FAB.tsx
+++ b/src/components/FAB.tsx
@@ -1,14 +1,9 @@
// src/components/FAB.tsx
-import { AppTheme } from "@/constants/theme";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import React from "react";
-import {
- I18nManager,
- StyleSheet,
- TouchableOpacity,
- TouchableOpacityProps,
-} from "react-native";
+import { AppTheme } from '@/constants/theme';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import React from 'react';
+import { I18nManager, StyleSheet, TouchableOpacity, TouchableOpacityProps } from 'react-native';
export const FAB = (props: TouchableOpacityProps) => {
const { theme } = useTheme();
@@ -16,11 +11,7 @@ export const FAB = (props: TouchableOpacityProps) => {
return (
-
+
);
};
@@ -28,16 +19,16 @@ export const FAB = (props: TouchableOpacityProps) => {
const getStyles = (theme: AppTheme) =>
StyleSheet.create({
container: {
- position: "absolute",
+ position: 'absolute',
bottom: theme.spacing.l,
- [I18nManager.isRTL ? "left" : "right"]: theme.spacing.l,
+ [I18nManager.isRTL ? 'left' : 'right']: theme.spacing.l,
width: 60,
height: 60,
borderRadius: 16,
backgroundColor: theme.colors.primary,
- justifyContent: "center",
- alignItems: "center",
- shadowColor: "#000",
+ justifyContent: 'center',
+ alignItems: 'center',
+ shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index 9d36a9a..e2638d5 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -1,11 +1,11 @@
// src/components/Header.tsx
-import { AppTheme } from "@/constants/theme";
-import { useTheme } from "@/core/theme/ThemeContext";
-import React from "react";
-import { StyleSheet } from "react-native";
-import { EdgeInsets } from "react-native-safe-area-context";
-import { Box } from "./base/Box";
-import { ThemedText } from "./base/ThemedText";
+import { AppTheme } from '@/constants/theme';
+import { useTheme } from '@/core/theme/ThemeContext';
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import { EdgeInsets } from 'react-native-safe-area-context';
+import { Box } from './base/Box';
+import { ThemedText } from './base/ThemedText';
export type HeaderProps = {
title: string;
@@ -27,9 +27,7 @@ export const Header = ({
return (
-
- {renderLeftAction && renderLeftAction()}
-
+ {renderLeftAction && renderLeftAction()}
@@ -42,9 +40,7 @@ export const Header = ({
)}
-
- {renderRightAction && renderRightAction()}
-
+ {renderRightAction && renderRightAction()}
);
};
@@ -52,9 +48,9 @@ export const Header = ({
const getStyles = (theme: AppTheme, insets: EdgeInsets) =>
StyleSheet.create({
container: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "space-between",
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
paddingHorizontal: theme.spacing.m,
height: 60 + insets.top,
paddingTop: insets.top,
@@ -62,13 +58,13 @@ const getStyles = (theme: AppTheme, insets: EdgeInsets) =>
},
actionSlot: {
width: 40,
- justifyContent: "center",
- alignItems: "center",
+ justifyContent: 'center',
+ alignItems: 'center',
},
titleContainer: {
flex: 1,
- justifyContent: "center",
- alignItems: "center",
+ justifyContent: 'center',
+ alignItems: 'center',
},
title: {
fontSize: theme.typography.fontSize.l,
diff --git a/src/components/Screen.tsx b/src/components/Screen.tsx
index a8eef32..9f78718 100644
--- a/src/components/Screen.tsx
+++ b/src/components/Screen.tsx
@@ -1,13 +1,13 @@
// src/components/Screen.tsx
-import { AppTheme } from "@/constants/theme";
-import { useTheme } from "@/core/theme/ThemeContext";
-import React from "react";
-import { StyleSheet } from "react-native";
-import { useSafeAreaInsets } from "react-native-safe-area-context";
-import { Box } from "./base/Box";
-import { Header, HeaderProps } from "./Header";
+import { AppTheme } from '@/constants/theme';
+import { useTheme } from '@/core/theme/ThemeContext';
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+import { Box } from './base/Box';
+import { Header, HeaderProps } from './Header';
-type ScreenProps = Partial> & {
+type ScreenProps = Partial> & {
children: React.ReactNode;
};
diff --git a/src/components/base/Box.tsx b/src/components/base/Box.tsx
index cf5caac..7d72e1f 100644
--- a/src/components/base/Box.tsx
+++ b/src/components/base/Box.tsx
@@ -1,6 +1,6 @@
// src/components/base/Box.tsx
-import { View, ViewProps } from "react-native";
-import React from "react";
+import { View, ViewProps } from 'react-native';
+import React from 'react';
// For now, it's a simple pass-through.
// It establishes a pattern for a component that will become more powerful.
diff --git a/src/components/base/ThemedText.tsx b/src/components/base/ThemedText.tsx
index eae1b96..49f7e18 100644
--- a/src/components/base/ThemedText.tsx
+++ b/src/components/base/ThemedText.tsx
@@ -1,7 +1,7 @@
// src/components/base/ThemedText.tsx
-import { useTheme } from "@/core/theme/ThemeContext";
-import React from "react";
-import { Text, TextProps } from "react-native";
+import { useTheme } from '@/core/theme/ThemeContext';
+import React from 'react';
+import { Text, TextProps } from 'react-native';
export const ThemedText = (props: TextProps) => {
const { theme } = useTheme();
diff --git a/src/components/base/ThemedTextInput.tsx b/src/components/base/ThemedTextInput.tsx
index 66252ef..3c0b43d 100644
--- a/src/components/base/ThemedTextInput.tsx
+++ b/src/components/base/ThemedTextInput.tsx
@@ -1,8 +1,8 @@
// src/components/base/ThemedTextInput.tsx
-import { AppTheme } from "@/constants/theme";
-import { useTheme } from "@/core/theme/ThemeContext";
-import React from "react";
-import { StyleSheet, TextInput, TextInputProps } from "react-native";
+import { AppTheme } from '@/constants/theme';
+import { useTheme } from '@/core/theme/ThemeContext';
+import React from 'react';
+import { StyleSheet, TextInput, TextInputProps } from 'react-native';
export const ThemedTextInput = (props: TextInputProps) => {
const { theme } = useTheme();
diff --git a/src/constants/colors.ts b/src/constants/colors.ts
index cf9b533..f48f72b 100644
--- a/src/constants/colors.ts
+++ b/src/constants/colors.ts
@@ -2,28 +2,28 @@
const palette = {
// Grays
- neutral100: "#FFFFFF",
- neutral200: "#F4F2F1",
- neutral300: "#D7CEC9",
- neutral400: "#B6ACA6",
- neutral500: "#978F8A",
- neutral600: "#564F4A",
- neutral700: "#3C3835",
- neutral800: "#2B2724",
- neutral900: "#1C1917",
+ neutral100: '#FFFFFF',
+ neutral200: '#F4F2F1',
+ neutral300: '#D7CEC9',
+ neutral400: '#B6ACA6',
+ neutral500: '#978F8A',
+ neutral600: '#564F4A',
+ neutral700: '#3C3835',
+ neutral800: '#2B2724',
+ neutral900: '#1C1917',
// Primary (Example: A muted green)
- primary100: "#E3F9E5",
- primary200: "#C1EAC5",
- primary300: "#A3D9A5",
- primary400: "#85C78A",
- primary500: "#67B56F",
- primary600: "#4F8B55",
+ primary100: '#E3F9E5',
+ primary200: '#C1EAC5',
+ primary300: '#A3D9A5',
+ primary400: '#85C78A',
+ primary500: '#67B56F',
+ primary600: '#4F8B55',
// Accents for statuses
- accentRed: "#D9534F",
- accentYellow: "#F0AD4E",
- accentGreen: "#5CB85C",
+ accentRed: '#D9534F',
+ accentYellow: '#F0AD4E',
+ accentGreen: '#5CB85C',
} as const;
export const colors = {
diff --git a/src/constants/icons.ts b/src/constants/icons.ts
index 74f82cb..705b71a 100644
--- a/src/constants/icons.ts
+++ b/src/constants/icons.ts
@@ -1,38 +1,36 @@
// src/constants/icons.ts
-import { MaterialCommunityIcons } from "@expo/vector-icons";
+import { MaterialCommunityIcons } from '@expo/vector-icons';
// A curated list of icons for custom deeds
-export const customDeedIcons: React.ComponentProps<
- typeof MaterialCommunityIcons
->["name"][] = [
- "book-open-page-variant",
- "charity",
- "run",
- "meditation",
- "brain",
- "account-group",
- "phone",
- "message-text",
- "heart",
- "star",
- "leaf",
- "water",
- "food-apple",
- "lightbulb-on",
- "school",
- "briefcase",
- "home",
- "camera",
- "walk",
- "human-greeting-variant",
- "tree",
- "earth",
- "pencil",
- "draw",
- "palette",
- "music-note",
- "moon-waning-crescent",
- "white-balance-sunny",
- "flag",
- "trophy",
+export const customDeedIcons: React.ComponentProps['name'][] = [
+ 'book-open-page-variant',
+ 'charity',
+ 'run',
+ 'meditation',
+ 'brain',
+ 'account-group',
+ 'phone',
+ 'message-text',
+ 'heart',
+ 'star',
+ 'leaf',
+ 'water',
+ 'food-apple',
+ 'lightbulb-on',
+ 'school',
+ 'briefcase',
+ 'home',
+ 'camera',
+ 'walk',
+ 'human-greeting-variant',
+ 'tree',
+ 'earth',
+ 'pencil',
+ 'draw',
+ 'palette',
+ 'music-note',
+ 'moon-waning-crescent',
+ 'white-balance-sunny',
+ 'flag',
+ 'trophy',
];
diff --git a/src/constants/theme.ts b/src/constants/theme.ts
index 41d9a93..7c12807 100644
--- a/src/constants/theme.ts
+++ b/src/constants/theme.ts
@@ -1,8 +1,8 @@
// src/constants/theme.ts
-import { colors } from "./colors";
-import { spacing } from "./spacing";
-import { typography } from "./typography";
+import { colors } from './colors';
+import { spacing } from './spacing';
+import { typography } from './typography';
export const theme = {
colors,
diff --git a/src/constants/typography.ts b/src/constants/typography.ts
index ce0f480..2877c80 100644
--- a/src/constants/typography.ts
+++ b/src/constants/typography.ts
@@ -10,10 +10,10 @@ export const typography = {
xxl: 32,
},
fontWeight: {
- light: "300",
- normal: "400",
- medium: "500",
- semibold: "600",
- bold: "700",
+ light: '300',
+ normal: '400',
+ medium: '500',
+ semibold: '600',
+ bold: '700',
},
} as const;
diff --git a/src/core/data/demoData.ts b/src/core/data/demoData.ts
index 446508b..e4c4cca 100644
--- a/src/core/data/demoData.ts
+++ b/src/core/data/demoData.ts
@@ -1,22 +1,22 @@
// src/core/data/demoData.ts
-import { subDays, formatISO } from "date-fns";
-import { DeedLog } from "./models";
+import { subDays, formatISO } from 'date-fns';
+import { DeedLog } from './models';
const PRAYER_DEED_IDS = [
- "prayer-fajr",
- "prayer-dhuhr",
- "prayer-asr",
- "prayer-maghrib",
- "prayer-isha",
+ 'prayer-fajr',
+ 'prayer-dhuhr',
+ 'prayer-asr',
+ 'prayer-maghrib',
+ 'prayer-isha',
];
// Weighted random selection to make data look more realistic
const getRandomStatusId = (): string => {
const rand = Math.random();
- if (rand < 0.4) return "jamaah"; // 40% chance
- if (rand < 0.75) return "on-time"; // 35% chance
- if (rand < 0.95) return "late"; // 20% chance
- return "missed"; // 5% chance
+ if (rand < 0.4) return 'jamaah'; // 40% chance
+ if (rand < 0.75) return 'on-time'; // 35% chance
+ if (rand < 0.95) return 'late'; // 20% chance
+ return 'missed'; // 5% chance
};
export const generateDemoLogs = (): DeedLog[] => {
@@ -26,7 +26,7 @@ export const generateDemoLogs = (): DeedLog[] => {
for (let i = 0; i < daysToGenerate; i++) {
const date = subDays(today, i);
- const dateString = formatISO(date, { representation: "date" });
+ const dateString = formatISO(date, { representation: 'date' });
for (const deedId of PRAYER_DEED_IDS) {
// Add a chance to skip logging a deed for a day to create gaps
diff --git a/src/core/data/mock.ts b/src/core/data/mock.ts
index 59c3a94..3659e83 100644
--- a/src/core/data/mock.ts
+++ b/src/core/data/mock.ts
@@ -1,56 +1,56 @@
// src/core/data/mock.ts
-import { Deed, DeedLog, DeedStatus } from "./models";
+import { Deed, DeedLog, DeedStatus } from './models';
const PRAYER_STATUSES: DeedStatus[] = [
- { id: "missed", icon: "close-circle", color: "missed" },
- { id: "late", icon: "clock-alert", color: "late" },
- { id: "on-time", icon: "clock-check", color: "onTime" },
- { id: "jamaah", icon: "check-all", color: "jamaah" },
+ { id: 'missed', icon: 'close-circle', color: 'missed' },
+ { id: 'late', icon: 'clock-alert', color: 'late' },
+ { id: 'on-time', icon: 'clock-check', color: 'onTime' },
+ { id: 'jamaah', icon: 'check-all', color: 'jamaah' },
];
export const GENERIC_STATUSES: DeedStatus[] = [
- { id: "missed", icon: "close", color: "late" },
- { id: "completed", icon: "check", color: "primary" },
+ { id: 'missed', icon: 'close', color: 'late' },
+ { id: 'completed', icon: 'check', color: 'primary' },
];
export const MOCK_DEEDS: Deed[] = [
{
- id: "prayer-fajr",
- name: "Fajr",
- icon: "weather-sunset-up",
- category: "PRAYERS",
+ id: 'prayer-fajr',
+ name: 'Fajr',
+ icon: 'weather-sunset-up',
+ category: 'PRAYERS',
statuses: PRAYER_STATUSES,
isCore: true,
},
{
- id: "prayer-dhuhr",
- name: "Dhuhr",
- icon: "weather-sunny",
- category: "PRAYERS",
+ id: 'prayer-dhuhr',
+ name: 'Dhuhr',
+ icon: 'weather-sunny',
+ category: 'PRAYERS',
statuses: PRAYER_STATUSES,
isCore: true,
},
{
- id: "prayer-asr",
- name: "Asr",
- icon: "weather-partly-cloudy",
- category: "PRAYERS",
+ id: 'prayer-asr',
+ name: 'Asr',
+ icon: 'weather-partly-cloudy',
+ category: 'PRAYERS',
statuses: PRAYER_STATUSES,
isCore: true,
},
{
- id: "prayer-maghrib",
- name: "Maghrib",
- icon: "weather-sunset-down",
- category: "PRAYERS",
+ id: 'prayer-maghrib',
+ name: 'Maghrib',
+ icon: 'weather-sunset-down',
+ category: 'PRAYERS',
statuses: PRAYER_STATUSES,
isCore: true,
},
{
- id: "prayer-isha",
- name: "Isha",
- icon: "weather-night",
- category: "PRAYERS",
+ id: 'prayer-isha',
+ name: 'Isha',
+ icon: 'weather-night',
+ category: 'PRAYERS',
statuses: PRAYER_STATUSES,
isCore: true,
},
@@ -59,61 +59,61 @@ export const MOCK_DEEDS: Deed[] = [
export const SUGGESTED_DEEDS: Deed[] = [
// PRAYERS
{
- id: "prayer-witr",
- name: "Witr",
- icon: "moon-waning-crescent",
- category: "PRAYERS",
+ id: 'prayer-witr',
+ name: 'Witr',
+ icon: 'moon-waning-crescent',
+ category: 'PRAYERS',
statuses: GENERIC_STATUSES,
isCore: false,
},
{
- id: "prayer-tahajjud",
- name: "Tahajjud",
- icon: "meditation",
- category: "PRAYERS",
+ id: 'prayer-tahajjud',
+ name: 'Tahajjud',
+ icon: 'meditation',
+ category: 'PRAYERS',
statuses: GENERIC_STATUSES,
isCore: false,
},
// QURAN
{
- id: "quran-reading",
- name: "Read Quran",
- icon: "book-open-page-variant",
- category: "QURAN",
+ id: 'quran-reading',
+ name: 'Read Quran',
+ icon: 'book-open-page-variant',
+ category: 'QURAN',
statuses: GENERIC_STATUSES,
isCore: false,
},
{
- id: "quran-memorizing",
- name: "Memorize Quran",
- icon: "brain",
- category: "QURAN",
+ id: 'quran-memorizing',
+ name: 'Memorize Quran',
+ icon: 'brain',
+ category: 'QURAN',
statuses: GENERIC_STATUSES,
isCore: false,
},
// LEARNING
{
- id: "learning-book",
- name: "Read a book",
- icon: "book-education",
- category: "LEARNING",
+ id: 'learning-book',
+ name: 'Read a book',
+ icon: 'book-education',
+ category: 'LEARNING',
statuses: GENERIC_STATUSES,
isCore: false,
},
// SOCIAL
{
- id: "social-charity",
- name: "Give Charity",
- icon: "charity",
- category: "SOCIAL",
+ id: 'social-charity',
+ name: 'Give Charity',
+ icon: 'charity',
+ category: 'SOCIAL',
statuses: GENERIC_STATUSES,
isCore: false,
},
{
- id: "social-family",
- name: "Call family",
- icon: "phone-in-talk",
- category: "SOCIAL",
+ id: 'social-family',
+ name: 'Call family',
+ icon: 'phone-in-talk',
+ category: 'SOCIAL',
statuses: GENERIC_STATUSES,
isCore: false,
},
@@ -122,34 +122,34 @@ export const SUGGESTED_DEEDS: Deed[] = [
export const MOCK_LOGS: DeedLog[] = [
// Today's logs
{
- id: "log-1",
- deedId: "prayer-fajr",
- date: "2025-08-29",
- statusId: "jamaah",
+ id: 'log-1',
+ deedId: 'prayer-fajr',
+ date: '2025-08-29',
+ statusId: 'jamaah',
},
{
- id: "log-2",
- deedId: "prayer-dhuhr",
- date: "2025-08-29",
- statusId: "on-time",
+ id: 'log-2',
+ deedId: 'prayer-dhuhr',
+ date: '2025-08-29',
+ statusId: 'on-time',
},
// Yesterday's logs
{
- id: "log-3",
- deedId: "prayer-fajr",
- date: "2025-08-28",
- statusId: "on-time",
+ id: 'log-3',
+ deedId: 'prayer-fajr',
+ date: '2025-08-28',
+ statusId: 'on-time',
},
{
- id: "log-4",
- deedId: "prayer-dhuhr",
- date: "2025-08-28",
- statusId: "late",
+ id: 'log-4',
+ deedId: 'prayer-dhuhr',
+ date: '2025-08-28',
+ statusId: 'late',
},
{
- id: "log-5",
- deedId: "prayer-asr",
- date: "2025-08-28",
- statusId: "jamaah",
+ id: 'log-5',
+ deedId: 'prayer-asr',
+ date: '2025-08-28',
+ statusId: 'jamaah',
},
];
diff --git a/src/core/data/models.ts b/src/core/data/models.ts
index f2cf0df..826e2da 100644
--- a/src/core/data/models.ts
+++ b/src/core/data/models.ts
@@ -1,6 +1,6 @@
// src/core/data/models.ts
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { ColorTheme } from "@/constants/theme";
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { ColorTheme } from '@/constants/theme';
/**
* Represents a single possible status for a deed.
@@ -8,12 +8,9 @@ import { ColorTheme } from "@/constants/theme";
*/
export type DeedStatus = {
id: string; // e.g., 'on-time', 'completed'
- icon: React.ComponentProps["name"];
+ icon: React.ComponentProps['name'];
// Use keys of our theme colors for type safety
- color: keyof Pick<
- ColorTheme,
- "jamaah" | "onTime" | "late" | "missed" | "primary"
- >;
+ color: keyof Pick;
};
/**
@@ -24,7 +21,7 @@ export type DeedStatus = {
* - 'yearly': The deed has a target count per year.
*/
export type DeedFrequency = {
- type: "daily" | "weekly" | "monthly" | "yearly";
+ type: 'daily' | 'weekly' | 'monthly' | 'yearly';
// For 'weekly', an array of numbers representing days (0=Sun, 1=Mon, ..., 6=Sat)
days?: number[];
// For 'monthly' or 'yearly', the target number of times
@@ -45,8 +42,8 @@ export type DeedGoal = {
export type Deed = {
id: string; // e.g., 'prayer-fajr'
name: string; // e.g., "Fajr"
- icon: React.ComponentProps["name"];
- category: "PRAYERS" | "QURAN" | "LEARNING" | "SOCIAL" | "CUSTOM";
+ icon: React.ComponentProps['name'];
+ category: 'PRAYERS' | 'QURAN' | 'LEARNING' | 'SOCIAL' | 'CUSTOM';
statuses: DeedStatus[];
isCore?: boolean; // True for fundamental deeds that cannot be edited/deleted
frequency?: DeedFrequency;
diff --git a/src/core/hooks/useForceUpdate.ts b/src/core/hooks/useForceUpdate.ts
index dd7dc57..50c8acc 100644
--- a/src/core/hooks/useForceUpdate.ts
+++ b/src/core/hooks/useForceUpdate.ts
@@ -1,4 +1,4 @@
-import { useState, useCallback } from "react";
+import { useState, useCallback } from 'react';
export const useForceUpdate = () => {
const [, updateState] = useState({});
return useCallback(() => updateState({}), []);
diff --git a/src/core/i18n/index.ts b/src/core/i18n/index.ts
index 5fdc8c5..61425fa 100644
--- a/src/core/i18n/index.ts
+++ b/src/core/i18n/index.ts
@@ -1,9 +1,9 @@
// src/core/i18n/index.ts
-import { I18n } from "i18n-js";
-import * as Localization from "expo-localization";
-import en from "./en.json";
-import ar from "./ar.json";
-import { enUS as enUSLocale, ar as arLocale } from "date-fns/locale";
+import { I18n } from 'i18n-js';
+import * as Localization from 'expo-localization';
+import en from './en.json';
+import ar from './ar.json';
+import { enUS as enUSLocale, ar as arLocale } from 'date-fns/locale';
// Set the key-value pairs for the different languages you want to support.
const i18n = new I18n({
@@ -18,7 +18,7 @@ export const dateLocales = {
// Set the locale once at the beginning of your app.
const locales = Localization.getLocales();
-const languageCode = locales[0]?.languageCode ?? "en";
+const languageCode = locales[0]?.languageCode ?? 'en';
i18n.locale = languageCode;
// When a value is missing from a language it'll fallback to another language with the key present.
diff --git a/src/core/services/notificationService.ts b/src/core/services/notificationService.ts
index e1e1d4a..48edd6b 100644
--- a/src/core/services/notificationService.ts
+++ b/src/core/services/notificationService.ts
@@ -1,9 +1,9 @@
// src/core/services/notificationService.ts
-import * as Notifications from "expo-notifications";
-import { Platform } from "react-native";
-import i18n from "../i18n";
+import * as Notifications from 'expo-notifications';
+import { Platform } from 'react-native';
+import i18n from '../i18n';
-const REMINDER_NOTIFICATION_ID = "daily-reminder";
+const REMINDER_NOTIFICATION_ID = 'daily-reminder';
// This must be called once to configure notification behavior
Notifications.setNotificationHandler({
@@ -23,22 +23,22 @@ const requestPermissions = async (): Promise => {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
- if (existingStatus !== "granted") {
+ if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
- if (finalStatus !== "granted") {
+ if (finalStatus !== 'granted') {
// Consider linking to settings here in a real app
return false;
}
- if (Platform.OS === "android") {
- await Notifications.setNotificationChannelAsync("default", {
- name: "default",
+ if (Platform.OS === 'android') {
+ await Notifications.setNotificationChannelAsync('default', {
+ name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
- lightColor: "#FF231F7C",
+ lightColor: '#FF231F7C',
});
}
@@ -52,7 +52,7 @@ const requestPermissions = async (): Promise => {
export const scheduleDailyReminder = async (time: string) => {
const hasPermission = await requestPermissions();
if (!hasPermission) {
- console.log("Notification permission not granted. Cannot schedule.");
+ console.log('Notification permission not granted. Cannot schedule.');
// Silently fail for now, but we could alert the user here.
return;
}
@@ -60,12 +60,12 @@ export const scheduleDailyReminder = async (time: string) => {
// Cancel any existing reminder to ensure we only have one.
await cancelAllReminders();
- const [hour, minute] = time.split(":").map(Number);
+ const [hour, minute] = time.split(':').map(Number);
await Notifications.scheduleNotificationAsync({
content: {
- title: i18n.t("notifications.reminderTitle"),
- body: i18n.t("notifications.reminderBody"),
+ title: i18n.t('notifications.reminderTitle'),
+ body: i18n.t('notifications.reminderBody'),
},
trigger: {
type: Notifications.SchedulableTriggerInputTypes.DAILY,
@@ -83,7 +83,7 @@ export const scheduleDailyReminder = async (time: string) => {
*/
export const cancelAllReminders = async () => {
await Notifications.cancelAllScheduledNotificationsAsync();
- console.log("All scheduled reminders have been canceled.");
+ console.log('All scheduled reminders have been canceled.');
};
// --- Developer Functions ---
@@ -92,11 +92,11 @@ export const cancelAllReminders = async () => {
* Sends a test notification immediately.
*/
export const sendTestNotification = async () => {
- console.log("Sending test notification now.");
+ console.log('Sending test notification now.');
await Notifications.scheduleNotificationAsync({
content: {
- title: i18n.t("notifications.testTitle"),
- body: i18n.t("notifications.testBody"),
+ title: i18n.t('notifications.testTitle'),
+ body: i18n.t('notifications.testBody'),
},
trigger: null, // null trigger sends it immediately
});
@@ -106,11 +106,11 @@ export const sendTestNotification = async () => {
* Schedules a test notification to be sent in 5 seconds.
*/
export const scheduleTestNotificationIn5s = async () => {
- console.log("Scheduling a test notification for 5 seconds from now.");
+ console.log('Scheduling a test notification for 5 seconds from now.');
await Notifications.scheduleNotificationAsync({
content: {
- title: i18n.t("notifications.scheduledTestTitle"),
- body: i18n.t("notifications.scheduledTestBody"),
+ title: i18n.t('notifications.scheduledTestTitle'),
+ body: i18n.t('notifications.scheduledTestBody'),
},
trigger: {
type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL,
diff --git a/src/core/storage/storageService.ts b/src/core/storage/storageService.ts
index 269a94a..8c6221e 100644
--- a/src/core/storage/storageService.ts
+++ b/src/core/storage/storageService.ts
@@ -1,6 +1,6 @@
// src/core/storage/storageService.ts
-import * as FileSystem from "expo-file-system";
-import { AppData } from "../store/appStore";
+import * as FileSystem from 'expo-file-system';
+import { AppData } from '../store/appStore';
const dataFilePath = `${FileSystem.documentDirectory}muhsinData.json`;
@@ -12,13 +12,13 @@ export const loadDataFromFile = async (): Promise => {
try {
const fileInfo = await FileSystem.getInfoAsync(dataFilePath);
if (!fileInfo.exists) {
- console.log("Data file does not exist. Starting fresh.");
+ console.log('Data file does not exist. Starting fresh.');
return null;
}
const content = await FileSystem.readAsStringAsync(dataFilePath);
return JSON.parse(content) as AppData;
} catch (error) {
- console.error("Failed to load data from file:", error);
+ console.error('Failed to load data from file:', error);
// In case of parsing error, etc., we treat it as no data found.
return null;
}
@@ -33,6 +33,6 @@ export const saveDataToFile = async (data: AppData): Promise => {
const content = JSON.stringify(data, null, 2); // Pretty print JSON
await FileSystem.writeAsStringAsync(dataFilePath, content);
} catch (error) {
- console.error("Failed to save data to file:", error);
+ console.error('Failed to save data to file:', error);
}
};
diff --git a/src/core/store/appStore.ts b/src/core/store/appStore.ts
index 46b19dd..e8523c1 100644
--- a/src/core/store/appStore.ts
+++ b/src/core/store/appStore.ts
@@ -1,28 +1,20 @@
// src/core/store/appStore.ts
-import { create } from "zustand";
-import { Deed, DeedLog, DeedStatus } from "../data/models";
-import {
- GENERIC_STATUSES,
- MOCK_DEEDS,
- MOCK_LOGS,
- SUGGESTED_DEEDS,
-} from "../data/mock";
-import { loadDataFromFile, saveDataToFile } from "../storage/storageService";
-import { formatISO } from "date-fns";
-import { Alert, Appearance, I18nManager } from "react-native";
-import { generateDemoLogs } from "../data/demoData";
-import i18n from "../i18n";
-import {
- cancelAllReminders,
- scheduleDailyReminder,
-} from "../services/notificationService";
+import { create } from 'zustand';
+import { Deed, DeedLog, DeedStatus } from '../data/models';
+import { GENERIC_STATUSES, MOCK_DEEDS, MOCK_LOGS, SUGGESTED_DEEDS } from '../data/mock';
+import { loadDataFromFile, saveDataToFile } from '../storage/storageService';
+import { formatISO } from 'date-fns';
+import { Alert, Appearance, I18nManager } from 'react-native';
+import { generateDemoLogs } from '../data/demoData';
+import i18n from '../i18n';
+import { cancelAllReminders, scheduleDailyReminder } from '../services/notificationService';
export type AppSettings = {
- theme: "system" | "light" | "dark";
+ theme: 'system' | 'light' | 'dark';
isHapticsEnabled: boolean;
isDevMode: boolean;
isDemoMode: boolean;
- language: "en" | "ar"; // Updated language options
+ language: 'en' | 'ar'; // Updated language options
isReminderEnabled: boolean;
reminderTime: string;
};
@@ -44,12 +36,7 @@ type AppState = AppData & {
type AppActions = {
initialize: () => Promise;
- addOrUpdateLog: (
- deed: Deed,
- date: Date,
- status: DeedStatus,
- value?: number,
- ) => void;
+ addOrUpdateLog: (deed: Deed, date: Date, status: DeedStatus, value?: number) => void;
addDeed: (deed: Deed) => void;
// DEED MANAGEMENT
initializeDraftDeed: (deedId?: string) => void;
@@ -59,8 +46,8 @@ type AppActions = {
deleteDeed: (deedId: string) => void;
setDeeds: (deeds: Deed[]) => void;
// SETTINGS
- setTheme: (theme: AppSettings["theme"]) => void;
- setLanguage: (language: AppSettings["language"]) => void;
+ setTheme: (theme: AppSettings['theme']) => void;
+ setLanguage: (language: AppSettings['language']) => void;
toggleHaptics: () => void;
setDevMode: (isDev: boolean) => void;
toggleDemoMode: () => Promise;
@@ -70,13 +57,13 @@ type AppActions = {
};
const defaultSettings: AppSettings = {
- theme: "system",
+ theme: 'system',
isHapticsEnabled: true,
isDevMode: false,
isDemoMode: false,
- language: "en",
+ language: 'en',
isReminderEnabled: false,
- reminderTime: "20:00",
+ reminderTime: '20:00',
};
const persistState = (state: AppState) => {
@@ -130,11 +117,11 @@ const useAppStore = create((set, get) => ({
});
}
- if (settings.theme !== "system") {
+ if (settings.theme !== 'system') {
Appearance.setColorScheme(settings.theme);
}
i18n.locale = settings.language;
- I18nManager.forceRTL(settings.language === "ar");
+ I18nManager.forceRTL(settings.language === 'ar');
if (settings.isReminderEnabled) {
scheduleDailyReminder(settings.reminderTime);
@@ -148,7 +135,7 @@ const useAppStore = create((set, get) => ({
if (currentLanguage === language) return;
i18n.locale = language;
- const isRTL = language === "ar";
+ const isRTL = language === 'ar';
const needsRestart = isRTL !== I18nManager.isRTL;
set((state) => ({ settings: { ...state.settings, language } }));
@@ -156,12 +143,12 @@ const useAppStore = create((set, get) => ({
if (needsRestart) {
Alert.alert(
- i18n.t("alerts.restartTitle"),
- i18n.t("alerts.restartMessage"),
+ i18n.t('alerts.restartTitle'),
+ i18n.t('alerts.restartMessage'),
[
{
- text: i18n.t("alerts.cancel"),
- style: "cancel",
+ text: i18n.t('alerts.cancel'),
+ style: 'cancel',
onPress: () => {
i18n.locale = currentLanguage;
set((state) => ({
@@ -171,8 +158,8 @@ const useAppStore = create((set, get) => ({
},
},
{
- text: i18n.t("alerts.restartNow"),
- style: "default",
+ text: i18n.t('alerts.restartNow'),
+ style: 'default',
onPress: async () => {
I18nManager.forceRTL(isRTL);
},
@@ -184,7 +171,7 @@ const useAppStore = create((set, get) => ({
},
addOrUpdateLog: (deed, date, status, value) => {
- const dateString = formatISO(date, { representation: "date" });
+ const dateString = formatISO(date, { representation: 'date' });
const currentLogs = get().logs;
const existingLogIndex = currentLogs.findIndex(
(log) => log.deedId === deed.id && log.date === dateString,
@@ -223,10 +210,10 @@ const useAppStore = create((set, get) => ({
} else {
set({
draftDeed: {
- name: "",
- icon: "star-outline",
- frequency: { type: "daily" },
- category: "CUSTOM",
+ name: '',
+ icon: 'star-outline',
+ frequency: { type: 'daily' },
+ category: 'CUSTOM',
statuses: GENERIC_STATUSES,
},
});
@@ -255,7 +242,7 @@ const useAppStore = create((set, get) => ({
id: `custom-${Date.now()}`,
name: draft.name,
icon: draft.icon,
- category: "CUSTOM",
+ category: 'CUSTOM',
statuses: GENERIC_STATUSES,
frequency: draft.frequency,
goal: draft.goal,
@@ -281,7 +268,7 @@ const useAppStore = create((set, get) => ({
setTheme: (theme) => {
set((state) => ({ settings: { ...state.settings, theme } }));
- if (theme === "system") {
+ if (theme === 'system') {
Appearance.setColorScheme(null);
} else {
Appearance.setColorScheme(theme);
@@ -320,10 +307,7 @@ const useAppStore = create((set, get) => ({
deeds: MOCK_DEEDS,
logs: generateDemoLogs(),
});
- Alert.alert(
- i18n.t("alerts.demoModeEnabledTitle"),
- i18n.t("alerts.demoModeEnabledMessage"),
- );
+ Alert.alert(i18n.t('alerts.demoModeEnabledTitle'), i18n.t('alerts.demoModeEnabledMessage'));
} else {
// Disabling Demo Mode: Reload user's original data from disk
const originalData = await loadDataFromFile();
@@ -331,10 +315,7 @@ const useAppStore = create((set, get) => ({
deeds: originalData?.deeds || MOCK_DEEDS,
logs: originalData?.logs || [],
});
- Alert.alert(
- i18n.t("alerts.demoModeDisabledTitle"),
- i18n.t("alerts.demoModeDisabledMessage"),
- );
+ Alert.alert(i18n.t('alerts.demoModeDisabledTitle'), i18n.t('alerts.demoModeDisabledMessage'));
}
// Persist the new state of `isDemoMode` in settings
persistState(get());
@@ -348,7 +329,7 @@ const useAppStore = create((set, get) => ({
};
set(initialState);
const theme = initialState.settings.theme;
- if (theme === "system") {
+ if (theme === 'system') {
Appearance.setColorScheme(null);
} else {
Appearance.setColorScheme(theme);
diff --git a/src/core/theme/ThemeContext.tsx b/src/core/theme/ThemeContext.tsx
index 4c6ebe9..d21ab90 100644
--- a/src/core/theme/ThemeContext.tsx
+++ b/src/core/theme/ThemeContext.tsx
@@ -1,8 +1,8 @@
// src/core/theme/ThemeContext.tsx
-import React, { createContext, useContext } from "react";
-import { useColorScheme } from "react-native";
-import { AppTheme, ColorTheme, theme as defaultTheme } from "@/constants/theme";
-import useAppStore from "../store/appStore";
+import React, { createContext, useContext } from 'react';
+import { useColorScheme } from 'react-native';
+import { AppTheme, ColorTheme, theme as defaultTheme } from '@/constants/theme';
+import useAppStore from '../store/appStore';
interface ThemeContextType {
theme: AppTheme;
@@ -16,13 +16,11 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
const userThemePreference = useAppStore((state) => state.settings.theme);
const activeColorScheme =
- userThemePreference === "system" ? systemColorScheme : userThemePreference;
+ userThemePreference === 'system' ? systemColorScheme : userThemePreference;
- const isDark = activeColorScheme === "dark";
+ const isDark = activeColorScheme === 'dark';
- const activeColors: ColorTheme = isDark
- ? defaultTheme.colors.dark
- : defaultTheme.colors.light;
+ const activeColors: ColorTheme = isDark ? defaultTheme.colors.dark : defaultTheme.colors.light;
const activeTheme: AppTheme = {
...defaultTheme,
@@ -30,16 +28,14 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
};
return (
-
- {children}
-
+ {children}
);
}
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
- throw new Error("useTheme must be used within a ThemeProvider");
+ throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
diff --git a/src/core/utils/dateFormatter.ts b/src/core/utils/dateFormatter.ts
index 0e4d413..24bf1ca 100644
--- a/src/core/utils/dateFormatter.ts
+++ b/src/core/utils/dateFormatter.ts
@@ -1,5 +1,5 @@
// src/core/utils/dateFormatter.ts
-import i18n from "../i18n";
+import i18n from '../i18n';
/**
* Formats a JavaScript Date object into a custom Hijri date string.
@@ -7,27 +7,26 @@ import i18n from "../i18n";
* @returns A formatted string, e.g., "Rab Awal 6, 1447".
*/
export const formatHijriDate = (date: Date): string => {
- const isArabic = i18n.locale.startsWith("ar");
+ const isArabic = i18n.locale.startsWith('ar');
// This formatter's only job is to give us the numeric parts of the Hijri date.
const hijriNumericParser = new Intl.DateTimeFormat(
- `${isArabic ? "ar" : "en"}-u-ca-islamic-nu-latn`,
+ `${isArabic ? 'ar' : 'en'}-u-ca-islamic-nu-latn`,
{
- year: "numeric",
- month: "numeric",
- day: "numeric",
+ year: 'numeric',
+ month: 'numeric',
+ day: 'numeric',
},
);
// Get the numeric parts of the date
const parts = hijriNumericParser.formatToParts(date);
- const day = parts.find((p) => p.type === "day")?.value;
- const monthIndex =
- parseInt(parts.find((p) => p.type === "month")?.value || "1", 10) - 1;
- const year = parts.find((p) => p.type === "year")?.value;
+ const day = parts.find((p) => p.type === 'day')?.value;
+ const monthIndex = parseInt(parts.find((p) => p.type === 'month')?.value || '1', 10) - 1;
+ const year = parts.find((p) => p.type === 'year')?.value;
// Look up our custom month name from the i18n files
- const hijriMonthName = (i18n.t("hijri_months") as string[])[monthIndex];
+ const hijriMonthName = (i18n.t('hijri_months') as string[])[monthIndex];
// Construct the final string exactly as desired
if (isArabic) {
diff --git a/src/core/utils/haptics.ts b/src/core/utils/haptics.ts
index 126e4f1..5829ecc 100644
--- a/src/core/utils/haptics.ts
+++ b/src/core/utils/haptics.ts
@@ -1,6 +1,6 @@
// src/core/utils/haptics.ts
-import * as Haptics from "expo-haptics";
-import useAppStore from "../store/appStore";
+import * as Haptics from 'expo-haptics';
+import useAppStore from '../store/appStore';
/**
* Triggers haptic feedback if the user has it enabled in settings.
diff --git a/src/features/deeds/ChildDeedLogItem.tsx b/src/features/deeds/ChildDeedLogItem.tsx
index 55c4310..40aff88 100644
--- a/src/features/deeds/ChildDeedLogItem.tsx
+++ b/src/features/deeds/ChildDeedLogItem.tsx
@@ -1,15 +1,15 @@
// src/features/deeds/ChildDeedLogItem.tsx
-import { Box } from "@/components/base/Box";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { Deed, DeedLog, DeedStatus } from "@/core/data/models";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { triggerHaptic } from "@/core/utils/haptics";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import React from "react";
-import { StyleSheet, TouchableOpacity, View } from "react-native";
+import { Box } from '@/components/base/Box';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { Deed, DeedLog, DeedStatus } from '@/core/data/models';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { triggerHaptic } from '@/core/utils/haptics';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import React from 'react';
+import { StyleSheet, TouchableOpacity, View } from 'react-native';
type ChildDeedLogItemProps = {
deed: Deed;
@@ -17,11 +17,7 @@ type ChildDeedLogItemProps = {
date: Date;
};
-export const ChildDeedLogItem = ({
- deed,
- log,
- date,
-}: ChildDeedLogItemProps) => {
+export const ChildDeedLogItem = ({ deed, log, date }: ChildDeedLogItemProps) => {
const { theme } = useTheme();
const styles = getStyles(theme);
const addOrUpdateLog = useAppStore((state) => state.addOrUpdateLog);
@@ -33,11 +29,7 @@ export const ChildDeedLogItem = ({
return (
-
+
{i18n.t(`deeds_names.${deed.id}`, { defaultValue: deed.name })}
@@ -52,16 +44,11 @@ export const ChildDeedLogItem = ({
{ borderColor: theme.colors[status.color] },
isSelected && { backgroundColor: theme.colors[status.color] },
]}
- onPress={() => handleLogStatus(status)}
- >
+ onPress={() => handleLogStatus(status)}>
);
@@ -74,8 +61,8 @@ export const ChildDeedLogItem = ({
const getStyles = (theme: AppTheme) =>
StyleSheet.create({
container: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
paddingVertical: theme.spacing.s,
},
deedName: {
@@ -84,7 +71,7 @@ const getStyles = (theme: AppTheme) =>
fontSize: theme.typography.fontSize.m,
},
actionsContainer: {
- flexDirection: "row",
+ flexDirection: 'row',
},
statusButton: {
padding: theme.spacing.s,
diff --git a/src/features/deeds/DeedListItem.tsx b/src/features/deeds/DeedListItem.tsx
index c42069f..d7a39fa 100644
--- a/src/features/deeds/DeedListItem.tsx
+++ b/src/features/deeds/DeedListItem.tsx
@@ -1,13 +1,13 @@
// src/features/deeds/DeedListItem.tsx
-import { Box } from "@/components/base/Box";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { Deed, DeedLog } from "@/core/data/models";
-import i18n from "@/core/i18n";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import React from "react";
-import { StyleSheet, TouchableOpacity } from "react-native";
+import { Box } from '@/components/base/Box';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { Deed, DeedLog } from '@/core/data/models';
+import i18n from '@/core/i18n';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import React from 'react';
+import { StyleSheet, TouchableOpacity } from 'react-native';
type DeedListItemProps = {
deed: Deed;
@@ -24,21 +24,15 @@ const renderStatus = (
if (!log) return null;
// Render goal progress if the deed has a goal and a value is logged
- if (deed.goal && typeof log.value === "number") {
+ if (deed.goal && typeof log.value === 'number') {
const isComplete = log.value >= deed.goal.value;
return (
-
+
+ ]}>
{log.value} / {deed.goal.value}
@@ -50,17 +44,8 @@ const renderStatus = (
if (!status) return null;
return (
-
-
+
+
);
};
@@ -72,11 +57,7 @@ export const DeedListItem = ({ deed, log, onPress }: DeedListItemProps) => {
return (
-
+
{i18n.t(`deeds_names.${deed.id}`, { defaultValue: deed.name })}
@@ -90,8 +71,8 @@ export const DeedListItem = ({ deed, log, onPress }: DeedListItemProps) => {
const getStyles = (theme: AppTheme) =>
StyleSheet.create({
container: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
padding: theme.spacing.m,
backgroundColor: theme.colors.foreground,
borderRadius: 8,
@@ -106,8 +87,8 @@ const getStyles = (theme: AppTheme) =>
paddingVertical: theme.spacing.xs,
paddingHorizontal: theme.spacing.s,
borderRadius: 16,
- justifyContent: "center",
- alignItems: "center",
+ justifyContent: 'center',
+ alignItems: 'center',
minWidth: 32,
},
goalInProgress: {
diff --git a/src/features/deeds/DeedManagerListItem.tsx b/src/features/deeds/DeedManagerListItem.tsx
index 31b3ee0..2038743 100644
--- a/src/features/deeds/DeedManagerListItem.tsx
+++ b/src/features/deeds/DeedManagerListItem.tsx
@@ -1,19 +1,19 @@
// FILE: src/features/deeds/DeedManagerListItem.tsx
-import { Box } from "@/components/base/Box";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { Deed } from "@/core/data/models";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { triggerHaptic } from "@/core/utils/haptics";
-import { HomeStackParamList } from "@/navigation/AppNavigator";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { useNavigation } from "@react-navigation/native";
-import { NativeStackNavigationProp } from "@react-navigation/native-stack";
-import React from "react";
-import { Alert, StyleSheet, TouchableOpacity } from "react-native";
-import { ScaleDecorator } from "react-native-draggable-flatlist";
+import { Box } from '@/components/base/Box';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { Deed } from '@/core/data/models';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { triggerHaptic } from '@/core/utils/haptics';
+import { HomeStackParamList } from '@/navigation/AppNavigator';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { useNavigation } from '@react-navigation/native';
+import { NativeStackNavigationProp } from '@react-navigation/native-stack';
+import React from 'react';
+import { Alert, StyleSheet, TouchableOpacity } from 'react-native';
+import { ScaleDecorator } from 'react-native-draggable-flatlist';
type DeedManagerListItemProps = {
deed: Deed;
@@ -23,11 +23,7 @@ type DeedManagerListItemProps = {
type DeedManagerNavigationProp = NativeStackNavigationProp;
-export const DeedManagerListItem = ({
- deed,
- drag,
- isDragging,
-}: DeedManagerListItemProps) => {
+export const DeedManagerListItem = ({ deed, drag, isDragging }: DeedManagerListItemProps) => {
const { theme } = useTheme();
const styles = getStyles(theme);
const navigation = useNavigation();
@@ -37,7 +33,7 @@ export const DeedManagerListItem = ({
const handleEdit = () => {
triggerHaptic();
- navigation.navigate("CreateDeed", { deedId: deed.id });
+ navigation.navigate('CreateDeed', { deedId: deed.id });
};
const handleDelete = () => {
@@ -46,13 +42,13 @@ export const DeedManagerListItem = ({
defaultValue: deed.name,
});
Alert.alert(
- i18n.t("alerts.deleteDeedTitle"),
- i18n.t("alerts.deleteDeedMessage", { deedName }),
+ i18n.t('alerts.deleteDeedTitle'),
+ i18n.t('alerts.deleteDeedMessage', { deedName }),
[
- { text: i18n.t("alerts.cancel"), style: "cancel" },
+ { text: i18n.t('alerts.cancel'), style: 'cancel' },
{
- text: i18n.t("alerts.delete"),
- style: "destructive",
+ text: i18n.t('alerts.delete'),
+ style: 'destructive',
onPress: () => deleteDeed(deed.id),
},
],
@@ -65,8 +61,7 @@ export const DeedManagerListItem = ({
onLongPress={drag}
delayLongPress={150}
activeOpacity={0.8}
- style={[styles.container, isDragging && styles.dragging]}
- >
+ style={[styles.container, isDragging && styles.dragging]}>
-
+
StyleSheet.create({
container: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
paddingEnd: theme.spacing.s,
backgroundColor: theme.colors.foreground,
borderRadius: 12,
marginBottom: theme.spacing.s,
},
dragging: {
- shadowColor: "#000",
+ shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
@@ -138,7 +130,7 @@ const getStyles = (theme: AppTheme) =>
flex: 1,
},
actionsContainer: {
- flexDirection: "row",
+ flexDirection: 'row',
},
actionButton: {
padding: theme.spacing.s,
diff --git a/src/features/deeds/IconSelectorModal.tsx b/src/features/deeds/IconSelectorModal.tsx
index 8a0d24f..5bce620 100644
--- a/src/features/deeds/IconSelectorModal.tsx
+++ b/src/features/deeds/IconSelectorModal.tsx
@@ -1,11 +1,11 @@
// src/features/deeds/IconSelectorModal.tsx
-import { ThemedText } from "@/components/base/ThemedText";
-import { customDeedIcons } from "@/constants/icons";
-import { AppTheme } from "@/constants/theme";
-import i18n from "@/core/i18n";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import React from "react";
+import { ThemedText } from '@/components/base/ThemedText';
+import { customDeedIcons } from '@/constants/icons';
+import { AppTheme } from '@/constants/theme';
+import i18n from '@/core/i18n';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import React from 'react';
import {
FlatList,
Modal,
@@ -14,24 +14,18 @@ import {
TouchableWithoutFeedback,
useWindowDimensions,
View,
-} from "react-native";
+} from 'react-native';
type IconSelectorModalProps = {
isVisible: boolean;
onClose: () => void;
- onSelectIcon: (
- icon: React.ComponentProps["name"],
- ) => void;
+ onSelectIcon: (icon: React.ComponentProps['name']) => void;
};
const ICON_SIZE = 40;
const PADDING = 16;
-export const IconSelectorModal = ({
- isVisible,
- onClose,
- onSelectIcon,
-}: IconSelectorModalProps) => {
+export const IconSelectorModal = ({ isVisible, onClose, onSelectIcon }: IconSelectorModalProps) => {
const { theme } = useTheme();
const styles = getStyles(theme);
const { width } = useWindowDimensions();
@@ -39,29 +33,19 @@ export const IconSelectorModal = ({
const numColumns = Math.floor((width - PADDING * 2) / (ICON_SIZE + PADDING));
return (
-
+
{/* This View prevents the close event from firing when tapping the container */}
-
- {i18n.t("iconSelector.title")}
-
+ {i18n.t('iconSelector.title')}
item}
numColumns={numColumns}
renderItem={({ item }) => (
- onSelectIcon(item)}
- >
+ onSelectIcon(item)}>
StyleSheet.create({
overlay: {
flex: 1,
- justifyContent: "center",
- alignItems: "center",
- backgroundColor: "rgba(0, 0, 0, 0.6)",
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
padding: PADDING,
},
container: {
- width: "100%",
- height: "80%", // Use height instead of maxHeight
+ width: '100%',
+ height: '80%', // Use height instead of maxHeight
backgroundColor: theme.colors.foreground,
borderRadius: 16,
padding: PADDING,
@@ -98,11 +82,11 @@ const getStyles = (theme: AppTheme) =>
title: {
fontSize: theme.typography.fontSize.l,
fontWeight: theme.typography.fontWeight.semibold,
- textAlign: "center",
+ textAlign: 'center',
marginBottom: theme.spacing.m,
},
grid: {
- justifyContent: "center",
+ justifyContent: 'center',
},
iconButton: {
padding: theme.spacing.s,
diff --git a/src/features/deeds/LogDeedModal.tsx b/src/features/deeds/LogDeedModal.tsx
index e412756..48fc969 100644
--- a/src/features/deeds/LogDeedModal.tsx
+++ b/src/features/deeds/LogDeedModal.tsx
@@ -1,22 +1,16 @@
// src/features/deeds/LogDeedModal.tsx
-import { Box } from "@/components/base/Box";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { Deed, DeedStatus } from "@/core/data/models";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { formatISO } from "date-fns";
-import React, { useMemo } from "react";
-import {
- Modal,
- StyleSheet,
- TouchableOpacity,
- View,
- ScrollView,
-} from "react-native";
-import { ChildDeedLogItem } from "./ChildDeedLogItem";
+import { Box } from '@/components/base/Box';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { Deed, DeedStatus } from '@/core/data/models';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { formatISO } from 'date-fns';
+import React, { useMemo } from 'react';
+import { Modal, StyleSheet, TouchableOpacity, View, ScrollView } from 'react-native';
+import { ChildDeedLogItem } from './ChildDeedLogItem';
type LogDeedModalProps = {
deed: Deed | null;
@@ -45,7 +39,7 @@ export const LogDeedModal = ({
}, [deed, allDeeds]);
const logsForDate = useMemo(() => {
- const dateString = formatISO(date, { representation: "date" });
+ const dateString = formatISO(date, { representation: 'date' });
return allLogs.filter((log) => log.date === dateString);
}, [date, allLogs]);
@@ -56,41 +50,27 @@ export const LogDeedModal = ({
});
return (
-
-
+
+
-
+
- {i18n.t("deeds.logDeedTitle", { deedName })}
+ {i18n.t('deeds.logDeedTitle', { deedName })}
{deed.statuses.map((status) => {
const statusLabelKey =
- deed.isCore || status.id !== "missed"
+ deed.isCore || status.id !== 'missed'
? `deeds_statuses.${status.id}`
: `deeds_statuses.generic-missed`;
return (
onLogStatus(status)}
- >
+ onPress={() => onLogStatus(status)}>
{i18n.t(statusLabelKey, {
- defaultValue: status.id.replace("-", " "),
+ defaultValue: status.id.replace('-', ' '),
})}
@@ -109,18 +89,11 @@ export const LogDeedModal = ({
{childDeeds.length > 0 && (
- {i18n.t("deeds.linkedDeeds")}
+ {i18n.t('deeds.linkedDeeds')}
{childDeeds.map((child) => {
const log = logsForDate.find((l) => l.deedId === child.id);
- return (
-
- );
+ return ;
})}
)}
@@ -135,31 +108,31 @@ const getStyles = (theme: AppTheme) =>
StyleSheet.create({
overlay: {
flex: 1,
- justifyContent: "center",
- alignItems: "center",
- backgroundColor: "rgba(0, 0, 0, 0.6)",
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
padding: theme.spacing.l,
},
container: {
- width: "100%",
- maxHeight: "80%",
+ width: '100%',
+ maxHeight: '80%',
backgroundColor: theme.colors.foreground,
borderRadius: 16,
padding: theme.spacing.l,
},
header: {
- alignItems: "center",
+ alignItems: 'center',
marginBottom: theme.spacing.m,
},
title: {
fontSize: theme.typography.fontSize.l,
fontWeight: theme.typography.fontWeight.semibold,
- textAlign: "center",
+ textAlign: 'center',
marginTop: theme.spacing.m,
},
optionRow: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
paddingVertical: theme.spacing.m,
},
optionLabel: {
@@ -176,7 +149,7 @@ const getStyles = (theme: AppTheme) =>
fontSize: theme.typography.fontSize.s,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textSecondary,
- textTransform: "uppercase",
+ textTransform: 'uppercase',
marginBottom: theme.spacing.s,
},
});
diff --git a/src/features/deeds/LogGoalModal.tsx b/src/features/deeds/LogGoalModal.tsx
index 0842ca8..24dc8d4 100644
--- a/src/features/deeds/LogGoalModal.tsx
+++ b/src/features/deeds/LogGoalModal.tsx
@@ -1,15 +1,15 @@
// src/features/deeds/LogGoalModal.tsx
-import { Box } from "@/components/base/Box";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { Deed, DeedLog } from "@/core/data/models";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { triggerHaptic } from "@/core/utils/haptics";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { formatISO } from "date-fns";
-import React, { useEffect, useMemo, useState } from "react";
+import { Box } from '@/components/base/Box';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { Deed, DeedLog } from '@/core/data/models';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { triggerHaptic } from '@/core/utils/haptics';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { formatISO } from 'date-fns';
+import React, { useEffect, useMemo, useState } from 'react';
import {
Modal,
ScrollView,
@@ -17,8 +17,8 @@ import {
TouchableOpacity,
TouchableWithoutFeedback,
View,
-} from "react-native";
-import { ChildDeedLogItem } from "./ChildDeedLogItem";
+} from 'react-native';
+import { ChildDeedLogItem } from './ChildDeedLogItem';
type LogGoalModalProps = {
deed: Deed | null;
@@ -50,7 +50,7 @@ export const LogGoalModal = ({
}, [deed, allDeeds]);
const logsForDate = useMemo(() => {
- const dateString = formatISO(date, { representation: "date" });
+ const dateString = formatISO(date, { representation: 'date' });
return allLogs.filter((log) => log.date === dateString);
}, [date, allLogs]);
@@ -80,32 +80,20 @@ export const LogGoalModal = ({
});
return (
-
+
-
+
- {i18n.t("deeds.logDeedTitle", { deedName })}
+ {i18n.t('deeds.logDeedTitle', { deedName })}
-
+
{value}
-
- {deed.goal.unit}
-
+ {deed.goal.unit}
-
+
-
- {i18n.t("deeds.save")}
-
+ {i18n.t('deeds.save')}
{childDeeds.length > 0 && (
- {i18n.t("deeds.linkedDeeds")}
+ {i18n.t('deeds.linkedDeeds')}
{childDeeds.map((child) => {
- const childLog = logsForDate.find(
- (l) => l.deedId === child.id,
- );
+ const childLog = logsForDate.find((l) => l.deedId === child.id);
return (
-
+
);
})}
@@ -170,40 +144,40 @@ const getStyles = (theme: AppTheme) =>
StyleSheet.create({
overlay: {
flex: 1,
- justifyContent: "center",
- alignItems: "center",
- backgroundColor: "rgba(0, 0, 0, 0.6)",
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
padding: theme.spacing.l,
},
container: {
- width: "100%",
- maxHeight: "80%",
+ width: '100%',
+ maxHeight: '80%',
backgroundColor: theme.colors.foreground,
borderRadius: 16,
padding: theme.spacing.l,
},
header: {
- alignItems: "center",
+ alignItems: 'center',
marginBottom: theme.spacing.m,
},
title: {
fontSize: theme.typography.fontSize.l,
fontWeight: theme.typography.fontWeight.semibold,
- textAlign: "center",
+ textAlign: 'center',
marginTop: theme.spacing.m,
},
counterContainer: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "space-around",
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-around',
marginBottom: theme.spacing.m,
},
counterButton: {
padding: theme.spacing.s,
},
valueDisplay: {
- alignItems: "center",
- justifyContent: "center",
+ alignItems: 'center',
+ justifyContent: 'center',
minWidth: 100,
},
counterValue: {
@@ -219,7 +193,7 @@ const getStyles = (theme: AppTheme) =>
backgroundColor: theme.colors.primary,
padding: theme.spacing.m,
borderRadius: 8,
- alignItems: "center",
+ alignItems: 'center',
marginBottom: theme.spacing.l,
},
saveButtonText: {
@@ -236,7 +210,7 @@ const getStyles = (theme: AppTheme) =>
fontSize: theme.typography.fontSize.s,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textSecondary,
- textTransform: "uppercase",
+ textTransform: 'uppercase',
marginBottom: theme.spacing.s,
},
});
diff --git a/src/features/deeds/SuggestedDeedListItem.tsx b/src/features/deeds/SuggestedDeedListItem.tsx
index 8f7ef4a..52515f0 100644
--- a/src/features/deeds/SuggestedDeedListItem.tsx
+++ b/src/features/deeds/SuggestedDeedListItem.tsx
@@ -1,13 +1,13 @@
// src/features/deeds/SuggestedDeedListItem.tsx
-import { Box } from "@/components/base/Box";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { Deed } from "@/core/data/models";
-import i18n from "@/core/i18n";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import React from "react";
-import { StyleSheet, TouchableOpacity } from "react-native";
+import { Box } from '@/components/base/Box';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { Deed } from '@/core/data/models';
+import i18n from '@/core/i18n';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import React from 'react';
+import { StyleSheet, TouchableOpacity } from 'react-native';
type SuggestedDeedListItemProps = {
deed: Deed;
@@ -15,31 +15,22 @@ type SuggestedDeedListItemProps = {
onPress: () => void;
};
-export const SuggestedDeedListItem = ({
- deed,
- isAdded,
- onPress,
-}: SuggestedDeedListItemProps) => {
+export const SuggestedDeedListItem = ({ deed, isAdded, onPress }: SuggestedDeedListItemProps) => {
const { theme } = useTheme();
const styles = getStyles(theme);
return (
-
+
{i18n.t(`deeds_names.${deed.id}`, { defaultValue: deed.name })}
+ style={[styles.addButton, isAdded && styles.addedButton]}>
@@ -51,8 +42,8 @@ export const SuggestedDeedListItem = ({
const getStyles = (theme: AppTheme) =>
StyleSheet.create({
container: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
padding: theme.spacing.m,
backgroundColor: theme.colors.foreground,
borderRadius: 8,
diff --git a/src/features/home/DateItem.tsx b/src/features/home/DateItem.tsx
index 3f25e62..243bca2 100644
--- a/src/features/home/DateItem.tsx
+++ b/src/features/home/DateItem.tsx
@@ -1,10 +1,10 @@
// src/features/home/DateItem.tsx
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { format, Locale } from "date-fns";
-import React from "react";
-import { StyleSheet, TouchableOpacity } from "react-native";
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { format, Locale } from 'date-fns';
+import React from 'react';
+import { StyleSheet, TouchableOpacity } from 'react-native';
export const ITEM_MARGIN = 4;
@@ -28,14 +28,11 @@ const DateItemComponent = ({
const { theme } = useTheme();
const styles = getStyles(theme, isSelected, isToday);
- const dayName = format(date, "E", { locale });
- const dayNumber = format(date, "d", { locale });
+ const dayName = format(date, 'E', { locale });
+ const dayNumber = format(date, 'd', { locale });
return (
-
+
{dayName}
@@ -49,25 +46,21 @@ export const DateItem = React.memo(DateItemComponent);
const getStyles = (theme: AppTheme, isSelected: boolean, isToday: boolean) =>
StyleSheet.create({
container: {
- height: "100%",
- justifyContent: "center",
- alignItems: "center",
+ height: '100%',
+ justifyContent: 'center',
+ alignItems: 'center',
paddingVertical: theme.spacing.s,
paddingHorizontal: theme.spacing.xs,
borderRadius: 8,
borderWidth: 2,
- borderColor: isToday ? theme.colors.primary : "transparent",
- backgroundColor: isSelected
- ? theme.colors.primary
- : theme.colors.foreground,
+ borderColor: isToday ? theme.colors.primary : 'transparent',
+ backgroundColor: isSelected ? theme.colors.primary : theme.colors.foreground,
marginHorizontal: ITEM_MARGIN,
},
dayName: {
fontSize: theme.typography.fontSize.s,
fontWeight: theme.typography.fontWeight.medium,
- color: isSelected
- ? theme.colors.primaryContrast
- : theme.colors.textSecondary,
+ color: isSelected ? theme.colors.primaryContrast : theme.colors.textSecondary,
},
dayNumber: {
fontSize: theme.typography.fontSize.l,
diff --git a/src/features/home/DateScroller.tsx b/src/features/home/DateScroller.tsx
index 1107509..a878e32 100644
--- a/src/features/home/DateScroller.tsx
+++ b/src/features/home/DateScroller.tsx
@@ -7,10 +7,10 @@ import {
startOfWeek,
subYears,
Locale,
-} from "date-fns";
-import React, { useCallback, useMemo, useRef } from "react";
-import { FlatList, useWindowDimensions, I18nManager } from "react-native";
-import { DateItem, ITEM_MARGIN } from "./DateItem";
+} from 'date-fns';
+import React, { useCallback, useMemo, useRef } from 'react';
+import { FlatList, useWindowDimensions, I18nManager } from 'react-native';
+import { DateItem, ITEM_MARGIN } from './DateItem';
type DateScrollerProps = {
selectedDate: Date;
@@ -18,11 +18,7 @@ type DateScrollerProps = {
locale: Locale;
};
-const DateScrollerComponent = ({
- selectedDate,
- onDateSelect,
- locale,
-}: DateScrollerProps) => {
+const DateScrollerComponent = ({ selectedDate, onDateSelect, locale }: DateScrollerProps) => {
const flatListRef = useRef(null);
const { width: screenWidth } = useWindowDimensions();
@@ -30,8 +26,7 @@ const DateScrollerComponent = ({
const containerPadding = 32;
const itemWidth =
- (screenWidth - containerPadding - numberOfVisibleDays * ITEM_MARGIN * 2) /
- numberOfVisibleDays;
+ (screenWidth - containerPadding - numberOfVisibleDays * ITEM_MARGIN * 2) / numberOfVisibleDays;
const dateRange = useMemo(() => {
const today = new Date();
diff --git a/src/features/settings/DevMenuModal.tsx b/src/features/settings/DevMenuModal.tsx
index 01bae18..f070788 100644
--- a/src/features/settings/DevMenuModal.tsx
+++ b/src/features/settings/DevMenuModal.tsx
@@ -1,17 +1,11 @@
// src/features/settings/DevMenuModal.tsx
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import React from "react";
-import {
- Modal,
- StyleSheet,
- Switch,
- TouchableWithoutFeedback,
- View,
-} from "react-native";
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import React from 'react';
+import { Modal, StyleSheet, Switch, TouchableWithoutFeedback, View } from 'react-native';
type DevMenuModalProps = {
isVisible: boolean;
@@ -25,23 +19,14 @@ export const DevMenuModal = ({ isVisible, onClose }: DevMenuModalProps) => {
const isDemoMode = useAppStore((state) => state.settings.isDemoMode);
return (
-
+
-
- {i18n.t("devMenu.title")}
-
+ {i18n.t('devMenu.title')}
-
- {i18n.t("devMenu.enableDemo")}
-
+ {i18n.t('devMenu.enableDemo')}
StyleSheet.create({
overlay: {
flex: 1,
- justifyContent: "center",
- alignItems: "center",
- backgroundColor: "rgba(0, 0, 0, 0.6)",
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
padding: theme.spacing.l,
},
container: {
- width: "100%",
+ width: '100%',
backgroundColor: theme.colors.foreground,
borderRadius: 16,
padding: theme.spacing.m,
@@ -78,13 +63,13 @@ const getStyles = (theme: AppTheme) =>
title: {
fontSize: theme.typography.fontSize.l,
fontWeight: theme.typography.fontWeight.semibold,
- textAlign: "center",
+ textAlign: 'center',
marginBottom: theme.spacing.m,
},
optionRow: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "space-between",
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
paddingVertical: theme.spacing.m,
paddingHorizontal: theme.spacing.s,
},
diff --git a/src/features/settings/SettingsListItem.tsx b/src/features/settings/SettingsListItem.tsx
index 19be26c..4b55dd5 100644
--- a/src/features/settings/SettingsListItem.tsx
+++ b/src/features/settings/SettingsListItem.tsx
@@ -1,19 +1,14 @@
// src/features/settings/SettingsListItem.tsx
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { triggerHaptic } from "@/core/utils/haptics";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import React from "react";
-import {
- StyleSheet,
- Switch,
- TouchableOpacity,
- I18nManager,
-} from "react-native";
-import { SettingsItem } from "./settingsData";
-import i18n from "@/core/i18n";
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { triggerHaptic } from '@/core/utils/haptics';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import React from 'react';
+import { StyleSheet, Switch, TouchableOpacity, I18nManager } from 'react-native';
+import { SettingsItem } from './settingsData';
+import i18n from '@/core/i18n';
type SettingsListItemProps = {
item: SettingsItem;
@@ -22,22 +17,17 @@ type SettingsListItemProps = {
onPress: (item: SettingsItem) => void;
};
-export const SettingsListItem = ({
- item,
- isFirst,
- isLast,
- onPress,
-}: SettingsListItemProps) => {
+export const SettingsListItem = ({ item, isFirst, isLast, onPress }: SettingsListItemProps) => {
const { theme } = useTheme();
const styles = getStyles(theme);
const settings = useAppStore((state) => state.settings);
const toggleHaptics = useAppStore((state) => state.toggleHaptics);
- const value = item.type === "toggle" ? settings[item.stateKey] : false;
+ const value = item.type === 'toggle' ? settings[item.stateKey] : false;
const handleToggle = () => {
- if (item.type === "toggle" && item.stateKey === "isHapticsEnabled") {
+ if (item.type === 'toggle' && item.stateKey === 'isHapticsEnabled') {
toggleHaptics();
triggerHaptic();
}
@@ -48,39 +38,26 @@ export const SettingsListItem = ({
onPress(item);
};
- const containerStyle = [
- styles.container,
- isFirst && styles.isFirst,
- isLast && styles.isLast,
- ];
+ const containerStyle = [styles.container, isFirst && styles.isFirst, isLast && styles.isLast];
return (
+ disabled={item.type === 'toggle'}
+ activeOpacity={0.7}>
+ style={[styles.label, item.type === 'action' && item.color ? { color: item.color } : {}]}>
{item.label}
- {item.type === "toggle" && (
+ {item.type === 'toggle' && (
)}
- {(item.type === "navigation" ||
- item.type === "modal" ||
- (item.type === "action" &&
- item.label !== i18n.t("settings.privacy") &&
- !item.color)) && (
+ {(item.type === 'navigation' ||
+ item.type === 'modal' ||
+ (item.type === 'action' && item.label !== i18n.t('settings.privacy') && !item.color)) && (
@@ -110,8 +85,8 @@ export const SettingsListItem = ({
const getStyles = (theme: AppTheme) =>
StyleSheet.create({
container: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
paddingHorizontal: theme.spacing.m,
paddingVertical: theme.spacing.s + 4,
backgroundColor: theme.colors.foreground,
diff --git a/src/features/settings/SettingsOptionModal.tsx b/src/features/settings/SettingsOptionModal.tsx
index 10b52e1..116f211 100644
--- a/src/features/settings/SettingsOptionModal.tsx
+++ b/src/features/settings/SettingsOptionModal.tsx
@@ -1,29 +1,20 @@
// src/features/settings/SettingsOptionModal.tsx
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import useAppStore, { AppSettings } from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { triggerHaptic } from "@/core/utils/haptics";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import React from "react";
-import {
- Modal,
- StyleSheet,
- TouchableOpacity,
- TouchableWithoutFeedback,
- View,
-} from "react-native";
-import { ModalSettingsItem } from "./settingsData";
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import useAppStore, { AppSettings } from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { triggerHaptic } from '@/core/utils/haptics';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import React from 'react';
+import { Modal, StyleSheet, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
+import { ModalSettingsItem } from './settingsData';
type SettingsOptionModalProps = {
modalData: ModalSettingsItem | null;
onClose: () => void;
};
-export const SettingsOptionModal = ({
- modalData,
- onClose,
-}: SettingsOptionModalProps) => {
+export const SettingsOptionModal = ({ modalData, onClose }: SettingsOptionModalProps) => {
const { theme } = useTheme();
const styles = getStyles(theme);
const { settings, setTheme, setLanguage } = useAppStore();
@@ -36,21 +27,16 @@ export const SettingsOptionModal = ({
triggerHaptic();
onClose();
setTimeout(() => {
- if (modalData.stateKey === "theme") {
+ if (modalData.stateKey === 'theme') {
setTheme(value);
- } else if (modalData.stateKey === "language") {
+ } else if (modalData.stateKey === 'language') {
setLanguage(value);
}
}, 250);
};
return (
-
+
@@ -60,17 +46,10 @@ export const SettingsOptionModal = ({
handleSelect(option.value)}
- >
-
- {option.label}
-
+ onPress={() => handleSelect(option.value)}>
+ {option.label}
{currentValue === option.value && (
-
+
)}
))}
@@ -86,13 +65,13 @@ const getStyles = (theme: AppTheme) =>
StyleSheet.create({
overlay: {
flex: 1,
- justifyContent: "center",
- alignItems: "center",
- backgroundColor: "rgba(0, 0, 0, 0.6)",
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
padding: theme.spacing.l,
},
container: {
- width: "100%",
+ width: '100%',
backgroundColor: theme.colors.foreground,
borderRadius: 16,
padding: theme.spacing.m,
@@ -100,12 +79,12 @@ const getStyles = (theme: AppTheme) =>
title: {
fontSize: theme.typography.fontSize.l,
fontWeight: theme.typography.fontWeight.semibold,
- textAlign: "center",
+ textAlign: 'center',
marginBottom: theme.spacing.m,
},
optionRow: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
paddingVertical: theme.spacing.m,
paddingHorizontal: theme.spacing.s,
},
diff --git a/src/features/settings/settingsData.ts b/src/features/settings/settingsData.ts
index 2ba25ac..5b98eb8 100644
--- a/src/features/settings/settingsData.ts
+++ b/src/features/settings/settingsData.ts
@@ -1,35 +1,34 @@
// FILE: src/features/settings/settingsData.ts
-import i18n from "@/core/i18n";
-import { AppSettings } from "@/core/store/appStore";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { SettingsStackParamList } from "@/navigation/AppNavigator";
+import i18n from '@/core/i18n';
+import { AppSettings } from '@/core/store/appStore';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { SettingsStackParamList } from '@/navigation/AppNavigator';
// --- Type Definitions ---
type BaseSettingsItem = {
label: string;
- icon: React.ComponentProps["name"];
+ icon: React.ComponentProps['name'];
};
export type NavigationSettingsItem = BaseSettingsItem & {
- type: "navigation";
+ type: 'navigation';
path: keyof SettingsStackParamList;
};
export type ToggleSettingsItem = BaseSettingsItem & {
- type: "toggle";
- stateKey: keyof Pick;
+ type: 'toggle';
+ stateKey: keyof Pick;
};
-export type ModalSettingsItem =
- BaseSettingsItem & {
- type: "modal";
- title: string;
- stateKey: T;
- options: { label: string; value: AppSettings[T] }[];
- };
+export type ModalSettingsItem = BaseSettingsItem & {
+ type: 'modal';
+ title: string;
+ stateKey: T;
+ options: { label: string; value: AppSettings[T] }[];
+};
export type ActionSettingsItem = BaseSettingsItem & {
- type: "action";
+ type: 'action';
action: () => void;
color?: string;
};
@@ -37,8 +36,8 @@ export type ActionSettingsItem = BaseSettingsItem & {
export type SettingsItem =
| NavigationSettingsItem
| ToggleSettingsItem
- | ModalSettingsItem<"theme">
- | ModalSettingsItem<"language">
+ | ModalSettingsItem<'theme'>
+ | ModalSettingsItem<'language'>
| ActionSettingsItem;
export type SettingsSection = {
@@ -47,93 +46,91 @@ export type SettingsSection = {
};
// --- Data Definition ---
-export const getSettingsData = (actions: {
- [key: string]: () => void;
-}): SettingsSection[] => [
+export const getSettingsData = (actions: { [key: string]: () => void }): SettingsSection[] => [
{
- title: i18n.t("settings.mainSettingsTitle"),
+ title: i18n.t('settings.mainSettingsTitle'),
data: [
{
- type: "navigation",
- label: i18n.t("settings.deedManager"),
- icon: "format-list-bulleted-square",
- path: "DeedManager",
+ type: 'navigation',
+ label: i18n.t('settings.deedManager'),
+ icon: 'format-list-bulleted-square',
+ path: 'DeedManager',
},
{
- type: "navigation",
- label: i18n.t("settings.notifications"),
- icon: "bell-outline",
- path: "Notifications",
+ type: 'navigation',
+ label: i18n.t('settings.notifications'),
+ icon: 'bell-outline',
+ path: 'Notifications',
},
{
- type: "modal",
- label: i18n.t("settings.appearance"),
- icon: "palette-outline",
- title: i18n.t("settings.appearanceTitle"),
- stateKey: "theme",
+ type: 'modal',
+ label: i18n.t('settings.appearance'),
+ icon: 'palette-outline',
+ title: i18n.t('settings.appearanceTitle'),
+ stateKey: 'theme',
options: [
- { label: i18n.t("settings.system"), value: "system" },
- { label: i18n.t("settings.light"), value: "light" },
- { label: i18n.t("settings.dark"), value: "dark" },
+ { label: i18n.t('settings.system'), value: 'system' },
+ { label: i18n.t('settings.light'), value: 'light' },
+ { label: i18n.t('settings.dark'), value: 'dark' },
],
},
{
- type: "toggle",
- label: i18n.t("settings.haptics"),
- icon: "vibrate",
- stateKey: "isHapticsEnabled",
+ type: 'toggle',
+ label: i18n.t('settings.haptics'),
+ icon: 'vibrate',
+ stateKey: 'isHapticsEnabled',
},
{
- type: "modal",
- label: i18n.t("settings.appLanguage"),
- icon: "translate",
- title: i18n.t("settings.languageTitle"),
- stateKey: "language",
+ type: 'modal',
+ label: i18n.t('settings.appLanguage'),
+ icon: 'translate',
+ title: i18n.t('settings.languageTitle'),
+ stateKey: 'language',
options: [
- { label: i18n.t("settings.english"), value: "en" },
- { label: i18n.t("settings.arabic"), value: "ar" },
+ { label: i18n.t('settings.english'), value: 'en' },
+ { label: i18n.t('settings.arabic'), value: 'ar' },
],
},
],
},
{
- title: i18n.t("settings.supportTitle"),
+ title: i18n.t('settings.supportTitle'),
data: [
{
- type: "action",
- label: i18n.t("settings.invite"),
- icon: "share-variant-outline",
+ type: 'action',
+ label: i18n.t('settings.invite'),
+ icon: 'share-variant-outline',
action: actions.invite,
},
{
- type: "action",
- label: i18n.t("settings.rate"),
- icon: "star-outline",
+ type: 'action',
+ label: i18n.t('settings.rate'),
+ icon: 'star-outline',
action: actions.rate,
},
{
- type: "action",
- label: i18n.t("settings.feedback"),
- icon: "email-outline",
+ type: 'action',
+ label: i18n.t('settings.feedback'),
+ icon: 'email-outline',
action: actions.feedback,
},
{
- type: "action",
- label: i18n.t("settings.privacy"),
- icon: "shield-lock-outline",
+ type: 'action',
+ label: i18n.t('settings.privacy'),
+ icon: 'shield-lock-outline',
action: actions.privacy,
},
],
},
{
- title: i18n.t("settings.dangerZoneTitle"),
+ title: i18n.t('settings.dangerZoneTitle'),
data: [
{
- type: "action",
- label: i18n.t("settings.reset"),
- icon: "alert-circle-outline",
+ type: 'action',
+ label: i18n.t('settings.reset'),
+ icon: 'alert-circle-outline',
action: actions.reset,
- color: "red",
+ color: 'red',
},
],
},
diff --git a/src/features/stats/ActivityGraph.tsx b/src/features/stats/ActivityGraph.tsx
index 4421dbb..f3f2cd2 100644
--- a/src/features/stats/ActivityGraph.tsx
+++ b/src/features/stats/ActivityGraph.tsx
@@ -1,18 +1,12 @@
// FILE: src/features/stats/ActivityGraph.tsx
-import { AppTheme } from "@/constants/theme";
-import { Deed, DeedLog } from "@/core/data/models";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { formatISO } from "date-fns";
-import React, {
- useRef,
- useEffect,
- useState,
- useMemo,
- useCallback,
-} from "react";
-import { FlatList, LayoutChangeEvent, StyleSheet, View } from "react-native";
-import { GraphColumn, GraphColumnData } from "./GraphColumn";
+import { AppTheme } from '@/constants/theme';
+import { Deed, DeedLog } from '@/core/data/models';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { formatISO } from 'date-fns';
+import React, { useRef, useEffect, useState, useMemo, useCallback } from 'react';
+import { FlatList, LayoutChangeEvent, StyleSheet, View } from 'react-native';
+import { GraphColumn, GraphColumnData } from './GraphColumn';
type ActivityGraphProps = {
deeds: Deed[];
@@ -20,11 +14,7 @@ type ActivityGraphProps = {
dateRange: Date[];
};
-export const ActivityGraph = ({
- deeds,
- logs,
- dateRange,
-}: ActivityGraphProps) => {
+export const ActivityGraph = ({ deeds, logs, dateRange }: ActivityGraphProps) => {
const { theme } = useTheme();
const [containerWidth, setContainerWidth] = useState(0);
const flatListRef = useRef(null);
@@ -42,17 +32,13 @@ export const ActivityGraph = ({
}
return dateRange.map((date) => {
- const dateString = formatISO(date, { representation: "date" });
+ const dateString = formatISO(date, { representation: 'date' });
const dailyLogs = logsByDate.get(dateString) || [];
const cells = deeds.map((deed) => {
const log = dailyLogs.find((l) => l.deedId === deed.id);
- const status = log
- ? deed.statuses.find((s) => s.id === log.statusId)
- : null;
- const cellColor = status
- ? theme.colors[status.color]
- : theme.colors.background;
+ const status = log ? deed.statuses.find((s) => s.id === log.statusId) : null;
+ const cellColor = status ? theme.colors[status.color] : theme.colors.background;
return {
id: deed.id,
color: cellColor,
@@ -72,21 +58,16 @@ export const ActivityGraph = ({
useEffect(() => {
if (containerWidth > 0) {
- setTimeout(
- () => flatListRef.current?.scrollToEnd({ animated: false }),
- 0,
- );
+ setTimeout(() => flatListRef.current?.scrollToEnd({ animated: false }), 0);
}
}, [containerWidth]);
const yAxisWidth = 24;
const daysToShow = 10;
- const availableGridWidth =
- containerWidth - yAxisWidth - theme.spacing.s - theme.spacing.m;
+ const availableGridWidth = containerWidth - yAxisWidth - theme.spacing.s - theme.spacing.m;
- const totalWidthPerColumn =
- containerWidth > 0 ? availableGridWidth / daysToShow : 0;
+ const totalWidthPerColumn = containerWidth > 0 ? availableGridWidth / daysToShow : 0;
const cellSize = (totalWidthPerColumn - 4) * 0.8;
@@ -94,13 +75,7 @@ export const ActivityGraph = ({
const renderItem = useCallback(
({ item }: { item: GraphColumnData }) => {
- return (
-
- );
+ return ;
},
[cellSize, totalWidthPerColumn],
);
@@ -150,14 +125,14 @@ const getStyles = (theme: AppTheme, cellSize: number) =>
height: (cellSize + 4) * 5 + 16 + theme.spacing.m * 2,
},
container: {
- flexDirection: "row",
+ flexDirection: 'row',
backgroundColor: theme.colors.foreground,
borderRadius: 16,
paddingVertical: theme.spacing.m,
paddingRight: theme.spacing.m,
},
yAxis: {
- justifyContent: "flex-start",
+ justifyContent: 'flex-start',
marginLeft: theme.spacing.s,
},
yAxisHeaderSpacer: {
@@ -166,7 +141,7 @@ const getStyles = (theme: AppTheme, cellSize: number) =>
},
yAxisItem: {
height: cellSize + 4,
- justifyContent: "center",
- alignItems: "center",
+ justifyContent: 'center',
+ alignItems: 'center',
},
});
diff --git a/src/features/stats/DeedStatsRow.tsx b/src/features/stats/DeedStatsRow.tsx
index d99978e..4d309fe 100644
--- a/src/features/stats/DeedStatsRow.tsx
+++ b/src/features/stats/DeedStatsRow.tsx
@@ -1,15 +1,15 @@
// src/features/stats/DeedStatsRow.tsx
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme, ColorTheme } from "@/constants/theme";
-import { Deed, DeedLog } from "@/core/data/models";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import React, { useMemo } from "react";
-import { StyleSheet, View, I18nManager } from "react-native";
-import { colors } from "@/constants/colors";
-import i18n from "@/core/i18n";
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme, ColorTheme } from '@/constants/theme';
+import { Deed, DeedLog } from '@/core/data/models';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import React, { useMemo } from 'react';
+import { StyleSheet, View, I18nManager } from 'react-native';
+import { colors } from '@/constants/colors';
+import i18n from '@/core/i18n';
-const PRAYER_STATUS_BREAKDOWN_ORDER = ["jamaah", "on-time", "late", "missed"];
+const PRAYER_STATUS_BREAKDOWN_ORDER = ['jamaah', 'on-time', 'late', 'missed'];
const isColorLight = (hexColor: string) => {
const r = parseInt(hexColor.slice(1, 3), 16);
@@ -18,13 +18,7 @@ const isColorLight = (hexColor: string) => {
return r * 0.299 + g * 0.587 + b * 0.114 > 186;
};
-export const DeedStatsRow = ({
- deed,
- logs,
-}: {
- deed: Deed;
- logs: DeedLog[];
-}) => {
+export const DeedStatsRow = ({ deed, logs }: { deed: Deed; logs: DeedLog[] }) => {
const { theme } = useTheme();
const styles = getStyles(theme);
@@ -48,9 +42,7 @@ export const DeedStatsRow = ({
id: statusId,
percentage: (count / total) * 100,
color: theme.colors[colorKey],
- textColor: isColorLight(hexColor)
- ? theme.colors.text
- : theme.colors.primaryContrast,
+ textColor: isColorLight(hexColor) ? theme.colors.text : theme.colors.primaryContrast,
};
}).filter((item) => item.percentage > 0);
}, [logs, deed, theme.colors]);
@@ -73,12 +65,9 @@ export const DeedStatsRow = ({
style={[
styles.barSegment,
{ width: `${stat.percentage}%`, backgroundColor: stat.color },
- ]}
- >
+ ]}>
{stat.percentage >= 15 && (
-
+
{stat.percentage.toFixed(0)}%
)}
@@ -92,30 +81,30 @@ export const DeedStatsRow = ({
const getStyles = (theme: AppTheme) =>
StyleSheet.create({
deedStatRow: {
- flexDirection: I18nManager.isRTL ? "row-reverse" : "row",
- alignItems: "center",
+ flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
+ alignItems: 'center',
marginBottom: theme.spacing.m,
},
deedStatIcon: {
marginHorizontal: theme.spacing.m,
},
deedStatName: {
- width: "25%",
+ width: '25%',
fontWeight: theme.typography.fontWeight.semibold,
- textAlign: I18nManager.isRTL ? "right" : "left",
+ textAlign: I18nManager.isRTL ? 'right' : 'left',
},
deedStatBar: {
flex: 1,
height: 24,
borderRadius: 12,
backgroundColor: theme.colors.background,
- flexDirection: "row",
- overflow: "hidden",
+ flexDirection: 'row',
+ overflow: 'hidden',
},
barSegment: {
- height: "100%",
- justifyContent: "center",
- alignItems: "center",
+ height: '100%',
+ justifyContent: 'center',
+ alignItems: 'center',
},
percentageText: {
fontSize: theme.typography.fontSize.xs,
diff --git a/src/features/stats/ExpandableSection.tsx b/src/features/stats/ExpandableSection.tsx
index 0cd0ebf..5e84a20 100644
--- a/src/features/stats/ExpandableSection.tsx
+++ b/src/features/stats/ExpandableSection.tsx
@@ -1,30 +1,18 @@
// src/features/stats/ExpandableSection.tsx
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import React, { useState } from "react";
-import {
- LayoutChangeEvent,
- StyleSheet,
- TouchableOpacity,
- View,
-} from "react-native";
-import Animated, {
- useAnimatedStyle,
- useSharedValue,
- withTiming,
-} from "react-native-reanimated";
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import React, { useState } from 'react';
+import { LayoutChangeEvent, StyleSheet, TouchableOpacity, View } from 'react-native';
+import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
type ExpandableSectionProps = {
title: string;
children: React.ReactNode;
};
-export const ExpandableSection = ({
- title,
- children,
-}: ExpandableSectionProps) => {
+export const ExpandableSection = ({ title, children }: ExpandableSectionProps) => {
const { theme } = useTheme();
const styles = getStyles(theme);
const [isExpanded, setIsExpanded] = useState(true);
@@ -67,11 +55,7 @@ export const ExpandableSection = ({
return (
-
+
{title}
wrapper: {
backgroundColor: theme.colors.foreground,
borderRadius: 16,
- overflow: "hidden",
+ overflow: 'hidden',
},
header: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "space-between",
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
padding: theme.spacing.m,
},
title: {
fontWeight: theme.typography.fontWeight.semibold,
},
contentContainer: {
- overflow: "hidden",
+ overflow: 'hidden',
},
content: {
- position: "absolute",
- width: "100%",
+ position: 'absolute',
+ width: '100%',
padding: theme.spacing.m,
paddingTop: 0,
},
diff --git a/src/features/stats/GoalStatsRow.tsx b/src/features/stats/GoalStatsRow.tsx
index a3d4ad6..f873565 100644
--- a/src/features/stats/GoalStatsRow.tsx
+++ b/src/features/stats/GoalStatsRow.tsx
@@ -1,20 +1,14 @@
// src/features/stats/GoalStatsRow.tsx
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { Deed, DeedLog } from "@/core/data/models";
-import i18n from "@/core/i18n";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import React, { useMemo } from "react";
-import { StyleSheet, View } from "react-native";
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { Deed, DeedLog } from '@/core/data/models';
+import i18n from '@/core/i18n';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import React, { useMemo } from 'react';
+import { StyleSheet, View } from 'react-native';
-export const GoalStatsRow = ({
- deed,
- logs,
-}: {
- deed: Deed;
- logs: DeedLog[];
-}) => {
+export const GoalStatsRow = ({ deed, logs }: { deed: Deed; logs: DeedLog[] }) => {
const { theme } = useTheme();
const styles = getStyles(theme);
@@ -37,27 +31,16 @@ export const GoalStatsRow = ({
return (
-
+
{i18n.t(`deeds_names.${deed.id}`, { defaultValue: deed.name })}
- {`${totalProgress} / ${deed.goal.value * logs.length} ${
- deed.goal.unit
- }`}
+ {`${totalProgress} / ${deed.goal.value * logs.length} ${deed.goal.unit}`}
-
+
);
@@ -69,8 +52,8 @@ const getStyles = (theme: AppTheme) =>
marginBottom: theme.spacing.m,
},
header: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
marginBottom: theme.spacing.s,
},
deedName: {
@@ -86,10 +69,10 @@ const getStyles = (theme: AppTheme) =>
height: 8,
borderRadius: 4,
backgroundColor: theme.colors.background,
- overflow: "hidden",
+ overflow: 'hidden',
},
progressBarFill: {
- height: "100%",
+ height: '100%',
backgroundColor: theme.colors.primary,
},
});
diff --git a/src/features/stats/GraphColumn.tsx b/src/features/stats/GraphColumn.tsx
index 70c8e3d..1ee7e6c 100644
--- a/src/features/stats/GraphColumn.tsx
+++ b/src/features/stats/GraphColumn.tsx
@@ -1,11 +1,11 @@
// FILE: src/features/stats/GraphColumn.tsx
// FILE: src/features/stats/GraphColumn.tsx
-import { AppTheme } from "@/constants/theme";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { format } from "date-fns";
-import React from "react";
-import { StyleSheet, View } from "react-native";
-import { ThemedText } from "@/components/base/ThemedText";
+import { AppTheme } from '@/constants/theme';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { format } from 'date-fns';
+import React from 'react';
+import { StyleSheet, View } from 'react-native';
+import { ThemedText } from '@/components/base/ThemedText';
// Define the shape of the pre-computed data
export type GraphColumnData = {
@@ -22,22 +22,15 @@ type GraphColumnProps = {
columnWidth: number;
};
-const GraphColumnComponent = ({
- data,
- cellSize,
- columnWidth,
-}: GraphColumnProps) => {
+const GraphColumnComponent = ({ data, cellSize, columnWidth }: GraphColumnProps) => {
const { theme } = useTheme();
const styles = getStyles(theme, cellSize);
return (
- {format(data.date, "d")}
+ {format(data.date, 'd')}
{data.cells.map((cell) => (
-
+
))}
);
@@ -48,8 +41,8 @@ export const GraphColumn = React.memo(GraphColumnComponent);
const getStyles = (theme: AppTheme, cellSize: number) =>
StyleSheet.create({
column: {
- justifyContent: "flex-start",
- alignItems: "center",
+ justifyContent: 'flex-start',
+ alignItems: 'center',
paddingHorizontal: 2,
},
dayText: {
diff --git a/src/features/stats/IntervalSwitcher.tsx b/src/features/stats/IntervalSwitcher.tsx
index c55b9d8..ce8391b 100644
--- a/src/features/stats/IntervalSwitcher.tsx
+++ b/src/features/stats/IntervalSwitcher.tsx
@@ -1,47 +1,36 @@
// src/features/stats/IntervalSwitcher.tsx
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import i18n from "@/core/i18n";
-import { useTheme } from "@/core/theme/ThemeContext";
-import React from "react";
-import { StyleSheet, TouchableOpacity, View } from "react-native";
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import i18n from '@/core/i18n';
+import { useTheme } from '@/core/theme/ThemeContext';
+import React from 'react';
+import { StyleSheet, TouchableOpacity, View } from 'react-native';
-export type TimeInterval = "week" | "month" | "year" | "all";
+export type TimeInterval = 'week' | 'month' | 'year' | 'all';
type IntervalSwitcherProps = {
selected: TimeInterval;
onSelect: (interval: TimeInterval) => void;
};
-export const IntervalSwitcher = ({
- selected,
- onSelect,
-}: IntervalSwitcherProps) => {
+export const IntervalSwitcher = ({ selected, onSelect }: IntervalSwitcherProps) => {
const { theme } = useTheme();
const styles = getStyles(theme);
const options: { label: string; value: TimeInterval }[] = [
- { label: i18n.t("intervals.week"), value: "week" },
- { label: i18n.t("intervals.month"), value: "month" },
- { label: i18n.t("intervals.year"), value: "year" },
- { label: i18n.t("intervals.allTime"), value: "all" },
+ { label: i18n.t('intervals.week'), value: 'week' },
+ { label: i18n.t('intervals.month'), value: 'month' },
+ { label: i18n.t('intervals.year'), value: 'year' },
+ { label: i18n.t('intervals.allTime'), value: 'all' },
];
return (
{options.map((opt) => (
onSelect(opt.value)}
- >
+ style={[styles.switcherButton, selected === opt.value && styles.switcherSelected]}
+ onPress={() => onSelect(opt.value)}>
+ style={[styles.switcherText, selected === opt.value && styles.switcherSelectedText]}>
{opt.label}
@@ -53,17 +42,17 @@ export const IntervalSwitcher = ({
const getStyles = (theme: AppTheme) =>
StyleSheet.create({
switcherContainer: {
- flexDirection: "row",
+ flexDirection: 'row',
backgroundColor: theme.colors.foreground,
borderRadius: 8,
- overflow: "hidden",
+ overflow: 'hidden',
marginBottom: theme.spacing.m,
marginTop: theme.spacing.m, // Added top margin
},
switcherButton: {
flex: 1,
padding: theme.spacing.m,
- alignItems: "center",
+ alignItems: 'center',
},
switcherSelected: {
backgroundColor: theme.colors.primary,
diff --git a/src/features/stats/SegmentedControl.tsx b/src/features/stats/SegmentedControl.tsx
index ea28edf..9f2bce2 100644
--- a/src/features/stats/SegmentedControl.tsx
+++ b/src/features/stats/SegmentedControl.tsx
@@ -1,15 +1,12 @@
// src/features/stats/SegmentedControl.tsx
-import React from "react";
-import { View, StyleSheet, useWindowDimensions } from "react-native";
-import Animated, {
- useAnimatedStyle,
- withSpring,
-} from "react-native-reanimated";
-import { AppTheme } from "@/constants/theme";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { ThemedText } from "@/components/base/ThemedText";
-import { TimeInterval } from "./types";
-import { TouchableOpacity } from "react-native-gesture-handler";
+import React from 'react';
+import { View, StyleSheet, useWindowDimensions } from 'react-native';
+import Animated, { useAnimatedStyle, withSpring } from 'react-native-reanimated';
+import { AppTheme } from '@/constants/theme';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { ThemedText } from '@/components/base/ThemedText';
+import { TimeInterval } from './types';
+import { TouchableOpacity } from 'react-native-gesture-handler';
type Option = {
label: string;
@@ -22,11 +19,7 @@ type SegmentedControlProps = {
onSelect: (interval: TimeInterval) => void;
};
-export const SegmentedControl = ({
- options,
- selected,
- onSelect,
-}: SegmentedControlProps) => {
+export const SegmentedControl = ({ options, selected, onSelect }: SegmentedControlProps) => {
const { theme } = useTheme();
const styles = getStyles(theme);
const { width } = useWindowDimensions();
@@ -52,25 +45,13 @@ export const SegmentedControl = ({
return (
-
+
{options.map((opt) => (
onSelect(opt.value)}
- >
-
+ onPress={() => onSelect(opt.value)}>
+
{opt.label}
@@ -82,7 +63,7 @@ export const SegmentedControl = ({
const getStyles = (theme: AppTheme) =>
StyleSheet.create({
container: {
- flexDirection: "row",
+ flexDirection: 'row',
backgroundColor: theme.colors.background,
borderRadius: 12,
padding: 4,
@@ -90,13 +71,13 @@ const getStyles = (theme: AppTheme) =>
marginVertical: theme.spacing.m,
},
indicator: {
- position: "absolute",
+ position: 'absolute',
top: 4,
left: 4,
- height: "100%",
+ height: '100%',
backgroundColor: theme.colors.foreground,
borderRadius: 8,
- shadowColor: "#000",
+ shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
@@ -104,8 +85,8 @@ const getStyles = (theme: AppTheme) =>
},
segment: {
flex: 1,
- justifyContent: "center",
- alignItems: "center",
+ justifyContent: 'center',
+ alignItems: 'center',
},
segmentText: {
fontWeight: theme.typography.fontWeight.semibold,
diff --git a/src/features/stats/SummaryBox.tsx b/src/features/stats/SummaryBox.tsx
index 86dfafc..c735f02 100644
--- a/src/features/stats/SummaryBox.tsx
+++ b/src/features/stats/SummaryBox.tsx
@@ -1,10 +1,10 @@
// src/features/stats/SummaryBox.tsx
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import i18n from "@/core/i18n";
-import { useTheme } from "@/core/theme/ThemeContext";
-import React from "react";
-import { StyleSheet, View } from "react-native";
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import i18n from '@/core/i18n';
+import { useTheme } from '@/core/theme/ThemeContext';
+import React from 'react';
+import { StyleSheet, View } from 'react-native';
type SummaryBoxProps = {
title: string;
@@ -13,32 +13,20 @@ type SummaryBoxProps = {
count: number;
};
-export const SummaryBox = ({
- title,
- color,
- percentage,
- count,
-}: SummaryBoxProps) => {
+export const SummaryBox = ({ title, color, percentage, count }: SummaryBoxProps) => {
const { theme } = useTheme();
const styles = getStyles(theme);
return (
-
+
{title}
-
- {percentage.toFixed(0)}%
-
-
- {i18n.t("stats.times", { count })}
-
+ {percentage.toFixed(0)}%
+ {i18n.t('stats.times', { count })}
@@ -48,7 +36,7 @@ export const SummaryBox = ({
const getStyles = (theme: AppTheme) =>
StyleSheet.create({
summaryBox: {
- width: "48%",
+ width: '48%',
backgroundColor: theme.colors.foreground,
borderRadius: 16,
padding: theme.spacing.m,
@@ -78,9 +66,9 @@ const getStyles = (theme: AppTheme) =>
borderRadius: 2,
backgroundColor: theme.colors.background,
marginTop: theme.spacing.m,
- overflow: "hidden",
+ overflow: 'hidden',
},
summaryBarFill: {
- height: "100%",
+ height: '100%',
},
});
diff --git a/src/features/stats/types.ts b/src/features/stats/types.ts
index 0150f4e..6705b44 100644
--- a/src/features/stats/types.ts
+++ b/src/features/stats/types.ts
@@ -1,2 +1,2 @@
// src/features/stats/types.ts
-export type TimeInterval = "week" | "month" | "year" | "all";
+export type TimeInterval = 'week' | 'month' | 'year' | 'all';
diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx
index 8a6a64e..f227b80 100644
--- a/src/navigation/AppNavigator.tsx
+++ b/src/navigation/AppNavigator.tsx
@@ -1,23 +1,23 @@
// FILE: src/navigation/AppNavigator.tsx
-import React from "react";
-import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
-import { createNativeStackNavigator } from "@react-navigation/native-stack";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { useTheme } from "@/core/theme/ThemeContext";
-import i18n from "@/core/i18n";
-import { NavigatorScreenParams } from "@react-navigation/native";
+import React from 'react';
+import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
+import { createNativeStackNavigator } from '@react-navigation/native-stack';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { useTheme } from '@/core/theme/ThemeContext';
+import i18n from '@/core/i18n';
+import { NavigatorScreenParams } from '@react-navigation/native';
// Import Screens
-import StatsScreen from "@/screens/StatsScreen";
-import HomeScreen from "@/screens/HomeScreen";
-import AddDeedScreen from "@/screens/AddDeedScreen";
-import CreateDeedScreen from "@/screens/CreateDeedScreen";
-import ConfigureFrequencyScreen from "@/screens/ConfigureFrequencyScreen";
-import ConfigureGoalScreen from "@/screens/ConfigureGoalScreen";
-import ConfigureParentScreen from "@/screens/ConfigureParentScreen";
-import SettingsScreen from "@/screens/SettingsScreen";
-import DeedManagerScreen from "@/screens/DeedManagerScreen";
-import NotificationsScreen from "@/screens/NotificationsScreen";
+import StatsScreen from '@/screens/StatsScreen';
+import HomeScreen from '@/screens/HomeScreen';
+import AddDeedScreen from '@/screens/AddDeedScreen';
+import CreateDeedScreen from '@/screens/CreateDeedScreen';
+import ConfigureFrequencyScreen from '@/screens/ConfigureFrequencyScreen';
+import ConfigureGoalScreen from '@/screens/ConfigureGoalScreen';
+import ConfigureParentScreen from '@/screens/ConfigureParentScreen';
+import SettingsScreen from '@/screens/SettingsScreen';
+import DeedManagerScreen from '@/screens/DeedManagerScreen';
+import NotificationsScreen from '@/screens/NotificationsScreen';
// Define Param Lists for Type Safety
export type HomeStackParamList = {
@@ -47,12 +47,10 @@ const HomeStack = createNativeStackNavigator();
const SettingsStack = createNativeStackNavigator();
function TabBarIcon(props: {
- name: React.ComponentProps["name"];
+ name: React.ComponentProps['name'];
color: string;
}) {
- return (
-
- );
+ return ;
}
// Main Tab Navigator
@@ -71,47 +69,31 @@ export default function AppNavigator() {
},
headerShown: false,
}}
- initialRouteName="Home"
- >
+ initialRouteName="Home">
(
-
- ),
+ title: i18n.t('tabs.stats'),
+ tabBarLabel: i18n.t('tabs.stats'),
+ tabBarIcon: ({ color }) => ,
}}
/>
,
- }}
- >
+ title: i18n.t('tabs.home'),
+ tabBarLabel: i18n.t('tabs.home'),
+ tabBarIcon: ({ color }) => ,
+ }}>
{() => (
-
+
-
-
-
+
+
+
)}
@@ -119,28 +101,15 @@ export default function AppNavigator() {
name="Settings"
options={{
headerShown: false,
- title: i18n.t("tabs.settings"),
- tabBarLabel: i18n.t("tabs.settings"),
- tabBarIcon: ({ color }) => ,
- }}
- >
+ title: i18n.t('tabs.settings'),
+ tabBarLabel: i18n.t('tabs.settings'),
+ tabBarIcon: ({ color }) => ,
+ }}>
{() => (
-
-
-
-
+
+
+
+
)}
diff --git a/src/screens/AddDeedScreen.tsx b/src/screens/AddDeedScreen.tsx
index 911f432..dbf7214 100644
--- a/src/screens/AddDeedScreen.tsx
+++ b/src/screens/AddDeedScreen.tsx
@@ -1,23 +1,20 @@
// FILE: src/screens/AddDeedScreen.tsx
-import { Screen } from "@/components/Screen";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { Deed } from "@/core/data/models";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { SuggestedDeedListItem } from "@/features/deeds/SuggestedDeedListItem";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { useNavigation } from "@react-navigation/native";
-import React, { useMemo } from "react";
-import { SectionList, StyleSheet, TouchableOpacity } from "react-native";
-import { HomeStackParamList } from "@/navigation/AppNavigator";
-import { NativeStackNavigationProp } from "@react-navigation/native-stack";
+import { Screen } from '@/components/Screen';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { Deed } from '@/core/data/models';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { SuggestedDeedListItem } from '@/features/deeds/SuggestedDeedListItem';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { useNavigation } from '@react-navigation/native';
+import React, { useMemo } from 'react';
+import { SectionList, StyleSheet, TouchableOpacity } from 'react-native';
+import { HomeStackParamList } from '@/navigation/AppNavigator';
+import { NativeStackNavigationProp } from '@react-navigation/native-stack';
-type AddDeedScreenNavigationProp = NativeStackNavigationProp<
- HomeStackParamList,
- "AddDeed"
->;
+type AddDeedScreenNavigationProp = NativeStackNavigationProp;
const AddDeedScreen = () => {
const navigation = useNavigation();
@@ -29,10 +26,7 @@ const AddDeedScreen = () => {
const suggestedDeeds = useAppStore((state) => state.suggestedDeeds);
const addDeed = useAppStore((state) => state.addDeed);
- const userDeedIds = useMemo(
- () => new Set(userDeeds.map((d) => d.id)),
- [userDeeds],
- );
+ const userDeedIds = useMemo(() => new Set(userDeeds.map((d) => d.id)), [userDeeds]);
const suggestedDeedsBySection = useMemo(() => {
const sections: { title: string; data: Deed[] }[] = [];
@@ -55,17 +49,12 @@ const AddDeedScreen = () => {
return (
(
navigation.goBack()}>
-
+
- )}
- >
+ )}>
item.id}
@@ -82,15 +71,10 @@ const AddDeedScreen = () => {
ListHeaderComponent={
navigation.navigate("CreateDeed", {})}
- >
-
+ onPress={() => navigation.navigate('CreateDeed', {})}>
+
- {i18n.t("deeds.createDeedButton")}
+ {i18n.t('deeds.createDeedButton')}
}
@@ -108,7 +92,7 @@ const getStyles = (theme: AppTheme) =>
fontWeight: theme.typography.fontWeight.bold,
fontSize: theme.typography.fontSize.s,
color: theme.colors.textSecondary,
- textTransform: "uppercase",
+ textTransform: 'uppercase',
marginBottom: theme.spacing.s,
marginTop: theme.spacing.l,
},
@@ -117,8 +101,8 @@ const getStyles = (theme: AppTheme) =>
paddingTop: theme.spacing.s,
},
createDeedButton: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
padding: theme.spacing.m,
backgroundColor: theme.colors.foreground,
borderRadius: 8,
diff --git a/src/screens/ConfigureFrequencyScreen.tsx b/src/screens/ConfigureFrequencyScreen.tsx
index 3f96243..a0b8a27 100644
--- a/src/screens/ConfigureFrequencyScreen.tsx
+++ b/src/screens/ConfigureFrequencyScreen.tsx
@@ -1,42 +1,42 @@
// FILE: src/screens/ConfigureFrequencyScreen.tsx
-import { Screen } from "@/components/Screen";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import { DeedFrequency } from "@/core/data/models";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { useNavigation } from "@react-navigation/native";
-import React from "react";
-import { StyleSheet, TouchableOpacity, View, TextInput } from "react-native";
+import { Screen } from '@/components/Screen';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import { DeedFrequency } from '@/core/data/models';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { useNavigation } from '@react-navigation/native';
+import React from 'react';
+import { StyleSheet, TouchableOpacity, View, TextInput } from 'react-native';
const DAYS = [
- { label: i18n.t("frequency.days_abbr.sun"), value: 0 },
- { label: i18n.t("frequency.days_abbr.mon"), value: 1 },
- { label: i18n.t("frequency.days_abbr.tue"), value: 2 },
- { label: i18n.t("frequency.days_abbr.wed"), value: 3 },
- { label: i18n.t("frequency.days_abbr.thu"), value: 4 },
- { label: i18n.t("frequency.days_abbr.fri"), value: 5 },
- { label: i18n.t("frequency.days_abbr.sat"), value: 6 },
+ { label: i18n.t('frequency.days_abbr.sun'), value: 0 },
+ { label: i18n.t('frequency.days_abbr.mon'), value: 1 },
+ { label: i18n.t('frequency.days_abbr.tue'), value: 2 },
+ { label: i18n.t('frequency.days_abbr.wed'), value: 3 },
+ { label: i18n.t('frequency.days_abbr.thu'), value: 4 },
+ { label: i18n.t('frequency.days_abbr.fri'), value: 5 },
+ { label: i18n.t('frequency.days_abbr.sat'), value: 6 },
];
const ConfigureFrequencyScreen = () => {
const navigation = useNavigation();
const { theme } = useTheme();
const styles = getStyles(theme);
const { draftDeed, updateDraftDeed } = useAppStore();
- const frequency = draftDeed?.frequency || { type: "daily" };
- const setType = (type: DeedFrequency["type"]) => {
+ const frequency = draftDeed?.frequency || { type: 'daily' };
+ const setType = (type: DeedFrequency['type']) => {
const newFrequency: DeedFrequency = { type };
- if (type === "weekly") {
+ if (type === 'weekly') {
newFrequency.days = frequency.days || [];
- } else if (type === "monthly" || type === "yearly") {
+ } else if (type === 'monthly' || type === 'yearly') {
newFrequency.count = frequency.count || 1;
}
updateDraftDeed({ frequency: newFrequency });
};
const toggleDay = (dayValue: number) => {
- if (frequency.type !== "weekly") return;
+ if (frequency.type !== 'weekly') return;
const currentDays = frequency.days || [];
const newDays = currentDays.includes(dayValue)
? currentDays.filter((d) => d !== dayValue)
@@ -51,48 +51,39 @@ const ConfigureFrequencyScreen = () => {
};
return (
(
navigation.goBack()}>
-
+
- )}
- >
+ )}>
-
- {i18n.t("frequency.type")}
-
+ {i18n.t('frequency.type')}
setType("daily")}
+ label={i18n.t('frequency.daily')}
+ selected={frequency.type === 'daily'}
+ onPress={() => setType('daily')}
/>
setType("weekly")}
+ label={i18n.t('frequency.weekly')}
+ selected={frequency.type === 'weekly'}
+ onPress={() => setType('weekly')}
/>
setType("monthly")}
+ label={i18n.t('frequency.monthly')}
+ selected={frequency.type === 'monthly'}
+ onPress={() => setType('monthly')}
/>
setType("yearly")}
+ label={i18n.t('frequency.yearly')}
+ selected={frequency.type === 'yearly'}
+ onPress={() => setType('yearly')}
/>
- {frequency.type === "weekly" && (
+ {frequency.type === 'weekly' && (
<>
-
- {i18n.t("frequency.selectDays")}
-
+ {i18n.t('frequency.selectDays')}
{DAYS.map((day) => (
{
>
)}
- {(frequency.type === "monthly" || frequency.type === "yearly") && (
+ {(frequency.type === 'monthly' || frequency.type === 'yearly') && (
<>
- {i18n.t("frequency.howManyTimes", {
- interval: frequency.type.replace("ly", ""),
+ {i18n.t('frequency.howManyTimes', {
+ interval: frequency.type.replace('ly', ''),
})}
@@ -132,13 +123,8 @@ const OptionButton = ({ label, selected, onPress }) => {
const { theme } = useTheme();
const styles = getStyles(theme);
return (
-
-
- {label}
-
+
+ {label}
);
};
@@ -146,13 +132,8 @@ const DayButton = ({ label, selected, onPress }) => {
const { theme } = useTheme();
const styles = getStyles(theme);
return (
-
-
- {label}
-
+
+ {label}
);
};
@@ -164,16 +145,16 @@ const getStyles = (theme: AppTheme) =>
marginTop: theme.spacing.l,
marginBottom: theme.spacing.s,
color: theme.colors.textSecondary,
- textTransform: "uppercase",
+ textTransform: 'uppercase',
fontWeight: theme.typography.fontWeight.bold,
},
optionGroup: {
- flexDirection: "row",
+ flexDirection: 'row',
backgroundColor: theme.colors.foreground,
borderRadius: 8,
- overflow: "hidden",
+ overflow: 'hidden',
},
- optionButton: { flex: 1, padding: theme.spacing.m, alignItems: "center" },
+ optionButton: { flex: 1, padding: theme.spacing.m, alignItems: 'center' },
selected: { backgroundColor: theme.colors.primary },
optionText: {
fontWeight: theme.typography.fontWeight.semibold,
@@ -182,8 +163,8 @@ const getStyles = (theme: AppTheme) =>
},
selectedText: { color: theme.colors.primaryContrast },
daySelector: {
- flexDirection: "row",
- justifyContent: "space-between",
+ flexDirection: 'row',
+ justifyContent: 'space-between',
backgroundColor: theme.colors.foreground,
borderRadius: 8,
padding: theme.spacing.s,
@@ -192,8 +173,8 @@ const getStyles = (theme: AppTheme) =>
flex: 1,
height: 44,
borderRadius: 8,
- justifyContent: "center",
- alignItems: "center",
+ justifyContent: 'center',
+ alignItems: 'center',
marginHorizontal: theme.spacing.xs / 2,
},
selectedDay: { backgroundColor: theme.colors.primary },
@@ -209,6 +190,6 @@ const getStyles = (theme: AppTheme) =>
countInput: {
color: theme.colors.text,
fontSize: theme.typography.fontSize.m,
- textAlign: "center",
+ textAlign: 'center',
},
});
diff --git a/src/screens/ConfigureGoalScreen.tsx b/src/screens/ConfigureGoalScreen.tsx
index 9fd4e1e..562d591 100644
--- a/src/screens/ConfigureGoalScreen.tsx
+++ b/src/screens/ConfigureGoalScreen.tsx
@@ -1,15 +1,15 @@
// FILE: src/screens/ConfigureGoalScreen.tsx
-import { Screen } from "@/components/Screen";
-import { ThemedText } from "@/components/base/ThemedText";
-import { ThemedTextInput } from "@/components/base/ThemedTextInput";
-import { AppTheme } from "@/constants/theme";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { useNavigation } from "@react-navigation/native";
-import React, { useState } from "react";
-import { StyleSheet, TouchableOpacity, View } from "react-native";
+import { Screen } from '@/components/Screen';
+import { ThemedText } from '@/components/base/ThemedText';
+import { ThemedTextInput } from '@/components/base/ThemedTextInput';
+import { AppTheme } from '@/constants/theme';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { useNavigation } from '@react-navigation/native';
+import React, { useState } from 'react';
+import { StyleSheet, TouchableOpacity, View } from 'react-native';
const ConfigureGoalScreen = () => {
const navigation = useNavigation();
@@ -17,8 +17,8 @@ const ConfigureGoalScreen = () => {
const styles = getStyles(theme);
const { draftDeed, updateDraftDeed } = useAppStore();
- const [value, setValue] = useState(draftDeed?.goal?.value?.toString() || "");
- const [unit, setUnit] = useState(draftDeed?.goal?.unit || "");
+ const [value, setValue] = useState(draftDeed?.goal?.value?.toString() || '');
+ const [unit, setUnit] = useState(draftDeed?.goal?.unit || '');
const handleValueChange = (text: string) => {
const num = parseInt(text, 10);
@@ -42,28 +42,23 @@ const ConfigureGoalScreen = () => {
return (
(
navigation.goBack()}>
-
+
- )}
- >
+ )}>
- {i18n.t("goal.target")}
+ {i18n.t('goal.target')}
- {i18n.t("goal.unit")}
+ {i18n.t('goal.unit')}
@@ -78,7 +73,7 @@ const getStyles = (theme: AppTheme) =>
container: { paddingTop: theme.spacing.m },
label: {
color: theme.colors.textSecondary,
- textTransform: "uppercase",
+ textTransform: 'uppercase',
fontWeight: theme.typography.fontWeight.bold,
marginBottom: theme.spacing.s,
marginTop: theme.spacing.m,
diff --git a/src/screens/ConfigureParentScreen.tsx b/src/screens/ConfigureParentScreen.tsx
index 4d8f5fc..a9b0dc2 100644
--- a/src/screens/ConfigureParentScreen.tsx
+++ b/src/screens/ConfigureParentScreen.tsx
@@ -1,14 +1,14 @@
// FILE: src/screens/ConfigureParentScreen.tsx
-import { Screen } from "@/components/Screen";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { useNavigation } from "@react-navigation/native";
-import React from "react";
-import { FlatList, StyleSheet, TouchableOpacity } from "react-native";
+import { Screen } from '@/components/Screen';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { useNavigation } from '@react-navigation/native';
+import React from 'react';
+import { FlatList, StyleSheet, TouchableOpacity } from 'react-native';
const ConfigureParentScreen = () => {
const navigation = useNavigation();
@@ -26,64 +26,36 @@ const ConfigureParentScreen = () => {
return (
(
navigation.goBack()}>
-
+
- )}
- >
+ )}>
item.id}
renderItem={({ item }) => (
handleSelect(item.id)}
- >
-
+ style={[styles.row, draftDeed?.parentId === item.id && styles.selectedRow]}
+ onPress={() => handleSelect(item.id)}>
+
{i18n.t(`deeds_names.${item.id}`, { defaultValue: item.name })}
{draftDeed?.parentId === item.id && (
-
+
)}
)}
ListHeaderComponent={
handleSelect(null)}
- >
-
-
- {i18n.t("deeds.none")}
-
+ onPress={() => handleSelect(null)}>
+
+ {i18n.t('deeds.none')}
{!draftDeed?.parentId && (
-
+
)}
}
@@ -97,8 +69,8 @@ export default ConfigureParentScreen;
const getStyles = (theme: AppTheme) =>
StyleSheet.create({
row: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
padding: theme.spacing.m,
backgroundColor: theme.colors.foreground,
borderRadius: 8,
diff --git a/src/screens/CreateDeedScreen.tsx b/src/screens/CreateDeedScreen.tsx
index 71f8578..6b22940 100644
--- a/src/screens/CreateDeedScreen.tsx
+++ b/src/screens/CreateDeedScreen.tsx
@@ -1,31 +1,22 @@
// FILE: src/screens/CreateDeedScreen.tsx
-import { Box } from "@/components/base/Box";
-import { ThemedText } from "@/components/base/ThemedText";
-import { ThemedTextInput } from "@/components/base/ThemedTextInput";
-import { Screen } from "@/components/Screen";
-import { AppTheme } from "@/constants/theme";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { IconSelectorModal } from "@/features/deeds/IconSelectorModal";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { useNavigation, useRoute, RouteProp } from "@react-navigation/native";
-import React, { useEffect, useState } from "react";
-import {
- Alert,
- StyleSheet,
- TouchableOpacity,
- View,
- ActivityIndicator,
-} from "react-native";
-import { HomeStackParamList } from "@/navigation/AppNavigator";
-import { NativeStackNavigationProp } from "@react-navigation/native-stack";
-
-type CreateDeedScreenNavigationProp = NativeStackNavigationProp<
- HomeStackParamList,
- "CreateDeed"
->;
-type CreateDeedScreenRouteProp = RouteProp;
+import { Box } from '@/components/base/Box';
+import { ThemedText } from '@/components/base/ThemedText';
+import { ThemedTextInput } from '@/components/base/ThemedTextInput';
+import { Screen } from '@/components/Screen';
+import { AppTheme } from '@/constants/theme';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { IconSelectorModal } from '@/features/deeds/IconSelectorModal';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
+import React, { useEffect, useState } from 'react';
+import { Alert, StyleSheet, TouchableOpacity, View, ActivityIndicator } from 'react-native';
+import { HomeStackParamList } from '@/navigation/AppNavigator';
+import { NativeStackNavigationProp } from '@react-navigation/native-stack';
+
+type CreateDeedScreenNavigationProp = NativeStackNavigationProp;
+type CreateDeedScreenRouteProp = RouteProp;
const CreateDeedScreen = () => {
const navigation = useNavigation();
@@ -36,13 +27,8 @@ const CreateDeedScreen = () => {
const deedId = route.params?.deedId;
const isEditMode = !!deedId;
- const {
- draftDeed,
- initializeDraftDeed,
- updateDraftDeed,
- clearDraftDeed,
- saveDraftDeed,
- } = useAppStore();
+ const { draftDeed, initializeDraftDeed, updateDraftDeed, clearDraftDeed, saveDraftDeed } =
+ useAppStore();
const allDeeds = useAppStore((state) => state.deeds);
const [isIconModalVisible, setIconModalVisible] = useState(false);
@@ -56,51 +42,44 @@ const CreateDeedScreen = () => {
const handleSave = () => {
if (!draftDeed?.name?.trim()) {
- Alert.alert(
- i18n.t("alerts.missingNameTitle"),
- i18n.t("alerts.missingNameMessage"),
- );
+ Alert.alert(i18n.t('alerts.missingNameTitle'), i18n.t('alerts.missingNameMessage'));
return;
}
saveDraftDeed();
navigation.goBack();
};
- const handleSelectIcon = (
- icon: React.ComponentProps["name"],
- ) => {
+ const handleSelectIcon = (icon: React.ComponentProps['name']) => {
updateDraftDeed({ icon });
setIconModalVisible(false);
};
const getFrequencyLabel = () => {
- if (!draftDeed?.frequency) return i18n.t("deeds.notSet");
- if (draftDeed.frequency.type === "daily") return i18n.t("frequency.daily");
- if (draftDeed.frequency.type === "weekly") {
+ if (!draftDeed?.frequency) return i18n.t('deeds.notSet');
+ if (draftDeed.frequency.type === 'daily') return i18n.t('frequency.daily');
+ if (draftDeed.frequency.type === 'weekly') {
const dayCount = draftDeed.frequency.days?.length || 0;
return dayCount === 1
- ? i18n.t("deeds.weeklyLabel_singular", { dayCount })
- : i18n.t("deeds.weeklyLabel", { dayCount });
+ ? i18n.t('deeds.weeklyLabel_singular', { dayCount })
+ : i18n.t('deeds.weeklyLabel', { dayCount });
}
- return i18n.t("deeds.notSet");
+ return i18n.t('deeds.notSet');
};
const getGoalLabel = () => {
- if (!draftDeed?.goal) return i18n.t("deeds.notSet");
+ if (!draftDeed?.goal) return i18n.t('deeds.notSet');
return `${draftDeed.goal.value} ${draftDeed.goal.unit}`;
};
const getParentLabel = () => {
- if (!draftDeed?.parentId) return i18n.t("deeds.none");
+ if (!draftDeed?.parentId) return i18n.t('deeds.none');
const parent = allDeeds.find((d) => d.id === draftDeed.parentId);
return parent
? i18n.t(`deeds_names.${parent.id}`, { defaultValue: parent.name })
- : i18n.t("deeds.none");
+ : i18n.t('deeds.none');
};
- const screenTitle = isEditMode
- ? i18n.t("screens.editDeed")
- : i18n.t("screens.createDeed");
+ const screenTitle = isEditMode ? i18n.t('screens.editDeed') : i18n.t('screens.createDeed');
if (!draftDeed) {
return (
@@ -116,60 +95,44 @@ const CreateDeedScreen = () => {
title={screenTitle}
renderLeftAction={() => (
navigation.goBack()}>
-
+
)}
renderRightAction={() => (
-
- {i18n.t("deeds.save")}
-
+ {i18n.t('deeds.save')}
- )}
- >
+ )}>
- setIconModalVisible(true)}
- >
-
+ setIconModalVisible(true)}>
+
updateDraftDeed({ name })}
style={styles.textInput}
/>
-
- {i18n.t("deeds.configuration")}
-
+ {i18n.t('deeds.configuration')}
navigation.navigate("ConfigureFrequency")}
+ onPress={() => navigation.navigate('ConfigureFrequency')}
/>
navigation.navigate("ConfigureGoal")}
+ onPress={() => navigation.navigate('ConfigureGoal')}
/>
navigation.navigate("ConfigureParent")}
+ onPress={() => navigation.navigate('ConfigureParent')}
/>
@@ -189,7 +152,7 @@ const ConfigRow = ({
value,
onPress,
}: {
- icon: React.ComponentProps["name"];
+ icon: React.ComponentProps['name'];
label: string;
value: string;
onPress?: () => void;
@@ -198,18 +161,10 @@ const ConfigRow = ({
const styles = getStyles(theme);
return (
-
+
{label}
{value}
-
+
);
};
@@ -225,8 +180,8 @@ const getStyles = (theme: AppTheme) =>
paddingTop: theme.spacing.m,
},
inputRow: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
backgroundColor: theme.colors.foreground,
borderRadius: 8,
padding: theme.spacing.s,
@@ -239,7 +194,7 @@ const getStyles = (theme: AppTheme) =>
},
textInput: {
flex: 1,
- backgroundColor: "transparent",
+ backgroundColor: 'transparent',
borderWidth: 0,
padding: 0,
},
@@ -247,13 +202,13 @@ const getStyles = (theme: AppTheme) =>
marginTop: theme.spacing.xl,
marginBottom: theme.spacing.s,
color: theme.colors.textSecondary,
- textTransform: "uppercase",
+ textTransform: 'uppercase',
fontWeight: theme.typography.fontWeight.bold,
fontSize: theme.typography.fontSize.s,
},
configRow: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
padding: theme.spacing.m,
backgroundColor: theme.colors.foreground,
borderRadius: 8,
diff --git a/src/screens/DeedManagerScreen.tsx b/src/screens/DeedManagerScreen.tsx
index e004898..6bb4fc4 100644
--- a/src/screens/DeedManagerScreen.tsx
+++ b/src/screens/DeedManagerScreen.tsx
@@ -1,18 +1,16 @@
// FILE: src/screens/DeedManagerScreen.tsx
-import { Screen } from "@/components/Screen";
-import { AppTheme } from "@/constants/theme";
-import { Deed } from "@/core/data/models";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { DeedManagerListItem } from "@/features/deeds/DeedManagerListItem";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import { useNavigation } from "@react-navigation/native";
-import React from "react";
-import { StyleSheet, TouchableOpacity } from "react-native";
-import DraggableFlatList, {
- RenderItemParams,
-} from "react-native-draggable-flatlist";
+import { Screen } from '@/components/Screen';
+import { AppTheme } from '@/constants/theme';
+import { Deed } from '@/core/data/models';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { DeedManagerListItem } from '@/features/deeds/DeedManagerListItem';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import { useNavigation } from '@react-navigation/native';
+import React from 'react';
+import { StyleSheet, TouchableOpacity } from 'react-native';
+import DraggableFlatList, { RenderItemParams } from 'react-native-draggable-flatlist';
const DeedManagerScreen = () => {
const navigation = useNavigation();
@@ -22,24 +20,17 @@ const DeedManagerScreen = () => {
const setDeeds = useAppStore((state) => state.setDeeds);
const renderItem = ({ item, drag, isActive }: RenderItemParams) => {
- return (
-
- );
+ return ;
};
return (
(
navigation.goBack()}>
-
+
- )}
- >
+ )}>
setDeeds(data)}
diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx
index 12504d4..4fb361e 100644
--- a/src/screens/HomeScreen.tsx
+++ b/src/screens/HomeScreen.tsx
@@ -1,34 +1,26 @@
// src/screens/HomeScreen.tsx
-import { Box } from "@/components/base/Box";
-import { Screen } from "@/components/Screen";
-import { ThemedText } from "@/components/base/ThemedText";
-import { FAB } from "@/components/FAB";
-import { AppTheme } from "@/constants/theme";
-import { Deed, DeedLog, DeedStatus } from "@/core/data/models";
-import i18n, { dateLocales } from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { formatHijriDate } from "@/core/utils/dateFormatter";
-import { DeedListItem } from "@/features/deeds/DeedListItem";
-import { LogDeedModal } from "@/features/deeds/LogDeedModal";
-import { LogGoalModal } from "@/features/deeds/LogGoalModal";
-import { DateScroller } from "@/features/home/DateScroller";
-import { HomeStackParamList } from "@/navigation/AppNavigator";
-import { useNavigation } from "@react-navigation/native";
-import { NativeStackNavigationProp } from "@react-navigation/native-stack";
-import { format, formatISO, getDay } from "date-fns";
-import React, { useMemo, useState } from "react";
-import {
- ActivityIndicator,
- I18nManager,
- SectionList,
- StyleSheet,
-} from "react-native";
-
-type HomeScreenNavigationProp = NativeStackNavigationProp<
- HomeStackParamList,
- "HomeMain"
->;
+import { Box } from '@/components/base/Box';
+import { Screen } from '@/components/Screen';
+import { ThemedText } from '@/components/base/ThemedText';
+import { FAB } from '@/components/FAB';
+import { AppTheme } from '@/constants/theme';
+import { Deed, DeedLog, DeedStatus } from '@/core/data/models';
+import i18n, { dateLocales } from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { formatHijriDate } from '@/core/utils/dateFormatter';
+import { DeedListItem } from '@/features/deeds/DeedListItem';
+import { LogDeedModal } from '@/features/deeds/LogDeedModal';
+import { LogGoalModal } from '@/features/deeds/LogGoalModal';
+import { DateScroller } from '@/features/home/DateScroller';
+import { HomeStackParamList } from '@/navigation/AppNavigator';
+import { useNavigation } from '@react-navigation/native';
+import { NativeStackNavigationProp } from '@react-navigation/native-stack';
+import { format, formatISO, getDay } from 'date-fns';
+import React, { useMemo, useState } from 'react';
+import { ActivityIndicator, I18nManager, SectionList, StyleSheet } from 'react-native';
+
+type HomeScreenNavigationProp = NativeStackNavigationProp;
const HomeScreen = () => {
const { theme } = useTheme();
@@ -36,8 +28,7 @@ const HomeScreen = () => {
const navigation = useNavigation();
// --- Global State ---
- const { isInitialized, deeds, logs, addOrUpdateLog, settings } =
- useAppStore();
+ const { isInitialized, deeds, logs, addOrUpdateLog, settings } = useAppStore();
const activeLocale = dateLocales[settings.language];
// --- Local State ---
@@ -45,14 +36,12 @@ const HomeScreen = () => {
const [isLogDeedModalVisible, setLogDeedModalVisible] = useState(false);
const [isLogGoalModalVisible, setLogGoalModalVisible] = useState(false);
const [selectedDeed, setSelectedDeed] = useState(null);
- const [selectedLog, setSelectedLog] = useState(
- undefined,
- );
+ const [selectedLog, setSelectedLog] = useState(undefined);
// --- Memos ---
const { gregorianDate, hijriDate } = useMemo(() => {
return {
- gregorianDate: format(selectedDate, "MMMM d", {
+ gregorianDate: format(selectedDate, 'MMMM d', {
locale: activeLocale,
}),
hijriDate: formatHijriDate(selectedDate),
@@ -60,7 +49,7 @@ const HomeScreen = () => {
}, [selectedDate, activeLocale]);
const logsForSelectedDate = useMemo(() => {
- const dateString = formatISO(selectedDate, { representation: "date" });
+ const dateString = formatISO(selectedDate, { representation: 'date' });
return logs.filter((log) => log.date === dateString);
}, [selectedDate, logs]);
@@ -75,13 +64,13 @@ const HomeScreen = () => {
return true;
}
if (
- deed.frequency.type === "daily" ||
- deed.frequency.type === "monthly" ||
- deed.frequency.type === "yearly"
+ deed.frequency.type === 'daily' ||
+ deed.frequency.type === 'monthly' ||
+ deed.frequency.type === 'yearly'
) {
return true;
}
- if (deed.frequency.type === "weekly") {
+ if (deed.frequency.type === 'weekly') {
return deed.frequency.days?.includes(dayOfWeek) ?? false;
}
return false;
@@ -128,8 +117,8 @@ const HomeScreen = () => {
// For goals, we determine status based on value.
const status =
value > 0
- ? selectedDeed.statuses.find((s) => s.id === "completed")!
- : selectedDeed.statuses.find((s) => s.id === "missed")!;
+ ? selectedDeed.statuses.find((s) => s.id === 'completed')!
+ : selectedDeed.statuses.find((s) => s.id === 'missed')!;
addOrUpdateLog(selectedDeed, selectedDate, status, value);
setLogGoalModalVisible(false);
setSelectedDeed(null);
@@ -137,7 +126,7 @@ const HomeScreen = () => {
if (!isInitialized) {
return (
-
+
@@ -161,17 +150,11 @@ const HomeScreen = () => {
)}
renderItem={({ item: deed }) => {
const log = logsForSelectedDate.find((l) => l.deedId === deed.id);
- return (
- handleOpenModal(deed)}
- />
- );
+ return handleOpenModal(deed)} />;
}}
contentContainerStyle={styles.listContent}
/>
- navigation.navigate("AddDeed")} />
+ navigation.navigate('AddDeed')} />
fontWeight: theme.typography.fontWeight.bold,
fontSize: theme.typography.fontSize.s,
color: theme.colors.textSecondary,
- textTransform: "uppercase",
+ textTransform: 'uppercase',
marginBottom: theme.spacing.s,
marginTop: theme.spacing.m,
- textAlign: I18nManager.isRTL ? "right" : "left",
+ textAlign: I18nManager.isRTL ? 'right' : 'left',
},
listContent: {
paddingTop: theme.spacing.s,
},
centered: {
flex: 1,
- justifyContent: "center",
- alignItems: "center",
+ justifyContent: 'center',
+ alignItems: 'center',
},
});
diff --git a/src/screens/NotificationsScreen.tsx b/src/screens/NotificationsScreen.tsx
index f70c880..55bcd10 100644
--- a/src/screens/NotificationsScreen.tsx
+++ b/src/screens/NotificationsScreen.tsx
@@ -1,28 +1,20 @@
// src/screens/NotificationsScreen.tsx
-import { Screen } from "@/components/Screen";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import i18n from "@/core/i18n";
+import { Screen } from '@/components/Screen';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import i18n from '@/core/i18n';
import {
scheduleTestNotificationIn5s,
sendTestNotification,
-} from "@/core/services/notificationService";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { MaterialCommunityIcons } from "@expo/vector-icons";
-import DateTimePicker, {
- DateTimePickerEvent,
-} from "@react-native-community/datetimepicker";
-import { format } from "date-fns";
-import { useNavigation } from "@react-navigation/native";
-import React, { useState } from "react";
-import {
- Platform,
- StyleSheet,
- Switch,
- TouchableOpacity,
- View,
-} from "react-native";
+} from '@/core/services/notificationService';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { MaterialCommunityIcons } from '@expo/vector-icons';
+import DateTimePicker, { DateTimePickerEvent } from '@react-native-community/datetimepicker';
+import { format } from 'date-fns';
+import { useNavigation } from '@react-navigation/native';
+import React, { useState } from 'react';
+import { Platform, StyleSheet, Switch, TouchableOpacity, View } from 'react-native';
const NotificationsScreen = () => {
const navigation = useNavigation();
@@ -35,13 +27,13 @@ const NotificationsScreen = () => {
const [isPickerVisible, setPickerVisible] = useState(false);
const reminderDate = new Date();
- const [hours, minutes] = (reminderTime || "20:00").split(":").map(Number);
+ const [hours, minutes] = (reminderTime || '20:00').split(':').map(Number);
reminderDate.setHours(hours);
reminderDate.setMinutes(minutes);
const onChange = (event: DateTimePickerEvent, selectedDate?: Date) => {
setPickerVisible(false);
- if (event.type === "set" && selectedDate) {
- setReminderTime(format(selectedDate, "HH:mm"));
+ if (event.type === 'set' && selectedDate) {
+ setReminderTime(format(selectedDate, 'HH:mm'));
}
};
const showPicker = () => {
@@ -51,22 +43,15 @@ const NotificationsScreen = () => {
return (
<>
(
navigation.goBack()}>
-
+
- )}
- >
+ )}>
-
- {i18n.t("settings.dailyReminder")}
-
+ {i18n.t('settings.dailyReminder')}
{
{isReminderEnabled && (
-
- {i18n.t("settings.time")}
-
+ {i18n.t('settings.time')}
{reminderTime}
{
{isDevMode && (
-
- {i18n.t("devMenu.title")}
-
-
-
- {i18n.t("devMenu.sendTest")}
-
+ {i18n.t('devMenu.title')}
+
+ {i18n.t('devMenu.sendTest')}
-
-
- {i18n.t("devMenu.scheduleTest")}
-
+
+ {i18n.t('devMenu.scheduleTest')}
)}
@@ -124,7 +95,7 @@ const NotificationsScreen = () => {
value={reminderDate}
mode="time"
is24Hour={true}
- display={Platform.OS === "ios" ? "spinner" : "default"}
+ display={Platform.OS === 'ios' ? 'spinner' : 'default'}
onChange={onChange}
/>
)}
@@ -140,12 +111,12 @@ const getStyles = (theme: AppTheme) =>
marginTop: theme.spacing.m,
backgroundColor: theme.colors.foreground,
borderRadius: 16,
- overflow: "hidden",
+ overflow: 'hidden',
},
row: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "space-between",
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
padding: theme.spacing.m,
borderBottomWidth: 1,
borderBottomColor: theme.colors.background,
@@ -154,8 +125,8 @@ const getStyles = (theme: AppTheme) =>
fontSize: theme.typography.fontSize.m,
},
valueContainer: {
- flexDirection: "row",
- alignItems: "center",
+ flexDirection: 'row',
+ alignItems: 'center',
},
valueText: {
color: theme.colors.textSecondary,
@@ -171,15 +142,15 @@ const getStyles = (theme: AppTheme) =>
fontSize: theme.typography.fontSize.s,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textSecondary,
- textTransform: "uppercase",
- textAlign: "center",
+ textTransform: 'uppercase',
+ textAlign: 'center',
marginBottom: theme.spacing.m,
},
devButton: {
backgroundColor: theme.colors.primary,
padding: theme.spacing.m,
borderRadius: 8,
- alignItems: "center",
+ alignItems: 'center',
marginBottom: theme.spacing.s,
},
devButtonText: {
diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx
index d659674..3f6cfa3 100644
--- a/src/screens/SettingsScreen.tsx
+++ b/src/screens/SettingsScreen.tsx
@@ -1,46 +1,32 @@
// FILE: src/screens/SettingsScreen.tsx
-import { Screen } from "@/components/Screen";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { DevMenuModal } from "@/features/settings/DevMenuModal";
-import {
- getSettingsData,
- ModalSettingsItem,
- SettingsItem,
-} from "@/features/settings/settingsData";
-import { SettingsListItem } from "@/features/settings/SettingsListItem";
-import { SettingsOptionModal } from "@/features/settings/SettingsOptionModal";
-import Constants from "expo-constants";
-import * as Linking from "expo-linking";
-import { useNavigation } from "@react-navigation/native";
-import React, { useMemo, useState } from "react";
-import {
- Alert,
- I18nManager,
- Pressable,
- SectionList,
- Share,
- StyleSheet,
- View,
-} from "react-native";
-import { SettingsStackParamList } from "@/navigation/AppNavigator";
-import { NativeStackNavigationProp } from "@react-navigation/native-stack";
+import { Screen } from '@/components/Screen';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { DevMenuModal } from '@/features/settings/DevMenuModal';
+import { getSettingsData, ModalSettingsItem, SettingsItem } from '@/features/settings/settingsData';
+import { SettingsListItem } from '@/features/settings/SettingsListItem';
+import { SettingsOptionModal } from '@/features/settings/SettingsOptionModal';
+import Constants from 'expo-constants';
+import * as Linking from 'expo-linking';
+import { useNavigation } from '@react-navigation/native';
+import React, { useMemo, useState } from 'react';
+import { Alert, I18nManager, Pressable, SectionList, Share, StyleSheet, View } from 'react-native';
+import { SettingsStackParamList } from '@/navigation/AppNavigator';
+import { NativeStackNavigationProp } from '@react-navigation/native-stack';
type SettingsScreenNavigationProp = NativeStackNavigationProp<
SettingsStackParamList,
- "SettingsMain"
+ 'SettingsMain'
>;
const SettingsScreen = () => {
const { theme } = useTheme();
const styles = getStyles(theme);
const navigation = useNavigation();
- const [modalData, setModalData] = useState | null>(
- null,
- );
+ const [modalData, setModalData] = useState | null>(null);
const { resetData, setDevMode } = useAppStore();
const isDevMode = useAppStore((state) => state.settings.isDevMode);
const language = useAppStore((state) => state.settings.language);
@@ -55,10 +41,7 @@ const SettingsScreen = () => {
setVersionTapCount(newCount);
if (newCount >= 7) {
setDevMode(true);
- Alert.alert(
- i18n.t("alerts.devModeTitle"),
- i18n.t("alerts.devModeMessage"),
- );
+ Alert.alert(i18n.t('alerts.devModeTitle'), i18n.t('alerts.devModeMessage'));
}
};
@@ -73,48 +56,35 @@ const SettingsScreen = () => {
invite: async () => {
try {
await Share.share({
- message: i18n.t("settings.shareMessage"),
+ message: i18n.t('settings.shareMessage'),
});
} catch (error) {
- console.error("Failed to share:", error);
+ console.error('Failed to share:', error);
}
},
rate: () => {
- const storeUrl = "market://details?id=com.uwayss.muhsin";
- Linking.openURL(storeUrl).catch((err) =>
- console.error("Couldn't load page", err),
- );
+ const storeUrl = 'market://details?id=com.uwayss.muhsin';
+ Linking.openURL(storeUrl).catch((err) => console.error("Couldn't load page", err));
},
feedback: () => {
- const subject = i18n.t("settings.feedbackSubject");
- const mailtoUrl = `mailto:support@uwayss.com?subject=${encodeURIComponent(
- subject,
- )}`;
- Linking.openURL(mailtoUrl).catch((err) =>
- console.error("Couldn't open mail client", err),
- );
+ const subject = i18n.t('settings.feedbackSubject');
+ const mailtoUrl = `mailto:support@uwayss.com?subject=${encodeURIComponent(subject)}`;
+ Linking.openURL(mailtoUrl).catch((err) => console.error("Couldn't open mail client", err));
},
reset: () => {
- Alert.alert(
- i18n.t("alerts.resetTitle"),
- i18n.t("alerts.resetMessage"),
- [
- {
- text: i18n.t("alerts.cancel"),
- style: "cancel",
- },
- {
- text: i18n.t("alerts.reset"),
- style: "destructive",
- onPress: () => resetData(),
- },
- ],
- );
+ Alert.alert(i18n.t('alerts.resetTitle'), i18n.t('alerts.resetMessage'), [
+ {
+ text: i18n.t('alerts.cancel'),
+ style: 'cancel',
+ },
+ {
+ text: i18n.t('alerts.reset'),
+ style: 'destructive',
+ onPress: () => resetData(),
+ },
+ ]);
},
- privacy: () =>
- Linking.openURL(
- "https://github.com/uwayss/muhsin/blob/main/PRIVACY.md",
- ),
+ privacy: () => Linking.openURL('https://github.com/uwayss/muhsin/blob/main/PRIVACY.md'),
}),
[resetData],
);
@@ -124,21 +94,21 @@ const SettingsScreen = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
[actions, language],
);
- const appVersion = Constants.expoConfig?.version ?? "N/A";
+ const appVersion = Constants.expoConfig?.version ?? 'N/A';
const handleItemPress = (item: SettingsItem) => {
- if (item.type === "navigation") {
+ if (item.type === 'navigation') {
navigation.navigate(item.path as any);
- } else if (item.type === "modal") {
+ } else if (item.type === 'modal') {
setModalData(item as ModalSettingsItem);
- } else if (item.type === "action") {
+ } else if (item.type === 'action') {
item.action();
}
};
return (
<>
-
+
item.label}
@@ -151,24 +121,17 @@ const SettingsScreen = () => {
/>
)}
renderSectionHeader={({ section: { title } }) =>
- title ? (
- {title}
- ) : null
+ title ? {title} : null
}
ItemSeparatorComponent={() => }
ListFooterComponent={
-
+
- {i18n.t("footer.appVersion", { version: appVersion })}
- {isDevMode && i18n.t("footer.devMode")}
-
-
- {i18n.t("footer.madeWith")}
+ {i18n.t('footer.appVersion', { version: appVersion })}
+ {isDevMode && i18n.t('footer.devMode')}
+ {i18n.t('footer.madeWith')}
}
@@ -177,14 +140,8 @@ const SettingsScreen = () => {
/>
- setModalData(null)}
- />
- setDevMenuVisible(false)}
- />
+ setModalData(null)} />
+ setDevMenuVisible(false)} />
>
);
};
@@ -197,11 +154,11 @@ const getStyles = (theme: AppTheme) =>
fontWeight: theme.typography.fontWeight.bold,
fontSize: theme.typography.fontSize.s,
color: theme.colors.textSecondary,
- textTransform: "uppercase",
+ textTransform: 'uppercase',
marginTop: theme.spacing.m + 4,
marginBottom: theme.spacing.s,
paddingHorizontal: theme.spacing.m,
- textAlign: I18nManager.isRTL ? "right" : "left",
+ textAlign: I18nManager.isRTL ? 'right' : 'left',
},
listContent: {
paddingBottom: theme.spacing.l,
@@ -212,7 +169,7 @@ const getStyles = (theme: AppTheme) =>
marginStart: theme.spacing.m,
},
footer: {
- alignItems: "center",
+ alignItems: 'center',
marginTop: theme.spacing.l,
},
footerText: {
diff --git a/src/screens/StatsScreen.tsx b/src/screens/StatsScreen.tsx
index 1b05ed4..83e5612 100644
--- a/src/screens/StatsScreen.tsx
+++ b/src/screens/StatsScreen.tsx
@@ -1,17 +1,17 @@
// FILE: src/screens/StatsScreen.tsx
-import { Screen } from "@/components/Screen";
-import { ThemedText } from "@/components/base/ThemedText";
-import { AppTheme } from "@/constants/theme";
-import i18n from "@/core/i18n";
-import useAppStore from "@/core/store/appStore";
-import { useTheme } from "@/core/theme/ThemeContext";
-import { ActivityGraph } from "@/features/stats/ActivityGraph";
-import { DeedStatsRow } from "@/features/stats/DeedStatsRow";
-import { ExpandableSection } from "@/features/stats/ExpandableSection";
-import { GoalStatsRow } from "@/features/stats/GoalStatsRow";
-import { SegmentedControl } from "@/features/stats/SegmentedControl";
-import { TimeInterval } from "@/features/stats/types";
-import { SummaryBox } from "@/features/stats/SummaryBox";
+import { Screen } from '@/components/Screen';
+import { ThemedText } from '@/components/base/ThemedText';
+import { AppTheme } from '@/constants/theme';
+import i18n from '@/core/i18n';
+import useAppStore from '@/core/store/appStore';
+import { useTheme } from '@/core/theme/ThemeContext';
+import { ActivityGraph } from '@/features/stats/ActivityGraph';
+import { DeedStatsRow } from '@/features/stats/DeedStatsRow';
+import { ExpandableSection } from '@/features/stats/ExpandableSection';
+import { GoalStatsRow } from '@/features/stats/GoalStatsRow';
+import { SegmentedControl } from '@/features/stats/SegmentedControl';
+import { TimeInterval } from '@/features/stats/types';
+import { SummaryBox } from '@/features/stats/SummaryBox';
import {
eachDayOfInterval,
endOfDay,
@@ -20,22 +20,16 @@ import {
startOfWeek,
startOfYear,
subDays,
-} from "date-fns";
-import React, { useEffect, useMemo, useState } from "react";
-import {
- ActivityIndicator,
- InteractionManager,
- ScrollView,
- StyleSheet,
- View,
-} from "react-native";
+} from 'date-fns';
+import React, { useEffect, useMemo, useState } from 'react';
+import { ActivityIndicator, InteractionManager, ScrollView, StyleSheet, View } from 'react-native';
const PRAYER_DEED_IDS = [
- "prayer-fajr",
- "prayer-dhuhr",
- "prayer-asr",
- "prayer-maghrib",
- "prayer-isha",
+ 'prayer-fajr',
+ 'prayer-dhuhr',
+ 'prayer-asr',
+ 'prayer-maghrib',
+ 'prayer-isha',
];
const StatsScreen = () => {
@@ -43,7 +37,7 @@ const StatsScreen = () => {
const styles = getStyles(theme);
const { isInitialized, deeds, logs } = useAppStore();
- const [interval, setInterval] = useState("week");
+ const [interval, setInterval] = useState('week');
const [isLoading, setIsLoading] = useState(true);
const [displayData, setDisplayData] = useState<{
@@ -52,26 +46,18 @@ const StatsScreen = () => {
} | null>(null);
const prayerDeeds = useMemo(
- () =>
- PRAYER_DEED_IDS.map((id) => deeds.find((d) => d.id === id)).filter(
- Boolean,
- ),
+ () => PRAYER_DEED_IDS.map((id) => deeds.find((d) => d.id === id)).filter(Boolean),
[deeds],
);
- const goalDeeds = useMemo(
- () => deeds.filter((d) => d.goal && !d.isCore),
- [deeds],
- );
+ const goalDeeds = useMemo(() => deeds.filter((d) => d.goal && !d.isCore), [deeds]);
// --- Data specifically for the graph (last 60 days) ---
const graphData = useMemo(() => {
const today = new Date();
const sixtyDaysAgo = startOfDay(subDays(today, 59)); // Changed from 89 to 59 for ~2 months
const dateRange = eachDayOfInterval({ start: sixtyDaysAgo, end: today });
- const relevantLogs = logs.filter(
- (log) => new Date(log.date) >= sixtyDaysAgo,
- );
+ const relevantLogs = logs.filter((log) => new Date(log.date) >= sixtyDaysAgo);
return { dateRange, logs: relevantLogs };
}, [logs]);
@@ -83,33 +69,26 @@ const StatsScreen = () => {
const today = new Date();
let dateRangeForFilter;
- if (interval === "all") {
+ if (interval === 'all') {
const firstLogDate =
- logs.length > 0
- ? new Date(logs.reduce((a, b) => (a.date < b.date ? a : b)).date)
- : today;
+ logs.length > 0 ? new Date(logs.reduce((a, b) => (a.date < b.date ? a : b)).date) : today;
dateRangeForFilter = { start: startOfDay(firstLogDate), end: today };
} else {
const range = { start: startOfWeek(today), end: endOfDay(today) };
- if (interval === "month") range.start = startOfMonth(today);
- if (interval === "year") range.start = startOfYear(today);
+ if (interval === 'month') range.start = startOfMonth(today);
+ if (interval === 'year') range.start = startOfYear(today);
dateRangeForFilter = range;
}
const filtered =
- interval === "all"
+ interval === 'all'
? logs
: logs.filter((log) => {
const logDate = startOfDay(new Date(log.date));
- return (
- logDate >= dateRangeForFilter.start &&
- logDate <= dateRangeForFilter.end
- );
+ return logDate >= dateRangeForFilter.start && logDate <= dateRangeForFilter.end;
});
- const prayerLogs = filtered.filter((log) =>
- PRAYER_DEED_IDS.includes(log.deedId),
- );
+ const prayerLogs = filtered.filter((log) => PRAYER_DEED_IDS.includes(log.deedId));
const total = prayerLogs.length;
const counts = prayerLogs.reduce(
(acc, log) => {
@@ -123,9 +102,9 @@ const StatsScreen = () => {
count: counts.jamaah || 0,
percentage: total > 0 ? ((counts.jamaah || 0) / total) * 100 : 0,
},
- "on-time": {
- count: counts["on-time"] || 0,
- percentage: total > 0 ? ((counts["on-time"] || 0) / total) * 100 : 0,
+ 'on-time': {
+ count: counts['on-time'] || 0,
+ percentage: total > 0 ? ((counts['on-time'] || 0) / total) * 100 : 0,
},
late: {
count: counts.late || 0,
@@ -146,10 +125,10 @@ const StatsScreen = () => {
}, [isInitialized, interval, logs]);
const segmentedOptions = [
- { label: i18n.t("intervals.week"), value: "week" as TimeInterval },
- { label: i18n.t("intervals.month"), value: "month" as TimeInterval },
- { label: i18n.t("intervals.year"), value: "year" as TimeInterval },
- { label: i18n.t("intervals.allTime"), value: "all" as TimeInterval },
+ { label: i18n.t('intervals.week'), value: 'week' as TimeInterval },
+ { label: i18n.t('intervals.month'), value: 'month' as TimeInterval },
+ { label: i18n.t('intervals.year'), value: 'year' as TimeInterval },
+ { label: i18n.t('intervals.allTime'), value: 'all' as TimeInterval },
];
const renderContent = () => {
@@ -162,39 +141,32 @@ const StatsScreen = () => {
}
return (
<>
-
+
-
- {i18n.t("stats.prayerPerformance")}
-
+ showsVerticalScrollIndicator={false}>
+ {i18n.t('stats.prayerPerformance')}
{
{goalDeeds.length > 0 && (
-
- {i18n.t("stats.goalProgress")}
-
+ {i18n.t('stats.goalProgress')}
{goalDeeds.map((deed) => (
l.deedId === deed.id,
- )}
+ logs={displayData.filteredLogs.filter((l) => l.deedId === deed.id)}
/>
))}
)}
-
- {i18n.t("stats.statsByDeed")}
-
-
+ {i18n.t('stats.statsByDeed')}
+
{prayerDeeds.map((deed) => (
-
+
))}
@@ -238,15 +200,9 @@ const StatsScreen = () => {
};
return (
-
-
- {i18n.t("stats.activity")}
-
-
+
+ {i18n.t('stats.activity')}
+
{renderContent()}
);
@@ -258,8 +214,8 @@ const getStyles = (theme: AppTheme) =>
StyleSheet.create({
centered: {
flex: 1,
- justifyContent: "center",
- alignItems: "center",
+ justifyContent: 'center',
+ alignItems: 'center',
},
scrollContent: {
paddingBottom: theme.spacing.xl,
@@ -272,9 +228,9 @@ const getStyles = (theme: AppTheme) =>
marginBottom: theme.spacing.s,
},
summaryGrid: {
- flexDirection: "row",
- flexWrap: "wrap",
- justifyContent: "space-between",
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'space-between',
marginHorizontal: -theme.spacing.xs, // Counteract summary box margin
},
card: {