Skip to content

Commit

Permalink
Merge pull request #37 from lrtlt/feature/channel-daily-questions
Browse files Browse the repository at this point in the history
feature: channel daily questions
  • Loading branch information
KestasVenslauskas authored Feb 24, 2024
2 parents 26accde + 337ac92 commit f3f5a57
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 47 deletions.
7 changes: 7 additions & 0 deletions app/api/Endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ export const putDailyQuestionVote = (questionId: number | string, answerId: numb
return `https://www.lrt.lt/api/v1/daily-question/${questionId}?answer=${answerId}`;
};

/**
* Gets daily question.
*/
export const getDailyQuestion = (questionId: number | string) => {
return `https://www.lrt.lt/api/v1/daily-question/${questionId}`;
};

/**
* Returns forecast for selected city
*/
Expand Down
3 changes: 3 additions & 0 deletions app/api/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ export type ChannelResponse = {
player_background_image: string;
is_permanent?: 0 | 1;
is_radio?: 0 | 1;
daily_question?: string | number;
};
prog: ProgramItemType[];
};
Expand Down Expand Up @@ -659,6 +660,8 @@ export type DailyQuestionChoice = {

export type DailyQuestionResponse = {
id: number;
can_vote: boolean;
is_ended: boolean;
title: string;
votes: number;
choices: DailyQuestionChoice[];
Expand Down
11 changes: 10 additions & 1 deletion app/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
categoryGet,
channelGet,
forecastGet,
getDailyQuestion,
homeGet,
liveFeedGet,
mediatekaGet,
Expand All @@ -13,15 +14,17 @@ import {
opusPlaylistGet,
popularArticlesGet,
programGet,
putDailyQuestionVote,
searchArticles,
weatherLocationsGet,
} from './Endpoints';
import {get} from './HttpClient';
import {get, put} from './HttpClient';
import {
ArticleContentResponse,
AudiotekaResponse,
CategoryArticlesResponse,
ChannelResponse,
DailyQuestionResponse,
ForecastLocation,
ForecastResponse,
HomeDataResponse,
Expand Down Expand Up @@ -84,3 +87,9 @@ export const fetchOpusPlaylist = () => get<OpusPlaylistResponse>(opusPlaylistGet

export const fetchLiveFeed = (id: string | number, count: number, order: 'asc' | 'desc') =>
get<LiveFeedResponse>(liveFeedGet(id, count, order));

export const setDailyQuestionVote = (questionId: number | string, choiceId: number | string) =>
put<any>(putDailyQuestionVote(questionId, choiceId));

export const fetchDailyQuestion = (questionId: number | string) =>
get<DailyQuestionResponse>(getDailyQuestion(questionId));
36 changes: 17 additions & 19 deletions app/components/dailyQuestion/DailyQuestionComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {selectDailyQuestionChoice} from '../../redux/selectors';
import {useTheme} from '../../Theme';
import {IconCheck} from '../svg';
import TextComponent from '../text/Text';
import {useSetDailyQuestionChoise} from './useSetDailyQuestionChoise';
import {useSetDailyQuestionChoice} from './useSetDailyQuestionChoice';

interface DailyQuestionComponentProps {
block: HomeBlockDailyQuestion;
Expand All @@ -18,9 +18,9 @@ const DailyQuestionComponent: React.FC<DailyQuestionComponentProps> = ({block})
const {colors, strings} = useTheme();

const answer = useSelector(selectDailyQuestionChoice);
const callVoteAPI = useSetDailyQuestionChoise();
const callVoteAPI = useSetDailyQuestionChoice();

const renderChoise = useCallback(
const renderChoice = useCallback(
(choice: DailyQuestionChoice) => {
return (
<View key={choice.id} style={{...styles.choiceContainer, borderColor: colors.buttonBorder}}>
Expand Down Expand Up @@ -82,22 +82,23 @@ const DailyQuestionComponent: React.FC<DailyQuestionComponentProps> = ({block})
return null;
}

const votingEnabled = dailyQuestion.can_vote && !dailyQuestion.is_ended;
const isUserVoteAccepted = answer && dailyQuestion.id === answer.daily_question_id;

const canUserVote = votingEnabled && !isUserVoteAccepted;
return (
<View style={{...styles.container, borderColor: colors.buttonBorder}}>
<View style={styles.topContainer}>
<TextComponent style={styles.title} fontFamily="PlayfairDisplay-Regular">
{dailyQuestion.title}
</TextComponent>
{isUserVoteAccepted && (
{!canUserVote && (
<TextComponent style={styles.subtitle}>
Iš viso balsavo:{' '}
<TextComponent style={{color: colors.primaryDark}}>{dailyQuestion.votes}</TextComponent>
</TextComponent>
)}
</View>
{dailyQuestion.choices.map(isUserVoteAccepted ? renderChoiceVoted : renderChoise)}
{dailyQuestion.choices.map(canUserVote ? renderChoice : renderChoiceVoted)}
</View>
);
};
Expand All @@ -106,20 +107,19 @@ export default React.memo(DailyQuestionComponent);

const styles = StyleSheet.create({
container: {
margin: 8,
borderWidth: 1,
flex: 1,
margin: 12,
borderWidth: 1,
},
topContainer: {
padding: 16,
},
choiceContainer: {
flexDirection: 'row',
paddingVertical: 8,
gap: 12,
paddingVertical: 16,
paddingHorizontal: 16,
borderTopWidth: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
Expand All @@ -130,15 +130,12 @@ const styles = StyleSheet.create({
},
choice: {
flex: 1,
height: '100%',
marginTop: 16,
fontSize: 15,
fontSize: 14,
textAlignVertical: 'center',
},
voteButton: {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 12,
marginLeft: 12,
},
voteText: {
fontSize: 13,
Expand All @@ -152,9 +149,10 @@ const styles = StyleSheet.create({
checkBubble: {
justifyContent: 'center',
alignItems: 'center',
width: 18,
height: 18,
borderRadius: 9,
alignSelf: 'center',
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: 'white',
marginRight: 8,
},
Expand Down
50 changes: 50 additions & 0 deletions app/components/dailyQuestion/DailyQuestionWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, {useEffect} from 'react';
import {StyleSheet, View} from 'react-native';
import useDailyQuestionData from './useDailyQuestionData';
import DailyQuestionComponent from './DailyQuestionComponent';
import {useSelector} from 'react-redux';
import {selectDailyQuestionChoice} from '../../redux/selectors';

interface DailyQuestionWrapperProps {
id: string | number;
}

const VOTES_REFRESH_RATE = 1000 * 15; // 15 sec

const DailyQuestionWrapper: React.FC<DailyQuestionWrapperProps> = ({id}) => {
const {question, refresh} = useDailyQuestionData(id);
const answer = useSelector(selectDailyQuestionChoice);

useEffect(() => {
let interval: NodeJS.Timeout;
if (question) {
const isAnswered = answer?.daily_question_id === question.data.id;
const isQuestionOngoing = question.data.can_vote && !question.data.is_ended;

if (isAnswered && isQuestionOngoing) {
interval = setInterval(() => {
refresh(question.data.id);
}, VOTES_REFRESH_RATE);
}
}
return () => clearInterval(interval);
}, [question, answer]);

if (question) {
return (
<View style={styles.container}>
<DailyQuestionComponent block={question} />
</View>
);
} else {
return null;
}
};

export default DailyQuestionWrapper;

const styles = StyleSheet.create({
container: {
flex: 1,
},
});
36 changes: 36 additions & 0 deletions app/components/dailyQuestion/useDailyQuestionData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {useCallback, useEffect, useState} from 'react';
import {fetchDailyQuestion} from '../../api';
import {HomeBlockDailyQuestion} from '../../api/Types';

type ReturnType = {
question?: HomeBlockDailyQuestion;
refresh: (id: string | number) => void;
};
const useDailyQuestionData = (id: string | number): ReturnType => {
const [question, setQuestion] = useState<HomeBlockDailyQuestion | undefined>();

const refresh = useCallback(async (id: string | number) => {
try {
const question = await fetchDailyQuestion(id);
if (question?.choices?.length) {
setQuestion({
type: 'daily_question',
data: question,
});
}
} catch (e) {
console.log('Error while fetching daily question', e);
}
}, []);

useEffect(() => {
refresh(id);
}, [id]);

return {
question,
refresh,
};
};

export default useDailyQuestionData;
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import {useCallback, useState} from 'react';
import {useDispatch} from 'react-redux';
import {putDailyQuestionVote} from '../../api/Endpoints';
import {put} from '../../api/HttpClient';
import {DailyQuestionChoice} from '../../api/Types';
import {setDailyQuestionChoice} from '../../redux/actions';
import {setDailyQuestionVote} from '../../api';

export const useSetDailyQuestionChoise = () => {
export const useSetDailyQuestionChoice = () => {
const [state, setState] = useState({
isLoading: false,
});

const dispatch = useDispatch();

const callApi = useCallback(
async (questionId: number, choise: DailyQuestionChoice) => {
async (questionId: number, choice: DailyQuestionChoice) => {
if (state.isLoading) {
console.log('Wait for the request to end...');
return;
}
setState({isLoading: true});

try {
const response = await put<any | null>(putDailyQuestionVote(questionId, choise.id));
const response = await setDailyQuestionVote(questionId, choice.id);
if (response.status >= 200 && response.status < 300) {
dispatch(setDailyQuestionChoice(questionId, choise));
dispatch(setDailyQuestionChoice(questionId, choice));
}
setState({isLoading: false});
} catch (e) {
Expand Down
32 changes: 16 additions & 16 deletions app/screens/channel/ChannelComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import {getSmallestDim} from '../../util/UI';
import {StreamData} from '../../components/videoComponent/useVideoData';
import TextComponent from '../../components/text/Text';

import CheckBox from '../../components/checkBox/CheckBox';
import ToggleButton from '../../components/settingsToggleButton/SettingsToggleButton';
import {CameraIcon, IconNote} from '../../components/svg';
import DailyQuestionWrapper from '../../components/dailyQuestion/DailyQuestionWrapper';

/** Count of visible program items below player */
const PROGRAM_ITEMS_VISIBLE = 2;
Expand Down Expand Up @@ -105,7 +104,7 @@ const ChannelComponent: React.FC<React.PropsWithChildren<Props>> = ({
: undefined;

return (
<View>
<View style={{flex: 1}}>
<View style={styles.playerContainer}>
<VideoComponent
key={channel_info.channel_id}
Expand All @@ -117,22 +116,23 @@ const ChannelComponent: React.FC<React.PropsWithChildren<Props>> = ({
streamData={selectedStream}
/>
</View>
{channel_info.daily_question && <DailyQuestionWrapper id={channel_info.daily_question} />}
<View style={{...styles.programContainer, backgroundColor: colors.greyBackground}}>
{streamSelectionComponent}

{programComponent}
{
<TouchableOpacity onPress={onProgramPressHandler}>
<Text
// eslint-disable-next-line react-native/no-inline-styles
style={{
...styles.fullProgramText,
marginTop: programComponent !== undefined ? 8 : 0,
backgroundColor: colors.background,
}}>
{strings.tvProgramButtonText}
</Text>
</TouchableOpacity>
}

<TouchableOpacity onPress={onProgramPressHandler}>
<Text
// eslint-disable-next-line react-native/no-inline-styles
style={{
...styles.fullProgramText,
marginTop: programComponent !== undefined ? 8 : 0,
backgroundColor: colors.background,
}}>
{strings.tvProgramButtonText}
</Text>
</TouchableOpacity>
</View>
</View>
);
Expand Down
7 changes: 2 additions & 5 deletions app/util/ImageUtil.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import {store} from '../redux/store';

export const BASE_IMG_URL = 'https://www.lrt.lt';

export type ImageSize = {
Expand Down Expand Up @@ -27,9 +25,8 @@ const sizes = [IMG_SIZE_XXS, IMG_SIZE_XS, IMG_SIZE_S, IMG_SIZE_M, IMG_SIZE_L, IM
export const getImageSizeForWidth = (width: number) => {
return (
sizes.find((s) => {
const offset = 100;
return s.width + offset >= width;
}) || IMG_SIZE_M
return s.width >= width;
}) || IMG_SIZE_S
);
};

Expand Down

0 comments on commit f3f5a57

Please sign in to comment.