From 24e137f680dea2363a282443e43119b34c74a140 Mon Sep 17 00:00:00 2001 From: Diego Zafferani <43524162+DiegoZaff@users.noreply.github.com> Date: Mon, 9 Jan 2023 02:40:16 +0100 Subject: [PATCH 01/17] Filters --- ...ngsScroll.tsx => ContentWrapperScroll.tsx} | 42 +++-- src/components/Groups/Filters.tsx | 176 ++++++++++++++++++ src/components/Groups/GroupsFiltered.tsx | 15 ++ src/components/Groups/OutlinedButton.tsx | 69 +++++++ src/components/Home/MainMenu.tsx | 4 +- src/components/Home/PoliSearchBar.tsx | 52 +++--- src/components/Settings/index.ts | 2 +- src/navigation/MainStackNavigator.tsx | 2 + src/navigation/NavigationTypes.ts | 1 + src/pages/Groups.tsx | 39 ++++ src/pages/settings/Help.tsx | 6 +- src/pages/settings/Settings.tsx | 6 +- 12 files changed, 366 insertions(+), 48 deletions(-) rename src/components/{Settings/SettingsScroll.tsx => ContentWrapperScroll.tsx} (68%) create mode 100644 src/components/Groups/Filters.tsx create mode 100644 src/components/Groups/GroupsFiltered.tsx create mode 100644 src/components/Groups/OutlinedButton.tsx create mode 100644 src/pages/Groups.tsx diff --git a/src/components/Settings/SettingsScroll.tsx b/src/components/ContentWrapperScroll.tsx similarity index 68% rename from src/components/Settings/SettingsScroll.tsx rename to src/components/ContentWrapperScroll.tsx index c99a15b1..3d298634 100644 --- a/src/components/Settings/SettingsScroll.tsx +++ b/src/components/ContentWrapperScroll.tsx @@ -7,9 +7,10 @@ import { usePalette } from "utils/colors" /** * General component useful for pages with a scrollable content. * It provides a navbar and a scrollview with margin and rounded corners. + * Default margin Top is 86 (proper margin for Settings Page) */ -export const SettingsScroll: FC<{ - title: string +export const ContentWrapperScroll: FC<{ + title?: string /** * Remove the navbar from the bottom of the page. */ @@ -18,6 +19,7 @@ export const SettingsScroll: FC<{ * Props for the navbar, see {@link NavBar} */ navbarOptions?: NavbarProps + marginTop?: number children: React.ReactNode }> = props => { @@ -32,30 +34,33 @@ export const SettingsScroll: FC<{ backgroundColor: isLight ? primary : background, }} > - - - {props.title} - - + + {props.title} + + + )} + {props.children} - {navbar ? : null} ) diff --git a/src/components/Groups/Filters.tsx b/src/components/Groups/Filters.tsx new file mode 100644 index 00000000..6cb9d2fe --- /dev/null +++ b/src/components/Groups/Filters.tsx @@ -0,0 +1,176 @@ +import React, { FC, useState } from "react" +import { View } from "react-native" +import { OutlinedButton } from "./OutlinedButton" +import { StyleSheet } from "react-native" +import { ModalCustomSettings, SelectTile } from "components/Settings" + +/* export interface FiltersProps { + prova?: string +} */ + +//This is a mess for now +const yearsList = ["2021/2022", "2020/2021", "2019/2020", "2018/2019"] +const coursesList = ["Triennale", "Magistrale", "Ciclo unico"] +const typesList = ["A", "B", "C", "bho"] +const platformsList = ["Telgram", "Watsapp", "Bho"] + +const getNameFromMode = (mode: string) => { + if (mode === "year") { + return "Anno" + } else if (mode === "course") { + return "Corso" + } else if (mode === "platform") { + return "Piattaforma" + } else { + return "Tipo" + } +} +export type ValidModalType = "year" | "course" | "type" | "platform" +const all = "Tutti" +export const Filters: FC = () => { + //show or hide modal + const [isModalShowing, setIsModalShowing] = useState(false) + //type of modal: year - type - course - platform + const [modalMode, setModalMode] = useState("year") + //items to show inside modal + const [modalItems, setModalItems] = useState(yearsList) + //currently selected item inside modal + const [selectedItem, setSelectedItem] = useState(all) + + const [year, setYear] = useState(all) + + const [course, setCourse] = useState(all) + + const [type, setType] = useState(all) + + const [platform, setPlatform] = useState(all) + + //update state when user taps "OK" in modal + const updateSelectedFilter = () => { + if (modalMode === "year") { + setYear(selectedItem) + } else if (modalMode === "course") { + setCourse(selectedItem) + } else if (modalMode === "platform") { + setPlatform(selectedItem) + } else { + setType(selectedItem) + } + } + //reset state on "reset" + const reset = () => { + setYear(all) + setCourse(all) + setType(all) + setPlatform(all) + } + return ( + + + { + setModalMode("year") + setModalItems(yearsList) + setSelectedItem(year) + setIsModalShowing(true) + }} + /> + { + setModalMode("course") + setModalItems(coursesList) + setSelectedItem(course) + setIsModalShowing(true) + }} + /> + { + setModalMode("type") + setModalItems(typesList) + setSelectedItem(type) + setIsModalShowing(true) + }} + /> + { + setModalMode("platform") + setModalItems(platformsList) + setSelectedItem(platform) + setIsModalShowing(true) + }} + /> + + + { + setIsModalShowing(false) + }} + selectedValue={selectedItem} + onOK={() => { + updateSelectedFilter() + setIsModalShowing(false) + }} + > + { + setSelectedItem(all) + }} + /> + {modalItems?.map((itemName, index) => { + return ( + { + setSelectedItem(modalItems[index]) + }} + /> + ) + })} + + + ) +} + +const styles = StyleSheet.create({ + buttonRightMargin: { + marginRight: 8, + }, + buttonBottomMargin: { + marginBottom: 8, + }, +}) diff --git a/src/components/Groups/GroupsFiltered.tsx b/src/components/Groups/GroupsFiltered.tsx new file mode 100644 index 00000000..af099f6c --- /dev/null +++ b/src/components/Groups/GroupsFiltered.tsx @@ -0,0 +1,15 @@ +import { Text } from "components/Text" +import React, { FC } from "react" +import { View } from "react-native" + +export interface GroupsFilteredProps { + string?: string +} + +export const GroupsFiltered: FC = props => { + return ( + + {props.string} + + ) +} diff --git a/src/components/Groups/OutlinedButton.tsx b/src/components/Groups/OutlinedButton.tsx new file mode 100644 index 00000000..1d9243c5 --- /dev/null +++ b/src/components/Groups/OutlinedButton.tsx @@ -0,0 +1,69 @@ +import { Text } from "components/Text" +import React, { FC } from "react" +import { Pressable, StyleProp, ViewStyle } from "react-native" +import { usePalette } from "utils/colors" +import { StyleSheet } from "react-native" +export interface OutlinedButtonProps { + text?: string + /** + * ResetButton + */ + isSpecial?: boolean + isSelected?: boolean + onPress?: () => void + buttonStyle?: StyleProp +} + +export const OutlinedButton: FC = props => { + const { isLight } = usePalette() + return ( + + + {props.text} + + + ) +} + +const styles = StyleSheet.create({ + button: { + borderWidth: 2, + height: 32, + minWidth: 100, + borderRadius: 60, + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + }, +}) diff --git a/src/components/Home/MainMenu.tsx b/src/components/Home/MainMenu.tsx index ebbd117d..26c10b70 100644 --- a/src/components/Home/MainMenu.tsx +++ b/src/components/Home/MainMenu.tsx @@ -162,7 +162,9 @@ export const MainMenu: FC<{ filter?: string }> = ({ filter }) => { if (isDeleting) setIsDeleting(false) if (buttonIcon.id === 9) setModalVisible(true) // TODO: actual navigation - if (!isDeleting && buttonIcon.id !== 9) { + if (buttonIcon.id === 5) { + navigate("Groups") + } else if (!isDeleting && buttonIcon.id !== 9) { navigate("Error404") } }} diff --git a/src/components/Home/PoliSearchBar.tsx b/src/components/Home/PoliSearchBar.tsx index d5ec1624..8f60331f 100644 --- a/src/components/Home/PoliSearchBar.tsx +++ b/src/components/Home/PoliSearchBar.tsx @@ -1,5 +1,11 @@ import React, { FC, useEffect, useState, useRef } from "react" -import { TextInput, Animated, Pressable } from "react-native" +import { + TextInput, + Animated, + Pressable, + StyleProp, + ViewStyle, +} from "react-native" import { usePalette } from "utils/colors" import { Canvas, ImageSVG, useSVG } from "@shopify/react-native-skia" import searchLight from "assets/menu/searchLight.svg" @@ -10,7 +16,8 @@ import searchDark from "assets/menu/searchDark.svg" */ export const PoliSearchBar: FC<{ onChange: (searchKey: string) => void -}> = ({ onChange }) => { + style?: StyleProp +}> = ({ onChange, style }) => { const { fieldBackground, fieldText, bodyText, isLight } = usePalette() const svg = useSVG(isLight ? searchLight : searchDark) @@ -36,26 +43,29 @@ export const PoliSearchBar: FC<{ return ( () @@ -21,6 +22,7 @@ export const MainStack: FC = () => { + ) } diff --git a/src/navigation/NavigationTypes.ts b/src/navigation/NavigationTypes.ts index ade07f25..6db61600 100644 --- a/src/navigation/NavigationTypes.ts +++ b/src/navigation/NavigationTypes.ts @@ -44,6 +44,7 @@ export type MainStackNavigatorParams = { Article: { article: Article } NewsList: { categoryName: string } Error404: undefined + Groups: undefined } export type SettingsStackNavigatorParams = { diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx new file mode 100644 index 00000000..5c19a055 --- /dev/null +++ b/src/pages/Groups.tsx @@ -0,0 +1,39 @@ +import React, { useState } from "react" +import { MainStackScreen } from "navigation/NavigationTypes" +import { View } from "react-native" +import { ContentWrapperScroll } from "components/ContentWrapperScroll" +import { Title } from "components/Text" +import { PoliSearchBar } from "components/Home" +import { Filters } from "components/Groups/Filters" +import { GroupsFiltered } from "components/Groups/GroupsFiltered" + +export const Groups: MainStackScreen<"Groups"> = () => { + const [search, setSearch] = useState("") + + const [isSearching, setIsSearching] = useState(false) + return ( + + + + Gruppi Corsi + { + setSearch(value) + if (value !== "") { + setIsSearching(true) + } else if (isSearching === true) { + setIsSearching(false) + } + }} + style={{ marginTop: 46, marginBottom: 24 }} + /> + {!isSearching ? ( + + ) : ( + + )} + + + + ) +} diff --git a/src/pages/settings/Help.tsx b/src/pages/settings/Help.tsx index cb1c5d78..46df9bc9 100644 --- a/src/pages/settings/Help.tsx +++ b/src/pages/settings/Help.tsx @@ -1,7 +1,7 @@ import React from "react" import { View } from "react-native" import { SettingsStackScreen } from "navigation/NavigationTypes" -import { SettingsScroll } from "components/Settings/SettingsScroll" +import { ContentWrapperScroll } from "components/ContentWrapperScroll" import { SettingTile } from "components/Settings/SettingTile" import { SettingOptions } from "utils/settings" @@ -28,12 +28,12 @@ export const settingsList: SettingOptions[] = [ */ export const Help: SettingsStackScreen<"Help"> = () => { return ( - + {settingsList.map((setting, index) => { return })} - + ) } diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index 47953055..bd7a3c68 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -1,7 +1,7 @@ import React, { useContext, useState } from "react" import { View } from "react-native" import { SettingsStackScreen, useNavigation } from "navigation/NavigationTypes" -import { SettingsScroll } from "components/Settings" +import { ContentWrapperScroll } from "components/Settings" import { Divider } from "components/Divider" import { SettingTile } from "components/Settings" import { settingsIcons } from "assets/settings" @@ -86,7 +86,7 @@ export const SettingsPage: SettingsStackScreen<"Settings"> = () => { return ( - + {loggedIn ? ( ) : ( @@ -108,7 +108,7 @@ export const SettingsPage: SettingsStackScreen<"Settings"> = () => { {settingsList.map((setting, index) => { return })} - + Date: Wed, 11 Jan 2023 23:14:20 +0100 Subject: [PATCH 02/17] groups api --- src/api/groups.ts | 45 +++++++++++++++++++ src/api/index.ts | 2 + src/components/Groups/Filters.tsx | 23 ++++++++-- .../{ModalSettings.tsx => ModalSelection.tsx} | 6 ++- src/components/Settings/index.ts | 2 +- src/pages/settings/Settings.tsx | 10 ++--- 6 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 src/api/groups.ts rename src/components/Settings/{ModalSettings.tsx => ModalSelection.tsx} (96%) diff --git a/src/api/groups.ts b/src/api/groups.ts new file mode 100644 index 00000000..14bd21f0 --- /dev/null +++ b/src/api/groups.ts @@ -0,0 +1,45 @@ +import { getIsoStringFromDaysPassed } from "utils/functions" +import { HttpClient, RequestOptions } from "./HttpClient" + +/* eslint-disable @typescript-eslint/naming-convention */ + +export interface GroupOptions { + name?: string + year?: string + degree?: string + type?: string + platform?: string + language?: string + office?: string +} + +const client = HttpClient.getInstance() + +/** + * Collection of endpoints related to Groups. + */ +export const groups = { + /** + * Retrieves groups from PoliNetwork server. + * Check {@link GroupOptions} for additional parameters. + */ + // ! temporary + async get(groupsOptions?: GroupOptions, options?: RequestOptions) { + const response = await client.poliNetworkInstance.get( + "/v1/groups/search", + { + ...options, + params: { + name: groupsOptions?.name, + year: groupsOptions?.year, + degree: groupsOptions?.degree, + type: groupsOptions?.type, + platform: groupsOptions?.platform, + language: groupsOptions?.language, + office: groupsOptions?.office, + }, + } + ) + return response.data.results + }, +} diff --git a/src/api/index.ts b/src/api/index.ts index 833178dc..3cef1fe4 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,5 +1,6 @@ import { articles } from "./articles" import { auth } from "./auth" +import { groups } from "./groups" import { tags } from "./tags" import { timetable } from "./timetable" import { user } from "./user" @@ -27,4 +28,5 @@ export const api = { tags, timetable, user, + groups, } diff --git a/src/components/Groups/Filters.tsx b/src/components/Groups/Filters.tsx index 6cb9d2fe..13a774dc 100644 --- a/src/components/Groups/Filters.tsx +++ b/src/components/Groups/Filters.tsx @@ -1,8 +1,9 @@ -import React, { FC, useState } from "react" +import React, { FC, useEffect, useState } from "react" import { View } from "react-native" import { OutlinedButton } from "./OutlinedButton" import { StyleSheet } from "react-native" -import { ModalCustomSettings, SelectTile } from "components/Settings" +import { ModalSelection, SelectTile } from "components/Settings" +import { api } from "api" /* export interface FiltersProps { prova?: string @@ -64,6 +65,20 @@ export const Filters: FC = () => { setType(all) setPlatform(all) } + + const searchGroups = async () => { + try { + //broken + const response = await api.groups.get({ name: "Informatica" }) + console.log(response) + } catch (error) { + console.log(error) + } + } + + useEffect(() => { + void searchGroups() + }, []) return ( { onPress={reset} /> - { @@ -161,7 +176,7 @@ export const Filters: FC = () => { /> ) })} - + ) } diff --git a/src/components/Settings/ModalSettings.tsx b/src/components/Settings/ModalSelection.tsx similarity index 96% rename from src/components/Settings/ModalSettings.tsx rename to src/components/Settings/ModalSelection.tsx index d7d52e4a..9f2ff791 100644 --- a/src/components/Settings/ModalSettings.tsx +++ b/src/components/Settings/ModalSelection.tsx @@ -4,7 +4,7 @@ import { Text } from "components/Text" import { usePalette } from "utils/colors" import { ButtonCustom } from "./ButtonCustom" -export interface ModalCustomSettingsProps { +export interface ModalSelectionProps { /** * content of the modal */ @@ -38,11 +38,13 @@ export interface ModalCustomSettingsProps { height?: number } +// ? maybe should move this out of Settings folder ? + /** * Custom Modal Component with two buttons at the bottom. * */ -export const ModalCustomSettings: FC = props => { +export const ModalSelection: FC = props => { const { backgroundSecondary, homeBackground, modalBarrier, isLight } = usePalette() diff --git a/src/components/Settings/index.ts b/src/components/Settings/index.ts index b4798f7b..b1393746 100644 --- a/src/components/Settings/index.ts +++ b/src/components/Settings/index.ts @@ -5,6 +5,6 @@ export * from "./SettingTile" export * from "./SelectTile" export * from "../ContentWrapperScroll" export * from "./UserDetailsTile" -export * from "./ModalSettings" +export * from "./ModalSelection" export * from "./UserAnonymousTile" export * from "./CareerColumn" diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index bd7a3c68..f23a2033 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -6,7 +6,7 @@ import { Divider } from "components/Divider" import { SettingTile } from "components/Settings" import { settingsIcons } from "assets/settings" import { UserDetailsTile } from "components/Settings" -import { ModalCustomSettings } from "components/Settings" +import { ModalSelection } from "components/Settings" import { CareerTile } from "components/Settings" import { SelectTile } from "components/Settings" import { UserAnonymousTile } from "components/Settings" @@ -110,7 +110,7 @@ export const SettingsPage: SettingsStackScreen<"Settings"> = () => { })} - = () => { /> ) })} - - + = () => { ) })} - + ) } From fc9adb3dbaabe898e2918cf9dba54231029e2d47 Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Fri, 13 Jan 2023 23:35:59 +0100 Subject: [PATCH 03/17] Group searching logic with mocked data and language filter --- src/api/groups.ts | 97 +++++++++++++- src/components/Divider.tsx | 18 ++- src/components/Groups/AnimatedLine.tsx | 51 ++++++++ .../Groups/ExpandablePoliSearchBar.tsx | 119 ++++++++++++++++++ src/components/Groups/Filters.tsx | 71 +++++------ src/components/Groups/GroupTile.tsx | 37 ++++++ src/components/Groups/GroupsFiltered.tsx | 15 --- src/components/Groups/OutlinedButton.tsx | 6 +- src/components/Text/BodyText.tsx | 4 +- src/pages/Groups.tsx | 108 +++++++++++++--- src/utils/groups.ts | 11 ++ src/utils/useMounted.ts | 43 +++++++ 12 files changed, 493 insertions(+), 87 deletions(-) create mode 100644 src/components/Groups/AnimatedLine.tsx create mode 100644 src/components/Groups/ExpandablePoliSearchBar.tsx create mode 100644 src/components/Groups/GroupTile.tsx delete mode 100644 src/components/Groups/GroupsFiltered.tsx create mode 100644 src/utils/groups.ts create mode 100644 src/utils/useMounted.ts diff --git a/src/api/groups.ts b/src/api/groups.ts index 14bd21f0..62d126d3 100644 --- a/src/api/groups.ts +++ b/src/api/groups.ts @@ -1,4 +1,3 @@ -import { getIsoStringFromDaysPassed } from "utils/functions" import { HttpClient, RequestOptions } from "./HttpClient" /* eslint-disable @typescript-eslint/naming-convention */ @@ -13,6 +12,19 @@ export interface GroupOptions { office?: string } +export interface MockedGroup { + name?: string + year?: string + id?: string + degree?: string + type?: string + platform?: string + language?: string + office?: string + school?: string + idLink?: string +} + const client = HttpClient.getInstance() /** @@ -23,7 +35,7 @@ export const groups = { * Retrieves groups from PoliNetwork server. * Check {@link GroupOptions} for additional parameters. */ - // ! temporary + // ! temporarily broken async get(groupsOptions?: GroupOptions, options?: RequestOptions) { const response = await client.poliNetworkInstance.get( "/v1/groups/search", @@ -42,4 +54,85 @@ export const groups = { ) return response.data.results }, + getMocked() { + return mockedGroups.groups + }, +} + +//random information +const mockedGroups = { + groups: [ + { + name: "GRUPPO 1 ITA", + year: "2022", + id: "1", + degree: "LT", + type: "S", + platform: "WA", + language: "ITA", + office: "Leonardo", + school: "idk", + idLink: "https://t.me/joinchat/9RcVXahIKchlMGZk", + }, + { + name: "GRUPPO 2 ITA", + year: "2021", + id: "1", + degree: "LT", + type: "S", + platform: "TG", + language: "ITA", + office: "Leonardo", + school: "idk", + idLink: "https://t.me/joinchat/lcaKVtappk83NzU0", + }, + { + name: "GRUPPO 3 ENG", + year: "2020", + id: "1", + degree: "LT", + type: "C", + platform: "TG", + language: "ENG", + office: "Leonardo", + school: "idk", + idLink: "https://chat.whatsapp.com/HDZd7mCzDg80dS4fCcSszy", + }, + { + name: "GRUPPO 4 ENG", + year: "2020", + id: "1", + degree: "LT", + type: "C", + platform: "TG", + language: "ENG", + office: "Leonardo", + school: "idk", + idLink: "https://t.me/joinchat/YEBlpQ_fzoZmYzI0", + }, + { + name: "GRUPPO 5 ITA", + year: "2020", + id: "1", + degree: "LT", + type: "C", + platform: "TG", + language: "ITA", + office: "Leonardo", + school: "idk", + idLink: "https://www.facebook.com/groups/170744940120942", + }, + { + name: "GRUPPO 6 ENG", + year: "2020", + id: "1", + degree: "LT", + type: "C", + platform: "TG", + language: "ENG", + office: "Leonardo", + school: "idk", + idLink: "https://t.me/joinchat/_9vETcjqnX5iNzNk", + }, + ], } diff --git a/src/components/Divider.tsx b/src/components/Divider.tsx index e2512791..3e7c2454 100644 --- a/src/components/Divider.tsx +++ b/src/components/Divider.tsx @@ -1,19 +1,25 @@ import React, { FC } from "react" -import { View } from "react-native" +import { View, ViewStyle } from "react-native" export interface DividerProps { color?: string height?: number + width?: number + style?: ViewStyle } export const Divider: FC = props => { return ( ) } diff --git a/src/components/Groups/AnimatedLine.tsx b/src/components/Groups/AnimatedLine.tsx new file mode 100644 index 00000000..a39973b8 --- /dev/null +++ b/src/components/Groups/AnimatedLine.tsx @@ -0,0 +1,51 @@ +import React, { FC } from "react" +import { Animated, Easing, ViewStyle } from "react-native" +import { usePalette } from "utils/colors" + +export interface AnimatedLineProps { + mounted: boolean + color?: string + height?: number + width?: number + style?: ViewStyle +} + +export const AnimatedLine: FC = props => { + const { isLight } = usePalette() + + const { current: widthAnim } = React.useRef( + new Animated.Value(1) + ) + + React.useEffect(() => { + if (props.mounted) { + Animated.timing(widthAnim, { + toValue: 250, + duration: 300, + easing: Easing.ease, + useNativeDriver: false, + }).start() + } else { + Animated.timing(widthAnim, { + toValue: 0, + duration: 300, + easing: Easing.ease, + useNativeDriver: false, + }).start() + } + }, [props.mounted]) + + return ( + + ) +} diff --git a/src/components/Groups/ExpandablePoliSearchBar.tsx b/src/components/Groups/ExpandablePoliSearchBar.tsx new file mode 100644 index 00000000..85b5a6b2 --- /dev/null +++ b/src/components/Groups/ExpandablePoliSearchBar.tsx @@ -0,0 +1,119 @@ +import { MockedGroup } from "api/groups" +import { PoliSearchBar } from "components/Home" +import React, { FC, useState } from "react" +import { ScrollView, View } from "react-native" +import { usePalette } from "utils/colors" +import { AnimatedLine } from "./AnimatedLine" +import { GroupTile } from "./GroupTile" +import { OutlinedButton } from "./OutlinedButton" + +export interface ExpandablePoliSearchBarProps { + setSearch: (val: string) => void + isSearching: boolean + setIsSearching: (val: boolean) => void + groups?: MockedGroup[] + language: ValidLanguageType + setLanguage: (val: ValidLanguageType) => void +} + +export type ValidLanguageType = "ITA" | "ENG" | undefined + +export const ExpandablePoliSearchBar: FC< + ExpandablePoliSearchBarProps +> = props => { + const { isLight } = usePalette() + + return ( + + + { + props.setSearch(val) + if (val !== "") { + props.setIsSearching(true) + } else if (props.isSearching === true) { + props.setIsSearching(false) + props.setLanguage(undefined) + } else { + props.setLanguage(undefined) + } + }} + style={{ marginTop: 0, marginBottom: 0 }} + /> + + + {props.groups && props.isSearching && ( + + + { + if (props.language === "ITA") { + props.setLanguage(undefined) + } else { + props.setLanguage("ITA") + } + }} + /> + { + if (props.language === "ENG") { + props.setLanguage(undefined) + } else { + props.setLanguage("ENG") + } + }} + /> + + + {props.groups.map((group, idx) => { + return ( + + ) + })} + + + )} + + + ) +} diff --git a/src/components/Groups/Filters.tsx b/src/components/Groups/Filters.tsx index 13a774dc..7c13a32d 100644 --- a/src/components/Groups/Filters.tsx +++ b/src/components/Groups/Filters.tsx @@ -1,13 +1,19 @@ -import React, { FC, useEffect, useState } from "react" +import React, { FC, useState } from "react" import { View } from "react-native" import { OutlinedButton } from "./OutlinedButton" import { StyleSheet } from "react-native" import { ModalSelection, SelectTile } from "components/Settings" -import { api } from "api" -/* export interface FiltersProps { - prova?: string -} */ +export interface FiltersProps { + year: string + setYear: (value: string) => void + course: string + setCourse: (value: string) => void + type: string + setType: (value: string) => void + platform: string + setPlatform: (value: string) => void +} //This is a mess for now const yearsList = ["2021/2022", "2020/2021", "2019/2020", "2018/2019"] @@ -28,7 +34,7 @@ const getNameFromMode = (mode: string) => { } export type ValidModalType = "year" | "course" | "type" | "platform" const all = "Tutti" -export const Filters: FC = () => { +export const Filters: FC = props => { //show or hide modal const [isModalShowing, setIsModalShowing] = useState(false) //type of modal: year - type - course - platform @@ -38,47 +44,26 @@ export const Filters: FC = () => { //currently selected item inside modal const [selectedItem, setSelectedItem] = useState(all) - const [year, setYear] = useState(all) - - const [course, setCourse] = useState(all) - - const [type, setType] = useState(all) - - const [platform, setPlatform] = useState(all) - //update state when user taps "OK" in modal const updateSelectedFilter = () => { if (modalMode === "year") { - setYear(selectedItem) + props.setYear(selectedItem) } else if (modalMode === "course") { - setCourse(selectedItem) + props.setCourse(selectedItem) } else if (modalMode === "platform") { - setPlatform(selectedItem) + props.setPlatform(selectedItem) } else { - setType(selectedItem) + props.setType(selectedItem) } } //reset state on "reset" const reset = () => { - setYear(all) - setCourse(all) - setType(all) - setPlatform(all) - } - - const searchGroups = async () => { - try { - //broken - const response = await api.groups.get({ name: "Informatica" }) - console.log(response) - } catch (error) { - console.log(error) - } + props.setYear(all) + props.setCourse(all) + props.setType(all) + props.setPlatform(all) } - useEffect(() => { - void searchGroups() - }, []) return ( { styles.buttonRightMargin, styles.buttonBottomMargin, ]} - isSelected={year !== all ? true : false} + isSelected={props.year !== all ? true : false} onPress={() => { setModalMode("year") setModalItems(yearsList) - setSelectedItem(year) + setSelectedItem(props.year) setIsModalShowing(true) }} /> @@ -108,33 +93,33 @@ export const Filters: FC = () => { styles.buttonRightMargin, styles.buttonBottomMargin, ]} - isSelected={course !== all ? true : false} + isSelected={props.course !== all ? true : false} onPress={() => { setModalMode("course") setModalItems(coursesList) - setSelectedItem(course) + setSelectedItem(props.course) setIsModalShowing(true) }} /> { setModalMode("type") setModalItems(typesList) - setSelectedItem(type) + setSelectedItem(props.type) setIsModalShowing(true) }} /> { setModalMode("platform") setModalItems(platformsList) - setSelectedItem(platform) + setSelectedItem(props.platform) setIsModalShowing(true) }} /> diff --git a/src/components/Groups/GroupTile.tsx b/src/components/Groups/GroupTile.tsx new file mode 100644 index 00000000..82b37ae2 --- /dev/null +++ b/src/components/Groups/GroupTile.tsx @@ -0,0 +1,37 @@ +import { BodyText } from "components/Text" +import React, { FC } from "react" +import { Linking, Pressable } from "react-native" +import { usePalette } from "utils/colors" + +export interface GroupTileProps { + name?: string + link?: string +} + +export const GroupTile: FC = props => { + const { isLight } = usePalette() + + const handlePress = async () => { + if (!props.link) { + return + } + // Checking if the link is supported for links with custom URL scheme. + const supported = await Linking.canOpenURL(props.link) + + if (supported) { + // Opening the link with some app, if the URL scheme is "http" the web link should be opened + // by some browser in the mobile + await Linking.openURL(props.link) + } + } + + return ( + + + {props.name} + + + ) +} diff --git a/src/components/Groups/GroupsFiltered.tsx b/src/components/Groups/GroupsFiltered.tsx deleted file mode 100644 index af099f6c..00000000 --- a/src/components/Groups/GroupsFiltered.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Text } from "components/Text" -import React, { FC } from "react" -import { View } from "react-native" - -export interface GroupsFilteredProps { - string?: string -} - -export const GroupsFiltered: FC = props => { - return ( - - {props.string} - - ) -} diff --git a/src/components/Groups/OutlinedButton.tsx b/src/components/Groups/OutlinedButton.tsx index 1d9243c5..df89c456 100644 --- a/src/components/Groups/OutlinedButton.tsx +++ b/src/components/Groups/OutlinedButton.tsx @@ -1,4 +1,4 @@ -import { Text } from "components/Text" +import { BodyText } from "components/Text" import React, { FC } from "react" import { Pressable, StyleProp, ViewStyle } from "react-native" import { usePalette } from "utils/colors" @@ -38,7 +38,7 @@ export const OutlinedButton: FC = props => { ]} onPress={props.onPress} > - = props => { }} > {props.text} - + ) } diff --git a/src/components/Text/BodyText.tsx b/src/components/Text/BodyText.tsx index b9790831..402248c6 100644 --- a/src/components/Text/BodyText.tsx +++ b/src/components/Text/BodyText.tsx @@ -22,7 +22,9 @@ export const BodyText: FC = props => { fontFamily: fontWeight === "900" ? "Roboto_900Black" - : fontWeight === "bold" || fontWeight === "700" + : fontWeight === "bold" || + fontWeight === "700" || + fontWeight === "600" ? "Roboto_700Bold" : fontWeight === "300" ? "Roboto_300Light" diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index 5c19a055..8f9c73e3 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -1,36 +1,110 @@ -import React, { useState } from "react" +import React, { useEffect, useState } from "react" import { MainStackScreen } from "navigation/NavigationTypes" import { View } from "react-native" import { ContentWrapperScroll } from "components/ContentWrapperScroll" import { Title } from "components/Text" -import { PoliSearchBar } from "components/Home" import { Filters } from "components/Groups/Filters" -import { GroupsFiltered } from "components/Groups/GroupsFiltered" +import { + ExpandablePoliSearchBar, + ValidLanguageType, +} from "components/Groups/ExpandablePoliSearchBar" +import { api } from "api" +import { MockedGroup } from "api/groups" +import { useMounted } from "utils/useMounted" +import { filterByLanguage } from "utils/groups" + +const all = "Tutti" export const Groups: MainStackScreen<"Groups"> = () => { const [search, setSearch] = useState("") const [isSearching, setIsSearching] = useState(false) + + const [year, setYear] = useState(all) + + const [course, setCourse] = useState(all) + + const [type, setType] = useState(all) + + const [platform, setPlatform] = useState(all) + + const [groups, setGroups] = useState(undefined) + + const [language, setLanguage] = useState() + + //when user selects "ITA" or "ENG" + const [filteredGroups, setFilteredGroups] = useState< + MockedGroup[] | undefined + >(undefined) + + //tracking first render + const isMounted = useMounted() + + //api call every time user enter a new character + const searchGroups = async () => { + if (isMounted) { + try { + //mocked + const response = await api.groups.getMocked() + console.log(response) + setGroups(response) + } catch (error) { + console.log(error) + } + } + } + useEffect(() => { + void searchGroups() + }, [search]) + + //filter items every time selected language changes + useEffect(() => { + if (isMounted && groups) { + if (language) { + const newFilteredGroups = filterByLanguage(groups, language) + setFilteredGroups(newFilteredGroups) + } else { + resetFilterLanguage() + } + } + }, [language]) + + //load groups to filtered groups every time a new response from api arrives. (language filters ignored) + useEffect(() => { + if (isMounted) { + setFilteredGroups(groups) + } + }, [groups]) + + //helper function to reset language filters + const resetFilterLanguage = () => { + setFilteredGroups(groups) + } return ( Gruppi Corsi - { - setSearch(value) - if (value !== "") { - setIsSearching(true) - } else if (isSearching === true) { - setIsSearching(false) - } - }} - style={{ marginTop: 46, marginBottom: 24 }} + setIsSearching(val)} + setSearch={val => setSearch(val)} + groups={filteredGroups} + language={language} + setLanguage={val => setLanguage(val)} /> - {!isSearching ? ( - - ) : ( - + + {!isSearching && ( + setYear(val)} + course={course} + setCourse={val => setCourse(val)} + type={type} + setType={val => setType(val)} + platform={platform} + setPlatform={val => setPlatform(val)} + /> )} diff --git a/src/utils/groups.ts b/src/utils/groups.ts new file mode 100644 index 00000000..fc501320 --- /dev/null +++ b/src/utils/groups.ts @@ -0,0 +1,11 @@ +import { MockedGroup } from "api/groups" + +/** + * return groups filtered by language + * see {@link Groups} Page + */ +export function filterByLanguage(groups: MockedGroup[], language: string) { + return groups.filter(group => { + return group.language === language + }) +} diff --git a/src/utils/useMounted.ts b/src/utils/useMounted.ts new file mode 100644 index 00000000..3c36dc4b --- /dev/null +++ b/src/utils/useMounted.ts @@ -0,0 +1,43 @@ +import { useEffect, useState } from "react" + +/** + * useful hook to keep track of first render in multiple useEffects + * + * from https://stackoverflow.com/questions/57240169/skip-first-useeffect-when-there-are-multiple-useeffects + * + * @example + * ```ts + * const [valueFirst, setValueFirst] = useState(0) + * const [valueSecond, setValueSecond] = useState(0) + * + * const isMounted = useMounted() + * + * //1st effect which should run whenever valueFirst change except + * //first time + * React.useEffect(() => { + * if (isMounted) { + * console.log("valueFirst ran") + * } + * + * }, [valueFirst]) + * + * + * //2nd effect which should run whenever valueFirst change except + * //first time + * React.useEffect(() => { + * if (isMounted) { + * console.log("valueSecond ran") + * } + * + * }, [valueSecond]) + * + * ``` + */ +export function useMounted() { + const [isMounted, setIsMounted] = useState(false) + + useEffect(() => { + setIsMounted(true) + }, []) + return isMounted +} From d95e989f40d72722fa9d79728bf7bb291c1a621d Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Sun, 15 Jan 2023 01:09:04 +0100 Subject: [PATCH 04/17] Adding proper filter values reordering code --- src/api/groups.ts | 7 +-- src/components/Groups/AnimatedLine.tsx | 3 ++ .../Groups/ExpandablePoliSearchBar.tsx | 2 +- src/components/Groups/Filters.tsx | 53 +++++++++++-------- src/components/Groups/GroupTile.tsx | 3 +- src/pages/Groups.tsx | 6 ++- src/utils/groups.ts | 14 +++++ 7 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/api/groups.ts b/src/api/groups.ts index 62d126d3..67d3c75d 100644 --- a/src/api/groups.ts +++ b/src/api/groups.ts @@ -1,4 +1,4 @@ -import { HttpClient, RequestOptions } from "./HttpClient" +/* import { HttpClient, RequestOptions } from "./HttpClient" */ /* eslint-disable @typescript-eslint/naming-convention */ @@ -25,7 +25,7 @@ export interface MockedGroup { idLink?: string } -const client = HttpClient.getInstance() +/* const client = HttpClient.getInstance() */ /** * Collection of endpoints related to Groups. @@ -35,6 +35,7 @@ export const groups = { * Retrieves groups from PoliNetwork server. * Check {@link GroupOptions} for additional parameters. */ + /* // ! temporarily broken async get(groupsOptions?: GroupOptions, options?: RequestOptions) { const response = await client.poliNetworkInstance.get( @@ -53,7 +54,7 @@ export const groups = { } ) return response.data.results - }, + }, */ getMocked() { return mockedGroups.groups }, diff --git a/src/components/Groups/AnimatedLine.tsx b/src/components/Groups/AnimatedLine.tsx index a39973b8..98045b11 100644 --- a/src/components/Groups/AnimatedLine.tsx +++ b/src/components/Groups/AnimatedLine.tsx @@ -3,6 +3,9 @@ import { Animated, Easing, ViewStyle } from "react-native" import { usePalette } from "utils/colors" export interface AnimatedLineProps { + /** + * animate when this value changes + */ mounted: boolean color?: string height?: number diff --git a/src/components/Groups/ExpandablePoliSearchBar.tsx b/src/components/Groups/ExpandablePoliSearchBar.tsx index 85b5a6b2..095181c1 100644 --- a/src/components/Groups/ExpandablePoliSearchBar.tsx +++ b/src/components/Groups/ExpandablePoliSearchBar.tsx @@ -1,6 +1,6 @@ import { MockedGroup } from "api/groups" import { PoliSearchBar } from "components/Home" -import React, { FC, useState } from "react" +import React, { FC } from "react" import { ScrollView, View } from "react-native" import { usePalette } from "utils/colors" import { AnimatedLine } from "./AnimatedLine" diff --git a/src/components/Groups/Filters.tsx b/src/components/Groups/Filters.tsx index 7c13a32d..cfd3c682 100644 --- a/src/components/Groups/Filters.tsx +++ b/src/components/Groups/Filters.tsx @@ -3,6 +3,7 @@ import { View } from "react-native" import { OutlinedButton } from "./OutlinedButton" import { StyleSheet } from "react-native" import { ModalSelection, SelectTile } from "components/Settings" +import { getNameFromMode, ValidModalType } from "utils/groups" export interface FiltersProps { year: string @@ -15,34 +16,42 @@ export interface FiltersProps { setPlatform: (value: string) => void } -//This is a mess for now -const yearsList = ["2021/2022", "2020/2021", "2019/2020", "2018/2019"] -const coursesList = ["Triennale", "Magistrale", "Ciclo unico"] -const typesList = ["A", "B", "C", "bho"] -const platformsList = ["Telgram", "Watsapp", "Bho"] +interface ModalItemList { + itemsToShow: string[] + itemsToSave: string[] +} -const getNameFromMode = (mode: string) => { - if (mode === "year") { - return "Anno" - } else if (mode === "course") { - return "Corso" - } else if (mode === "platform") { - return "Piattaforma" - } else { - return "Tipo" - } +const yearsList: ModalItemList = { + itemsToShow: ["2021/2022", "2020/2021", "2019/2020", "2018/2019"], + itemsToSave: ["2021", "2020", "2019", "2018"], +} +const coursesList: ModalItemList = { + itemsToShow: ["Triennale", "Magistrale", "Ciclo unico"], + itemsToSave: ["LT", "LM", "LU"], +} + +const typesList: ModalItemList = { + itemsToShow: ["Scuola", "Corso", "Extra"], + itemsToSave: ["S", "C", "E"], } -export type ValidModalType = "year" | "course" | "type" | "platform" + +const platformsList: ModalItemList = { + itemsToShow: ["Whatsapp", "Facebook", "Telegram"], + itemsToSave: ["WA", "FB", "TG"], +} + +//to avoid writing mistakes const all = "Tutti" + export const Filters: FC = props => { //show or hide modal const [isModalShowing, setIsModalShowing] = useState(false) //type of modal: year - type - course - platform const [modalMode, setModalMode] = useState("year") //items to show inside modal - const [modalItems, setModalItems] = useState(yearsList) + const [modalItems, setModalItems] = useState(yearsList) //currently selected item inside modal - const [selectedItem, setSelectedItem] = useState(all) + const [selectedItem, setSelectedItem] = useState(all) //update state when user taps "OK" in modal const updateSelectedFilter = () => { @@ -149,14 +158,16 @@ export const Filters: FC = props => { setSelectedItem(all) }} /> - {modalItems?.map((itemName, index) => { + {modalItems?.itemsToShow.map((itemName, index) => { return ( { - setSelectedItem(modalItems[index]) + setSelectedItem(modalItems.itemsToSave[index]) }} /> ) diff --git a/src/components/Groups/GroupTile.tsx b/src/components/Groups/GroupTile.tsx index 82b37ae2..3b2aef7f 100644 --- a/src/components/Groups/GroupTile.tsx +++ b/src/components/Groups/GroupTile.tsx @@ -19,8 +19,7 @@ export const GroupTile: FC = props => { const supported = await Linking.canOpenURL(props.link) if (supported) { - // Opening the link with some app, if the URL scheme is "http" the web link should be opened - // by some browser in the mobile + // Opening the link with some app await Linking.openURL(props.link) } } diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index 8f9c73e3..6f770d83 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -20,6 +20,8 @@ export const Groups: MainStackScreen<"Groups"> = () => { const [isSearching, setIsSearching] = useState(false) + // ? I'd like to typecheck year, course, etc... but I need dynamic type checking + // ? and it's very ugly, and need to use type guards or some library, so maybe it is not worth it? const [year, setYear] = useState(all) const [course, setCourse] = useState(all) @@ -41,11 +43,11 @@ export const Groups: MainStackScreen<"Groups"> = () => { const isMounted = useMounted() //api call every time user enter a new character - const searchGroups = async () => { + const searchGroups = () => { if (isMounted) { try { //mocked - const response = await api.groups.getMocked() + const response = api.groups.getMocked() console.log(response) setGroups(response) } catch (error) { diff --git a/src/utils/groups.ts b/src/utils/groups.ts index fc501320..9d86cacf 100644 --- a/src/utils/groups.ts +++ b/src/utils/groups.ts @@ -9,3 +9,17 @@ export function filterByLanguage(groups: MockedGroup[], language: string) { return group.language === language }) } + +export type ValidModalType = "year" | "course" | "type" | "platform" + +export const getNameFromMode = (mode: ValidModalType) => { + if (mode === "year") { + return "Anno" + } else if (mode === "course") { + return "Corso" + } else if (mode === "platform") { + return "Piattaforma" + } else { + return "Tipo" + } +} From 3080d85af4721526541686cf59936b28e071fcc6 Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Fri, 20 Jan 2023 02:04:17 +0100 Subject: [PATCH 05/17] groups api draft --- src/api/groups.ts | 58 ++++++++------- .../Groups/ExpandablePoliSearchBar.tsx | 14 +++- src/components/Groups/Filters.tsx | 2 +- src/pages/Groups.tsx | 74 +++++++++++++++---- src/utils/groups.ts | 21 +++++- 5 files changed, 120 insertions(+), 49 deletions(-) diff --git a/src/api/groups.ts b/src/api/groups.ts index 67d3c75d..c8cd49a2 100644 --- a/src/api/groups.ts +++ b/src/api/groups.ts @@ -1,4 +1,4 @@ -/* import { HttpClient, RequestOptions } from "./HttpClient" */ +import { HttpClient, RequestOptions } from "./HttpClient" /* eslint-disable @typescript-eslint/naming-convention */ @@ -12,20 +12,23 @@ export interface GroupOptions { office?: string } -export interface MockedGroup { - name?: string - year?: string +export interface Group { + class: string + office: string id?: string degree?: string - type?: string - platform?: string - language?: string - office?: string school?: string - idLink?: string + id_link: string + language: string + type_?: string + year?: string + platform: string + permanent_id?: number + last_updated?: string + link_is_working?: string } -/* const client = HttpClient.getInstance() */ +const client = HttpClient.getInstance() /** * Collection of endpoints related to Groups. @@ -35,26 +38,25 @@ export const groups = { * Retrieves groups from PoliNetwork server. * Check {@link GroupOptions} for additional parameters. */ - /* + // ! temporarily broken async get(groupsOptions?: GroupOptions, options?: RequestOptions) { - const response = await client.poliNetworkInstance.get( - "/v1/groups/search", - { - ...options, - params: { - name: groupsOptions?.name, - year: groupsOptions?.year, - degree: groupsOptions?.degree, - type: groupsOptions?.type, - platform: groupsOptions?.platform, - language: groupsOptions?.language, - office: groupsOptions?.office, - }, - } - ) - return response.data.results - }, */ + const response = await client.poliNetworkInstance.get<{ + groups: Group[] + }>("/v1/groups", { + ...options, + params: { + name: groupsOptions?.name, + year: groupsOptions?.year, + degree: groupsOptions?.degree, + type: groupsOptions?.type, + platform: groupsOptions?.platform, + language: groupsOptions?.language, + office: groupsOptions?.office, + }, + }) + return response.data.groups + }, getMocked() { return mockedGroups.groups }, diff --git a/src/components/Groups/ExpandablePoliSearchBar.tsx b/src/components/Groups/ExpandablePoliSearchBar.tsx index 095181c1..de813da7 100644 --- a/src/components/Groups/ExpandablePoliSearchBar.tsx +++ b/src/components/Groups/ExpandablePoliSearchBar.tsx @@ -1,8 +1,9 @@ -import { MockedGroup } from "api/groups" +import { Group } from "api/groups" import { PoliSearchBar } from "components/Home" import React, { FC } from "react" import { ScrollView, View } from "react-native" import { usePalette } from "utils/colors" +import { createGroupLink } from "utils/groups" import { AnimatedLine } from "./AnimatedLine" import { GroupTile } from "./GroupTile" import { OutlinedButton } from "./OutlinedButton" @@ -11,7 +12,7 @@ export interface ExpandablePoliSearchBarProps { setSearch: (val: string) => void isSearching: boolean setIsSearching: (val: boolean) => void - groups?: MockedGroup[] + groups?: Group[] language: ValidLanguageType setLanguage: (val: ValidLanguageType) => void } @@ -40,6 +41,7 @@ export const ExpandablePoliSearchBar: FC< marginTop: 46, marginBottom: 24, height: props.isSearching ? 268 : undefined, + width: 285, borderBottomRightRadius: 8, borderBottomLeftRadius: 8, borderTopRightRadius: 28, @@ -104,8 +106,12 @@ export const ExpandablePoliSearchBar: FC< {props.groups.map((group, idx) => { return ( ) diff --git a/src/components/Groups/Filters.tsx b/src/components/Groups/Filters.tsx index cfd3c682..e48f46cd 100644 --- a/src/components/Groups/Filters.tsx +++ b/src/components/Groups/Filters.tsx @@ -23,7 +23,7 @@ interface ModalItemList { const yearsList: ModalItemList = { itemsToShow: ["2021/2022", "2020/2021", "2019/2020", "2018/2019"], - itemsToSave: ["2021", "2020", "2019", "2018"], + itemsToSave: ["2021/2022", "2020/2021", "2019/2020", "2018/2019"], } const coursesList: ModalItemList = { itemsToShow: ["Triennale", "Magistrale", "Ciclo unico"], diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index 6f770d83..57594d1f 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -8,14 +8,21 @@ import { ExpandablePoliSearchBar, ValidLanguageType, } from "components/Groups/ExpandablePoliSearchBar" -import { api } from "api" -import { MockedGroup } from "api/groups" +import { api, RetryType } from "api" +import { Group } from "api/groups" import { useMounted } from "utils/useMounted" -import { filterByLanguage } from "utils/groups" +import { filterByLanguage, msPassedBetween } from "utils/groups" +import { wait } from "utils/functions" const all = "Tutti" export const Groups: MainStackScreen<"Groups"> = () => { + const [lastSearchTime, setLastSearchTime] = useState( + undefined + ) + + const [needSearching, setNeedSearching] = useState(0) + const [search, setSearch] = useState("") const [isSearching, setIsSearching] = useState(false) @@ -30,26 +37,50 @@ export const Groups: MainStackScreen<"Groups"> = () => { const [platform, setPlatform] = useState(all) - const [groups, setGroups] = useState(undefined) + const [groups, setGroups] = useState(undefined) const [language, setLanguage] = useState() //when user selects "ITA" or "ENG" - const [filteredGroups, setFilteredGroups] = useState< - MockedGroup[] | undefined - >(undefined) + const [filteredGroups, setFilteredGroups] = useState( + undefined + ) //tracking first render const isMounted = useMounted() - //api call every time user enter a new character - const searchGroups = () => { + //serch if len > 5 char and last search was more than 3000ms ago, if not search in (3000ms - time)ms + //if the user doesnt search for something else + const searchGroups = async () => { if (isMounted) { + if (search.length < 5) { + if (groups !== undefined) { + setGroups(undefined) + } + return + } try { - //mocked - const response = api.groups.getMocked() - console.log(response) - setGroups(response) + const now = new Date() + const msPassed = msPassedBetween(lastSearchTime, now) + if (msPassed === undefined || msPassed >= 3000) { + console.log("searching") + setLastSearchTime(now) + const response = await api.groups.get( + { + name: search, + year: year === all ? undefined : year, + degree: course === all ? undefined : course, + type: type === all ? undefined : type, + platform: platform === all ? undefined : platform, + }, + { maxRetries: 1, retryType: RetryType.RETRY_N_TIMES } + ) + console.log("response arrived") + setGroups(response) + } else { + console.log(msPassed + " passsed, reschedule research") + await researchIn(3000 - msPassed) + } } catch (error) { console.log(error) } @@ -57,7 +88,22 @@ export const Groups: MainStackScreen<"Groups"> = () => { } useEffect(() => { void searchGroups() - }, [search]) + }, [search, needSearching]) + + /** + * reserach in (3000 - time) ms if search string doesnt change in this span of time. + * Fired every time a search is canceled because it didnt pass enough time from last search. + **/ + const researchIn = async (time: number) => { + const prevSearch = search + const prevNeedSearching = needSearching + await wait(time) + // ! not working properly + if (prevSearch === search && prevNeedSearching === needSearching) { + setNeedSearching(needSearching + 1) + console.log("research fired!!") + } + } //filter items every time selected language changes useEffect(() => { diff --git a/src/utils/groups.ts b/src/utils/groups.ts index 9d86cacf..b1803021 100644 --- a/src/utils/groups.ts +++ b/src/utils/groups.ts @@ -1,10 +1,10 @@ -import { MockedGroup } from "api/groups" +import { Group } from "api/groups" /** * return groups filtered by language * see {@link Groups} Page */ -export function filterByLanguage(groups: MockedGroup[], language: string) { +export function filterByLanguage(groups: Group[], language: string) { return groups.filter(group => { return group.language === language }) @@ -23,3 +23,20 @@ export const getNameFromMode = (mode: ValidModalType) => { return "Tipo" } } + +export function createGroupLink(idLink: string, platform: string) { + if (platform === "TG") { + return `https://t.me/joinchat/${idLink}` + } else if (platform === "WA") { + return `https://chat.whatsapp.com/${idLink}` + } else { + return `https://www.facebook.com/groups/${idLink}` + } +} + +export function msPassedBetween(start: Date | undefined, end: Date) { + if (start === undefined) { + return undefined + } + return end.getTime() - start.getTime() +} From 837500b2835d3057a497fda37dcda2d0874af0f6 Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Sat, 21 Jan 2023 02:38:56 +0100 Subject: [PATCH 06/17] fixed search logic --- src/pages/Groups.tsx | 83 ++++++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index 57594d1f..3f380228 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -16,12 +16,23 @@ import { wait } from "utils/functions" const all = "Tutti" +const sleepTime = 500 //ms + export const Groups: MainStackScreen<"Groups"> = () => { + //keep track of latest search request time (successful or not) const [lastSearchTime, setLastSearchTime] = useState( undefined ) + //for forcing api search request if needeed + const [needSearching, setNeedSearching] = useState(false) + + //for triggering api search request side effect + //reason : I want to trigger it only if I set needSearching to True, not to False + const [rescheduleSearch, setRescheduleSearch] = useState(0) - const [needSearching, setNeedSearching] = useState(0) + //for triggering api request when user doesnt write in a time span of `sleepTime` + //reason: I don't want to send api request on every character change. + const [prevSearch, setPrevSearch] = useState("") const [search, setSearch] = useState("") @@ -49,8 +60,17 @@ export const Groups: MainStackScreen<"Groups"> = () => { //tracking first render const isMounted = useMounted() - //serch if len > 5 char and last search was more than 3000ms ago, if not search in (3000ms - time)ms - //if the user doesnt search for something else + /** + * Api search request side effect. + * + * How this mess works (or should work, actually it seems like it works for now): + * 1) gets called every time search text changes or rescheduleSearch is incremented (for forcing search request) + * 2) if len(search) < 5 do nothing and delete store groups if any. + * 3) if no other searches were done before, send search request to api. Store time of search in `lastSearchTime`. + * 4) successive requests are allowed if: last search request time (successful or not) was more than `sleepTime` (500ms) ago + * 5) if a request is not allowed, store `lastTimeSearch` anyway, then check `sleepTime` (500ms) later if the user has kept writing. + * if he has, then do nothing because side effect will be called again, if he hasn't, then force search request with latest search value. + */ const searchGroups = async () => { if (isMounted) { if (search.length < 5) { @@ -62,12 +82,16 @@ export const Groups: MainStackScreen<"Groups"> = () => { try { const now = new Date() const msPassed = msPassedBetween(lastSearchTime, now) - if (msPassed === undefined || msPassed >= 3000) { - console.log("searching") + if ( + msPassed === undefined || + msPassed >= sleepTime || + needSearching === true + ) { + //update last time search setLastSearchTime(now) const response = await api.groups.get( { - name: search, + name: search.trimEnd(), year: year === all ? undefined : year, degree: course === all ? undefined : course, type: type === all ? undefined : type, @@ -75,11 +99,17 @@ export const Groups: MainStackScreen<"Groups"> = () => { }, { maxRetries: 1, retryType: RetryType.RETRY_N_TIMES } ) - console.log("response arrived") setGroups(response) + //reset need searching for next render + setNeedSearching(false) } else { - console.log(msPassed + " passsed, reschedule research") - await researchIn(3000 - msPassed) + // ? over optimization maybe: this prevents api request if user keeps writing fast. + // ? maybe I should request every 1 seconds even if user is still writing? debatable + setLastSearchTime(now) + await wait(sleepTime) + //in the next render you have the previous and current search text, and can compare changes + //this triggers Reschedule Search Side Effect + setPrevSearch(search) } } catch (error) { console.log(error) @@ -88,22 +118,25 @@ export const Groups: MainStackScreen<"Groups"> = () => { } useEffect(() => { void searchGroups() - }, [search, needSearching]) + }, [search, rescheduleSearch]) /** - * reserach in (3000 - time) ms if search string doesnt change in this span of time. - * Fired every time a search is canceled because it didnt pass enough time from last search. - **/ - const researchIn = async (time: number) => { - const prevSearch = search - const prevNeedSearching = needSearching - await wait(time) - // ! not working properly - if (prevSearch === search && prevNeedSearching === needSearching) { - setNeedSearching(needSearching + 1) - console.log("research fired!!") + * Reschedule Search Side Effect + * + * evaluate if user stopped writing characters in a time span of `sleepTime` seconds + */ + useEffect(() => { + if (isMounted) { + if (prevSearch !== search) { + //Do nothing + return + } else { + //force search in next frame + setRescheduleSearch(rescheduleSearch + 1) + setNeedSearching(true) + } } - } + }, [prevSearch]) //filter items every time selected language changes useEffect(() => { @@ -120,7 +153,11 @@ export const Groups: MainStackScreen<"Groups"> = () => { //load groups to filtered groups every time a new response from api arrives. (language filters ignored) useEffect(() => { if (isMounted) { - setFilteredGroups(groups) + let newGroups = groups + if (language !== undefined && newGroups) { + newGroups = filterByLanguage(newGroups, language) + } + setFilteredGroups(newGroups) } }, [groups]) From cff329620c323e164610cff2f1c45df24cd1485b Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Sun, 22 Jan 2023 02:35:19 +0100 Subject: [PATCH 07/17] Reordering by Year --- src/api/groups.ts | 2 +- .../Groups/ExpandablePoliSearchBar.tsx | 4 +- src/pages/Groups.tsx | 20 ++++++--- src/utils/groups.ts | 44 ++++++++++++++++++- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/api/groups.ts b/src/api/groups.ts index c8cd49a2..f4c08fb4 100644 --- a/src/api/groups.ts +++ b/src/api/groups.ts @@ -21,7 +21,7 @@ export interface Group { id_link: string language: string type_?: string - year?: string + year: string | null platform: string permanent_id?: number last_updated?: string diff --git a/src/components/Groups/ExpandablePoliSearchBar.tsx b/src/components/Groups/ExpandablePoliSearchBar.tsx index de813da7..f92018c5 100644 --- a/src/components/Groups/ExpandablePoliSearchBar.tsx +++ b/src/components/Groups/ExpandablePoliSearchBar.tsx @@ -106,7 +106,9 @@ export const ExpandablePoliSearchBar: FC< {props.groups.map((group, idx) => { return ( = () => { //for forcing api search request if needeed const [needSearching, setNeedSearching] = useState(false) - //for triggering api search request side effect + //for triggering api search request side effect (used in conjunction with `needSearching`) //reason : I want to trigger it only if I set needSearching to True, not to False const [rescheduleSearch, setRescheduleSearch] = useState(0) @@ -52,7 +56,7 @@ export const Groups: MainStackScreen<"Groups"> = () => { const [language, setLanguage] = useState() - //when user selects "ITA" or "ENG" + //actually displayed groups (filtered by language) const [filteredGroups, setFilteredGroups] = useState( undefined ) @@ -63,11 +67,11 @@ export const Groups: MainStackScreen<"Groups"> = () => { /** * Api search request side effect. * - * How this mess works (or should work, actually it seems like it works for now): + * How this works (general ideas): * 1) gets called every time search text changes or rescheduleSearch is incremented (for forcing search request) - * 2) if len(search) < 5 do nothing and delete store groups if any. + * 2) if len(search) < 5 do nothing and delete stored groups if there are any. * 3) if no other searches were done before, send search request to api. Store time of search in `lastSearchTime`. - * 4) successive requests are allowed if: last search request time (successful or not) was more than `sleepTime` (500ms) ago + * 4) successive requests are allowed if last search request time (successful or not) was more than `sleepTime` (500ms) ago * 5) if a request is not allowed, store `lastTimeSearch` anyway, then check `sleepTime` (500ms) later if the user has kept writing. * if he has, then do nothing because side effect will be called again, if he hasn't, then force search request with latest search value. */ @@ -154,6 +158,10 @@ export const Groups: MainStackScreen<"Groups"> = () => { useEffect(() => { if (isMounted) { let newGroups = groups + + if (year === all && newGroups) { + newGroups = orderByMostRecentYear(newGroups) + } if (language !== undefined && newGroups) { newGroups = filterByLanguage(newGroups, language) } diff --git a/src/utils/groups.ts b/src/utils/groups.ts index b1803021..c8f8b8e4 100644 --- a/src/utils/groups.ts +++ b/src/utils/groups.ts @@ -4,12 +4,54 @@ import { Group } from "api/groups" * return groups filtered by language * see {@link Groups} Page */ -export function filterByLanguage(groups: Group[], language: string) { +export function filterByLanguage(groups: Group[], language?: string) { return groups.filter(group => { return group.language === language }) } +/** + * return groups ordered by most recent year using a bubble sort algorithm + * see {@link Groups} Page + */ +export function orderByMostRecentYear(groups: Group[]) { + let hasChanged: boolean + try { + do { + hasChanged = false + for (let n = 0; n < groups.length - 1; n++) { + if ( + compareBiYear(groups[n].year, groups[n + 1].year) === true + ) { + //swap + let temp = groups[n] + groups[n] = groups[n + 1] + groups[n + 1] = temp + hasChanged = true + } + } + } while (hasChanged === true) + } catch (error) { + console.log(error) + } + return groups +} + +function compareBiYear(first: string | null, second: string | null) { + if ((first === null || first === "?/?") && second !== null) { + return true + } else if (second === null || first === null) { + return false + } + const regex = /^\d{4}\/\d{4}$/ + if (regex.test(first) && regex.test(second)) { + if (parseInt(first.substring(5)) < parseInt(second.substring(5))) { + return true + } + } + return false +} + export type ValidModalType = "year" | "course" | "type" | "platform" export const getNameFromMode = (mode: ValidModalType) => { From c67eec30b5c5ce8362a228904ebd0f9c8605e2f0 Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Sun, 22 Jan 2023 02:35:39 +0100 Subject: [PATCH 08/17] lint error --- src/utils/groups.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/groups.ts b/src/utils/groups.ts index c8f8b8e4..98f9d6f6 100644 --- a/src/utils/groups.ts +++ b/src/utils/groups.ts @@ -24,7 +24,7 @@ export function orderByMostRecentYear(groups: Group[]) { compareBiYear(groups[n].year, groups[n + 1].year) === true ) { //swap - let temp = groups[n] + const temp = groups[n] groups[n] = groups[n + 1] groups[n + 1] = temp hasChanged = true From dc7718d43af84c0f53460d5666c4617899be3877 Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Mon, 23 Jan 2023 01:47:09 +0100 Subject: [PATCH 09/17] New Groups Page Design --- src/api/groups.ts | 2 +- src/components/ContentWrapperScroll.tsx | 4 +- .../Groups/AnimatedPoliSearchBar.tsx | 40 ++++ .../Groups/ExpandablePoliSearchBar.tsx | 127 ----------- src/components/Groups/Filters.tsx | 2 + src/components/Groups/GroupTile.tsx | 70 ++++-- src/components/Groups/ModalGroup.tsx | 151 +++++++++++++ src/components/Groups/PageWrapper.tsx | 56 +++++ src/components/Home/PoliSearchBar.tsx | 15 ++ src/components/Settings/ButtonCustom.tsx | 6 +- src/pages/Groups.tsx | 210 +++++++++++++----- 11 files changed, 475 insertions(+), 208 deletions(-) create mode 100644 src/components/Groups/AnimatedPoliSearchBar.tsx delete mode 100644 src/components/Groups/ExpandablePoliSearchBar.tsx create mode 100644 src/components/Groups/ModalGroup.tsx create mode 100644 src/components/Groups/PageWrapper.tsx diff --git a/src/api/groups.ts b/src/api/groups.ts index f4c08fb4..1e1702e9 100644 --- a/src/api/groups.ts +++ b/src/api/groups.ts @@ -18,7 +18,7 @@ export interface Group { id?: string degree?: string school?: string - id_link: string + link_id: string language: string type_?: string year: string | null diff --git a/src/components/ContentWrapperScroll.tsx b/src/components/ContentWrapperScroll.tsx index 3d298634..b238b91c 100644 --- a/src/components/ContentWrapperScroll.tsx +++ b/src/components/ContentWrapperScroll.tsx @@ -10,6 +10,8 @@ import { usePalette } from "utils/colors" * Default margin Top is 86 (proper margin for Settings Page) */ export const ContentWrapperScroll: FC<{ + children: React.ReactNode + title?: string /** * Remove the navbar from the bottom of the page. @@ -20,8 +22,6 @@ export const ContentWrapperScroll: FC<{ */ navbarOptions?: NavbarProps marginTop?: number - - children: React.ReactNode }> = props => { const { background, isLight, primary } = usePalette() diff --git a/src/components/Groups/AnimatedPoliSearchBar.tsx b/src/components/Groups/AnimatedPoliSearchBar.tsx new file mode 100644 index 00000000..c3678fa3 --- /dev/null +++ b/src/components/Groups/AnimatedPoliSearchBar.tsx @@ -0,0 +1,40 @@ +import { Group } from "api/groups" +import { PoliSearchBar } from "components/Home" +import React, { FC } from "react" +import { ScrollView, View, ViewStyle } from "react-native" +import { usePalette } from "utils/colors" +import { createGroupLink } from "utils/groups" +import { AnimatedLine } from "./AnimatedLine" +import { GroupTile } from "./GroupTile" +import { OutlinedButton } from "./OutlinedButton" + +export interface AnimatedPoliSearchBarProps { + setSearch: (val: string) => void + isSearching: boolean + setIsSearching: (val: boolean) => void + groups?: Group[] + style?: ViewStyle +} + +export const AnimatedPoliSearchBar: FC< + AnimatedPoliSearchBarProps +> = props => { + const { isLight } = usePalette() + + return ( + + { + props.setSearch(val) + if (val !== "") { + props.setIsSearching(true) + } else if (props.isSearching === true) { + props.setIsSearching(false) + } + }} + style={{ marginTop: 0, marginBottom: 0 }} + /> + + + ) +} diff --git a/src/components/Groups/ExpandablePoliSearchBar.tsx b/src/components/Groups/ExpandablePoliSearchBar.tsx deleted file mode 100644 index f92018c5..00000000 --- a/src/components/Groups/ExpandablePoliSearchBar.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { Group } from "api/groups" -import { PoliSearchBar } from "components/Home" -import React, { FC } from "react" -import { ScrollView, View } from "react-native" -import { usePalette } from "utils/colors" -import { createGroupLink } from "utils/groups" -import { AnimatedLine } from "./AnimatedLine" -import { GroupTile } from "./GroupTile" -import { OutlinedButton } from "./OutlinedButton" - -export interface ExpandablePoliSearchBarProps { - setSearch: (val: string) => void - isSearching: boolean - setIsSearching: (val: boolean) => void - groups?: Group[] - language: ValidLanguageType - setLanguage: (val: ValidLanguageType) => void -} - -export type ValidLanguageType = "ITA" | "ENG" | undefined - -export const ExpandablePoliSearchBar: FC< - ExpandablePoliSearchBarProps -> = props => { - const { isLight } = usePalette() - - return ( - - - { - props.setSearch(val) - if (val !== "") { - props.setIsSearching(true) - } else if (props.isSearching === true) { - props.setIsSearching(false) - props.setLanguage(undefined) - } else { - props.setLanguage(undefined) - } - }} - style={{ marginTop: 0, marginBottom: 0 }} - /> - - - {props.groups && props.isSearching && ( - - - { - if (props.language === "ITA") { - props.setLanguage(undefined) - } else { - props.setLanguage("ITA") - } - }} - /> - { - if (props.language === "ENG") { - props.setLanguage(undefined) - } else { - props.setLanguage("ENG") - } - }} - /> - - - {props.groups.map((group, idx) => { - return ( - - ) - })} - - - )} - - - ) -} diff --git a/src/components/Groups/Filters.tsx b/src/components/Groups/Filters.tsx index e48f46cd..2e9305b9 100644 --- a/src/components/Groups/Filters.tsx +++ b/src/components/Groups/Filters.tsx @@ -14,6 +14,7 @@ export interface FiltersProps { setType: (value: string) => void platform: string setPlatform: (value: string) => void + forceSearch: () => void } interface ModalItemList { @@ -71,6 +72,7 @@ export const Filters: FC = props => { props.setCourse(all) props.setType(all) props.setPlatform(all) + props.forceSearch() } return ( diff --git a/src/components/Groups/GroupTile.tsx b/src/components/Groups/GroupTile.tsx index 3b2aef7f..b1898583 100644 --- a/src/components/Groups/GroupTile.tsx +++ b/src/components/Groups/GroupTile.tsx @@ -1,36 +1,66 @@ +import { Divider } from "components/Divider" import { BodyText } from "components/Text" import React, { FC } from "react" -import { Linking, Pressable } from "react-native" +import { Pressable, View } from "react-native" import { usePalette } from "utils/colors" export interface GroupTileProps { name?: string link?: string + onClick?: () => void } export const GroupTile: FC = props => { const { isLight } = usePalette() - const handlePress = async () => { - if (!props.link) { - return - } - // Checking if the link is supported for links with custom URL scheme. - const supported = await Linking.canOpenURL(props.link) - - if (supported) { - // Opening the link with some app - await Linking.openURL(props.link) - } - } - return ( - - + - {props.name} - - + + + + + {props.name} + + + + -:- members + + + + + + ) } diff --git a/src/components/Groups/ModalGroup.tsx b/src/components/Groups/ModalGroup.tsx new file mode 100644 index 00000000..5014bd3a --- /dev/null +++ b/src/components/Groups/ModalGroup.tsx @@ -0,0 +1,151 @@ +import React, { FC } from "react" +import { View, Modal, StyleSheet, Pressable } from "react-native" +import { usePalette } from "utils/colors" +import { ButtonCustom } from "components/Settings" +import { Canvas, ImageSVG, useSVG } from "@shopify/react-native-skia" +import { deleteSvg as icon } from "assets/modal" +import { Group } from "api/groups" + +export interface ModalGroupProps { + /** + * content of the modal + */ + children: React.ReactNode + + /** + * whether ot not to show the modal + */ + isShowing: boolean + /** + * this function hides the modal by changing the state in the parent component + */ + onClose: () => void + + /** + * function called when button "OK" is pressed + */ + onJoin: (group?: Group) => void + + group?: Group + /** + * modal wrapper height, specify if height is fixed + */ + height?: number +} + +/** + * Custom Modal Component with two buttons at the bottom. + * + */ +export const ModalGroup: FC = props => { + const { backgroundSecondary, homeBackground, modalBarrier, isLight } = + usePalette() + + const deleteSvg = useSVG(icon.svg) + return ( + //TODO: animationType fade or slide? + + + + props.onClose()} + > + + + {deleteSvg && ( + + )} + + + + + {props.children} + + props.onJoin(props.group)} + /> + + + + + + ) +} + +const styles = StyleSheet.create({ + pageWrapper: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + contentWrapper: { + flexDirection: "column", + justifyContent: "space-between", + width: 320, + borderRadius: 12, + marginHorizontal: 15, + + shadowColor: "#000", + shadowOffset: { + width: 0, + height: 3, + }, + shadowOpacity: 0.27, + shadowRadius: 4.65, + elevation: 6, + }, + title: { + fontSize: 32, + fontWeight: "900", + }, +}) diff --git a/src/components/Groups/PageWrapper.tsx b/src/components/Groups/PageWrapper.tsx new file mode 100644 index 00000000..8c46e51e --- /dev/null +++ b/src/components/Groups/PageWrapper.tsx @@ -0,0 +1,56 @@ +import React, { FC } from "react" +import { View, ViewStyle } from "react-native" +import { NavBar, NavbarProps } from "components/NavBar" +import { usePalette } from "utils/colors" + +/** + * Groups page Wrapper + */ +export const PageWrapper: FC<{ + children: React.ReactNode + + title?: string + /** + * Remove the navbar from the bottom of the page. + */ + hideNavbar?: boolean + /** + * Props for the navbar, see {@link NavBar} + */ + navbarOptions?: NavbarProps + marginTop?: number + style?: ViewStyle +}> = props => { + const { background, isLight, primary } = usePalette() + + const navbar = !props.hideNavbar + + return ( + + + {props.children} + + {navbar ? : null} + + ) +} diff --git a/src/components/Home/PoliSearchBar.tsx b/src/components/Home/PoliSearchBar.tsx index 8f60331f..d5237e85 100644 --- a/src/components/Home/PoliSearchBar.tsx +++ b/src/components/Home/PoliSearchBar.tsx @@ -5,6 +5,7 @@ import { Pressable, StyleProp, ViewStyle, + Keyboard, } from "react-native" import { usePalette } from "utils/colors" import { Canvas, ImageSVG, useSVG } from "@shopify/react-native-skia" @@ -25,6 +26,20 @@ export const PoliSearchBar: FC<{ const [isFocused, setIsFocused] = useState(false) const shadowAnim = useRef(new Animated.Value(0)).current const inputText = useRef(null) + + useEffect(() => { + const keyboardDidHideListener = Keyboard.addListener( + "keyboardDidHide", + () => { + inputText.current?.blur() + } + ) + + return () => { + keyboardDidHideListener.remove() + } + }, []) + useEffect(() => { const duration = 100 if (isFocused) diff --git a/src/components/Settings/ButtonCustom.tsx b/src/components/Settings/ButtonCustom.tsx index 0dce8349..958fc1f6 100644 --- a/src/components/Settings/ButtonCustom.tsx +++ b/src/components/Settings/ButtonCustom.tsx @@ -12,6 +12,8 @@ export interface ButtonCustomProps { light?: boolean onPress?: () => void buttonStyle?: ViewStyle + overrideLight?: string + overrideDark?: string } /** * Custom button component. Specify param `light` to select button type @@ -26,8 +28,8 @@ export const ButtonCustom: FC = props => { { backgroundColor: props.light ? isLight - ? palette.lighter - : palette.darker + ? (props.overrideLight ?? palette.lighter) + : (props.overrideDark ?? palette.darker) : isLight ? palette.darker : palette.lighter, diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index 589ef804..b772329e 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -1,22 +1,22 @@ import React, { useEffect, useState } from "react" import { MainStackScreen } from "navigation/NavigationTypes" -import { View } from "react-native" -import { ContentWrapperScroll } from "components/ContentWrapperScroll" -import { Title } from "components/Text" +import { Linking, ScrollView, View } from "react-native" +import { BodyText, Title } from "components/Text" import { Filters } from "components/Groups/Filters" -import { - ExpandablePoliSearchBar, - ValidLanguageType, -} from "components/Groups/ExpandablePoliSearchBar" import { api, RetryType } from "api" import { Group } from "api/groups" import { useMounted } from "utils/useMounted" import { - filterByLanguage, + createGroupLink, msPassedBetween, orderByMostRecentYear, } from "utils/groups" import { wait } from "utils/functions" +import { AnimatedPoliSearchBar } from "components/Groups/AnimatedPoliSearchBar" +import { GroupTile } from "components/Groups/GroupTile" +import { PageWrapper } from "components/Groups/PageWrapper" +import { usePalette } from "utils/colors" +import { ModalGroup } from "components/Groups/ModalGroup" const all = "Tutti" @@ -54,13 +54,17 @@ export const Groups: MainStackScreen<"Groups"> = () => { const [groups, setGroups] = useState(undefined) - const [language, setLanguage] = useState() + /* const [language, setLanguage] = useState() */ - //actually displayed groups (filtered by language) - const [filteredGroups, setFilteredGroups] = useState( + //actually displayed groups (Ordered by language) + const [orderedGroups, setOrderedGroups] = useState( undefined ) + const [isModalShowing, setIsModalShowing] = useState(false) + + const [modalGroup, setModalGroup] = useState(undefined) + //tracking first render const isMounted = useMounted() @@ -136,25 +140,19 @@ export const Groups: MainStackScreen<"Groups"> = () => { return } else { //force search in next frame - setRescheduleSearch(rescheduleSearch + 1) - setNeedSearching(true) + forceSearch() } } }, [prevSearch]) - //filter items every time selected language changes + //if filters are applied after search, search again useEffect(() => { if (isMounted && groups) { - if (language) { - const newFilteredGroups = filterByLanguage(groups, language) - setFilteredGroups(newFilteredGroups) - } else { - resetFilterLanguage() - } + forceSearch() } - }, [language]) + }, [course, year, type, platform]) - //load groups to filtered groups every time a new response from api arrives. (language filters ignored) + //load groups to Ordered groups every time a new response from api arrives. useEffect(() => { if (isMounted) { let newGroups = groups @@ -162,45 +160,145 @@ export const Groups: MainStackScreen<"Groups"> = () => { if (year === all && newGroups) { newGroups = orderByMostRecentYear(newGroups) } - if (language !== undefined && newGroups) { - newGroups = filterByLanguage(newGroups, language) - } - setFilteredGroups(newGroups) + setOrderedGroups(newGroups) } }, [groups]) - //helper function to reset language filters - const resetFilterLanguage = () => { - setFilteredGroups(groups) + const forceSearch = () => { + setRescheduleSearch(rescheduleSearch + 1) + setNeedSearching(true) } + + const { isLight } = usePalette() return ( - - - - Gruppi Corsi - setIsSearching(val)} - setSearch={val => setSearch(val)} - groups={filteredGroups} - language={language} - setLanguage={val => setLanguage(val)} - /> - - {!isSearching && ( - setYear(val)} - course={course} - setCourse={val => setCourse(val)} - type={type} - setType={val => setType(val)} - platform={platform} - setPlatform={val => setPlatform(val)} - /> - )} + + + Gruppi Corsi + setIsSearching(val)} + setSearch={val => setSearch(val)} + groups={orderedGroups} + style={{ marginTop: 36, marginBottom: 22 }} + /> + setYear(val)} + course={course} + setCourse={val => setCourse(val)} + type={type} + setType={val => setType(val)} + platform={platform} + setPlatform={val => setPlatform(val)} + forceSearch={forceSearch} + /> + + + + {orderedGroups?.map((group, idx) => { + return ( + { + setModalGroup(group) + setIsModalShowing(true) + }} + /> + ) + })} + - - + setIsModalShowing(false)} + onJoin={async (group?: Group) => { + if (!group?.link_id) { + return + } + + const link = createGroupLink( + group.link_id, + group.platform + ) + // Checking if the link is supported for links with custom URL scheme. + const supported = await Linking.canOpenURL(link) + + if (supported) { + // Opening the link with some app + await Linking.openURL(link) + } + }} + > + + + + {modalGroup?.class} + + + + --:-- members, --:-- online + + + {modalGroup?.year} {modalGroup?.platform} + + + + + + ) } From 816e558989cd9dd2a8a98394cc7aba485a395bbb Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Mon, 23 Jan 2023 11:31:22 +0100 Subject: [PATCH 10/17] General fixes --- src/api/groups.ts | 85 +---------------- .../Groups/AnimatedPoliSearchBar.tsx | 8 +- src/components/Groups/GroupTile.tsx | 5 +- src/components/Groups/ModalGroup.tsx | 7 +- src/components/Groups/ModalGroupItem.tsx | 68 ++++++++++++++ src/pages/Groups.tsx | 93 ++++--------------- src/utils/groups.ts | 31 ++++--- 7 files changed, 114 insertions(+), 183 deletions(-) create mode 100644 src/components/Groups/ModalGroupItem.tsx diff --git a/src/api/groups.ts b/src/api/groups.ts index 1e1702e9..29242ea4 100644 --- a/src/api/groups.ts +++ b/src/api/groups.ts @@ -21,7 +21,7 @@ export interface Group { link_id: string language: string type_?: string - year: string | null + year: string | null //probably I should use | null evreywhere? platform: string permanent_id?: number last_updated?: string @@ -38,8 +38,6 @@ export const groups = { * Retrieves groups from PoliNetwork server. * Check {@link GroupOptions} for additional parameters. */ - - // ! temporarily broken async get(groupsOptions?: GroupOptions, options?: RequestOptions) { const response = await client.poliNetworkInstance.get<{ groups: Group[] @@ -57,85 +55,4 @@ export const groups = { }) return response.data.groups }, - getMocked() { - return mockedGroups.groups - }, -} - -//random information -const mockedGroups = { - groups: [ - { - name: "GRUPPO 1 ITA", - year: "2022", - id: "1", - degree: "LT", - type: "S", - platform: "WA", - language: "ITA", - office: "Leonardo", - school: "idk", - idLink: "https://t.me/joinchat/9RcVXahIKchlMGZk", - }, - { - name: "GRUPPO 2 ITA", - year: "2021", - id: "1", - degree: "LT", - type: "S", - platform: "TG", - language: "ITA", - office: "Leonardo", - school: "idk", - idLink: "https://t.me/joinchat/lcaKVtappk83NzU0", - }, - { - name: "GRUPPO 3 ENG", - year: "2020", - id: "1", - degree: "LT", - type: "C", - platform: "TG", - language: "ENG", - office: "Leonardo", - school: "idk", - idLink: "https://chat.whatsapp.com/HDZd7mCzDg80dS4fCcSszy", - }, - { - name: "GRUPPO 4 ENG", - year: "2020", - id: "1", - degree: "LT", - type: "C", - platform: "TG", - language: "ENG", - office: "Leonardo", - school: "idk", - idLink: "https://t.me/joinchat/YEBlpQ_fzoZmYzI0", - }, - { - name: "GRUPPO 5 ITA", - year: "2020", - id: "1", - degree: "LT", - type: "C", - platform: "TG", - language: "ITA", - office: "Leonardo", - school: "idk", - idLink: "https://www.facebook.com/groups/170744940120942", - }, - { - name: "GRUPPO 6 ENG", - year: "2020", - id: "1", - degree: "LT", - type: "C", - platform: "TG", - language: "ENG", - office: "Leonardo", - school: "idk", - idLink: "https://t.me/joinchat/_9vETcjqnX5iNzNk", - }, - ], } diff --git a/src/components/Groups/AnimatedPoliSearchBar.tsx b/src/components/Groups/AnimatedPoliSearchBar.tsx index c3678fa3..d1044cdd 100644 --- a/src/components/Groups/AnimatedPoliSearchBar.tsx +++ b/src/components/Groups/AnimatedPoliSearchBar.tsx @@ -1,12 +1,9 @@ import { Group } from "api/groups" import { PoliSearchBar } from "components/Home" import React, { FC } from "react" -import { ScrollView, View, ViewStyle } from "react-native" -import { usePalette } from "utils/colors" -import { createGroupLink } from "utils/groups" +import { View, ViewStyle } from "react-native" import { AnimatedLine } from "./AnimatedLine" -import { GroupTile } from "./GroupTile" -import { OutlinedButton } from "./OutlinedButton" + export interface AnimatedPoliSearchBarProps { setSearch: (val: string) => void @@ -19,7 +16,6 @@ export interface AnimatedPoliSearchBarProps { export const AnimatedPoliSearchBar: FC< AnimatedPoliSearchBarProps > = props => { - const { isLight } = usePalette() return ( diff --git a/src/components/Groups/GroupTile.tsx b/src/components/Groups/GroupTile.tsx index b1898583..f84abe1f 100644 --- a/src/components/Groups/GroupTile.tsx +++ b/src/components/Groups/GroupTile.tsx @@ -5,8 +5,7 @@ import { Pressable, View } from "react-native" import { usePalette } from "utils/colors" export interface GroupTileProps { - name?: string - link?: string + text?: string onClick?: () => void } @@ -45,7 +44,7 @@ export const GroupTile: FC = props => { color: isLight ? "#454773" : "#fff", }} > - {props.name} + {props.text} void /** - * function called when button "OK" is pressed + * function called when button "JOIN GROUP" is pressed */ onJoin: (group?: Group) => void @@ -34,12 +34,11 @@ export interface ModalGroupProps { } /** - * Custom Modal Component with two buttons at the bottom. + * Custom Modal Component for Groups Page. * */ export const ModalGroup: FC = props => { - const { backgroundSecondary, homeBackground, modalBarrier, isLight } = - usePalette() + const { backgroundSecondary, modalBarrier } = usePalette() const deleteSvg = useSVG(icon.svg) return ( diff --git a/src/components/Groups/ModalGroupItem.tsx b/src/components/Groups/ModalGroupItem.tsx new file mode 100644 index 00000000..31fc1bb5 --- /dev/null +++ b/src/components/Groups/ModalGroupItem.tsx @@ -0,0 +1,68 @@ +import { BodyText } from "components/Text" +import React, { FC } from "react" +import { View } from "react-native" +import { usePalette } from "utils/colors" +import { Group } from "api/groups" + +export interface ModalGroupItemProps { + /** + * ResetButton + */ + group?: Group +} + +export const ModalGroupItem: FC = props => { + const { isLight } = usePalette() + return ( + + + + {props.group?.class} + + + + --:-- members, --:-- online + + + {props.group?.year} {props.group?.platform} + + + + ) +} diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index b772329e..ede8382d 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react" import { MainStackScreen } from "navigation/NavigationTypes" import { Linking, ScrollView, View } from "react-native" -import { BodyText, Title } from "components/Text" +import { Title } from "components/Text" import { Filters } from "components/Groups/Filters" import { api, RetryType } from "api" import { Group } from "api/groups" @@ -15,12 +15,12 @@ import { wait } from "utils/functions" import { AnimatedPoliSearchBar } from "components/Groups/AnimatedPoliSearchBar" import { GroupTile } from "components/Groups/GroupTile" import { PageWrapper } from "components/Groups/PageWrapper" -import { usePalette } from "utils/colors" import { ModalGroup } from "components/Groups/ModalGroup" +import { ModalGroupItem } from "components/Groups/ModalGroupItem" const all = "Tutti" -const sleepTime = 500 //ms +const deltaTime = 500 //ms export const Groups: MainStackScreen<"Groups"> = () => { //keep track of latest search request time (successful or not) @@ -34,7 +34,7 @@ export const Groups: MainStackScreen<"Groups"> = () => { //reason : I want to trigger it only if I set needSearching to True, not to False const [rescheduleSearch, setRescheduleSearch] = useState(0) - //for triggering api request when user doesnt write in a time span of `sleepTime` + //for triggering api request when user doesnt write in a time span of `deltaTime` //reason: I don't want to send api request on every character change. const [prevSearch, setPrevSearch] = useState("") @@ -54,9 +54,7 @@ export const Groups: MainStackScreen<"Groups"> = () => { const [groups, setGroups] = useState(undefined) - /* const [language, setLanguage] = useState() */ - - //actually displayed groups (Ordered by language) + //actually displayed groups (Ordered by year) const [orderedGroups, setOrderedGroups] = useState( undefined ) @@ -75,9 +73,10 @@ export const Groups: MainStackScreen<"Groups"> = () => { * 1) gets called every time search text changes or rescheduleSearch is incremented (for forcing search request) * 2) if len(search) < 5 do nothing and delete stored groups if there are any. * 3) if no other searches were done before, send search request to api. Store time of search in `lastSearchTime`. - * 4) successive requests are allowed if last search request time (successful or not) was more than `sleepTime` (500ms) ago - * 5) if a request is not allowed, store `lastTimeSearch` anyway, then check `sleepTime` (500ms) later if the user has kept writing. - * if he has, then do nothing because side effect will be called again, if he hasn't, then force search request with latest search value. + * 4) successive requests are allowed if last search request time (successful or not) was more than `deltaTime` (500ms) ago + * 5) if a request is not allowed, store `lastTimeSearch` anyway, then check `deltaTime` (500ms) later if the user has kept writing. + * if he has, then do nothing because side effect will be called again, if he hasn't, + * then force search request with latest search value. */ const searchGroups = async () => { if (isMounted) { @@ -92,7 +91,7 @@ export const Groups: MainStackScreen<"Groups"> = () => { const msPassed = msPassedBetween(lastSearchTime, now) if ( msPassed === undefined || - msPassed >= sleepTime || + msPassed >= deltaTime || needSearching === true ) { //update last time search @@ -111,10 +110,8 @@ export const Groups: MainStackScreen<"Groups"> = () => { //reset need searching for next render setNeedSearching(false) } else { - // ? over optimization maybe: this prevents api request if user keeps writing fast. - // ? maybe I should request every 1 seconds even if user is still writing? debatable setLastSearchTime(now) - await wait(sleepTime) + await wait(deltaTime) //in the next render you have the previous and current search text, and can compare changes //this triggers Reschedule Search Side Effect setPrevSearch(search) @@ -131,7 +128,7 @@ export const Groups: MainStackScreen<"Groups"> = () => { /** * Reschedule Search Side Effect * - * evaluate if user stopped writing characters in a time span of `sleepTime` seconds + * evaluate if user stopped writing characters in a time span of `deltaTime` seconds */ useEffect(() => { if (isMounted) { @@ -156,7 +153,10 @@ export const Groups: MainStackScreen<"Groups"> = () => { useEffect(() => { if (isMounted) { let newGroups = groups - + //if response from api arrives when we no longer expect it + if (newGroups && search.length < 5) { + newGroups = undefined + } if (year === all && newGroups) { newGroups = orderByMostRecentYear(newGroups) } @@ -164,12 +164,12 @@ export const Groups: MainStackScreen<"Groups"> = () => { } }, [groups]) + //force search in next render const forceSearch = () => { setRescheduleSearch(rescheduleSearch + 1) setNeedSearching(true) } - const { isLight } = usePalette() return ( = () => { marginBottom: 93, }} > - + {orderedGroups?.map((group, idx) => { return ( { setModalGroup(group) @@ -247,56 +243,7 @@ export const Groups: MainStackScreen<"Groups"> = () => { } }} > - - - - {modalGroup?.class} - - - - --:-- members, --:-- online - - - {modalGroup?.year} {modalGroup?.platform} - - - + diff --git a/src/utils/groups.ts b/src/utils/groups.ts index 98f9d6f6..5a5e32d8 100644 --- a/src/utils/groups.ts +++ b/src/utils/groups.ts @@ -1,15 +1,5 @@ import { Group } from "api/groups" -/** - * return groups filtered by language - * see {@link Groups} Page - */ -export function filterByLanguage(groups: Group[], language?: string) { - return groups.filter(group => { - return group.language === language - }) -} - /** * return groups ordered by most recent year using a bubble sort algorithm * see {@link Groups} Page @@ -38,16 +28,31 @@ export function orderByMostRecentYear(groups: Group[]) { } function compareBiYear(first: string | null, second: string | null) { - if ((first === null || first === "?/?") && second !== null) { + //apparently null != undefined :( code breaks if I use undefined instead of null + if ((first === "?/?" || first === null) && second !== null) { return true } else if (second === null || first === null) { return false } - const regex = /^\d{4}\/\d{4}$/ - if (regex.test(first) && regex.test(second)) { + //standard year format "2021/2022" + const regexStandard = /^\d{4}\/\d{4}$/ + //for inconsistencies in the db ex "2021/22" + const regexNonStandard = /^\d{4}\/\d{2}$/ + if ( + (regexStandard.test(first) && regexStandard.test(second)) || + (regexNonStandard.test(first) && regexNonStandard.test(second)) + ) { if (parseInt(first.substring(5)) < parseInt(second.substring(5))) { return true } + } else if (regexNonStandard.test(first) && regexStandard.test(second)) { + if (parseInt(first.substring(5)) < parseInt(second.substring(7))) { + return true + } + } else if (regexStandard.test(first) && regexNonStandard.test(second)) { + if (parseInt(first.substring(7)) < parseInt(second.substring(5))) { + return true + } } return false } From bff3567f92501c45f72018a436565286b737a343 Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Sun, 29 Jan 2023 10:43:08 +0100 Subject: [PATCH 11/17] added svg platform icons --- assets/groups/facebook.svg | 4 +++ assets/groups/index.ts | 32 ++++++++++++++++++ assets/groups/telegram.svg | 10 ++++++ assets/groups/whatsapp.svg | 21 ++++++++++++ src/components/Groups/GroupTile.tsx | 42 ++++++++++++++++++++++-- src/components/Groups/ModalGroupItem.tsx | 34 +++++++++++++++++-- src/pages/Groups.tsx | 2 ++ src/utils/groups.ts | 13 +++++++- 8 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 assets/groups/facebook.svg create mode 100644 assets/groups/index.ts create mode 100644 assets/groups/telegram.svg create mode 100644 assets/groups/whatsapp.svg diff --git a/assets/groups/facebook.svg b/assets/groups/facebook.svg new file mode 100644 index 00000000..0ab81d19 --- /dev/null +++ b/assets/groups/facebook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/groups/index.ts b/assets/groups/index.ts new file mode 100644 index 00000000..3cc1a4cf --- /dev/null +++ b/assets/groups/index.ts @@ -0,0 +1,32 @@ +import { DataSourceParam } from "@shopify/react-native-skia" +import whatsapp from "./whatsapp.svg" +import facebook from "./facebook.svg" +import telegram from "./telegram.svg" + +/** + * list of tray icons + */ +export const platformIconList = ["telegram", "whatsapp", "facebook"] as const + +export type PlatformIcon = typeof platformIconList[number] + +export const platformIcons: Record< + PlatformIcon, + { svg: DataSourceParam; width: number; heigth: number } +> = { + whatsapp: { + svg: whatsapp, + width: 24, + heigth: 24, + }, + telegram: { + svg: telegram, + width: 24, + heigth: 24, + }, + facebook: { + svg: facebook, + width: 24, + heigth: 24, + }, +} \ No newline at end of file diff --git a/assets/groups/telegram.svg b/assets/groups/telegram.svg new file mode 100644 index 00000000..bbc6a5e4 --- /dev/null +++ b/assets/groups/telegram.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/groups/whatsapp.svg b/assets/groups/whatsapp.svg new file mode 100644 index 00000000..5f7a5387 --- /dev/null +++ b/assets/groups/whatsapp.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Groups/GroupTile.tsx b/src/components/Groups/GroupTile.tsx index f84abe1f..2d50c9ed 100644 --- a/src/components/Groups/GroupTile.tsx +++ b/src/components/Groups/GroupTile.tsx @@ -1,3 +1,9 @@ +import { + Canvas, + DataSourceParam, + ImageSVG, + useSVG, +} from "@shopify/react-native-skia" import { Divider } from "components/Divider" import { BodyText } from "components/Text" import React, { FC } from "react" @@ -6,12 +12,16 @@ import { usePalette } from "utils/colors" export interface GroupTileProps { text?: string + icon?: { svg: DataSourceParam; width: number; heigth: number } onClick?: () => void } export const GroupTile: FC = props => { const { isLight } = usePalette() + const iconSvg = useSVG(props.icon?.svg) + + return ( = props => { + > + {props.icon && iconSvg && ( + + + {iconSvg && ( + + )} + + + )} + = props => { const { isLight } = usePalette() + const icon = choosePlatformIcon(props.group?.platform) + const iconSvg = useSVG(icon?.svg) + + const scaleFactor = 2.5 + return ( = props => { style={{ width: 88, height: 88, - backgroundColor: isLight ? "#454773" : "#fff", borderRadius: 44, marginTop: 16, marginBottom: 8, + justifyContent: "center", + alignItems: "center", }} - /> + > + {icon && iconSvg && ( + + {iconSvg && ( + + )} + + )} + = props => { marginTop: 16, }} > - {props.group?.year} {props.group?.platform} + {props.group?.year} diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index ede8382d..af41eb13 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -7,6 +7,7 @@ import { api, RetryType } from "api" import { Group } from "api/groups" import { useMounted } from "utils/useMounted" import { + choosePlatformIcon, createGroupLink, msPassedBetween, orderByMostRecentYear, @@ -216,6 +217,7 @@ export const Groups: MainStackScreen<"Groups"> = () => { setModalGroup(group) setIsModalShowing(true) }} + icon={choosePlatformIcon(group.platform)} /> ) })} diff --git a/src/utils/groups.ts b/src/utils/groups.ts index 5a5e32d8..87281643 100644 --- a/src/utils/groups.ts +++ b/src/utils/groups.ts @@ -1,5 +1,5 @@ import { Group } from "api/groups" - +import { platformIcons } from "assets/groups" /** * return groups ordered by most recent year using a bubble sort algorithm * see {@link Groups} Page @@ -87,3 +87,14 @@ export function msPassedBetween(start: Date | undefined, end: Date) { } return end.getTime() - start.getTime() } + +export function choosePlatformIcon(platform? : string){ + if(platform === "TG"){ + return platformIcons.telegram + }else if(platform ==="FB"){ + return platformIcons.facebook + }else if(platform === "WA"){ + return platformIcons.whatsapp + } + return undefined +} \ No newline at end of file From fcf77568a63bb4a62fd1fe0b833dfa25e793a2c5 Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Sun, 29 Jan 2023 13:33:33 +0100 Subject: [PATCH 12/17] added 2022/2023 --- src/components/Groups/Filters.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Groups/Filters.tsx b/src/components/Groups/Filters.tsx index 2e9305b9..e2ffcc29 100644 --- a/src/components/Groups/Filters.tsx +++ b/src/components/Groups/Filters.tsx @@ -23,8 +23,8 @@ interface ModalItemList { } const yearsList: ModalItemList = { - itemsToShow: ["2021/2022", "2020/2021", "2019/2020", "2018/2019"], - itemsToSave: ["2021/2022", "2020/2021", "2019/2020", "2018/2019"], + itemsToShow: [ "2022/2023","2021/2022", "2020/2021", "2019/2020", "2018/2019"], + itemsToSave: [ "2022/2023","2021/2022", "2020/2021", "2019/2020", "2018/2019"], } const coursesList: ModalItemList = { itemsToShow: ["Triennale", "Magistrale", "Ciclo unico"], From a9be3e255f4155fb714d4fae65d2962d3e5f28f2 Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Tue, 31 Jan 2023 23:39:07 +0100 Subject: [PATCH 13/17] pr requested changes --- assets/groups/index.ts | 2 +- .../Groups/AnimatedPoliSearchBar.tsx | 25 +- src/components/Groups/Filters.tsx | 127 +++++----- src/components/Groups/GroupTile.tsx | 1 - src/components/Groups/ModalGroup.tsx | 10 +- src/components/Settings/ButtonCustom.tsx | 10 +- src/components/Settings/CareerTile.tsx | 2 +- src/components/Settings/ModalSelection.tsx | 6 - src/pages/Groups.tsx | 220 +++++------------- src/pages/settings/Settings.tsx | 2 - src/utils/groups.ts | 11 +- 11 files changed, 150 insertions(+), 266 deletions(-) diff --git a/assets/groups/index.ts b/assets/groups/index.ts index 3cc1a4cf..fd402523 100644 --- a/assets/groups/index.ts +++ b/assets/groups/index.ts @@ -26,7 +26,7 @@ export const platformIcons: Record< }, facebook: { svg: facebook, - width: 24, + width: 25, heigth: 24, }, } \ No newline at end of file diff --git a/src/components/Groups/AnimatedPoliSearchBar.tsx b/src/components/Groups/AnimatedPoliSearchBar.tsx index d1044cdd..a990a25e 100644 --- a/src/components/Groups/AnimatedPoliSearchBar.tsx +++ b/src/components/Groups/AnimatedPoliSearchBar.tsx @@ -1,36 +1,29 @@ -import { Group } from "api/groups" import { PoliSearchBar } from "components/Home" -import React, { FC } from "react" +import React, { FC, useState } from "react" import { View, ViewStyle } from "react-native" import { AnimatedLine } from "./AnimatedLine" - export interface AnimatedPoliSearchBarProps { - setSearch: (val: string) => void - isSearching: boolean - setIsSearching: (val: boolean) => void - groups?: Group[] + onSearch: (val: string) => void style?: ViewStyle } -export const AnimatedPoliSearchBar: FC< - AnimatedPoliSearchBarProps -> = props => { - +export const AnimatedPoliSearchBar: FC = props => { + const [isSearching, setIsSearching] = useState(false) return ( { - props.setSearch(val) + props.onSearch(val) if (val !== "") { - props.setIsSearching(true) - } else if (props.isSearching === true) { - props.setIsSearching(false) + setIsSearching(true) + } else if (isSearching === true) { + setIsSearching(false) } }} style={{ marginTop: 0, marginBottom: 0 }} /> - + ) } diff --git a/src/components/Groups/Filters.tsx b/src/components/Groups/Filters.tsx index e2ffcc29..a2fec793 100644 --- a/src/components/Groups/Filters.tsx +++ b/src/components/Groups/Filters.tsx @@ -6,15 +6,15 @@ import { ModalSelection, SelectTile } from "components/Settings" import { getNameFromMode, ValidModalType } from "utils/groups" export interface FiltersProps { - year: string - setYear: (value: string) => void - course: string - setCourse: (value: string) => void - type: string - setType: (value: string) => void - platform: string - setPlatform: (value: string) => void - forceSearch: () => void + filters: Filters + onFilterChange: (filters: Filters) => void +} + +export interface Filters { + year?: string + course?: string + platform?: string + type?: string } interface ModalItemList { @@ -23,8 +23,20 @@ interface ModalItemList { } const yearsList: ModalItemList = { - itemsToShow: [ "2022/2023","2021/2022", "2020/2021", "2019/2020", "2018/2019"], - itemsToSave: [ "2022/2023","2021/2022", "2020/2021", "2019/2020", "2018/2019"], + itemsToShow: [ + "2022/2023", + "2021/2022", + "2020/2021", + "2019/2020", + "2018/2019", + ], + itemsToSave: [ + "2022/2023", + "2021/2022", + "2020/2021", + "2019/2020", + "2018/2019", + ], } const coursesList: ModalItemList = { itemsToShow: ["Triennale", "Magistrale", "Ciclo unico"], @@ -41,8 +53,6 @@ const platformsList: ModalItemList = { itemsToSave: ["WA", "FB", "TG"], } -//to avoid writing mistakes -const all = "Tutti" export const Filters: FC = props => { //show or hide modal @@ -52,27 +62,13 @@ export const Filters: FC = props => { //items to show inside modal const [modalItems, setModalItems] = useState(yearsList) //currently selected item inside modal - const [selectedItem, setSelectedItem] = useState(all) + const [selectedItem, setSelectedItem] = useState( + undefined + ) - //update state when user taps "OK" in modal - const updateSelectedFilter = () => { - if (modalMode === "year") { - props.setYear(selectedItem) - } else if (modalMode === "course") { - props.setCourse(selectedItem) - } else if (modalMode === "platform") { - props.setPlatform(selectedItem) - } else { - props.setType(selectedItem) - } - } //reset state on "reset" const reset = () => { - props.setYear(all) - props.setCourse(all) - props.setType(all) - props.setPlatform(all) - props.forceSearch() + props.onFilterChange({}) } return ( @@ -81,63 +77,56 @@ export const Filters: FC = props => { style={{ flexDirection: "row", flexWrap: "wrap", - paddingHorizontal: 10, }} > { setModalMode("year") setModalItems(yearsList) - setSelectedItem(props.year) + setSelectedItem(props.filters.year) setIsModalShowing(true) }} /> { setModalMode("course") setModalItems(coursesList) - setSelectedItem(props.course) + setSelectedItem(props.filters.course) setIsModalShowing(true) }} /> { setModalMode("type") setModalItems(typesList) - setSelectedItem(props.type) + setSelectedItem(props.filters.type) setIsModalShowing(true) }} /> { setModalMode("platform") setModalItems(platformsList) - setSelectedItem(props.platform) + setSelectedItem(props.filters.platform) setIsModalShowing(true) }} /> @@ -147,17 +136,36 @@ export const Filters: FC = props => { onClose={() => { setIsModalShowing(false) }} - selectedValue={selectedItem} onOK={() => { - updateSelectedFilter() + if (modalMode === "course") { + props.onFilterChange({ + ...props.filters, + course: selectedItem, + }) + } else if (modalMode === "platform") { + props.onFilterChange({ + ...props.filters, + platform: selectedItem, + }) + } else if (modalMode === "year") { + props.onFilterChange({ + ...props.filters, + year: selectedItem, + }) + } else if (modalMode === "type") { + props.onFilterChange({ + ...props.filters, + type: selectedItem, + }) + } setIsModalShowing(false) }} > { - setSelectedItem(all) + setSelectedItem(undefined) }} /> {modalItems?.itemsToShow.map((itemName, index) => { @@ -180,10 +188,9 @@ export const Filters: FC = props => { } const styles = StyleSheet.create({ - buttonRightMargin: { - marginRight: 8, - }, - buttonBottomMargin: { + buttonCustomMargin: { + marginRight: 4, + marginLeft: 4, marginBottom: 8, }, }) diff --git a/src/components/Groups/GroupTile.tsx b/src/components/Groups/GroupTile.tsx index 2d50c9ed..d9852971 100644 --- a/src/components/Groups/GroupTile.tsx +++ b/src/components/Groups/GroupTile.tsx @@ -21,7 +21,6 @@ export const GroupTile: FC = props => { const iconSvg = useSVG(props.icon?.svg) - return ( = props => { - const { backgroundSecondary, modalBarrier } = usePalette() + const { backgroundSecondary, modalBarrier, isLight } = usePalette() const deleteSvg = useSVG(icon.svg) return ( @@ -65,7 +65,6 @@ export const ModalGroup: FC = props => { height: 30, backgroundColor: "#ffffff", borderRadius: 15, - marginTop: 96, marginBottom: 8, justifyContent: "center", alignItems: "center", @@ -108,8 +107,11 @@ export const ModalGroup: FC = props => { }} > props.onJoin(props.group)} /> diff --git a/src/components/Settings/ButtonCustom.tsx b/src/components/Settings/ButtonCustom.tsx index 958fc1f6..51ff3606 100644 --- a/src/components/Settings/ButtonCustom.tsx +++ b/src/components/Settings/ButtonCustom.tsx @@ -11,9 +11,7 @@ export interface ButtonCustomProps { */ light?: boolean onPress?: () => void - buttonStyle?: ViewStyle - overrideLight?: string - overrideDark?: string + style?: ViewStyle } /** * Custom button component. Specify param `light` to select button type @@ -28,14 +26,14 @@ export const ButtonCustom: FC = props => { { backgroundColor: props.light ? isLight - ? (props.overrideLight ?? palette.lighter) - : (props.overrideDark ?? palette.darker) + ? (palette.lighter) + : ( palette.darker) : isLight ? palette.darker : palette.lighter, minWidth: 130, }, - props.buttonStyle, + props.style, ]} onPress={props.onPress} > diff --git a/src/components/Settings/CareerTile.tsx b/src/components/Settings/CareerTile.tsx index a3e2b1aa..161c9ee6 100644 --- a/src/components/Settings/CareerTile.tsx +++ b/src/components/Settings/CareerTile.tsx @@ -31,7 +31,7 @@ export const CareerTile: FC = props => { diff --git a/src/components/Settings/ModalSelection.tsx b/src/components/Settings/ModalSelection.tsx index 9f2ff791..e7b609cb 100644 --- a/src/components/Settings/ModalSelection.tsx +++ b/src/components/Settings/ModalSelection.tsx @@ -26,12 +26,6 @@ export interface ModalSelectionProps { */ onOK: () => void - /** - * input value of `onOk` function. - * Usually the current state value of a {@link RadioButtonGroup} - */ - selectedValue: string - /** * modal wrapper height, specify if height is fixed */ diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index af41eb13..4089cfc0 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -9,56 +9,24 @@ import { useMounted } from "utils/useMounted" import { choosePlatformIcon, createGroupLink, - msPassedBetween, orderByMostRecentYear, } from "utils/groups" -import { wait } from "utils/functions" + import { AnimatedPoliSearchBar } from "components/Groups/AnimatedPoliSearchBar" import { GroupTile } from "components/Groups/GroupTile" import { PageWrapper } from "components/Groups/PageWrapper" import { ModalGroup } from "components/Groups/ModalGroup" import { ModalGroupItem } from "components/Groups/ModalGroupItem" -const all = "Tutti" - -const deltaTime = 500 //ms +const deltaTime = 100 //ms +let searchTimeout: NodeJS.Timeout export const Groups: MainStackScreen<"Groups"> = () => { - //keep track of latest search request time (successful or not) - const [lastSearchTime, setLastSearchTime] = useState( - undefined - ) - //for forcing api search request if needeed - const [needSearching, setNeedSearching] = useState(false) - - //for triggering api search request side effect (used in conjunction with `needSearching`) - //reason : I want to trigger it only if I set needSearching to True, not to False - const [rescheduleSearch, setRescheduleSearch] = useState(0) - - //for triggering api request when user doesnt write in a time span of `deltaTime` - //reason: I don't want to send api request on every character change. - const [prevSearch, setPrevSearch] = useState("") - const [search, setSearch] = useState("") - const [isSearching, setIsSearching] = useState(false) - - // ? I'd like to typecheck year, course, etc... but I need dynamic type checking - // ? and it's very ugly, and need to use type guards or some library, so maybe it is not worth it? - const [year, setYear] = useState(all) - - const [course, setCourse] = useState(all) + const [filters, setFilters] = useState({}) - const [type, setType] = useState(all) - - const [platform, setPlatform] = useState(all) - - const [groups, setGroups] = useState(undefined) - - //actually displayed groups (Ordered by year) - const [orderedGroups, setOrderedGroups] = useState( - undefined - ) + const [groups, setGroups] = useState([]) const [isModalShowing, setIsModalShowing] = useState(false) @@ -68,136 +36,64 @@ export const Groups: MainStackScreen<"Groups"> = () => { const isMounted = useMounted() /** - * Api search request side effect. - * - * How this works (general ideas): - * 1) gets called every time search text changes or rescheduleSearch is incremented (for forcing search request) - * 2) if len(search) < 5 do nothing and delete stored groups if there are any. - * 3) if no other searches were done before, send search request to api. Store time of search in `lastSearchTime`. - * 4) successive requests are allowed if last search request time (successful or not) was more than `deltaTime` (500ms) ago - * 5) if a request is not allowed, store `lastTimeSearch` anyway, then check `deltaTime` (500ms) later if the user has kept writing. - * if he has, then do nothing because side effect will be called again, if he hasn't, - * then force search request with latest search value. + * Api search request. */ const searchGroups = async () => { if (isMounted) { - if (search.length < 5) { - if (groups !== undefined) { - setGroups(undefined) - } + if (search.length < 4) { + setGroups([]) return } try { - const now = new Date() - const msPassed = msPassedBetween(lastSearchTime, now) - if ( - msPassed === undefined || - msPassed >= deltaTime || - needSearching === true - ) { - //update last time search - setLastSearchTime(now) - const response = await api.groups.get( - { - name: search.trimEnd(), - year: year === all ? undefined : year, - degree: course === all ? undefined : course, - type: type === all ? undefined : type, - platform: platform === all ? undefined : platform, - }, - { maxRetries: 1, retryType: RetryType.RETRY_N_TIMES } - ) - setGroups(response) - //reset need searching for next render - setNeedSearching(false) - } else { - setLastSearchTime(now) - await wait(deltaTime) - //in the next render you have the previous and current search text, and can compare changes - //this triggers Reschedule Search Side Effect - setPrevSearch(search) - } + //update last time search + const response = await api.groups.get( + { + name: search.trimEnd(), + year: filters.year, + platform: filters.platform, + type: filters.type, + degree: filters.course, + }, + { maxRetries: 1, retryType: RetryType.RETRY_N_TIMES } + ) + setGroups(response) + + //reset need searching for next render } catch (error) { console.log(error) } } } - useEffect(() => { - void searchGroups() - }, [search, rescheduleSearch]) - /** - * Reschedule Search Side Effect - * - * evaluate if user stopped writing characters in a time span of `deltaTime` seconds - */ useEffect(() => { - if (isMounted) { - if (prevSearch !== search) { - //Do nothing - return - } else { - //force search in next frame - forceSearch() - } - } - }, [prevSearch]) + clearTimeout(searchTimeout) + searchTimeout = setTimeout(() => { + void searchGroups() + }, deltaTime) + }, [search]) //if filters are applied after search, search again useEffect(() => { - if (isMounted && groups) { - forceSearch() - } - }, [course, year, type, platform]) - - //load groups to Ordered groups every time a new response from api arrives. - useEffect(() => { - if (isMounted) { - let newGroups = groups - //if response from api arrives when we no longer expect it - if (newGroups && search.length < 5) { - newGroups = undefined - } - if (year === all && newGroups) { - newGroups = orderByMostRecentYear(newGroups) - } - setOrderedGroups(newGroups) - } - }, [groups]) - - //force search in next render - const forceSearch = () => { - setRescheduleSearch(rescheduleSearch + 1) - setNeedSearching(true) - } + if (isMounted && groups) void searchGroups() + }, [filters]) return ( Gruppi Corsi setIsSearching(val)} - setSearch={val => setSearch(val)} - groups={orderedGroups} + onSearch={val => setSearch(val)} style={{ marginTop: 36, marginBottom: 22 }} /> setYear(val)} - course={course} - setCourse={val => setCourse(val)} - type={type} - setType={val => setType(val)} - platform={platform} - setPlatform={val => setPlatform(val)} - forceSearch={forceSearch} + onFilterChange={filters => setFilters(filters)} + filters={filters} /> = () => { }} > - {orderedGroups?.map((group, idx) => { + {orderByMostRecentYear( + groups, + filters.year !== undefined + )?.map((group, idx) => { return ( = () => { })} - setIsModalShowing(false)} - onJoin={async (group?: Group) => { - if (!group?.link_id) { - return - } - - const link = createGroupLink( - group.link_id, - group.platform - ) - // Checking if the link is supported for links with custom URL scheme. - const supported = await Linking.canOpenURL(link) - - if (supported) { - // Opening the link with some app - await Linking.openURL(link) - } - }} - > - - + setIsModalShowing(false)} + onJoin={async (group?: Group) => { + if (!group?.link_id) { + return + } + + const link = createGroupLink(group.link_id, group.platform) + // Checking if the link is supported for links with custom URL scheme. + const supported = await Linking.canOpenURL(link) + + if (supported) { + // Opening the link with some app + await Linking.openURL(link) + } + }} + > + + ) } diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index f23a2033..4391640c 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -113,7 +113,6 @@ export const SettingsPage: SettingsStackScreen<"Settings"> = () => { { //restore real theme value setSelectedTheme(theme) @@ -140,7 +139,6 @@ export const SettingsPage: SettingsStackScreen<"Settings"> = () => { { //restore selectedCareer to career if (career) setSelectedCareer(career) diff --git a/src/utils/groups.ts b/src/utils/groups.ts index 87281643..f53ea26e 100644 --- a/src/utils/groups.ts +++ b/src/utils/groups.ts @@ -4,7 +4,10 @@ import { platformIcons } from "assets/groups" * return groups ordered by most recent year using a bubble sort algorithm * see {@link Groups} Page */ -export function orderByMostRecentYear(groups: Group[]) { +export function orderByMostRecentYear(groups: Group[], isOrdered : boolean) { + if(isOrdered){ + return groups + } let hasChanged: boolean try { do { @@ -81,12 +84,6 @@ export function createGroupLink(idLink: string, platform: string) { } } -export function msPassedBetween(start: Date | undefined, end: Date) { - if (start === undefined) { - return undefined - } - return end.getTime() - start.getTime() -} export function choosePlatformIcon(platform? : string){ if(platform === "TG"){ From 992a968a7f964cc05e74e2c002df0706d4e0ae68 Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Wed, 1 Feb 2023 00:37:29 +0100 Subject: [PATCH 14/17] margin fix --- src/pages/Groups.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index 4089cfc0..44aedad2 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -101,6 +101,7 @@ export const Groups: MainStackScreen<"Groups"> = () => { flex: 1, marginTop: 40, marginBottom: 93, + marginHorizontal: 8, }} > From 20dd210d6b13286b59a6d832341c9c5cb795de5d Mon Sep 17 00:00:00 2001 From: DiegoZaff Date: Wed, 1 Feb 2023 11:11:16 +0100 Subject: [PATCH 15/17] Flatlist and ordered groups fix --- src/api/groups.ts | 2 +- src/pages/Groups.tsx | 39 +++++++++++++++++++-------------------- src/utils/groups.ts | 5 +---- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/api/groups.ts b/src/api/groups.ts index 29242ea4..64d59351 100644 --- a/src/api/groups.ts +++ b/src/api/groups.ts @@ -15,7 +15,7 @@ export interface GroupOptions { export interface Group { class: string office: string - id?: string + id: string degree?: string school?: string link_id: string diff --git a/src/pages/Groups.tsx b/src/pages/Groups.tsx index 44aedad2..5ce3d506 100644 --- a/src/pages/Groups.tsx +++ b/src/pages/Groups.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react" import { MainStackScreen } from "navigation/NavigationTypes" -import { Linking, ScrollView, View } from "react-native" +import { FlatList, Linking, View } from "react-native" import { Title } from "components/Text" import { Filters } from "components/Groups/Filters" import { api, RetryType } from "api" @@ -40,7 +40,7 @@ export const Groups: MainStackScreen<"Groups"> = () => { */ const searchGroups = async () => { if (isMounted) { - if (search.length < 4) { + if (search.length < 3) { setGroups([]) return } @@ -77,6 +77,9 @@ export const Groups: MainStackScreen<"Groups"> = () => { if (isMounted && groups) void searchGroups() }, [filters]) + const orderedGroups = + filters.year === undefined ? orderByMostRecentYear(groups) : groups + return ( = () => { marginHorizontal: 8, }} > - - {orderByMostRecentYear( - groups, - filters.year !== undefined - )?.map((group, idx) => { - return ( - { - setModalGroup(group) - setIsModalShowing(true) - }} - icon={choosePlatformIcon(group.platform)} - /> - ) - })} - + ( + { + setModalGroup(group.item) + setIsModalShowing(true) + }} + icon={choosePlatformIcon(group.item.platform)} + /> + )} + keyExtractor={item => item.id} + /> Date: Wed, 1 Feb 2023 23:49:45 +0100 Subject: [PATCH 16/17] fix no members --- src/api/groups.ts | 1 + src/components/Groups/GroupTile.tsx | 21 ++++++++++++--------- src/components/Groups/ModalGroupItem.tsx | 22 ++++++++++++---------- src/pages/Groups.tsx | 1 + 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/api/groups.ts b/src/api/groups.ts index 64d59351..b2dbc8f7 100644 --- a/src/api/groups.ts +++ b/src/api/groups.ts @@ -26,6 +26,7 @@ export interface Group { permanent_id?: number last_updated?: string link_is_working?: string + members?: string } const client = HttpClient.getInstance() diff --git a/src/components/Groups/GroupTile.tsx b/src/components/Groups/GroupTile.tsx index d9852971..fe62bf93 100644 --- a/src/components/Groups/GroupTile.tsx +++ b/src/components/Groups/GroupTile.tsx @@ -13,6 +13,7 @@ import { usePalette } from "utils/colors" export interface GroupTileProps { text?: string icon?: { svg: DataSourceParam; width: number; heigth: number } + members?: string onClick?: () => void } @@ -84,15 +85,17 @@ export const GroupTile: FC = props => { {props.text} - - -:- members - + {props.members && ( + + {props.members} members + + )} diff --git a/src/components/Groups/ModalGroupItem.tsx b/src/components/Groups/ModalGroupItem.tsx index 318c0b08..c6eac75b 100644 --- a/src/components/Groups/ModalGroupItem.tsx +++ b/src/components/Groups/ModalGroupItem.tsx @@ -69,16 +69,18 @@ export const ModalGroupItem: FC = props => { {props.group?.class} - - --:-- members, --:-- online - + {props.group?.members && ( + + {props.group.members} members + + )} = () => { renderItem={group => ( { setModalGroup(group.item) setIsModalShowing(true) From d105cab1b26196f292eb1050e10f0d2fa6ae1bd0 Mon Sep 17 00:00:00 2001 From: Tommaso Morganti Date: Fri, 3 Feb 2023 16:46:52 +0100 Subject: [PATCH 17/17] small styling fixes --- src/components/Groups/GroupTile.tsx | 2 +- src/pages/Groups.tsx | 57 +++++++++++++---------------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/components/Groups/GroupTile.tsx b/src/components/Groups/GroupTile.tsx index fe62bf93..bfd5aac3 100644 --- a/src/components/Groups/GroupTile.tsx +++ b/src/components/Groups/GroupTile.tsx @@ -23,7 +23,7 @@ export const GroupTile: FC = props => { const iconSvg = useSVG(props.icon?.svg) return ( - + = () => { return ( - + Gruppi Corsi setSearch(val)} @@ -98,32 +92,31 @@ export const Groups: MainStackScreen<"Groups"> = () => { onFilterChange={filters => setFilters(filters)} filters={filters} /> - - - ( - { - setModalGroup(group.item) - setIsModalShowing(true) - }} - icon={choosePlatformIcon(group.item.platform)} - /> - )} - keyExtractor={item => item.id} - /> - + ( + { + setModalGroup(item) + setIsModalShowing(true) + }} + icon={choosePlatformIcon(item.platform)} + /> + )} + />