init
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2025-09-02 14:49:16 +08:00
commit 38ba663466
2885 changed files with 391107 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
/* eslint-disable react/jsx-no-bind */
import React from 'react';
import { Text, TextStyle, View, ViewStyle } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { IconCloseLarge } from '../../../base/icons/svg';
import { getLocalParticipant } from '../../../base/participants/functions';
import Button from '../../../base/ui/components/native/Button';
import IconButton from '../../../base/ui/components/native/IconButton';
import Switch from '../../../base/ui/components/native/Switch';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import { editPoll, removePoll } from '../../actions';
import { isSubmitAnswerDisabled } from '../../functions';
import AbstractPollAnswer, { AbstractProps } from '../AbstractPollAnswer';
import { dialogStyles, pollsStyles } from './styles';
const PollAnswer = (props: AbstractProps) => {
const {
checkBoxStates,
poll,
pollId,
sendPoll,
setCheckbox,
setCreateMode,
skipAnswer,
skipChangeVote,
submitAnswer,
t
} = props;
const { changingVote, saved: pollSaved } = poll;
const dispatch = useDispatch();
const localParticipant = useSelector(getLocalParticipant);
const { PRIMARY, SECONDARY } = BUTTON_TYPES;
return (
<>
<View style = { dialogStyles.headerContainer as ViewStyle }>
<View>
<Text style = { dialogStyles.questionText as TextStyle } >{ poll.question }</Text>
<Text style = { dialogStyles.questionOwnerText as TextStyle } >{
t('polls.by', { name: localParticipant?.name })
}
</Text>
</View>
{
pollSaved && <IconButton
onPress = { () => dispatch(removePoll(pollId, poll)) }
src = { IconCloseLarge } />
}
</View>
<View
id = 'answer-content'
style = { pollsStyles.answerContent as ViewStyle }>
{
poll.answers.map((answer, index: number) => (
<View
key = { index }
style = { pollsStyles.switchRow as ViewStyle } >
<Switch
checked = { checkBoxStates[index] }
disabled = { poll.saved }
id = 'answer-switch'
onChange = { state => setCheckbox(index, state) } />
<Text style = { pollsStyles.switchLabel as TextStyle }>
{ answer.name }
</Text>
</View>
))
}
</View>
{
pollSaved
? <View style = { pollsStyles.buttonRow as ViewStyle }>
<Button
accessibilityLabel = 'polls.answer.edit'
id = { t('polls.answer.edit') }
labelKey = 'polls.answer.edit'
onClick = { () => {
setCreateMode(true);
dispatch(editPoll(pollId, true));
} }
style = { pollsStyles.pollCreateButton }
type = { SECONDARY } />
<Button
accessibilityLabel = 'polls.answer.send'
id = { t('polls.answer.send') }
labelKey = 'polls.answer.send'
onClick = { sendPoll }
style = { pollsStyles.pollCreateButton }
type = { PRIMARY } />
</View>
: <View style = { pollsStyles.buttonRow as ViewStyle }>
<Button
accessibilityLabel = 'polls.answer.skip'
id = { t('polls.answer.skip') }
labelKey = 'polls.answer.skip'
onClick = { changingVote ? skipChangeVote : skipAnswer }
style = { pollsStyles.pollCreateButton }
type = { SECONDARY } />
<Button
accessibilityLabel = 'polls.answer.submit'
disabled = { isSubmitAnswerDisabled(checkBoxStates) }
id = { t('polls.answer.submit') }
labelKey = 'polls.answer.submit'
onClick = { submitAnswer }
style = { pollsStyles.pollCreateButton }
type = { PRIMARY } />
</View>
}
</>
);
};
/*
* We apply AbstractPollAnswer to fill in the AbstractProps common
* to both the web and native implementations.
*/
// eslint-disable-next-line new-cap
export default AbstractPollAnswer(PollAnswer);

View File

@@ -0,0 +1,220 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { FlatList, Platform, TextInput, View, ViewStyle } from 'react-native';
import { Divider } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import Button from '../../../base/ui/components/native/Button';
import Input from '../../../base/ui/components/native/Input';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import { editPoll } from '../../actions';
import { ANSWERS_LIMIT, CHAR_LIMIT } from '../../constants';
import AbstractPollCreate, { AbstractProps } from '../AbstractPollCreate';
import { dialogStyles, pollsStyles } from './styles';
const PollCreate = (props: AbstractProps) => {
const {
addAnswer,
answers,
editingPoll,
editingPollId,
isSubmitDisabled,
onSubmit,
question,
removeAnswer,
setAnswer,
setCreateMode,
setQuestion,
t
} = props;
const answerListRef = useRef<FlatList>(null);
const dispatch = useDispatch();
/*
* This ref stores the Array of answer input fields, allowing us to focus on them.
* This array is maintained by registerFieldRef and the useEffect below.
*/
const answerInputs = useRef<TextInput[]>([]);
const registerFieldRef = useCallback((i, input) => {
if (input === null) {
return;
}
answerInputs.current[i] = input;
}, [ answerInputs ]);
useEffect(() => {
answerInputs.current = answerInputs.current.slice(0, answers.length);
setTimeout(() => {
answerListRef.current?.scrollToEnd({ animated: true });
}, 1000);
}, [ answers ]);
/*
* This state allows us to requestFocus asynchronously, without having to worry
* about whether a newly created input field has been rendered yet or not.
*/
const [ lastFocus, requestFocus ] = useState<number | null>(null);
const { PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
useEffect(() => {
if (lastFocus === null) {
return;
}
const input = answerInputs.current[lastFocus];
if (input === undefined) {
return;
}
input.focus();
}, [ answerInputs, lastFocus ]);
const onQuestionKeyDown = useCallback(() => {
answerInputs.current[0].focus();
}, []);
// Called on keypress in answer fields
const onAnswerKeyDown = useCallback((index: number, ev) => {
const { key } = ev.nativeEvent;
const currentText = answers[index].name;
if (key === 'Backspace' && currentText === '' && answers.length > 1) {
removeAnswer(index);
requestFocus(index > 0 ? index - 1 : 0);
}
}, [ answers, addAnswer, removeAnswer, requestFocus ]);
/* eslint-disable react/no-multi-comp */
const createRemoveOptionButton = (onPress: () => void) => (
<Button
id = { t('polls.create.removeOption') }
labelKey = 'polls.create.removeOption'
labelStyle = { dialogStyles.optionRemoveButtonText }
onClick = { onPress }
style = { dialogStyles.optionRemoveButton }
type = { TERTIARY } />
);
const pollCreateButtonsContainerStyles = Platform.OS === 'android'
? pollsStyles.pollCreateButtonsContainerAndroid : pollsStyles.pollCreateButtonsContainerIos;
/* eslint-disable react/jsx-no-bind */
const renderListItem = ({ index }: { index: number; }) => {
const isIdenticalAnswer
= answers.slice(0, index).length === 0 ? false : answers.slice(0, index).some(prevAnswer =>
prevAnswer.name === answers[index].name
&& prevAnswer.name !== '' && answers[index].name !== '');
return (
<View
id = 'option-container'
style = { dialogStyles.optionContainer as ViewStyle }>
<Input
blurOnSubmit = { false }
bottomLabel = { (
isIdenticalAnswer ? t('polls.errors.notUniqueOption', { index: index + 1 }) : '') }
error = { isIdenticalAnswer }
id = { `polls-answer-input-${index}` }
label = { t('polls.create.pollOption', { index: index + 1 }) }
maxLength = { CHAR_LIMIT }
onChange = { name => setAnswer(index,
{
name,
voters: []
}) }
onKeyPress = { ev => onAnswerKeyDown(index, ev) }
placeholder = { t('polls.create.answerPlaceholder', { index: index + 1 }) }
// This is set to help the touch event not be propagated to any subviews.
pointerEvents = { 'auto' }
ref = { input => registerFieldRef(index, input) }
value = { answers[index].name } />
{
answers.length > 2
&& createRemoveOptionButton(() => removeAnswer(index))
}
</View>
);
};
const renderListHeaderComponent = useMemo(() => (
<>
<Input
autoFocus = { true }
blurOnSubmit = { false }
customStyles = {{ container: dialogStyles.customContainer }}
id = { t('polls.create.pollQuestion') }
label = { t('polls.create.pollQuestion') }
maxLength = { CHAR_LIMIT }
onChange = { setQuestion }
onSubmitEditing = { onQuestionKeyDown }
placeholder = { t('polls.create.questionPlaceholder') }
// This is set to help the touch event not be propagated to any subviews.
pointerEvents = { 'auto' }
value = { question } />
<Divider style = { pollsStyles.fieldSeparator as ViewStyle } />
</>
), [ question ]);
return (
<View style = { pollsStyles.pollCreateContainer as ViewStyle }>
<View style = { pollsStyles.pollCreateSubContainer as ViewStyle }>
<FlatList
ListHeaderComponent = { renderListHeaderComponent }
data = { answers }
extraData = { answers }
keyExtractor = { (item, index) => index.toString() }
ref = { answerListRef }
renderItem = { renderListItem } />
<View style = { pollCreateButtonsContainerStyles as ViewStyle }>
<Button
accessibilityLabel = 'polls.create.addOption'
disabled = { answers.length >= ANSWERS_LIMIT }
id = { t('polls.create.addOption') }
labelKey = 'polls.create.addOption'
onClick = { () => {
// adding and answer
addAnswer();
requestFocus(answers.length);
} }
style = { pollsStyles.pollCreateAddButton }
type = { SECONDARY } />
<View
style = { pollsStyles.buttonRow as ViewStyle }>
<Button
accessibilityLabel = 'polls.create.cancel'
id = { t('polls.create.cancel') }
labelKey = 'polls.create.cancel'
onClick = { () => {
setCreateMode(false);
editingPollId
&& editingPoll?.editing
&& dispatch(editPoll(editingPollId, false));
} }
style = { pollsStyles.pollCreateButton }
type = { SECONDARY } />
<Button
accessibilityLabel = 'polls.create.save'
disabled = { isSubmitDisabled }
id = { t('polls.create.save') }
labelKey = 'polls.create.save'
onClick = { onSubmit }
style = { pollsStyles.pollCreateButton }
type = { PRIMARY } />
</View>
</View>
</View>
</View>
);
};
/*
* We apply AbstractPollCreate to fill in the AbstractProps common
* to both the web and native implementations.
*/
// eslint-disable-next-line new-cap
export default AbstractPollCreate(PollCreate);

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { View, ViewStyle } from 'react-native';
import { useSelector } from 'react-redux';
import { shouldShowResults } from '../../functions';
import PollAnswer from './PollAnswer';
import PollResults from './PollResults';
import { pollsStyles } from './styles';
interface IProps {
/**
* Id of the poll.
*/
pollId: string;
/**
* Create mode control.
*/
setCreateMode: (mode: boolean) => void;
}
const PollItem = ({ pollId, setCreateMode }: IProps) => {
const showResults = useSelector(shouldShowResults(pollId));
return (
<View
id = 'poll-item-container'
style = { pollsStyles.pollItemContainer as ViewStyle }>
{ showResults
? <PollResults
key = { pollId }
pollId = { pollId } />
: <PollAnswer
pollId = { pollId }
setCreateMode = { setCreateMode } />
}
</View>
);
};
export default PollItem;

View File

@@ -0,0 +1,149 @@
import React, { useCallback } from 'react';
import { FlatList, Text, TextStyle, View, ViewStyle } from 'react-native';
import Button from '../../../base/ui/components/native/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import {
default as AbstractPollResults,
type AbstractProps,
type AnswerInfo
} from '../AbstractPollResults';
import { dialogStyles, pollsStyles, resultsStyles } from './styles';
/**
* Component that renders the poll results.
*
* @param {Props} props - The passed props.
* @returns {React.Node}
*/
const PollResults = (props: AbstractProps) => {
const {
answers,
changeVote,
creatorName,
haveVoted,
question,
showDetails,
t,
toggleIsDetailed
} = props;
/**
* Render a header summing up answer information.
*
* @param {string} answer - The name of the answer.
* @param {number} percentage - The percentage of voters.
* @param {number} nbVotes - The number of collected votes.
* @returns {React.Node}
*/
const renderHeader = (answer: string, percentage: number, nbVotes: number) => (
<View style = { resultsStyles.answerHeader as ViewStyle }>
<Text style = { resultsStyles.answer as TextStyle }>{ answer }</Text>
<View>
<Text style = { resultsStyles.answer as TextStyle }>({nbVotes}) {percentage}%</Text>
</View>
</View>
);
/**
* Render voters of and answer.
*
* @param {AnswerInfo} answer - The answer info.
* @returns {React.Node}
*/
const renderRow = useCallback((answer: AnswerInfo) => {
const { name, percentage, voters, voterCount } = answer;
if (showDetails) {
return (
<View style = { resultsStyles.answerContainer as ViewStyle }>
{ renderHeader(name, percentage, voterCount) }
<View style = { resultsStyles.barContainer as ViewStyle }>
<View style = { [ resultsStyles.bar, { width: `${percentage}%` } ] as ViewStyle[] } />
</View>
{ voters && voterCount > 0
&& <View style = { resultsStyles.voters as ViewStyle }>
{/* @ts-ignore */}
{voters.map(({ id, name: voterName }) =>
(<Text
key = { id }
style = { resultsStyles.voter as TextStyle }>
{ voterName }
</Text>)
)}
</View>}
</View>
);
}
// else, we display a simple list
// We add a progress bar by creating an empty view of width equal to percentage.
return (
<View style = { resultsStyles.answerContainer as ViewStyle }>
{ renderHeader(answer.name, percentage, voterCount) }
<View style = { resultsStyles.barContainer as ViewStyle }>
<View style = { [ resultsStyles.bar, { width: `${percentage}%` } ] as ViewStyle[] } />
</View>
</View>
);
}, [ showDetails ]);
/* eslint-disable react/jsx-no-bind */
return (
<View>
<Text
id = 'question-text'
style = { dialogStyles.questionText as TextStyle } >{ question }</Text>
<Text
id = 'poll-owner-text'
style = { dialogStyles.questionOwnerText as TextStyle } >
{ t('polls.by', { name: creatorName }) }
</Text>
<FlatList
data = { answers }
keyExtractor = { (item, index) => index.toString() }
renderItem = { answer => renderRow(answer.item) } />
<View style = { pollsStyles.bottomLinks as ViewStyle }>
<Button
id = {
showDetails
? t('polls.results.hideDetailedResults')
: t('polls.results.showDetailedResults')
}
labelKey = {
showDetails
? 'polls.results.hideDetailedResults'
: 'polls.results.showDetailedResults'
}
labelStyle = { pollsStyles.toggleText }
onClick = { toggleIsDetailed }
type = { BUTTON_TYPES.TERTIARY } />
<Button
id = {
haveVoted
? t('polls.results.changeVote')
: t('polls.results.vote')
}
labelKey = {
haveVoted
? 'polls.results.changeVote'
: 'polls.results.vote'
}
labelStyle = { pollsStyles.toggleText }
onClick = { changeVote }
type = { BUTTON_TYPES.TERTIARY } />
</View>
</View>
);
};
/*
* We apply AbstractPollResults to fill in the AbstractProps common
* to both the web and native implementations.
*/
// eslint-disable-next-line new-cap
export default AbstractPollResults(PollResults);

View File

@@ -0,0 +1,70 @@
import React, { useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, TextStyle, View, ViewStyle } from 'react-native';
import { Text } from 'react-native-paper';
import { useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import Icon from '../../../base/icons/components/Icon';
import { IconMessage } from '../../../base/icons/svg';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
import PollItem from './PollItem';
import { pollsStyles } from './styles';
interface IPollListProps {
setCreateMode: (mode: boolean) => void;
}
const PollsList = ({ setCreateMode }: IPollListProps) => {
const polls = useSelector((state: IReduxState) => state['features/polls'].polls);
const { t } = useTranslation();
const listPolls = Object.keys(polls);
const renderItem = useCallback(({ item }) => (
<PollItem
key = { item }
pollId = { item }
setCreateMode = { setCreateMode } />)
, []);
const flatlistRef = useRef<FlatList>(null);
const scrollToBottom = () => {
flatlistRef.current?.scrollToEnd({ animated: true });
};
useEffect(() => {
scrollToBottom();
}, [ polls ]);
return (
<>
{
listPolls.length === 0
&& <View style = { pollsStyles.noPollContent as ViewStyle }>
<Icon
color = { BaseTheme.palette.icon03 }
size = { 160 }
src = { IconMessage } />
<Text
id = 'no-polls-text'
style = { pollsStyles.noPollText as TextStyle } >
{
t('polls.results.empty')
}
</Text>
</View>
}
<FlatList
data = { listPolls }
extraData = { listPolls }
// eslint-disable-next-line react/jsx-no-bind
keyExtractor = { (item, index) => index.toString() }
ref = { flatlistRef }
renderItem = { renderItem } />
</>
);
};
export default PollsList;

View File

@@ -0,0 +1,77 @@
import { useNavigation } from '@react-navigation/native';
import React, { useEffect } from 'react';
import { Platform } from 'react-native';
import { useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { StyleType } from '../../../base/styles/functions.any';
import Button from '../../../base/ui/components/native/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import { ChatTabs } from '../../../chat/constants';
import { TabBarLabelCounter }
from '../../../mobile/navigation/components/TabBarLabelCounter';
import {
default as AbstractPollsPane,
type AbstractProps
} from '../AbstractPollsPane';
import PollCreate from './PollCreate';
import PollsList from './PollsList';
import { pollsStyles } from './styles';
const PollsPane = (props: AbstractProps) => {
const { createMode, isCreatePollsDisabled, onCreate, setCreateMode, t } = props;
const navigation = useNavigation();
const isPollsTabFocused = useSelector((state: IReduxState) => state['features/chat'].focusedTab === ChatTabs.POLLS);
const { nbUnreadPolls } = useSelector((state: IReduxState) => state['features/polls']);
useEffect(() => {
const activeUnreadPollsNr = !isPollsTabFocused && nbUnreadPolls > 0;
navigation.setOptions({
// eslint-disable-next-line react/no-multi-comp
tabBarLabel: () => (
<TabBarLabelCounter
activeUnreadNr = { activeUnreadPollsNr }
isFocused = { isPollsTabFocused }
label = { t('chat.tabs.polls') }
nbUnread = { nbUnreadPolls } />
)
});
}, [ isPollsTabFocused, nbUnreadPolls ]);
const createPollButtonStyles = Platform.OS === 'android'
? pollsStyles.createPollButtonAndroid : pollsStyles.createPollButtonIos;
return (
<JitsiScreen
contentContainerStyle = { pollsStyles.pollPane as StyleType }
disableForcedKeyboardDismiss = { true }
hasExtraHeaderHeight = { true }
style = { pollsStyles.pollPaneContainer as StyleType }>
{
createMode
? <PollCreate setCreateMode = { setCreateMode } />
: <>
<PollsList setCreateMode = { setCreateMode } />
{!isCreatePollsDisabled && <Button
accessibilityLabel = 'polls.create.create'
id = { t('polls.create.create') }
labelKey = 'polls.create.create'
onClick = { onCreate }
style = { createPollButtonStyles }
type = { BUTTON_TYPES.PRIMARY } />}
</>
}
</JitsiScreen>
);
};
/*
* We apply AbstractPollsPane to fill in the AbstractProps common
* to both the web and native implementations.
*/
// eslint-disable-next-line new-cap
export default AbstractPollsPane(PollsPane);

View File

@@ -0,0 +1,239 @@
import { createStyleSheet } from '../../../base/styles/functions.native';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
export const dialogStyles = createStyleSheet({
headerContainer: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between'
},
customContainer: {
marginBottom: BaseTheme.spacing[3],
marginHorizontal: BaseTheme.spacing[3],
marginTop: BaseTheme.spacing[2]
},
questionText: {
...BaseTheme.typography.bodyShortBold,
color: BaseTheme.palette.text01,
marginLeft: BaseTheme.spacing[1]
},
questionOwnerText: {
...BaseTheme.typography.bodyShortBold,
color: BaseTheme.palette.text03,
marginBottom: BaseTheme.spacing[2],
marginLeft: BaseTheme.spacing[1]
},
optionContainer: {
flexDirection: 'column',
marginTop: BaseTheme.spacing[3],
marginHorizontal: BaseTheme.spacing[3]
},
optionRemoveButton: {
width: 128
},
optionRemoveButtonText: {
color: BaseTheme.palette.link01
},
field: {
borderWidth: 1,
borderColor: BaseTheme.palette.ui06,
borderRadius: BaseTheme.shape.borderRadius,
color: BaseTheme.palette.text01,
fontSize: 14,
paddingBottom: BaseTheme.spacing[2],
paddingLeft: BaseTheme.spacing[3],
paddingRight: BaseTheme.spacing[3],
paddingTop: BaseTheme.spacing[2]
}
});
export const resultsStyles = createStyleSheet({
title: {
fontSize: 24,
fontWeight: 'bold'
},
barContainer: {
backgroundColor: '#ccc',
borderRadius: 3,
width: '100%',
height: 6,
marginTop: 2
},
bar: {
backgroundColor: BaseTheme.palette.action01,
borderRadius: BaseTheme.shape.borderRadius,
height: 6
},
voters: {
backgroundColor: BaseTheme.palette.ui04,
borderColor: BaseTheme.palette.ui03,
borderRadius: BaseTheme.shape.borderRadius,
borderWidth: 1,
padding: BaseTheme.spacing[2],
marginTop: BaseTheme.spacing[2]
},
voter: {
color: BaseTheme.palette.text01
},
answerContainer: {
marginHorizontal: BaseTheme.spacing[1],
marginVertical: BaseTheme.spacing[3],
maxWidth: '100%'
},
answerHeader: {
flexDirection: 'row',
justifyContent: 'space-between'
},
answer: {
color: BaseTheme.palette.text01,
flexShrink: 1
},
answerVoteCount: {
paddingLeft: 10
},
chatQuestion: {
fontWeight: 'bold'
}
});
export const pollsStyles = createStyleSheet({
noPollContent: {
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
top: '25%'
},
noPollText: {
flex: 1,
color: BaseTheme.palette.text03,
textAlign: 'center',
maxWidth: '70%'
},
pollItemContainer: {
backgroundColor: BaseTheme.palette.uiBackground,
borderColor: BaseTheme.palette.ui06,
borderRadius: BaseTheme.shape.borderRadius,
boxShadow: BaseTheme.shape.boxShadow,
borderWidth: 1,
padding: BaseTheme.spacing[2],
margin: BaseTheme.spacing[3]
},
pollCreateContainer: {
flex: 1
},
pollCreateSubContainer: {
flex: 1,
marginTop: BaseTheme.spacing[3]
},
pollCreateButtonsContainerAndroid: {
marginBottom: BaseTheme.spacing[8],
marginHorizontal: BaseTheme.spacing[3]
},
pollCreateButtonsContainerIos: {
marginBottom: BaseTheme.spacing[5],
marginHorizontal: BaseTheme.spacing[3]
},
pollSendLabel: {
color: BaseTheme.palette.text01
},
pollSendDisabledLabel: {
color: BaseTheme.palette.text03
},
buttonRow: {
flexDirection: 'row',
justifyContent: 'space-between'
},
answerContent: {
marginBottom: BaseTheme.spacing[2]
},
switchRow: {
alignItems: 'center',
flexDirection: 'row',
padding: BaseTheme.spacing[2]
},
switchLabel: {
color: BaseTheme.palette.text01,
marginLeft: BaseTheme.spacing[2]
},
pollCreateAddButton: {
marginHorizontal: BaseTheme.spacing[1],
marginVertical: BaseTheme.spacing[2]
},
pollCreateButton: {
marginHorizontal: BaseTheme.spacing[1],
flex: 1
},
toggleText: {
color: BaseTheme.palette.action01
},
createPollButtonIos: {
marginHorizontal: 20,
marginVertical: BaseTheme.spacing[5]
},
createPollButtonAndroid: {
marginHorizontal: 20,
marginVertical: BaseTheme.spacing[5]
},
pollPane: {
flex: 1,
padding: BaseTheme.spacing[2]
},
pollPaneContainer: {
backgroundColor: BaseTheme.palette.ui01,
flex: 1
},
bottomLinks: {
flexDirection: 'row',
justifyContent: 'space-between',
marginHorizontal: BaseTheme.spacing[1]
},
fieldSeparator: {
borderBottomWidth: 1,
borderColor: BaseTheme.palette.ui05,
marginTop: BaseTheme.spacing[3]
}
});