Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import and export subjects colors and names #392

Closed
wants to merge 20 commits into from
Closed
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: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"reanimated-color-picker": "^3.0.4",
"scolengo-api": "^3.0.5",
"text-encoding": "^0.7.0",
"expo-document-picker": "~12.0.2",
"turboself-api": "^2.1.6",
"zustand": "^4.5.2"
},
Expand Down
196 changes: 170 additions & 26 deletions src/views/settings/SettingsSubjects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ import { NativeItem, NativeList, NativeText } from "@/components/Global/NativeCo
import { useCurrentAccount } from "@/stores/account";
import MissingItem from "@/components/Global/MissingItem";
import BottomSheet from "@/components/Modals/PapillonBottomSheet";
import { Trash2, Check, X } from "lucide-react-native";
import { Trash2, ArrowDownCircle, ArrowUpCircle, Check, X } from "lucide-react-native";
import ColorIndicator from "@/components/Lessons/ColorIndicator";
import { COLORS_LIST } from "@/services/shared/Subject";
import type { Screen } from "@/router/helpers/types";
import SubjectContainerCard from "@/components/Settings/SubjectContainerCard";
import { set } from "lodash";
import * as Clipboard from "expo-clipboard";
import * as FileSystem from "expo-file-system";
import * as DocumentPicker from "expo-document-picker";

const MemoizedNativeItem = React.memo(NativeItem);
const MemoizedNativeList = React.memo(NativeList);
Expand All @@ -33,6 +36,7 @@ type Item = [key: string, value: { color: string; pretty: string; emoji: string;

const SettingsSubjects: Screen<"SettingsSubjects"> = ({ navigation }) => {
const account = useCurrentAccount(store => store.account!);
const subjectsForCopy = account.personalization.subjects;
const mutateProperty = useCurrentAccount(store => store.mutateProperty);
const insets = useSafeAreaInsets();
const colors = useTheme().colors;
Expand Down Expand Up @@ -69,6 +73,25 @@ const SettingsSubjects: Screen<"SettingsSubjects"> = ({ navigation }) => {
);
}, []);

const setSubjectsFromJson = (json: { [key: string]: { color: string; pretty: string; emoji: string; } }) => {
const newSubjects: Item[] = Object.entries(json) as Item[];
setSubjects(newSubjects);
setLocalSubjects(newSubjects);
mutateProperty("personalization", {
...account.personalization,
subjects: json,
});
};

const isJsonable = (strTest: string) => {
try {
JSON.parse(strTest);
return true;
} catch (e) {
return false;
}
};

const debouncedUpdateSubject = useMemo(
() => debounce((subjectKey: string, updates: Partial<Item[1]>) => {
updateSubject(subjectKey, updates);
Expand Down Expand Up @@ -131,31 +154,152 @@ const SettingsSubjects: Screen<"SettingsSubjects"> = ({ navigation }) => {
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<TouchableOpacity
onPress={() => {
Alert.alert(
"Réinitialiser les matières",
"Voulez-vous vraiment réinitialiser les matières ?",
[
{ text: "Annuler", style: "cancel" },
{ text: "Réinitialiser", style: "destructive", onPress: () => {
setSubjects([]);
setLocalSubjects([]);
setCurrentTitle("");
setCurrentEmoji("");

mutateProperty("personalization", {
...account.personalization,
subjects: {},
});
}},
]
);
}}
style={{ marginRight: 2 }}
>
<Trash2 size={22} color={colors.primary} />
</TouchableOpacity>
<View style={{ flexDirection: "row", alignItems: "center" }}>

<TouchableOpacity
onPress={() => {
Alert.alert(
"Exporter",
"Exporter les couleurs actuelles ?",
[
{ text: "Annuler", style: "cancel" },
{
text: "Presse papier",
style: "destructive",
onPress: () => {
const writeToClipboard = async (text: string) => {
await Clipboard.setStringAsync(text);
};
writeToClipboard(JSON.stringify(subjectsForCopy));
}
},
{
text: "Fichier",
style: "destructive",
onPress: () => {
const saveFile = async (text: string) => {
try {
// Demande à l'utilisateur de choisir un dossier
const permissions = await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync();
if (!permissions.granted) {
Alert.alert("Permission refusée", "Impossible d'accéder au dossier.");
return;
}
const fileContent = text;
const fileName = "papillon_colors.json";
await FileSystem.StorageAccessFramework.createFileAsync(
permissions.directoryUri,
fileName,
"application/json"
).then(async (uri) => {
await FileSystem.writeAsStringAsync(uri, fileContent, {
encoding: FileSystem.EncodingType.UTF8,
});
Alert.alert("Succès", "Fichier enregistré avec succès !");
});
} catch (error) {
console.error("Erreur SAF :", error);
Alert.alert("Erreur", "Impossible de sauvegarder le fichier.");
}
};
saveFile(JSON.stringify(subjectsForCopy));
}
},
]
);
}}
style={{ marginRight: 15 }}
>
<ArrowUpCircle size={22} color={colors.primary} />
</TouchableOpacity>

{/* Bouton importer */}
<TouchableOpacity
onPress={() => {
Alert.alert(
"Importer",
"Importer une table de couleurs depuis le presse papier ?",
[
{ text: "Annuler", style: "cancel" },
{
text: "Presse papier",
style: "destructive",
onPress: () => {
const getClipboard = async () => {
const result = await Clipboard.getStringAsync();
if (isJsonable(result)) {
setSubjectsFromJson(JSON.parse(result));
}
codeuriii marked this conversation as resolved.
Show resolved Hide resolved
};
getClipboard();
}
},
{
text: "Fichier",
style: "destructive",
onPress: () => {
const pickAndReadJsonFile = async () => {
try {
const result = await DocumentPicker.getDocumentAsync({
type: "application/json",
});

if (result.canceled) {
console.log("Sélection annulée.");
return;
}
const { uri } = result.assets[0];
const fileContent = await FileSystem.readAsStringAsync(uri, {
encoding: FileSystem.EncodingType.UTF8,
});

if (isJsonable(fileContent)) {
setSubjectsFromJson(JSON.parse(fileContent));
}
codeuriii marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
console.error("Erreur lors de la lecture du fichier :", err);
}
};
pickAndReadJsonFile();
}
},
]
);
}}
style={{ marginRight: 15 }}
>
<ArrowDownCircle size={22} color={colors.primary} />
</TouchableOpacity>

<TouchableOpacity
onPress={() => {
Alert.alert(
"Réinitialiser les matières",
"Voulez-vous vraiment réinitialiser les matières ?",
[
{ text: "Annuler", style: "cancel" },
{
text: "Réinitialiser",
style: "destructive",
onPress: () => {
setSubjects([]);
setLocalSubjects([]);
setCurrentTitle("");

mutateProperty("personalization", {
...account.personalization,
subjects: {},
});
}
},
]
);
}}
style={{ marginRight: 2 }}
>
<Trash2 size={22} color={colors.primary} />
</TouchableOpacity>
</View>
),
});
}, [navigation, colors.primary]);
Expand Down
Loading