Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
<!-- Internet access -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- For Android 13+ (API 33+) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<!-- For Android 10 and below -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />

<!-- For Android 11+ -->
<!-- Only use MANAGE_EXTERNAL_STORAGE if you need full access, else rely on SAF -->
<!-- Optional: only if your app is a file manager or needs all-file access -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

<application
android:name=".MainApplication"
android:label="@string/app_name"
Expand Down
138 changes: 95 additions & 43 deletions src/screens/user-settings/SettingsScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// src/screens/user-settings/SettingsScreen.tsx

import React, { useState, useEffect } from 'react';
import {
View,
Expand All @@ -17,7 +19,7 @@ import {
import Slider from '@react-native-community/slider';
import MultiSelect from 'react-native-multiple-select';
import useStore from '../../store/store';
import { saveScheduleToDevice, shareSchedule } from '../../utils/exportSchedule';
import { saveScheduleToDevice, shareSchedule, saveScheduleToDeviceWithName } from '../../utils/exportSchedule';
import pickCSVFile, { pickCSVFileRaw } from '../../utils/csv-picker';
import { importAndAddToRegisterFromContent } from '../../utils/csv-import';

Expand All @@ -37,6 +39,8 @@ const SettingsScreen: React.FC = () => {
registers,
activeRegister,
selectedRegisters,
viewingRegisters,
setViewingRegisters,
setDefaultTargetPercentage,
updateAllRegistersTargetPercentage,
selectedSchedules,
Expand All @@ -54,7 +58,15 @@ const SettingsScreen: React.FC = () => {
const [newTargetValue, setNewTargetValue] = useState(defaultTargetPercentage.toString());
const [localLeadTime, setLocalLeadTime] = useState(notificationLeadTime);
const [localSchedules, setLocalSchedules] = useState<string[]>(selectedSchedules);

// CSV filename modal state
const [showFileNameModal, setShowFileNameModal] = useState(false);
const [csvFileName, setCsvFileName] = useState('MySchedule');
const [isSaving, setIsSaving] = useState(false);

const registerOptions = Object.keys(registers).map(key => ({
id: key,
name: registers[parseInt(key)].name,
}));
// Effects
useEffect(() => {
setAppVersion(packageJson.version);
Expand Down Expand Up @@ -103,56 +115,56 @@ const SettingsScreen: React.FC = () => {
};

// Export functionality handlers
const handleSaveScheduleToDevice = async () => {
// Show modal to enter filename
const handleSaveScheduleToDevice = () => {
setShowFileNameModal(true);
};

// Actually save with filename
const handleConfirmSaveFileName = async () => {
setIsSaving(true);
try {
console.log('Starting save to device...');
console.log('Available registers:', Object.keys(registers));
console.log('Selected registers from store:', selectedRegisters);

// Use all registers if no specific selection is available
const currentSelectedRegisters = selectedRegisters && selectedRegisters.length > 0
? selectedRegisters
const currentSelectedRegisters = selectedRegisters && selectedRegisters.length > 0
? selectedRegisters
: Object.keys(registers).map(key => parseInt(key, 10));

console.log('Using registers for export:', currentSelectedRegisters);

if (currentSelectedRegisters.length === 0) {
Alert.alert('No Data', 'No registers found to export. Please create some schedules first.');
setIsSaving(false);
return;
}

await saveScheduleToDevice({
selectedRegisters: currentSelectedRegisters,
registers
});
await saveScheduleToDeviceWithName({
selectedRegisters: currentSelectedRegisters,
registers
}, csvFileName);
setShowFileNameModal(false);
} catch (error) {
console.error('Save to device error:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
Alert.alert('Error', `Failed to save schedule to device: ${errorMessage}`);
}
setIsSaving(false);
};

const handleShareSchedule = async () => {
try {
console.log('Starting share schedule...');
console.log('Available registers:', Object.keys(registers));
console.log('Selected registers from store:', selectedRegisters);

// Use all registers if no specific selection is available
const currentSelectedRegisters = selectedRegisters && selectedRegisters.length > 0
? selectedRegisters
const currentSelectedRegisters = selectedRegisters && selectedRegisters.length > 0
? selectedRegisters
: Object.keys(registers).map(key => parseInt(key, 10));

console.log('Using registers for share:', currentSelectedRegisters);

if (currentSelectedRegisters.length === 0) {
Alert.alert('No Data', 'No registers found to share. Please create some schedules first.');
return;
}
await shareSchedule({
selectedRegisters: currentSelectedRegisters,
registers

await shareSchedule({
selectedRegisters: currentSelectedRegisters,
registers
});
} catch (error) {
console.error('Share schedule error:', error);
Expand All @@ -164,7 +176,7 @@ const SettingsScreen: React.FC = () => {
const handleImportSchedule = async () => {
try {
console.log('Starting CSV import...');

if (!registers[activeRegister]) {
Alert.alert('Error', 'No active register found. Please create a register first.');
return;
Expand All @@ -173,7 +185,7 @@ const SettingsScreen: React.FC = () => {
// Use raw CSV content instead of parsed data
const csvContent = await pickCSVFileRaw();
console.log('Raw CSV Content received:', csvContent);

if (!csvContent) {
console.log('No CSV content received (user cancelled or error)');
return;
Expand Down Expand Up @@ -238,7 +250,6 @@ const SettingsScreen: React.FC = () => {
<Text style={styles.infoValue}>{registerInfo.name}</Text>
<Text style={styles.infoSubtext}>{registerInfo.totalCards} subjects</Text>
</View>

{/* Preferences Section */}
<View style={styles.settingsSection}>
<Text style={styles.sectionTitle}>Preferences</Text>
Expand Down Expand Up @@ -294,6 +305,7 @@ const SettingsScreen: React.FC = () => {
</View>
</View>


{/* Notifications & Alerts Section */}
<View style={styles.settingsSection}>
<Text style={styles.sectionTitle}>Notifications & Alerts</Text>
Expand Down Expand Up @@ -333,29 +345,71 @@ const SettingsScreen: React.FC = () => {
/>
<Button title="Save Settings" onPress={handleSave} color="#4CAF50" />
</View>

{/* Utilities Section */}
<View style={styles.settingsSection}>
<Text style={styles.sectionTitle}>Utilities</Text>

<TouchableOpacity style={styles.utilityButton} onPress={handleSaveScheduleToDevice}>
<View style={styles.utilityButtonContent}>
<Image
source={require('../../assets/icons/save.png')}
style={styles.utilityIcon}
<Image
source={require('../../assets/icons/save.png')}
style={styles.utilityIcon}
/>
<View style={styles.utilityTextContainer}>
<Text style={styles.utilityButtonText}>Save Schedule to Device</Text>
<Text style={styles.utilityButtonDescription}>Save your schedule as CSV to Downloads</Text>
</View>
</View>
</TouchableOpacity>
{/* CSV Filename Modal */}
<Modal
visible={showFileNameModal}
transparent={true}
animationType="fade"
onRequestClose={() => setShowFileNameModal(false)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContainer}>
<Text style={styles.modalTitle}>Enter CSV Filename</Text>
<Text style={styles.modalSubtitle}>This will be the name of your exported CSV file.</Text>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
value={csvFileName}
onChangeText={setCsvFileName}
placeholder="Enter filename"
placeholderTextColor="#71717A"
maxLength={40}
selectTextOnFocus={true}
autoFocus={true}
/>
<Text style={styles.percentSymbol}>.csv</Text>
</View>
<View style={styles.modalButtons}>
<TouchableOpacity
style={[styles.modalButton, styles.cancelButton]}
onPress={() => setShowFileNameModal(false)}
disabled={isSaving}
>
<Text style={styles.cancelButtonText}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modalButton, styles.saveButton]}
onPress={handleConfirmSaveFileName}
disabled={isSaving || !csvFileName.trim()}
>
<Text style={styles.saveButtonText}>{isSaving ? 'Saving...' : 'Save'}</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>

<TouchableOpacity style={styles.utilityButton} onPress={handleShareSchedule}>
<View style={styles.utilityButtonContent}>
<Image
source={require('../../assets/icons/share.png')}
style={styles.utilityIcon}
<Image
source={require('../../assets/icons/share.png')}
style={styles.utilityIcon}
/>
<View style={styles.utilityTextContainer}>
<Text style={styles.utilityButtonText}>Share Schedule</Text>
Expand All @@ -366,9 +420,9 @@ const SettingsScreen: React.FC = () => {

<TouchableOpacity style={styles.utilityButton} onPress={handleImportSchedule}>
<View style={styles.utilityButtonContent}>
<Image
source={require('../../assets/icons/export.png')}
style={styles.utilityIcon}
<Image
source={require('../../assets/icons/export.png')}
style={styles.utilityIcon}
/>
<View style={styles.utilityTextContainer}>
<Text style={styles.utilityButtonText}>Import Schedule from CSV</Text>
Expand Down Expand Up @@ -456,7 +510,6 @@ const SettingsScreen: React.FC = () => {
</>
);
};

// Styles
const styles = StyleSheet.create({
container: {
Expand Down Expand Up @@ -713,5 +766,4 @@ const styles = StyleSheet.create({
color: '#A1A1AA',
},
});

export default SettingsScreen;
Loading
Loading