Why is there a lag in videos on React Native Expo app? - performance

I am building a video-based app on Expo / React Native, and there is sometimes and sometimes not a lag when the videos are playing.
When there isn't a lag, tapping the screen leads to the next video instantly. When there is a lag, tapping the screen leads to the next video after a few seconds.
I also have a progress bar that indicates the percentage completed of the video (similar to Instagram stories). When there isn't a lag, the progress bar completes right when the video completes. When there is a lag, the progress bar is usually only 75% complete when the video has completed.
Here is a link to my app: https://expo.io/#vaibhavverma9/realtalk
I am trying to figure out why my app goes so slowly sometimes and goes smoothly other times.
I use animated bars for the progress bars, if that helps at all.
function ProgressBarsContainer () {
const width = progressBarWidth();
return (
<View style={homeStyles.progressBarContainer}>
{initAnimatedBars(width)}
</View>
)
}
function initAnimatedBars(width) {
const animatedBars = [];
for (let i = 0; i < currentUserVideoCount; i++){
if(i < videoIndex){
animatedBars.push(
<ProgressBar
progress={1}
width={width}
key={i}
color="#734f96"
unfilledColor="#E6E6FA"
borderColor="#000"
borderRadius={1}
height={3}
/>
)
} else if (i == videoIndex) {
animatedBars.push(
<ProgressBar
key={i}
progress={currentProgress}
width={width}
color="#734f96"
unfilledColor="#E6E6FA"
borderColor="#000"
borderRadius={1}
height={3}
/>
)
} else {
animatedBars.push(
<ProgressBar
progress={0}
width={width}
key={i}
color="#734f96"
unfilledColor="#E6E6FA"
borderColor="#000"
borderRadius={1}
height={3}
/>
)
}
}
return animatedBars;
}
I also render multiple videos at the same time for performance.
import React, { useEffect, useRef, useState } from 'react';
import { Video, Audio } from 'expo-av';
import { View, Animated } from 'react-native';
export default function MultipleVideos(props) {
const renderedGroup = props.limit;
let initialIndex;
let indexLimit;
if (props.first) {
const renderedIndex = Math.round(props.userIndex / renderedGroup);
initialIndex = renderedIndex * renderedGroup;
indexLimit = renderedIndex * renderedGroup + renderedGroup / 2;
} else {
const renderedIndex = Math.floor(props.userIndex / renderedGroup);
initialIndex = renderedIndex * renderedGroup + renderedGroup / 2;
indexLimit = renderedIndex * renderedGroup + renderedGroup;
}
useEffect(() => {
Audio.setAudioModeAsync({
playsInSilentModeIOS: true
});
}, []);
const playingData = {
isMuted: false,
shouldPlay: props.shouldPlay,
playbackObject: props.playbackObject,
_onPlaybackStatusUpdate: props._onPlaybackStatusUpdate,
display: "flex"
}
const nonplayingData = {
isMuted: true,
shouldPlay: false,
playbackObject: null,
_onPlaybackStatusUpdate: null,
display: "none"
}
const renderedVideos = [];
if(props.videoData) {
for(let i = initialIndex; i < indexLimit; i++){
const user = props.videoData.users[i];
if(user){
for (let j = 0; j < user.userVideos.length; j++){
const muxPlaybackId = user.userVideos[j].muxPlaybackId;
const muxPlaybackUrl = 'https://stream.mux.com/' + muxPlaybackId + '.m3u8';
if(props.userIndex == i && props.videoIndex == j){
renderedVideos.push(
<PlayingVideo
key={muxPlaybackId}
playbackObject={playingData.playbackObject}
source={muxPlaybackUrl}
isMuted={playingData.isMuted}
shouldPlay={playingData.shouldPlay}
_onPlaybackStatusUpdate={playingData._onPlaybackStatusUpdate}
display={playingData.display}
/>
)
}
else {
renderedVideos.push(
<PlayingVideo
key={muxPlaybackId}
playbackObject={nonplayingData.playbackObject}
source={muxPlaybackUrl}
isMuted={nonplayingData.isMuted}
shouldPlay={nonplayingData.shouldPlay}
_onPlaybackStatusUpdate={nonplayingData._onPlaybackStatusUpdate}
display={nonplayingData.display}
/>
)
}
}
}
}
return(
<View>
{renderedVideos}
</View>
)
} else {
return null;
}
}
function PlayingVideo({playbackObject, source, isMuted, shouldPlay, _onPlaybackStatusUpdate, display}){
return(
<Video
ref={playbackObject}
source={{uri: source}}
// posterSource={{ uri: posterSource}}
// posterStyle={{ width: '100%', height: '100%', display: display, flex: 1 }}
rate={1.0}
volume={1.0}
isMuted={isMuted}
resizeMode="cover"
usePoster={true}
shouldPlay={shouldPlay}
onPlaybackStatusUpdate={_onPlaybackStatusUpdate}
progressUpdateIntervalMillis={50}
isLooping
style={{ width: '100%', height: '100%', display: display}}
>
</Video>
)
}
Please let me know if there is something that comes to mind that might help improve performance. Thanks :)

Related

React Native Camera is able to capture images but is NOT recording video

I am trying to use react-native-camera package to create an app to take pictures and record videos.
I took the example code and converted it to functional component, since its necessary for my app. I am able to take pictures and store in local cache, but function for recording video is not working, and not showing any output when I console.log it. Do I need to implement stop recording also? I am new to react native and and I didn't find reference for functional implementation anywhere properly. I am confused regarding this. Below is the code, with two buttons, one for picture and one for video
...
import { RNCamera } from "react-native-camera";
const App = () => {
let [flash, setFlash] = useState("off");
let [zoom, setZoom] = useState(0);
let [autoFocus, setAutoFocus] = useState("on");
let [depth, setDepth] = useState(0);
let [type, setType] = useState("back");
let [permission, setPermission] = useState("undetermined");
let [isRecording, setIsRecording] = useState("false");
let [recordingOptions, setRecordingOptions] = useState({
mute: false,
maxDuration: 10,
quality: RNCamera.Constants.VideoQuality["360p"],
});
let cameraRef = useRef(null);
useEffect(() => {
Permissions.check("photo").then((response) => {
// Response is one of: 'authorized', 'denied', 'restricted', or 'undetermined'
setPermission(response);
});
}, []);
const toggleFlash = () => {
setFlash(flashModeOrder[flash]);
};
const zoomOut = () => {
setZoom(zoom - 0.1 < 0 ? 0 : zoom - 0.1);
};
const zoomIn = () => {
setZoom(zoom + 0.1 > 1 ? 1 : zoom + 0.1);
};
const takePicture = async () => {
if (cameraRef) {
const options = { quality: 0.5, base64: true };
const data = await cameraRef.current.takePictureAsync(options);
console.log(data.uri);
}
};
const takeVideo = async () => {
if (cameraRef && !isRecording) {
try {
console.log(recordingOptions);
const recordoptions = {
mute: false,
maxDuration: 10,
quality: RNCamera.Constants.VideoQuality["360p"],
};
const promise = cameraRef.current.recordAsync(recordOptions);
if (promise) {
setIsRecording(true);
const data = await promise;
console.log("takeVideo", data.uri);
}
} catch (e) {
console.error(e);
}
}
};
return (
<View style={styles.container}>
<RNCamera
ref={cameraRef}
style={styles.preview}
type={type}
flashMode={flash}
/>
<View style={{ flex: 0, flexDirection: "row", justifyContent: "center" }}>
<TouchableOpacity onPress={takePicture} style={styles.capture}>
<Text style={{ fontSize: 14 }}> TAKE PICTURE </Text>
</TouchableOpacity>
</View>
<View style={{ flex: 0, flexDirection: "row", justifyContent: "center" }}>
<TouchableOpacity onPress={takeVideo} style={styles.capture}>
<Text style={{ fontSize: 14 }}> TAKE VIDEO </Text>
</TouchableOpacity>
</View>
</View>
);
};
export default App;
Refactor these lines
const promise = cameraRef.current.recordAsync(recordOptions);
if (promise) {
setIsRecording(true);
const data = await promise;
console.log("takeVideo", data.uri);
}
To
setIsRecording(true);
const data = await cameraRef.current.recordAsync(recordOptions);
console.log(data);

Camera rotation and OrbitControls in react-three/fiber

I am trying to create a scene with react-three/fiber and react-three/drei. I want to use a PerspectiveCamera and be able to pan/zoom/rotate with the mouse, but I am also trying to add some buttons that can update the camera position and target in order to have different views (eg. top view, bottom view, side view, etc). I have achieved the latter part and my buttons seem to be working as I update the target x,y,z and position x,y,z using props.
The only problem is that the camera is not responding to the mouse so I only get a fixed camera position and target.
I have included all the scene codes below.
import React,{ useRef, useState, useEffect} from 'react'
import * as THREE from 'three';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { PerspectiveCamera, Icosahedron, OrbitControls } from '#react-three/drei'
import { Canvas, useThree } from "#react-three/fiber";
function VisualizationComponent(props) {
const width = window.innerWidth;
const height = window.innerHeight;
const [controls, setControls] = useState(null);
const [threeState, setThreeState] = useState(null);
const [treeStateInitialized, setThreeStateInitialized] = useState(false);
useEffect(()=>{
if(threeState){
_.forOwn(props.objects, (value, key) => {
threeState.scene.current.add(value);
});
}
return () => {
if(controls) controls.dispose();
}
},[])
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const { objects } = props
const prevState = usePrevious({objects});
const mainCamera = useRef();
useEffect(() => {
if(!threeState) return;
if (
!treeStateInitialized ||
shouldUpdateObjects(props.objects, prevState.objects)
) {
setThreeStateInitialized(true);
garbageCollectOldObjects();
addDefaultObjects();
_.forOwn(props.objects, (value, key) => {
threeState.scene.add(value);
});
}
})
const addDefaultObjects = () => {
if (threeState) {
var hemiLight = new THREE.HemisphereLight( 0xffffbb, 0x080820, 0.2 );
hemiLight.position.set( 0, 0, 1 );
threeState.scene.add( hemiLight );
}
}
const garbageCollectOldObjects = () => {
while (threeState && threeState.scene.children.length) {
const oldObject = threeState.scene.children[0];
oldObject.traverse((child) => {
if (child.geometry) {
child.geometry?.dispose();
if(child.material && Array.isArray(child.material)){
child.material.forEach(d => d.dispose());
}else{
child.material?.dispose();
}
}
});
threeState.scene.remove(oldObject);
}
}
const shouldUpdateObjects = (currentObjects,nextObjects) => {
const result = false;
let currentDigest = 1;
let nextDigest = 1;
_.forIn(currentObjects, (value, key) => {
currentDigest *= value.id;
});
_.forIn(nextObjects, (value, key) => {
nextDigest *= value.id;
});
return currentDigest !== nextDigest;
}
const hasAncestorWhichDisablesThreeJs = (element) => {
if (!element) return false;
let isEditable = false;
for (let i = 0; i < element.classList.length; i++) {
if (element.classList[i] === 'disable-threejs-controls') {
isEditable = true;
}
}
return isEditable ||
hasAncestorWhichDisablesThreeJs(element.parentElement);
}
const initializeScene = (state) => {
setThreeState(state);
addDefaultObjects();
}
return (
<div
id="threejs-controllers-div"
className='threejs-container'
onMouseOver={ (e) => {
const target = e.target;
if (!target || !controls) return true;
if (hasAncestorWhichDisablesThreeJs(target)) {
controls.enabled = false;
} else {
controls.enabled = true;
}
} }
>
<Canvas
className='threejs'
onCreated={ (state) => {initializeScene(state)}}
shadows={true}
gl={
{
'shadowMap.enabled' : true,
'alpha' : true
}
}
>
<PerspectiveCamera
makeDefault
ref={mainCamera}
position-x={props.cameraX || 0}
position-y={props.cameraY || -20}
position-z={props.cameraZ || 20}
up={[0, 0, 1]}
fov={ 15 }
aspect={ width / height }
near={ 1 }
far={ 10000 }
visible={false}
controls={controls}
/>
<OrbitControls
ref={controls}
camera={mainCamera.current}
domElement={document.getElementById("threejs-controllers-div")}
enabled={true}
enablePan={true}
enableZoom={true}
enableRotate={true}
target-x={props.targetX || 0}
target-y={props.targetY || 0}
target-z={props.targetZ || 0}
/>
</Canvas>
<div className='threejs-react-container'>
{ props.children }
</div>
</div>
)
}
VisualizationComponent.propTypes = {
children: PropTypes.node.isRequired,
objects: PropTypes.object.isRequired,
cameraX: PropTypes.number,
cameraY: PropTypes.number,
cameraZ: PropTypes.number,
targetX: PropTypes.number,
targetY: PropTypes.number,
targetZ: PropTypes.number,
};
export default withRouter(VisualizationComponent);

How do I test the useEffect React hook when it includes a 'document.addEventListener' inside it?

Here is my useEffect Call:
const ref = useRef(null);
useEffect(() => {
const clickListener = (e: MouseEvent) => {
if (ref.current.contains(e.target as Node)) return;
closePopout();
}
document.addEventListener('click', clickListener);
return () => {
document.removeEventListener('click', clickListener);
closePopout();
}
}, [ref, closePopout]);
I'm using this to control a popout menu. When you click on the menu icon to bring up the menu it will open it up. When you click anywhere that isn't the popout it closes the popout. Or when the component gets cleaned up it closes the popout as well.
I'm using #testing-library/react-hooks to render the hooks:
https://github.com/testing-library/react-hooks-testing-library
We are also using TypeScript so if there is any TS specific stuff that would be very helpful as well.
Hopefully this is enough info. If not let me know.
EDIT:
I am using two companion hooks. I'm doing quite a bit in it and I was hoping to simplify the question but here is the full code for the hooks. The top hook (useWithPopoutMenu) is called when the PopoutMenu component is rendered. The bottom one is called inside the body of the PopoutMenu component.
// for use when importing the component
export const useWithPopoutMenu = () => {
const [isOpen, setIsOpenTo] = useState(false);
const [h, setHorizontal] = useState(0);
const [v, setVertical] = useState(0);
const close = useCallback(() => setIsOpenTo(false), []);
return {
isOpen,
menuEvent: {h, v, isOpen, close} as PopoutMenuEvent,
open: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
setIsOpenTo(true);
setHorizontal(e.clientX);
setVertical(e.clientY);
},
close
};
}
type UsePopoutMenuArgs = {
menuEvent: PopoutMenuEvent
padding: number
tickPosition: number
horizontalFix: number | null
verticalFix: number | null
hPosition: number
vPosition: number
borderColor: string
}
// for use inside the component its self
export const usePopoutMenu = ({
menuEvent,
padding,
tickPosition,
horizontalFix,
verticalFix,
hPosition,
vPosition,
borderColor
}: UsePopoutMenuArgs) => {
const ref = useRef() as MutableRefObject<HTMLDivElement>;
useEffect(() => {
const handleClick = (e: MouseEvent) => {
if (ref.current.contains(e.target as Node)) return;
menuEvent.close();
}
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
menuEvent.close();
}
}, [menuEvent.close, ref]);
const menuContainerStyle = useMemo(() => {
const left = horizontalFix || menuEvent.h;
const top = verticalFix || menuEvent.v;
return {
padding,
left,
top,
marginLeft: hPosition,
marginTop: vPosition,
border: `1px solid ${borderColor}`
}
}, [
padding,
horizontalFix,
verticalFix,
menuEvent,
hPosition,
vPosition,
borderColor
]);
const backgroundArrowStyle = useMemo(() => {
return {
marginLeft: `-${padding + 6}px`,
marginTop: 4 - padding + tickPosition,
}
},[padding, tickPosition]);
const foregroundArrowStyle = useMemo(() => {
return {
marginLeft: `-${padding + 5}px`,
marginTop: 4 - padding + tickPosition,
}
},[padding, tickPosition]);
return {
ref,
menuContainerStyle,
backgroundArrowStyle,
foregroundArrowStyle
}
}
Here is the component:
type PopoutMenuProps = {
children: React.ReactChild | React.ReactChild[] // normal props.children
menuEvent: PopoutMenuEvent
padding?: number // padding that goes around the
tickPosition?: number // how far down the tick is from the top
borderColor?: string // border color
bgColor?: string // background color
horizontalFix?: number | null
verticalFix?: number | null
vPosition?: number
hPosition?: number
}
const Container = styled.div`
position: fixed;
display: block;
padding: 0;
border-radius: 4px;
background-color: white;
z-index: 10;
`;
const Arrow = styled.div`
position: absolute;
`;
const PopoutMenu = ({
children,
menuEvent,
padding = 16,
tickPosition = 10,
borderColor = Style.color.gray.medium,
bgColor = Style.color.white,
vPosition = -20,
hPosition = 10,
horizontalFix = null,
verticalFix = null
}: PopoutMenuProps) => {
const binding = usePopoutMenu({
menuEvent,
padding,
tickPosition,
vPosition,
hPosition,
horizontalFix,
verticalFix,
borderColor
});
return (
<Container ref={binding.ref} style={binding.menuContainerStyle}>
<Arrow style={binding.backgroundArrowStyle}>
<Left color={borderColor} />
</Arrow>
<Arrow style={binding.foregroundArrowStyle}>
<Left color={bgColor} />
</Arrow>
{children}
</Container>
);
}
export default PopoutMenu;
Usage is something like this:
const Parent () => {
const popoutMenu = useWithPopoutMenu();
return (
...
<ComponentThatOpensThePopout onClick={popoutMenu.open}>...
...
{popoutMenu.isOpen && <PopoutMenu menuEvent={menuEvent}>PopoutMenu Content</PopoutMenu>}
);
}
Do you need to test the hook in isolation?
Testing the component that consumes the hook would be much easier and it would also be a more realistic test, pseudo code below:
render(<PopoverConsumer />);
userEvent.click(screen.getByRole('button', { name: 'Menu' });
expect(screen.getByRole('dialog')).toBeInTheDocument();
userEvent.click(screen.getByText('somewhere outside');
expect(screen.getByRole('dialog')).not.toBeInTheDocument();

Is there a way to "simulate" pressing the refresh button to refresh a List?

Is there a way to "simulate" pressing the refresh button to refresh a List? I have a list that I want it to update every 10 seconds. Is there a way to "press" the refresh button every 10 seconds?
My list name is ActiveJobsList.
This is what I have at the moment:
export function autoRefresh() {
var counter = 10;
var id;
if(location.href.includes("activejobs")) {
id = setInterval(function() {
counter--;
if(counter < 0 && location.href.includes("activejobs")) {
// What should go here?
clearInterval(id);
}
}, 1000);
}
else if (!location.href.includes("activejobs"))
{
clearInterval(id);
}
}
Okay so I managed to figure it out.
I used
var x = document.getElementsByTagName('button');
console.log(x);
To figure out which button corresponded to the refresh button for admin-on-rest. In my case, it was the second button in the array.
Here is my updated code.
export function autoRefresh() {
var counter = 30;
var id;
if(location.href.includes("activejobs")) {
id = setInterval(function() {
counter--;
if(counter < 0 && location.href.includes("activejobs")) {
document.getElementsByTagName('button')[1].click();
counter = 30;
}
}, 1000);
}
else if (!location.href.includes("activejobs"))
{
counter = 30;
}
}
You could leverage React.Component.shouldComponentUpdate(), on your ActiveJobsList
https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate
I have created a component that provides a drop down menu for auto update setting. Here is the code and below it is an example of how to invoke it.
class AutoUpdt extends Component {
static propTypes = { setAutoUpdate : PropTypes.func
, interval : PropTypes.array
, iconColor : PropTypes.any
}
static defaultProps = { interval : [10,30,60,120,300,600,900,1800,3600]
, iconColor : '#00bcd4'
}
constructor(props) { super(props)
this.state = { open : false
, needrefresh : false
, intervaltime : false
}
}
handleTouchTap(event) { event.preventDefault()
this.setState({ open: true, anchorEl: event.currentTarget, })
}
handleRequestClose() { this.setState({ open: false, })
}
handleShow(event) { let intervaltime = event.currentTarget.innerText.toLowerCase().split(' (secs)')[0].trim()
let newintevaltime = (this.state.intervaltime === false) ? intervaltime : false
this.props.setAutoUpdate( newintevaltime )
this.setState({ open: false, needrefresh: true, intervaltime : newintevaltime})
}
render() {
return ( <div style={{ display: 'inline-block' }}>
<IconButton tooltip="Set Auto Update"
iconStyle={{ color: this.props.iconColor }}
onTouchTap={this.handleTouchTap.bind(this)} ><AutoIcon /></IconButton>
<Popover open={this.state.open}
anchorEl={this.state.anchorEl}
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'left', vertical: 'top' }}
onRequestClose={this.handleRequestClose.bind(this)} >
<Menu>
{this.props.interval.map( el =>
<ListItem style={( el.toString() !== this.state.intervaltime )
? { color:'#00bcd4' , margin: 0, padding : 2 }
: { color: '#f48fb1' , margin: 0, padding : 2 } }
data-key={ el.toString()}
key={el.toString()}
primaryText={ el.toString() + ' (secs)'}
onTouchTap={this.handleShow.bind(this)} /> )}
</Menu >
</Popover>
</div>)
}
}
// It is invoked by using these two functions in another component
checkMounted(){ this.props.checkMounted && this.props.checkMounted() && this.updateData()
}
setAutoUpdate = ( intervaltimer, checkMounted) => {
const this_ = this
this.state.intervaltimer && clearInterval(this.state.intervaltimer)
this.setState( intervaltimer ? { intervaltimer : setInterval( this_.checkMounted.bind(this_), +intervaltimer * 1000) } : { intervaltimer : false} )
}
// And using this line in the render function of the calling component
{ this.props.hasAuto && <AutoUpdt setAutoUpdate={this.setAutoUpdate} icon={<NavigationRefresh />} /> }

Referring to a child view from the parent view on click or tap event in TI

In appcelerator TI code - I have a month scrollable view to which I have added week views and to which I have added days as views.
The expectation on click event of a day I should be able to retrieve properties of the date. However on singletap event I am getting reference to the week view and not able to get the child view "days view. how can I get a reference to the days view on single tap to click event?
Code -Widget.js
var args = arguments[0] || {};
var Moment = require('alloy/moment');
var ROWS = 6;
var COLUMNS = 7;
_.defaults(args, {
// Data
current_date: Moment(),
active_dates: [],
min_date: Moment().subtract(6, 'months'),
max_date: Moment().add(6, 'months'),
// Style
backgroundColor: 'transparent',
dateBackgroundColor: 'transparent',
todayBackgroundColor: '#af80',
dateTextColor: '#fff',
todayTextColor: '#000',
activePinColor: '#f39911',
inactivePinColor: 'transparent',
selectedBackgroundColor: '#60f39911',
fontFamily: '',
// Behaviour
allowInactiveSelection: false,
fillMonth: false,
enablePastDays: false
});
var active_dates = args.active_dates ? getMomentDates(args.active_dates) : [];
var current_page = 0;
/////////////
// Methods //
/////////////
function refreshArrows() {
$.leftBtn.opacity = current_page <= 0 ? 0.4 : 1;
$.rightBtn.opacity = current_page >= $.monthScroll.views.length - 1 ? 0.4 : 1;
}
function getDayLabels() {
var days = Moment.weekdaysMin();
days.push(days.shift()); // Moment week has Sunday at index 0
_.each(days, function(day, i) {
var width = Math.floor($.calendar.rect.width / COLUMNS);
var $label = $.UI.create('Label', {
classes: ['dayLabel'],
width: width,
text: day.charAt(0),
left: i * width,
font: {
fontFamily: args.fontFamily
}
});
$.dayLabels.add($label);
});
}
function getMomentDates(dates) {
return _.map(dates, function(date) {
return Moment(date);
});
}
function isInMomentsList(date, dates) {
return _.find(dates, function(day) {
return date.isSame(day, 'day');
});
}
function getDayContainer(number) {
var $this = $.UI.create('View', {
classes: ['day'],
width: Math.floor($.monthScroll.rect.width / COLUMNS),
height: Math.floor($.monthScroll.rect.height / ROWS),
backgroundColor: args.dateBackgroundColor,
opacity: 1,
date: null,
active: null,
});
$this.add($.UI.create('Label', {
classes: ['dayNumber'],
color: '#fff',
text: number,
font: {
fontFamily: args.fontFamily
}
}));
$this.add($.UI.create('View', {
classes: ['dayDot'],
backgroundColor: 'transparent'
}));
return $this;
}
function setItemDate($item, date) {
$item.date = date;
$item.children[0].text = date.date();
}
function setItemActive($item, active) {
$item.active = active;
$item.children[1].backgroundColor = active ? args.activePinColor : args.inactivePinColor;
}
function setItemToday($item, is_today) {
$item.backgroundColor = is_today ? args.todayBackgroundColor : args.dateBackgroundColor;
$item.children[0].color = is_today ? args.todayTextColor : args.dateTextColor;
}
function setItemCurrent($item, current) {
$item.opacity = current ? 1 : 0.5;
}
function getMonthView(month, year) {
var month_rows = [];
var start_date = Moment().month(month).year(year).startOf('month').startOf('week');
var end_date = Moment().month(month).year(year).endOf('month').endOf('week');
// Month skeleton
var $month_view = $.UI.create('View', {
classes: ['month'],
month: month,
year: year,
backgroundColor: args.backgroundColor,
ready: false
});
// Month activity indicator
var $loader = Ti.UI.createActivityIndicator({
style: OS_IOS ? Ti.UI.iPhone.ActivityIndicatorStyle.BIG : Ti.UI.ActivityIndicatorStyle.BIG,
center: {
x: '50%',
y: '50%'
}
});
$month_view.add($loader);
$month_view.__loader = $loader;
$loader.show();
return $month_view;
}
function buildMonth($month_view, dates) {
if (!$month_view || $month_view.ready) return;
var start_date = Moment().month($month_view.month).year($month_view.year).startOf('month').startOf('week');
var end_date = Moment().month($month_view.month).year($month_view.year).endOf('month').endOf('week');
var $days_container = Ti.UI.createView({
height: Ti.UI.FILL,
width: Ti.UI.FILL
});
// Separators
for (var i = 0; i < ROWS; i++) {
$days_container.add($.UI.create('View', {
classes: ['hr'],
top: (i+1) * Math.floor($.monthScroll.rect.height / ROWS)
}));
}
// Add day containers
for (var d = 0; d < ROWS*COLUMNS; d++) {
var curday = Moment(start_date).add(d, 'days');
// If fillMonth is disabled, add only this month's days
if (curday.month() === $month_view.month || args.fillMonth == true) {
var $curview = getDayContainer(curday.date());
var row = Math.floor(d/COLUMNS);
var col = d % COLUMNS;
setItemDate($curview, curday);
setItemActive($curview, isInMomentsList(curday, dates));
setItemCurrent($curview, !curday.isBefore(Moment(), 'day') || (args.enablePastDays == true && (curday.month() === $month_view.month)));
setItemToday($curview, curday.isSame(Moment(), 'day'));
$curview.top = row * ($curview.height);
$curview.left = col * ($curview.width);
$days_container.add($curview);
}
}
$month_view.add($days_container);
$month_view.ready = true;
$month_view.__loader.hide();
}
function buildCalendar() {
$.main.removeEventListener('postlayout', buildCalendar);
// Add top labels
getDayLabels();
// Create the calendar views
var curmonth_index = -1; var i = 0;
for (var m = Moment(args.min_date); m.diff(Moment(args.max_date)) <= 0; m.add(1, 'months')) {
if (m.isSame(Moment(), 'month')) curmonth_index = i;
var monthview = getMonthView(m.month(), m.year());
$.monthScroll.addView(monthview);
i++;
}
$.monthScroll.currentPage = current_page = curmonth_index > 0 ? curmonth_index : 0;
refreshCalendarMonth(current_page);
refreshArrows();
}
function refreshCalendarMonth(m) {
var month_date = Moment().month($.monthScroll.views[m].month).year($.monthScroll.views[m].year);
$.monthName.text = month_date.format('MMMM').toUpperCase();
$.monthYear.text = month_date.format('YYYY');
buildMonth($.monthScroll.views[m], args.active_dates);
if (current_page - 1 > -1) buildMonth($.monthScroll.views[m-1], args.active_dates);
if (current_page + 1 < 12) buildMonth($.monthScroll.views[m+1], args.active_dates);
}
///////////////
// Listeners //
///////////////
$.main.addEventListener('postlayout', buildCalendar);
$.monthScroll.addEventListener('scroll', function(e) {
if (e.currentPage === current_page) return;
current_page = e.currentPage;
refreshArrows();
refreshCalendarMonth(current_page);
});
$.monthScroll.addEventListener('click', function(e) {
if (!e.source.date || (!e.source.active && !args.allowInactiveSelection) || (args.enablePastDays == false && e.source.date.isBefore(Moment(), 'day'))) return;
e.source.animate({ backgroundColor: args.selectedBackgroundColor, duration: 150, autoreverse: true });
$.trigger('selected', {
date: e.source.date,
active: e.source.active
});
});
$.leftBtn.addEventListener('click', function() {
$.monthScroll.movePrevious();
});
$.rightBtn.addEventListener('click', function() {
$.monthScroll.moveNext();
});
//////////
// Init //
//////////
$.monthName.font = {
fontFamily: args.fontFamily,
fontWeight: 'bold'
};
$.monthYear.font = {
fontFamily: args.fontFamily,
fontWeight: 'light'
};
Widget.xml
<Alloy>
<Window backgroundColor="#110ee1" class="container" exitOnClose="true" id="widget" title="DailyRead" top="0">
<View id="main">
<View class="bar" id="header">
<View class="hr" top="0"/>
<View class="ctrlBtn" id="leftBtn">
<ImageView id="leftArrow"/>
</View>
<View class="headerText">
<Label id="monthName"/>
<Label id="monthYear"/>
</View>
<View class="ctrlBtn" id="rightBtn">
<ImageView id="rightArrow"/>
</View>
<View bottom="0" class="hr"/>
</View>
<View class="sp1/2"/>
<View id="calendar">
<View id="dayLabels"/>
<View backgroundColor="#fff" class="hr" height="2"/>
<ScrollableView id="monthScroll"/>
</View>
</View>
</Window>
</Alloy>
As far as I understood the question I would add more information to the day view. E.g. if you create everything through a loop just add month, weeks to the day view as a property. Then set all views but the day view to touchEnabled:false and just add the click event to the day view. Then you can read event.source.day/event.source.week/event.source.month inside the click-event.
If that doesn't help please add some example code to your question.

Resources