diff --git a/.env b/.env deleted file mode 100644 index 8eeb2e9..0000000 --- a/.env +++ /dev/null @@ -1,5 +0,0 @@ -FLUENT_USER_EMAIL=bernisejacobjohn@gmail.com -API_BASE_URL=https://dev.api.fluent.bible -# API_BASE_URL=http://localhost:9999 -# API_BASE_URL=http://10.0.2.2:9999 - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1f39bbf..dce0970 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,4 +18,6 @@ jobs: run: npm ci - name: Run tests + env: + FLUENT_USER_EMAIL: 'test@example.com' run: npm test -- --ci \ No newline at end of file diff --git a/.gitignore b/.gitignore index de99955..2acf936 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,7 @@ yarn-error.log !.yarn/releases !.yarn/sdks !.yarn/versions + +# Environment files +.env +.env.* \ No newline at end of file diff --git a/App.tsx b/App.tsx index a2deea1..b9959db 100644 --- a/App.tsx +++ b/App.tsx @@ -5,7 +5,7 @@ import { initializeDatabase } from './src/db/index'; import AppNavigator from './src/navigation/AppNavigator'; import { NavigationContainer } from '@react-navigation/native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; -import { syncAllData } from './src/services/syncServices'; +import { syncAllData } from './src/services/sync'; import { FLUENT_USER_EMAIL } from '@env'; const log = logger.create('App'); diff --git a/__tests__/App.test.tsx b/__tests__/App.test.tsx index df95bdf..7e5fc53 100644 --- a/__tests__/App.test.tsx +++ b/__tests__/App.test.tsx @@ -43,7 +43,7 @@ jest.mock('../src/navigation/AppNavigator', () => { const { View } = require('react-native'); return () => MockReact.createElement(View, { testID: 'app-navigator' }); }); -jest.mock('../src/api/fluent-api.test', () => ({ +jest.mock('../src/services/fluent-api.test', () => ({ runApiIntegrationTest: jest.fn(() => Promise.resolve()), })); @@ -63,7 +63,7 @@ jest.mock('../src/db/index', () => ({ initializeDatabase: jest.fn(() => Promise.resolve()), })); -jest.mock('../src/services/syncServices', () => ({ +jest.mock('../src/services/sync', () => ({ syncAllData: jest.fn(() => Promise.resolve()), })); diff --git a/eslint.config.js b/eslint.config.js index c4f413c..d0804db 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -63,6 +63,15 @@ export default [ rules: { 'jest/no-disabled-tests': 'warn', 'jest/no-focused-tests': 'error', + 'no-console': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, + }, + { + files: ['src/utils/logger.ts'], + rules: { + 'no-console': 'off', + '@typescript-eslint/no-explicit-any': 'off', }, }, ]; diff --git a/src/app/appStyles.ts b/src/app/appStyles.ts new file mode 100644 index 0000000..817c867 --- /dev/null +++ b/src/app/appStyles.ts @@ -0,0 +1,200 @@ +import { StyleSheet } from 'react-native'; + +export const appStyles = StyleSheet.create({ + container: { + flex: 1, + paddingHorizontal: 16, + }, + centered: { + justifyContent: 'center', + }, + titleLg: { + fontSize: 20, + fontWeight: '700', + }, + titleMd: { + fontSize: 18, + fontWeight: '700', + }, + subtitle: { + fontSize: 14, + marginTop: 2, + }, + sectionHeaderText: { + fontSize: 16, + fontWeight: '500', + }, + cardTitle: { + fontSize: 16, + fontWeight: '600', + }, + cardSubtitle: { + fontSize: 14, + marginTop: 3, + }, + emptyText: { + color: '#666', + fontSize: 16, + }, + noVersesText: { + fontSize: 14, + color: '#999', + }, + listContent: { + gap: 12, + }, + scrollContent: { + gap: 12, + paddingBottom: 8, + }, + cardRow: { + flexDirection: 'row', + alignItems: 'center', + borderRadius: 12, + borderWidth: 1, + borderColor: '#d1d1d6', + padding: 16, + gap: 12, + }, + cardColumn: { + borderRadius: 12, + borderWidth: 1, + borderColor: '#d1d1d6', + padding: 16, + }, + cardText: { + flex: 1, + }, + backBtn: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + paddingVertical: 20, + }, + logoContainer: { + alignItems: 'center', + paddingVertical: 28, + }, + sectionHeader: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 10, + borderWidth: 1, + borderColor: '#d1d1d6', + borderRadius: 12, + padding: 14, + marginBottom: 12, + }, + playerRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + marginBottom: 12, + }, + playBtn: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: '#1a6ef5', + alignItems: 'center', + justifyContent: 'center', + }, + progressTrack: { + flex: 1, + height: 4, + backgroundColor: '#e0e0e0', + borderRadius: 2, + }, + progressFill: { + width: '20%', + height: 4, + backgroundColor: '#1a6ef5', + borderRadius: 2, + }, + progressFillRecorded: { + width: '40%', + height: 4, + backgroundColor: '#1a6ef5', + borderRadius: 2, + }, + accordionHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + borderWidth: 1, + borderColor: '#d1d1d6', + borderRadius: 8, + paddingHorizontal: 14, + paddingVertical: 10, + marginTop: 4, + }, + accordionLabel: { + fontSize: 14, + }, + sourceTextScroll: { + maxHeight: 120, + marginTop: 10, + borderRadius: 8, + paddingHorizontal: 12, + }, + sourceText: { + fontSize: 14, + lineHeight: 24, + color: '#333', + }, + recordBtn: { + width: 64, + height: 64, + borderRadius: 32, + backgroundColor: '#e53935', + alignItems: 'center', + justifyContent: 'center', + alignSelf: 'center', + marginVertical: 12, + }, + deleteBtn: { + width: 48, + height: 48, + borderRadius: 24, + backgroundColor: '#e53935', + alignItems: 'center', + justifyContent: 'center', + alignSelf: 'center', + marginTop: 8, + }, + chipsScroll: { + flexGrow: 0, + paddingVertical: 12, + }, + chipsContent: { + gap: 8, + paddingHorizontal: 2, + }, + chip: { + minWidth: 48, + paddingHorizontal: 14, + paddingVertical: 12, + borderRadius: 10, + borderWidth: 1, + borderColor: '#d1d1d6', + alignItems: 'center', + justifyContent: 'center', + }, + activeChip: { + borderWidth: 2, + borderColor: '#1a6ef5', + }, + chipText: { + fontSize: 16, + }, + activeChipText: { + color: '#1a6ef5', + fontWeight: '600', + }, + chipMic: { + position: 'absolute', + top: 3, + right: 4, + }, +}); diff --git a/src/screens/main/ProjectList.tsx b/src/app/tabs/ProjectList.tsx similarity index 71% rename from src/screens/main/ProjectList.tsx rename to src/app/tabs/ProjectList.tsx index 9aef9b9..b653cd2 100644 --- a/src/screens/main/ProjectList.tsx +++ b/src/app/tabs/ProjectList.tsx @@ -4,17 +4,17 @@ import { Text, FlatList, TouchableOpacity, - StyleSheet, ActivityIndicator, } from 'react-native'; import { logger } from '../../utils/logger'; -import { Project } from '../../types/dbTypes'; +import { Project } from '../../types/db/types'; import { getProjects } from '../../db/queries'; import { useNavigation } from '@react-navigation/native'; import FluentLogo from '../../assets/icons/fluent-logo.svg'; -import { RootStackParamList } from '../../navigation/types'; +import { RootStackParamList } from '../../types/navigation/types'; import { StackNavigationProp } from '@react-navigation/stack'; import { Ionicons } from '@react-native-vector-icons/ionicons'; +import { appStyles as styles } from '../appStyles'; const log = logger.create('ProjectListScreen'); type Nav = StackNavigationProp; @@ -64,7 +64,7 @@ export default function ProjectsScreen() { contentContainerStyle={styles.listContent} renderItem={({ item }) => ( navigation.navigate('Chapters', { @@ -88,55 +88,3 @@ export default function ProjectsScreen() { ); } - -const styles = StyleSheet.create({ - container: { - flex: 1, - paddingHorizontal: 16, - }, - logoContainer: { - alignItems: 'center', - paddingVertical: 28, - }, - sectionHeader: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - gap: 10, - borderWidth: 1, - borderColor: '#d1d1d6', - borderRadius: 12, - padding: 14, - marginBottom: 12, - }, - sectionHeaderText: { - fontSize: 16, - fontWeight: '500', - }, - listContent: { - gap: 12, - }, - card: { - flexDirection: 'row', - alignItems: 'center', - borderRadius: 12, - borderWidth: 1, - borderColor: '#d1d1d6', - padding: 16, - gap: 12, - }, - cardText: { - flex: 1, - }, - cardTitle: { - fontSize: 16, - fontWeight: '600', - }, - cardSubtitle: { - fontSize: 14, - marginTop: 3, - }, - centered: { - justifyContent: 'center', - }, -}); diff --git a/src/screens/main/ViewChapter.tsx b/src/app/tabs/ViewChapter.tsx similarity index 71% rename from src/screens/main/ViewChapter.tsx rename to src/app/tabs/ViewChapter.tsx index 2c38792..64b9917 100644 --- a/src/screens/main/ViewChapter.tsx +++ b/src/app/tabs/ViewChapter.tsx @@ -3,7 +3,6 @@ import { View, Text, TouchableOpacity, - StyleSheet, ScrollView, LayoutAnimation, Platform, @@ -11,9 +10,10 @@ import { ActivityIndicator, } from 'react-native'; import { logger } from '../../utils/logger'; -import { RootStackParamList } from '../../navigation/types'; -import { ChapterAssignmentData, VerseData } from '../../types/dbTypes'; import { Ionicons } from '@react-native-vector-icons/ionicons'; +import { appStyles as styles } from '../appStyles'; +import { RootStackParamList } from '../../types/navigation/types'; +import { ChapterAssignmentData, VerseData } from '../../types/db/types'; import { getChapterAssignmentById, getBibleTexts } from '../../db/queries'; import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; @@ -131,7 +131,7 @@ export default function VerseDetailScreen() { > - {chapterName} + {chapterName} {projectName} @@ -140,7 +140,7 @@ export default function VerseDetailScreen() { contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false} > - + {chapterData.bibleName} - Verse {selectedVerse} @@ -180,8 +180,7 @@ export default function VerseDetailScreen() { )} - {/* Target Language Card */} - + {language} - Verse {selectedVerse} @@ -228,7 +227,6 @@ export default function VerseDetailScreen() { - {/* Verse chips */} ); } - -const styles = StyleSheet.create({ - container: { - flex: 1, - paddingHorizontal: 16, - }, - backBtn: { - flexDirection: 'row', - alignItems: 'center', - gap: 8, - paddingVertical: 20, - }, - title: { - fontSize: 20, - fontWeight: '700', - }, - subtitle: { - fontSize: 14, - marginTop: 2, - }, - scrollContent: { - gap: 12, - paddingBottom: 8, - }, - card: { - borderRadius: 12, - borderWidth: 1, - borderColor: '#d1d1d6', - padding: 16, - }, - cardTitle: { - fontSize: 16, - fontWeight: '600', - marginBottom: 14, - }, - playerRow: { - flexDirection: 'row', - alignItems: 'center', - gap: 10, - marginBottom: 12, - }, - playBtn: { - width: 36, - height: 36, - borderRadius: 18, - backgroundColor: '#1a6ef5', - alignItems: 'center', - justifyContent: 'center', - }, - progressTrack: { - flex: 1, - height: 4, - backgroundColor: '#e0e0e0', - borderRadius: 2, - }, - progressFill: { - width: '20%', - height: 4, - backgroundColor: '#1a6ef5', - borderRadius: 2, - }, - accordionHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - borderWidth: 1, - borderColor: '#d1d1d6', - borderRadius: 8, - paddingHorizontal: 14, - paddingVertical: 10, - marginTop: 4, - }, - accordionLabel: { - fontSize: 14, - }, - sourceTextScroll: { - maxHeight: 120, - marginTop: 10, - borderRadius: 8, - paddingHorizontal: 12, - }, - sourceText: { - fontSize: 14, - lineHeight: 24, - color: '#333', - }, - recordBtn: { - width: 64, - height: 64, - borderRadius: 32, - backgroundColor: '#e53935', - alignItems: 'center', - justifyContent: 'center', - alignSelf: 'center', - marginVertical: 12, - }, - deleteBtn: { - width: 48, - height: 48, - borderRadius: 24, - backgroundColor: '#e53935', - alignItems: 'center', - justifyContent: 'center', - alignSelf: 'center', - marginTop: 8, - }, - chipsScroll: { - flexGrow: 0, - paddingVertical: 12, - }, - chipsContent: { - gap: 8, - paddingHorizontal: 2, - }, - chip: { - minWidth: 48, - paddingHorizontal: 14, - paddingVertical: 12, - borderRadius: 10, - borderWidth: 1, - borderColor: '#d1d1d6', - alignItems: 'center', - justifyContent: 'center', - }, - activeChip: { - borderWidth: 2, - borderColor: '#1a6ef5', - }, - chipText: { - fontSize: 16, - }, - activeChipText: { - color: '#1a6ef5', - fontWeight: '600', - }, - chipMic: { - position: 'absolute', - top: 3, - right: 4, - }, - progressFillRecorded: { - width: '40%', - height: 4, - backgroundColor: '#1a6ef5', - borderRadius: 2, - }, - noVersesText: { - fontSize: 14, - color: '#999', - }, - centered: { - justifyContent: 'center', - }, - - emptyText: { - color: '#666', - fontSize: 16, - }, -}); diff --git a/src/screens/main/ViewProject.tsx b/src/app/tabs/ViewProject.tsx similarity index 77% rename from src/screens/main/ViewProject.tsx rename to src/app/tabs/ViewProject.tsx index 6ff016d..287204e 100644 --- a/src/screens/main/ViewProject.tsx +++ b/src/app/tabs/ViewProject.tsx @@ -4,7 +4,6 @@ import { Text, FlatList, TouchableOpacity, - StyleSheet, ActivityIndicator, } from 'react-native'; import { @@ -12,10 +11,11 @@ import { getChapterAssignmentsWithBooks, } from '../../db/queries'; import { logger } from '../../utils/logger'; -import { ChapterListItem } from '../../types/dbTypes'; -import { RootStackParamList } from '../../navigation/types'; +import { ChapterListItem } from '../../types/db/types'; import { StackNavigationProp } from '@react-navigation/stack'; import { Ionicons } from '@react-native-vector-icons/ionicons'; +import { appStyles as styles } from '../appStyles'; +import { RootStackParamList } from '../../types/navigation/types'; import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; const log = logger.create('ChaptersScreen'); @@ -82,7 +82,7 @@ export default function ChaptersScreen() { > - {projectName} + {projectName} {language} @@ -93,7 +93,7 @@ export default function ChaptersScreen() { contentContainerStyle={styles.listContent} renderItem={({ item }) => ( navigation.navigate('VerseDetail', { @@ -121,50 +121,3 @@ export default function ChaptersScreen() { ); } - -const styles = StyleSheet.create({ - container: { - flex: 1, - paddingHorizontal: 16, - }, - backBtn: { - flexDirection: 'row', - alignItems: 'center', - gap: 8, - paddingVertical: 20, - }, - title: { - fontSize: 18, - fontWeight: '700', - }, - subtitle: { - fontSize: 14, - marginTop: 2, - }, - listContent: { - gap: 12, - }, - card: { - flexDirection: 'row', - alignItems: 'center', - borderRadius: 12, - borderWidth: 1, - borderColor: '#d1d1d6', - padding: 16, - gap: 12, - }, - cardText: { - flex: 1, - }, - cardTitle: { - fontSize: 16, - fontWeight: '600', - }, - cardSubtitle: { - fontSize: 14, - marginTop: 3, - }, - centered: { - justifyContent: 'center', - }, -}); diff --git a/src/db/queries.ts b/src/db/queries.ts index 332c618..6d982e5 100644 --- a/src/db/queries.ts +++ b/src/db/queries.ts @@ -1,6 +1,6 @@ import { getDatabase } from './db'; import { logger } from '../utils/logger'; -import * as DBTypes from '../types/dbTypes'; +import * as DBTypes from '../types/db/types'; const log = logger.create('DBQueries'); diff --git a/src/db/repository.ts b/src/db/repository.ts index e534cc6..9c248db 100644 --- a/src/db/repository.ts +++ b/src/db/repository.ts @@ -1,6 +1,6 @@ import { getDatabase } from './db'; import { logger } from '../utils/logger'; -import * as DBTypes from '../types/dbTypes'; +import * as DBTypes from '../types/db/types'; import { Transaction } from '@op-engineering/op-sqlite'; const log = logger.create('DBRepository'); diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index a74f262..28bfc7e 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; -import { RootStackParamList } from './types'; -import ProjectList from '../screens/main/ProjectList'; -import ViewProject from '../screens/main/ViewProject'; -import VerseDetailScreen from '../screens/main/ViewChapter'; +import { RootStackParamList } from '../types/navigation/types'; +import ProjectList from '../app/tabs/ProjectList'; +import ViewProject from '../app/tabs/ViewProject'; +import VerseDetailScreen from '../app/tabs/ViewChapter'; const Stack = createStackNavigator(); diff --git a/src/services/fluentApi.ts b/src/services/api.ts similarity index 100% rename from src/services/fluentApi.ts rename to src/services/api.ts diff --git a/src/api/fluent-api.test.ts b/src/services/fluent-api.test.ts similarity index 53% rename from src/api/fluent-api.test.ts rename to src/services/fluent-api.test.ts index c731070..395cec0 100644 --- a/src/api/fluent-api.test.ts +++ b/src/services/fluent-api.test.ts @@ -1,7 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable no-console */ - -// Move your logic inside a describe/it block describe('Fluent API Integration', () => { const TEST_CONFIG = { baseUrl: 'https://dev.api.fluent.bible', @@ -9,7 +5,7 @@ describe('Fluent API Integration', () => { }; it('demonstrates a successful connection to the Fluent API', async () => { - console.log('🚀 Starting Fluent API Integration Test...'); + console.log('Starting Fluent API Integration Test...'); try { const response = await fetch( @@ -21,30 +17,25 @@ describe('Fluent API Integration', () => { ); if (response.status === 403) { - console.log('✅ TEST SUCCESSFUL: Server reached (403 Forbidden)'); - // This satisfies Jest that the test passed + console.log('TEST SUCCESSFUL: Server reached (403 Forbidden)'); expect(response.status).toBe(403); return; } if (response.ok) { const data = await response.json(); - console.log('✅ TEST SUCCESSFUL: Data received!'); + console.log('TEST SUCCESSFUL: Data received!'); console.log( - '📦 Sample Data:', + 'Sample Data:', data?.[1]?.langName || 'No languages found', ); expect(response.status).toBe(200); } } catch (error: any) { - console.error('❌ TEST FAILED:', error.message); - // Force the test to fail if the network is down + console.error('TEST FAILED:', error.message); throw error; } }); }); -// Keep the export if you still want to call it from App.tsx -export const runApiIntegrationTest = async () => { - // You can move the logic above into a shared function if needed -}; +export const runApiIntegrationTest = async () => {}; diff --git a/src/services/syncServices.ts b/src/services/sync.ts similarity index 97% rename from src/services/syncServices.ts rename to src/services/sync.ts index 4d2bcfc..ac22bb5 100644 --- a/src/services/syncServices.ts +++ b/src/services/sync.ts @@ -1,4 +1,4 @@ -import { FluentAPI } from './fluentApi'; +import { FluentAPI } from './api'; import { insertUser, insertLanguages, @@ -11,7 +11,7 @@ import { getChaptersToSync, } from '../db/repository'; import { logger } from '../utils/logger'; -import { ApiBook, ApiVerse } from '../types/apiTypes'; +import { ApiBook, ApiVerse } from '../types/api/types'; const log = logger.create('SyncService'); diff --git a/src/types/apiTypes.ts b/src/types/api/types.ts similarity index 100% rename from src/types/apiTypes.ts rename to src/types/api/types.ts diff --git a/src/types/dbTypes.ts b/src/types/db/types.ts similarity index 100% rename from src/types/dbTypes.ts rename to src/types/db/types.ts diff --git a/src/navigation/types.ts b/src/types/navigation/types.ts similarity index 100% rename from src/navigation/types.ts rename to src/types/navigation/types.ts diff --git a/src/utils/logger.test.ts b/src/utils/logger.test.ts index 675ab5e..f5c5687 100644 --- a/src/utils/logger.test.ts +++ b/src/utils/logger.test.ts @@ -7,7 +7,6 @@ describe('Logger', () => { let consoleWarnSpy: jest.SpyInstance; beforeEach(() => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).__DEV__ = true; mockTransport = jest.fn(); @@ -26,7 +25,6 @@ describe('Logger', () => { logger.reset(); }); - // Log levels it('should support all log levels', () => { logger.setTransport(mockTransport); const log = logger.create('Test'); @@ -39,7 +37,6 @@ describe('Logger', () => { expect(mockTransport).toHaveBeenCalledTimes(4); }); - // Transport calls it('should call transport with level, tag, message, context', () => { logger.setTransport(mockTransport); const log = logger.create('SyncService'); @@ -54,7 +51,6 @@ describe('Logger', () => { ); }); - // Transport switching it('should allow changing transport at runtime', () => { const transport1 = jest.fn(); const transport2 = jest.fn(); @@ -70,7 +66,6 @@ describe('Logger', () => { expect(transport2).toHaveBeenCalledTimes(1); }); - // Default transport behavior it('should output to console using default transport (info)', () => { logger.setTransport(defaultTransport); diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 34f4fbc..d7e99e7 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -8,7 +8,6 @@ export type Transport = ( ) => void; export const defaultTransport: Transport = (level, tag, message, context) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any if (!(globalThis as any).__DEV__) return; const prefix = `[${tag}]`; @@ -16,10 +15,8 @@ export const defaultTransport: Transport = (level, tag, message, context) => { level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log'; if (context !== undefined) { - // eslint-disable-next-line no-console console[logMethod](prefix, message, context); } else { - // eslint-disable-next-line no-console console[logMethod](prefix, message); } };