From 14e9aee02f638b30ecaba9d6b1f3fadcba76ba5b Mon Sep 17 00:00:00 2001 From: Linus109 Date: Wed, 3 Dec 2025 22:18:35 +0100 Subject: [PATCH] Flatlist -> Flashlist; added WIP MonthSelector. No actual months yet, but it's an infinitly scrolling list --- package-lock.json | 152 +++++++++++++++++++++------------------- package.json | 5 +- src/app/Calender.tsx | 161 ++++++++++++++++++++++++++++++++++++++++--- src/app/Chat.tsx | 10 ++- 4 files changed, 243 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c515bd..4f3d0c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", + "@shopify/flash-list": "^2.0.2", "expo": "~54.0.25", "expo-constants": "~18.0.10", "expo-font": "~14.0.9", @@ -29,8 +30,8 @@ "react-dom": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", - "react-native-reanimated": "~3.17.4", - "react-native-safe-area-context": "5.4.0", + "react-native-reanimated": "~4.1.1", + "react-native-safe-area-context": "5.6.0", "react-native-screens": "~4.16.0", "react-native-web": "~0.21.0", "react-native-worklets": "0.5.1" @@ -1677,9 +1678,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1689,7 +1690,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -3113,17 +3114,17 @@ "license": "MIT" }, "node_modules/@react-navigation/bottom-tabs": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.8.6.tgz", - "integrity": "sha512-0wGtU+I1rCUjvAqKtzD2dwQaTICFf5J233vkg20cLrx8LNQPAgSsbnsDSM6S315OOoVLCIL1dcrNv7ExLBlWfw==", + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.8.8.tgz", + "integrity": "sha512-WS84QCOEdARICYnpu4OSIOeCNsWuWuHi+WO1FUw2rBwKZnrmTT6g+Mv3wL+YqtnRGv5FuLexysVgOFouHrJCpQ==", "license": "MIT", "dependencies": { - "@react-navigation/elements": "^2.8.3", + "@react-navigation/elements": "^2.8.4", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { - "@react-navigation/native": "^7.1.21", + "@react-navigation/native": "^7.1.22", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", @@ -3131,9 +3132,9 @@ } }, "node_modules/@react-navigation/core": { - "version": "7.13.2", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.13.2.tgz", - "integrity": "sha512-A0pFeZlKp+FJob2lVr7otDt3M4rsSJrnAfXWoWR9JVeFtfEXsH/C0s7xtpDCMRUO58kzSBoTF1GYzoMC5DLD4g==", + "version": "7.13.3", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.13.3.tgz", + "integrity": "sha512-jW0YKzHA3aFx0e6G2kzz42PWFhTes0hEJNWKaC5cyii9s+QFostplwdVna+/D8e1vCCdMDx9SfFfmx0mj9R86Q==", "license": "MIT", "dependencies": { "@react-navigation/routers": "^7.5.2", @@ -3150,9 +3151,9 @@ } }, "node_modules/@react-navigation/elements": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.8.3.tgz", - "integrity": "sha512-0c5nSDPP3bUFujgkSVqqMShaAup3XIxNe1KTK9LSmwKgWEneyo6OPIjIdiEwPlZvJZKi7ag5hDjacQLGwO0LGA==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.8.4.tgz", + "integrity": "sha512-AKqJ4kjDLlWBuF2kPFalw1bcQglPqmhFMQnwuPpaD23M5dDbW620JBv89qsSNM3LRIERjvuluv1yguqBmBdTBA==", "license": "MIT", "dependencies": { "color": "^4.2.3", @@ -3161,7 +3162,7 @@ }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", - "@react-navigation/native": "^7.1.21", + "@react-navigation/native": "^7.1.22", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" @@ -3173,12 +3174,12 @@ } }, "node_modules/@react-navigation/native": { - "version": "7.1.21", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.21.tgz", - "integrity": "sha512-mhpAewdivBL01ibErr91FUW9bvKhfAF6Xv/yr6UOJtDhv0jU6iUASUcA3i3T8VJCOB/vxmoke7VDp8M+wBFs/Q==", + "version": "7.1.22", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.22.tgz", + "integrity": "sha512-WuaS4iVFfuHIR6wIYcBA/ZF9/++bbtr0cEO7ohinc3PE+7PZuVJr7KgdrAFay3OI6GmqW0cmuUKZ0BPPDwQ7dw==", "license": "MIT", "dependencies": { - "@react-navigation/core": "^7.13.2", + "@react-navigation/core": "^7.13.3", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", @@ -3190,18 +3191,18 @@ } }, "node_modules/@react-navigation/native-stack": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.8.0.tgz", - "integrity": "sha512-iRqQY+IYB610BJY/335/kdNDhXQ8L9nPUlIT+DSk88FA86+C+4/vek8wcKw8IrfwdorT4m+6TY0v7Qnrt+WLKQ==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.8.2.tgz", + "integrity": "sha512-98Kp9A80/1KM9BdDdxuheaPd2tMoASeuUpKOiD9+ST6Zdgnf6B2OuGlmITH/db1IOb7DIfn6bXVWOC3X2CMePA==", "license": "MIT", "dependencies": { - "@react-navigation/elements": "^2.8.3", + "@react-navigation/elements": "^2.8.4", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { - "@react-navigation/native": "^7.1.21", + "@react-navigation/native": "^7.1.22", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", @@ -3224,6 +3225,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@shopify/flash-list": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-2.2.0.tgz", + "integrity": "sha512-mL61IofcfBNRZ/qazIf+pghGULkcZUQ7EZNldH1JBbIjtDb25ADSiQrt62ZTnRz0H5+bPFEZUmN9+WChHzX8pw==", + "license": "MIT", + "peerDependencies": { + "@babel/runtime": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -4590,9 +4602,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.31", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", - "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", + "version": "2.8.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", + "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -5589,9 +5601,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.259", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", - "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "version": "1.5.262", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", + "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -9752,9 +9764,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", + "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" @@ -10530,9 +10542,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.3.tgz", + "integrity": "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==", "dev": true, "license": "MIT", "peer": true, @@ -11242,44 +11254,37 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.17.5.tgz", - "integrity": "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.5.tgz", + "integrity": "sha512-UA6VUbxwhRjEw2gSNrvhkusUq3upfD3Cv+AnB07V+kC8kpvwRVI+ivwY95ePbWNFkFpP+Y2Sdw1WHpHWEV+P2Q==", "license": "MIT", "dependencies": { - "@babel/plugin-transform-arrow-functions": "^7.0.0-0", - "@babel/plugin-transform-class-properties": "^7.0.0-0", - "@babel/plugin-transform-classes": "^7.0.0-0", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", - "@babel/plugin-transform-optional-chaining": "^7.0.0-0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", - "@babel/plugin-transform-template-literals": "^7.0.0-0", - "@babel/plugin-transform-unicode-regex": "^7.0.0-0", - "@babel/preset-typescript": "^7.16.7", - "convert-source-map": "^2.0.0", - "invariant": "^2.2.4", - "react-native-is-edge-to-edge": "1.1.7" + "react-native-is-edge-to-edge": "^1.2.1", + "semver": "7.7.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", - "react-native": "*" + "react-native": "*", + "react-native-worklets": ">=0.5.0" } }, - "node_modules/react-native-reanimated/node_modules/react-native-is-edge-to-edge": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz", - "integrity": "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" + "node_modules/react-native-reanimated/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/react-native-safe-area-context": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz", - "integrity": "sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.0.tgz", + "integrity": "sha512-tJas3YOdsuCg3kepCTGF3LWZp9onMbb9Agju2xfs2kRX8d/5TMUPmupBpjerk/B7Tv/zeJnk+qp5neA96Y0otQ==", "license": "MIT", "peerDependencies": { "react": "*", @@ -11453,9 +11458,9 @@ } }, "node_modules/react-remove-scroll": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", - "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -14003,15 +14008,18 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index 7b860a0..e3246d9 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", + "@shopify/flash-list": "^2.0.2", "expo": "~54.0.25", "expo-constants": "~18.0.10", "expo-font": "~14.0.9", @@ -32,8 +33,8 @@ "react-dom": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", - "react-native-reanimated": "~3.17.4", - "react-native-safe-area-context": "5.4.0", + "react-native-reanimated": "~4.1.1", + "react-native-safe-area-context": "5.6.0", "react-native-screens": "~4.16.0", "react-native-web": "~0.21.0", "react-native-worklets": "0.5.1" diff --git a/src/app/Calender.tsx b/src/app/Calender.tsx index 6698704..5fe108a 100644 --- a/src/app/Calender.tsx +++ b/src/app/Calender.tsx @@ -1,9 +1,10 @@ -import { Pressable, Text, View } from "react-native"; +import { Animated, Modal, Pressable, Text, View } from "react-native"; import { DAYS, MONTHS, Month } from "../Constants"; import Header from "../components/Header"; -import { useMemo, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import currentTheme from "../Themes"; import BaseBackground from "../components/BaseBackground"; +import { FlashList } from "@shopify/flash-list"; // TODO: month selection dropdown menu @@ -39,6 +40,115 @@ const Calendar = () => { ); }; +type MonthSelectorProps = { + modalVisible: boolean; + onClose: () => void; + position: { top: number; left: number; width: number }; +}; + +const MonthSelector = ({ + modalVisible, + onClose, + position, +}: MonthSelectorProps) => { + const heightAnim = useRef(new Animated.Value(0)).current; + type ItemType = { id: string; text: string }; + const listRef = useRef>>(null); + const [monthSelectorData, setMonthSelectorData] = useState(() => { + const initial = []; + for (let i = 1; i <= 10; i++) { + initial.push({ id: i.toString(), text: `number ${i}` }); + } + return initial; + }); + + const appendToTestData = ( + startIndex: number, + numberOfEntries: number, + appendToStart: boolean, + ) => { + // create new data + const newData = []; + for (let i = 0; i < numberOfEntries; i++) { + const newIndex = startIndex + i + 1; + const newEntry = { + id: newIndex + "", + text: `number ${newIndex}`, + }; + if (appendToStart) { + newData.unshift(newEntry); + } else { + newData.push(newEntry); + } + } + // add new data + if (appendToStart) { + setMonthSelectorData([...newData, ...monthSelectorData]); + } else { + setMonthSelectorData([...monthSelectorData, ...newData]); + } + }; + + useEffect(() => { + if (modalVisible) { + Animated.timing(heightAnim, { + toValue: 200, + duration: 200, + useNativeDriver: false, + }).start(); + } else { + // reset on close + heightAnim.setValue(0); + } + }, [modalVisible]); + + const renderItem = ({ item }: { item: ItemType }) => ( + + + {item.text} + + + ); + + return ( + + + + item.id} + data={monthSelectorData} + initialScrollIndex={5} + onEndReachedThreshold={0.5} + onEndReached={() => + appendToTestData(monthSelectorData.length, 10, false) + } + onStartReachedThreshold={0.5} + onStartReached={() => + appendToTestData(monthSelectorData.length, 10, true) + } + renderItem={renderItem} + /> + + + + ); +}; + type CalendarHeaderProps = { changeMonth: (delta: number) => void; monthIndex: number; @@ -46,15 +156,52 @@ type CalendarHeaderProps = { }; const CalendarHeader = (props: CalendarHeaderProps) => { + const [modalVisible, setModalVisible] = useState(false); + const [dropdownPosition, setDropdownPosition] = useState({ + top: 0, + left: 0, + width: 0, + }); + const containerRef = useRef(null); + const prevMonth = () => props.changeMonth(-1); const nextMonth = () => props.changeMonth(1); + const measureAndOpen = () => { + containerRef.current?.measureInWindow((x, y, width, height) => { + setDropdownPosition({ top: y + height, left: x, width }); + setModalVisible(true); + }); + }; + return (
- - {MONTHS[props.monthIndex]} {props.currentYear} - + + + {MONTHS[props.monthIndex]} {props.currentYear} + + + v + + + setModalVisible(false)} + position={dropdownPosition} + /> "} />
); @@ -150,9 +297,7 @@ const SingleDay = (props: SingleDayProps) => { }} > {props.date.getDate()} diff --git a/src/app/Chat.tsx b/src/app/Chat.tsx index 91fcd63..ffcf803 100644 --- a/src/app/Chat.tsx +++ b/src/app/Chat.tsx @@ -1,8 +1,9 @@ -import { View, Text, FlatList, TextInput } from "react-native"; +import { View, Text, TextInput } from "react-native"; import currentTheme from "../Themes"; import { useState } from "react"; import Header from "../components/Header"; import BaseBackground from "../components/BaseBackground"; +import { FlashList } from "@shopify/flash-list"; // TODO: better shadows for everything // (maybe with extra library because of differences between android and ios) @@ -246,8 +247,7 @@ const Chat = () => { return ( - ( { height={item.height} /> )} + maintainVisibleContentPosition={{ + autoscrollToBottomThreshold: 0.2, + startRenderingFromBottom: true, + }} keyExtractor={(item) => item.id} // extraData={selectedId} might need this later for re-rendering />