-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Laggy view on android #137
Comments
I will check it soon, as I haven’t tested much on Android devices yet |
@Raphitpt Screen.Recording.2024-10-21.at.12.07.43.mp4 |
Hi, thanks for your response. import React, {
useContext,
useEffect,
useState,
useRef,
} from "react";
import {
View,
StyleSheet,
Dimensions,
ActivityIndicator,
Text,
Platform,
} from "react-native";
import { TimelineCalendar, MomentConfig } from "@howljs/calendar-kit";
import { ThemeContext } from "../../utils/themeContext";
import { useNavigation, useIsFocused } from "@react-navigation/native";
import fetchTimetable from "../../api/Timetable/timetable";
import AsyncStorage from "@react-native-async-storage/async-storage";
const getTimetable = async () => {
try {
const response = await fetchTimetable();
if (response) {
return response;
}
} catch (error) {
console.error("Error fetching timetable:", error);
return null;
}
};
const Semaine = () => {
const navigator = useNavigation();
const { colors } = useContext(ThemeContext);
const isFocused = useIsFocused(); // Hook to check if the screen is focused
const calendarRef = useRef(null); // Ref for TimelineCalendar
const [timetable, setTimetable] = useState(null);
const [colorAlternant, setColorAlternant] = useState(colors.grey);
const [colorTimetable, setColorTimetable] = useState(colors.blue_variable);
const getColorAlternant = async () => {
try {
let storedColor = await AsyncStorage.getItem("color_alternant");
if (storedColor) {
console.log(storedColor);
storedColor = storedColor.replace(/['"]+/g, "");
setColorAlternant(storedColor);
}
} catch (error) {
console.error("Failed to fetch color from storage:", error);
}
};
const getColorTimetable = async () => {
try {
let storedColor = await AsyncStorage.getItem("color_timetable");
if (storedColor) {
storedColor = storedColor.replace(/['"]+/g, "");
setColorTimetable(storedColor);
}
} catch (error) {
console.error("Failed to fetch color from storage:", error);
}
};
useEffect(() => {
// Fetch timetable data when the screen is focused
if (isFocused) {
getTimetable().then((response) => {
setTimetable(response);
});
getColorAlternant();
getColorTimetable();
}
}, [isFocused]); // Dependency array includes isFocused
MomentConfig.updateLocale("fr", {
weekdaysShort: "Dim_Lun_Mar_Mer_Jeu_Ven_Sam".split("_"),
});
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
gap: 10,
paddingTop: 10,
},
itemContainer: {
flex: 1,
width: Dimensions.get("window").width,
justifyContent: "start",
paddingHorizontal: "5%",
},
eventTitle: {
fontFamily: "Ubuntu_500Medium",
color: colors.white,
fontSize: 11,
maxWidth: "100%",
alignItems: "start",
},
eventBack: {
paddingVertical: 1,
},
eventContainer: {
height: "100%",
paddingVertical: 10,
paddingHorizontal: 10,
borderRadius: 10,
backgroundColor: colorTimetable,
},
eventTitleAlternance: {
fontFamily: "Ubuntu_500Medium",
includeFontPadding: false,
fontSize: 15,
width: 85,
color: colors.white,
alignItems: "center",
justifyContent: "center",
transform: [{ rotate: "-90deg" }],
},
eventContainerAlternance: {
height: "100%",
width: "100%",
borderRadius: 10,
justifyContent: "center",
alignItems: "center",
backgroundColor: colorAlternant,
},
});
if (!timetable) {
return (
<View style={styles.container}>
<ActivityIndicator
size="large"
color={colors.blue_variable}
style={{ alignItems: "center" }}
/>
</View>
);
}
let height = Dimensions.get("screen").height / 17.7;
height = Platform.OS === "ios" ? height : height + 2;
return (
<View style={styles.container}>
<TimelineCalendar
minDate={"2024-09-02"}
showWeekNumber={true}
start={8}
end={18.5}
viewMode="workWeek"
events={timetable}
showNowIndicator={true}
spaceFromTop={4}
locale="fr"
ref={calendarRef}
allowPinchToZoom
minTimeIntervalHeight={height}
initialTimeIntervalHeight={height}
timeZone="Europe/Paris"
theme={{
backgroundColor: colors.background,
dayNumberContainer: {
backgroundColor: colors.background,
},
colors: {
background: colors.background,
border: colors.grey,
text: colors.black,
},
textStyle: {
fontFamily: "Ubuntu_500Medium",
},
todayNumberContainer: {
backgroundColor: colors.blue_variable,
},
todayNumber: {
color: colors.white,
},
todayName: {
color: colors.blue_variable,
},
dayName: {
color: colors.grey,
fontFamily: "Ubuntu_400Regular",
},
dayNumber: {
color: colors.grey,
fontFamily: "Ubuntu_400Regular",
},
leftBarText: {
fontFamily: "Ubuntu_500Medium",
color: colors.black,
textTransform: "capitalize",
fontSize: 12,
},
hourText: {
fontFamily: "Ubuntu_400Regular",
fontSize: 12,
color: colors.grey,
},
cellBorderColor: colors.grey,
}}
onPressEvent={(event) => {
navigator.navigate("DetailEvent", { event });
}}
renderEventContent={(event) => {
if (event.duration > 10) {
return (
<View style={styles.eventBack}>
<View style={styles.eventContainerAlternance}>
<Text style={styles.eventTitleAlternance} numberOfLines={1}>
{event.title}
</Text>
</View>
</View>
);
}
return (
<View style={styles.eventBack}>
<View style={styles.eventContainer}>
<Text
style={styles.eventTitle}
numberOfLines={4}
ellipsizeMode="tail"
>
{event.title}
</Text>
</View>
</View>
);
}}
/>
</View>
);
};
export default Semaine; Thanks for you |
@Raphitpt Are you still using version 1.x? |
Wait for me, I will help you convert the above code to version 2.x |
@howljs Oh, sorry, I copied my code to the wrong branch, here is v2: import React, {useContext, useEffect, useState, useRef, forwardRef, useImperativeHandle} from "react";
import {
View,
StyleSheet,
ActivityIndicator,
Text,
Dimensions, Platform,
} from "react-native";
import {
CalendarBody,
CalendarContainer,
CalendarHeader,
} from "@howljs/calendar-kit";
import { ThemeContext } from "../../utils/themeContext";
import { useNavigation, useIsFocused } from "@react-navigation/native";
import fetchTimetable from "../../api/Timetable/timetable";
const getTimetable = async () => {
try {
const response = await fetchTimetable();
return response || null;
} catch (error) {
console.error("Error fetching timetable:", error);
return null;
}
};
const INITIAL_DATE = new Date(
new Date().getFullYear(),
new Date().getMonth(),
new Date().getDate()
).toISOString();
const Semaine = forwardRef((props, ref) => {
const { colors } = useContext(ThemeContext);
const calendarRef = useRef(null);
const isFocused = useIsFocused();
const [timetable, setTimetable] = useState([]);
const navigator = useNavigation();
useEffect(() => {
if (isFocused) {
getTimetable().then(data => setTimetable(data));
}
}, [isFocused]);
const localHandleGoToToday = () => {
calendarRef.current?.goToDate({
date: INITIAL_DATE,
animatedDate: true,
animatedHour: true,
});
};
useImperativeHandle(ref, () => ({
goToToday: localHandleGoToToday,
}));
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background,
gap: 10,
paddingTop: 10,
},
itemContainer: {
flex: 1,
width: Dimensions.get("window").width,
justifyContent: "start",
paddingHorizontal: "5%",
},
eventTitle: {
fontFamily: "Ubuntu_500Medium",
color: colors.blue950,
fontSize: 11,
maxWidth: "100%",
alignItems: "start",
},
eventBack: {
paddingVertical: 2,
paddingHorizontal: 2
},
eventContainer: {
height: "100%",
paddingVertical: 10,
paddingHorizontal: 10,
borderRadius: 10,
// backgroundColor: colorTimetable,
backgroundColor: colors.blue200,
// position: "relative",
// overflow: "hidden",
},
// beforeElement: {
// width: 5,
// height: 400,
// backgroundColor: colors.blue500,
// position: "absolute",
// left: 0,
// top: 0,
// },
eventTitleAlternance: {
fontFamily: "Ubuntu_500Medium",
includeFontPadding: false,
fontSize: 15,
width: 85,
color: colors.blue950,
alignItems: "center",
justifyContent: "center",
transform: [{ rotate: "-90deg" }],
},
eventContainerAlternance: {
height: "100%",
width: "100%",
borderRadius: 10,
justifyContent: "center",
alignItems: "center",
backgroundColor: colors.blue200,
},
});
if (!timetable) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color={colors.blue_variable} />
</View>
);
}
const height =
Dimensions.get("screen").height / 17.7 +
(Platform.OS === "android" ? 1 : 0);
const initialesLocale = {
fr: {
weekDayShort: 'Dim._Lun._Mar._Mer._Jeu._Ven._Sam.'.split('_'),
meridiem: { ante: "AM", post: "PM" }
},
}
return (
<View style={styles.container}>
<CalendarContainer
viewMode={"week"}
minDate={"2024-09-02"}
showWeekNumber={true}
ref={calendarRef}
numberOfDays={5}
start={8 * 60}
end={18.5 * 60}
hideWeekDays={[6, 7]}
events={timetable}
isLoading={!timetable}
scrollToNow
scrollByDay={false}
initialDate={INITIAL_DATE}
initialLocales={initialesLocale}
locale={"fr"}
isShowHalfLine={true}
initialTimeIntervalHeight={height}
onPressEvent={(event) => {
const eventWithSerializedDate = {
...event,
start: {
...event.start,
dateTime: event.start.dateTime.toISOString(),
},
end: {
...event.end,
dateTime: event.end.dateTime.toISOString(),
},
};
navigator.navigate("DetailEvent", { event: eventWithSerializedDate });
}}
theme={{
backgroundColor: colors.background,
dayNumberContainer: {
backgroundColor: colors.background,
},
colors: {
background: colors.background,
border: colors.gray_clear,
text: colors.blue950,
},
textStyle: {
fontFamily: "Ubuntu_500Medium",
},
todayNumberContainer: {
backgroundColor: colors.blue700,
},
todayNumber: {
color: colors.white,
},
todayName: {
color: colors.blue700,
},
dayName: {
color: colors.grey,
fontFamily: "Ubuntu_400Regular",
},
dayNumber: {
color: colors.grey,
fontFamily: "Ubuntu_400Regular",
},
leftBarText: {
fontFamily: "Ubuntu_500Medium",
color: colors.blue950,
textTransform: "capitalize",
fontSize: 12,
},
hourTextStyle: {
fontFamily: "Ubuntu_400Regular",
fontSize: 12,
color: colors.grey,
},
// Week number
weekNumber: {
color: colors.blue950,
fontFamily: "Ubuntu_500Medium",
},
weekNumberContainer: {
backgroundColor: colors.blue100,
},
headerContainer: {
borderBottomWidth: 0
}
}}
>
<CalendarHeader />
<CalendarBody
renderEvent={(event) => {
if (event._internal.duration > 600) {
return (
<View style={styles.eventBack}>
<View style={styles.eventContainerAlternance}>
<Text style={styles.eventTitleAlternance} numberOfLines={1}>
{event.title}
</Text>
</View>
</View>
);
}
return (
<View style={styles.eventBack}>
<View style={styles.eventContainer}>
{/* <View style={styles.beforeElement} /> */}
<Text
style={styles.eventTitle}
numberOfLines={4}
ellipsizeMode="tail"
>
{event.title}
</Text>
</View>
</View>
);
}}
/>
</CalendarContainer>
</View>
);
});
export default Semaine; |
Please use Example code: import {
CalendarBody,
CalendarContainer,
CalendarHeader,
CalendarKitHandle,
DeepPartial,
EventItem as EventItemType,
LocaleConfigsProps,
parseDateTime,
ThemeConfigs,
WeekdayNumbers,
} from '@howljs/calendar-kit';
import { useIsFocused } from '@react-navigation/native';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
ActivityIndicator,
Dimensions,
Platform,
StyleSheet,
Text,
View,
} from 'react-native';
/**
* Test data
*/
const randomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
const testData: Record<string, EventItemType[]> = {};
const generateEvents = (date: string) => {
const totalPages = 3;
const totalEvents = 20;
let events: EventItemType[] = [];
for (let index = -1; index < totalPages - 1; index++) {
const startDate = parseDateTime(date)
.plus({
days: index * 7,
})
.setZone('Europe/Paris', { keepLocalTime: true });
const cacheKey = startDate.toFormat('yyyy-MM-dd');
if (testData[cacheKey]) {
events = events.concat(testData[cacheKey]);
} else {
const newEvents = new Array(totalEvents).fill(0).map((_, eventIndex) => {
const randomDateByIndex = startDate.plus({
days: Math.floor(Math.random() * 7),
hours:
Math.floor(Math.random() * (CALENDAR_END - CALENDAR_START)) +
CALENDAR_START,
minutes: Math.round((Math.random() * 60) / 30) * 30,
});
const duration = (Math.floor(Math.random() * 15) + 1) * 15;
const endDate = randomDateByIndex.plus({ minutes: duration });
const durationMinutes = Math.floor(duration / 60000);
const id = `event_${cacheKey}_${eventIndex}`;
const eventItem = {
id,
start: {
dateTime: randomDateByIndex.toString(),
},
end: {
dateTime: endDate.toString(),
},
title: `Event ${index + 1}`,
color: 'transparent',
duration: durationMinutes,
};
return eventItem;
});
testData[cacheKey] = newEvents;
events = events.concat(newEvents);
}
}
return events;
};
/**
* End test data
*/
const initialLocales: Record<string, Partial<LocaleConfigsProps>> = {
fr: {
weekDayShort: 'Dim_Lun_Mar_Mer_Jeu_Ven_Sam'.split('_'),
meridiem: { ante: 'AM', post: 'PM' },
more: 'Plus',
},
};
const CALENDAR_START = 8;
const CALENDAR_END = 18.5;
const SCREEN_WIDTH = Dimensions.get('screen').width;
const SCREEN_HEIGHT = Dimensions.get('screen').height;
const HEADER_HEIGHT = 250;
const HEIGHT =
(SCREEN_HEIGHT - HEADER_HEIGHT) / (CALENDAR_END - CALENDAR_START);
const TIME_INTERVAL_HEIGHT = Platform.OS === 'ios' ? HEIGHT : HEIGHT + 2;
const buildCalendarTheme = (
colors: Record<string, string>
): DeepPartial<ThemeConfigs> => {
return {
colors: {
background: colors.background,
border: colors.grey,
text: colors.black,
onBackground: colors.white,
onSurface: colors.white,
},
dayNumberContainer: {
backgroundColor: colors.background,
},
textStyle: {
fontFamily: 'Ubuntu_500Medium',
},
todayNumberContainer: {
backgroundColor: colors.blue_variable,
},
todayNumber: {
color: colors.white,
},
todayName: {
color: colors.blue_variable,
},
dayName: {
color: colors.grey,
fontFamily: 'Ubuntu_400Regular',
},
dayNumber: {
color: colors.grey,
fontFamily: 'Ubuntu_400Regular',
},
weekNumber: {
fontFamily: 'Ubuntu_500Medium',
color: colors.black,
textTransform: 'capitalize',
fontSize: 12,
},
hourTextStyle: {
fontFamily: 'Ubuntu_400Regular',
fontSize: 12,
color: colors.grey,
},
};
};
const hideWeekDays: WeekdayNumbers[] = [6, 7];
const getColorAlternant = async () => {
try {
// let storedColor = await AsyncStorage.getItem('color_alternant');
// if (storedColor) {
// console.log(storedColor);
// storedColor = storedColor.replace(/['"]+/g, '');
// setColorAlternant(storedColor);
// }
await new Promise((resolve) => setTimeout(resolve, 500));
return randomColor();
} catch (error) {
console.error('Failed to fetch color from storage:', error);
}
};
const getColorTimetable = async () => {
try {
// let storedColor = await AsyncStorage.getItem('color_timetable');
// if (storedColor) {
// storedColor = storedColor.replace(/['"]+/g, '');
// setColorTimetable(storedColor);
// }
await new Promise((resolve) => setTimeout(resolve, 500));
return randomColor();
} catch (error) {
console.error('Failed to fetch color from storage:', error);
// Handle return default color
}
};
const getTimetable = async (date?: string) => {
try {
// const response = await fetchTimetable();
// if (response) {
// return response;
// }
// Simulate a delay
await new Promise((resolve) => setTimeout(resolve, 500));
const response = generateEvents(
date ?? parseDateTime(new Date()).startOf('week').toFormat('yyyy-MM-dd')
);
return response;
} catch (error) {
console.error('Error fetching timetable:', error);
return [];
}
};
const Semaine = () => {
// const navigator = useNavigation();
// const { colors } = useContext(ThemeContext);
const colors = {
background: '#1A1B21',
grey: '#46464C',
blue_variable: '#007AFF',
white: '#FFF',
black: '#000',
};
const isFocused = useIsFocused(); // Hook to check if the screen is focused
const calendarRef = useRef<CalendarKitHandle>(null); // Ref for TimelineCalendar
const [timetable, setTimetable] = useState<EventItemType[] | null>(null);
const [colorAlternant, setColorAlternant] = useState(colors.grey);
const [colorTimetable, setColorTimetable] = useState(colors.blue_variable);
useEffect(() => {
// Fetch timetable data when the screen is focused
if (isFocused) {
getTimetable().then((response) => {
setTimetable(response);
});
getColorAlternant().then((response) => {
if (response) {
setColorAlternant(response);
}
});
getColorTimetable().then((response) => {
if (response) {
setColorTimetable(response);
}
});
}
}, [isFocused]);
const renderEvent = useCallback(
(event: EventItemType) => (
<EventItem
event={event}
titleColor={colors.white}
alternanceBackgroundColor={colorAlternant}
timetableBackgroundColor={colorTimetable}
/>
),
[colorAlternant, colorTimetable, colors.white]
);
const calendarTheme = useMemo(() => buildCalendarTheme(colors), [colors]);
return (
<View style={[styles.container, { backgroundColor: colors.background }]}>
<CalendarContainer
minDate={'2024-09-02'}
showWeekNumber
start={CALENDAR_START * 60}
end={CALENDAR_END * 60}
numberOfDays={5}
scrollByDay={false}
hideWeekDays={hideWeekDays}
events={timetable ?? []}
spaceFromTop={4}
initialLocales={initialLocales}
locale="fr"
ref={calendarRef}
allowPinchToZoom
minTimeIntervalHeight={TIME_INTERVAL_HEIGHT}
initialTimeIntervalHeight={TIME_INTERVAL_HEIGHT}
timeZone="Europe/Paris"
theme={calendarTheme}
isLoading={!timetable}
onDateChanged={(date) => {
getTimetable(date).then((response) => {
setTimetable(response);
});
}}
onPressEvent={(event) => {
console.log(event);
// navigator.navigate('DetailEvent', { event });
}}>
<CalendarHeader />
<CalendarBody renderEvent={renderEvent} />
</CalendarContainer>
</View>
);
};
export default Semaine;
const EventItem = ({
event,
titleColor,
alternanceBackgroundColor,
timetableBackgroundColor,
}: {
event: EventItemType;
titleColor: string;
alternanceBackgroundColor: string;
timetableBackgroundColor: string;
}) => {
if (event.duration > 10) {
return (
<View style={styles.eventBack}>
<View
style={[
styles.eventContainerAlternance,
{ backgroundColor: alternanceBackgroundColor },
]}>
<Text
style={[styles.eventTitleAlternance, { color: titleColor }]}
numberOfLines={1}>
{event.title}
</Text>
</View>
</View>
);
}
return (
<View style={styles.eventBack}>
<View
style={[
styles.eventContainer,
{ backgroundColor: timetableBackgroundColor },
]}>
<Text
style={[styles.eventTitle, { color: titleColor }]}
numberOfLines={4}
ellipsizeMode="tail">
{event.title}
</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
gap: 10,
paddingTop: 10,
},
itemContainer: {
flex: 1,
width: SCREEN_WIDTH,
justifyContent: 'flex-start',
paddingHorizontal: '5%',
},
eventTitle: {
fontFamily: 'Ubuntu_500Medium',
fontSize: 11,
maxWidth: '100%',
alignItems: 'flex-start',
},
eventBack: {
paddingVertical: 1,
},
eventContainer: {
height: '100%',
paddingVertical: 10,
paddingHorizontal: 10,
borderRadius: 10,
overflow: 'hidden',
},
eventTitleAlternance: {
fontFamily: 'Ubuntu_500Medium',
includeFontPadding: false,
fontSize: 15,
width: 85,
alignItems: 'center',
justifyContent: 'center',
transform: [{ rotate: '-90deg' }],
},
eventContainerAlternance: {
height: '100%',
width: '100%',
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
},
loading: {
justifyContent: 'center',
alignItems: 'center',
},
}); |
I can confirm. On android it seems to be very laggy and slow. |
Do you test with useCallback ? and wich version use RN ? |
That’s correct. The performance on Android isn’t as good as on iOS, so you need to ensure you’re always using useCallback with custom components to avoid unnecessary rerenders. And useMemo with array or object values (for example: highlightDates, unavailableHours…) |
Have you tried my code above? Is it still as slow as before? |
Yes I just tested, it's the same. I think it comes from recyclerlistview right? |
I’m not sure what the issue is. Please try running my release version on your device. |
I test your version, and it's work, but there is no event displayed |
Tested it as well, but it is still laggy. Lagging while changing date, also big delay on event creation. Had to long press for 2 seconds for event to start creating. After touch end it takes 2-3 seconds to react |
I see that you’re using React Native directly rather than Expo. Try disabling the debug mode or building your app into an APK file, and then test it again. This is the video from my device. The code I’m using is the code I previously sent to you. Calendar.Kit.Example.Recording.mp4
|
Here is how it works on real device which i tested: 2024-10-22.14.19.58.mp4 |
There is a pretty long delay on event creation. I just long pressed and removed my finger without further touch events and it takes around 2-3 seconds to react. You can also see in the video that swiping horizontally is also lagging. Maybe not super critical but not smooth enough |
The drag-and-drop feature on Android is having issues; you need to tap once more after holding down to activate the drag-and-drop. This build is mainly to check if the swiping is lagging or not. As for the drag-and-drop feature, that’s a bug I’ll need to fix soon :D |
I think it's the events that are bugging, because in your example you generate them as you go, whereas for me they are all present. |
Have you tried with a case that has few events yet? For example, 2-3 events or something like that, does it slow down? If there are too many events, it becomes an issue because I haven't implemented event caching yet. How many events do your usual cases involve on average? |
no i will test this. it is for a high school schedule. so i would say there are over 400-500 events in total |
In fact the problem I think, what creates the laggy and the drop in fps, is that there is a lag in the body and the header, and so that's what causes the problem right? |
Because even without an event there is lag |
The same code runs smoothly on my friend's system but lags on mine. Not sure why. :) |
It works nice on pretty powerful devices like flagships. But it is super laggy on lowend devices. That's why I actually quit this library and created my custom calendar.. |
i am running in Samsung Galaxy S23 i dont think its lowend device |
I'm in the process of checking and improving performance on Android, hopefully it will be stable in the next version soon |
Thank You! |
newArchEnabled=false in gradle.properties solved the problem for me |
Hi, I noticed a strange bug when I test the calendar, when you scroll quickly on the calendar, it makes it bug and also the whole app but only on android. see the video
trim.6BF6C371-A75F-4B6A-B2B6-DBC64EA93BC6.MOV
The text was updated successfully, but these errors were encountered: