Skip to content

Commit 4108789

Browse files
Merge pull request #239 from nepalcodes/issue/230-dictionary-audio-icon
feat: use icon for dictionary audio playback
2 parents 0ad5512 + 18aabf4 commit 4108789

File tree

4 files changed

+112
-38
lines changed

4 files changed

+112
-38
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
.DS_Store
2+
.python_modules/

nepalingo-web/src/components/Card.tsx

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import React, { useState } from "react";
1+
import React from "react";
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
33
import { faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
44
import { useLanguage } from "@/hooks/Langauge";
5+
import useAudioPlayer from "@/hooks/useAudioPlayer";
56

67
interface CardProps {
78
Word: string;
@@ -22,25 +23,28 @@ const Card: React.FC<CardProps> = ({
2223
PronounciationUrl,
2324
viewType,
2425
}) => {
25-
const [audio, setAudio] = useState<HTMLAudioElement | null>(null);
2626
const { selectedLanguage } = useLanguage();
27+
const { togglePlayback } = useAudioPlayer(PronounciationUrl);
2728

28-
const handlePronunciation = (event: React.MouseEvent) => {
29+
const handlePronunciation = (event: React.MouseEvent<HTMLButtonElement>) => {
2930
event.stopPropagation();
30-
if (PronounciationUrl) {
31-
if (audio) {
32-
if (!audio.paused) {
33-
audio.pause();
34-
audio.currentTime = 0;
35-
} else {
36-
audio.play();
37-
}
38-
} else {
39-
const newAudio = new Audio(PronounciationUrl);
40-
setAudio(newAudio);
41-
newAudio.play();
42-
}
31+
togglePlayback();
32+
};
33+
34+
const PronunciationButton = () => {
35+
if (!PronounciationUrl) {
36+
return null;
4337
}
38+
39+
return (
40+
<button
41+
type="button"
42+
onClick={handlePronunciation}
43+
className="absolute right-4 bottom-4 z-10 text-white"
44+
>
45+
<FontAwesomeIcon icon={faVolumeHigh} />
46+
</button>
47+
);
4448
};
4549

4650
return (
@@ -72,14 +76,7 @@ const Card: React.FC<CardProps> = ({
7276
<p className="text-lg text-white sm:text-lg md:text-xl lg:text-2xl">
7377
{Pronunciation}
7478
</p>
75-
{PronounciationUrl && (
76-
<button
77-
onClick={handlePronunciation}
78-
className="absolute right-4 bottom-4 z-10 text-white"
79-
>
80-
<FontAwesomeIcon icon={faVolumeHigh} />
81-
</button>
82-
)}
79+
<PronunciationButton />
8380
</div>
8481
<div className="relative w-full h-[30%] rounded-b-2xl overflow-hidden flex items-center justify-center">
8582
{ImageUrl && (
@@ -125,14 +122,7 @@ const Card: React.FC<CardProps> = ({
125122
<p className="text-lg sm:text-lg md:text-xl lg:text-2xl">
126123
{Pronunciation}
127124
</p>
128-
{PronounciationUrl && (
129-
<button
130-
onClick={handlePronunciation}
131-
className="absolute right-4 bottom-4 z-10 text-white"
132-
>
133-
<FontAwesomeIcon icon={faVolumeHigh} />
134-
</button>
135-
)}
125+
<PronunciationButton />
136126
</div>
137127
<div className="relative h-[70%] w-full rounded-b-2xl overflow-hidden flex items-center justify-center">
138128
{ImageUrl && (

nepalingo-web/src/components/SearchResponseCard.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
import { Meaning } from "@/hooks/useDictionary";
22
import React from "react";
3+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4+
import { faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
5+
import useAudioPlayer from "@/hooks/useAudioPlayer";
36

47
const SearchResponseCard = ({ meaning }: { meaning: Meaning }) => {
8+
const { togglePlayback } = useAudioPlayer(meaning.audio?.uri);
9+
10+
const handlePronunciation = (event: React.MouseEvent<HTMLButtonElement>) => {
11+
event.stopPropagation();
12+
13+
togglePlayback();
14+
};
15+
516
return (
617
<div
718
key={meaning.meaningOriginal}
@@ -19,12 +30,15 @@ const SearchResponseCard = ({ meaning }: { meaning: Meaning }) => {
1930
</p>
2031
)}
2132
</div>
22-
{meaning.audio && (
23-
<audio
24-
controls
25-
src={meaning.audio.uri}
26-
className="max-w-28 "
27-
></audio>
33+
{meaning.audio?.uri && (
34+
<button
35+
type="button"
36+
onClick={handlePronunciation}
37+
className="self-start text-white text-2xl transition-colors hover:text-primary focus-visible:outline-none"
38+
aria-label={`Play pronunciation for ${meaning.meaningOriginal}`}
39+
>
40+
<FontAwesomeIcon icon={faVolumeHigh} />
41+
</button>
2842
)}
2943
</div>
3044
<div className="flex flex-row flex-wrap gap-2 mt-2">
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { useCallback, useEffect, useState } from "react";
2+
3+
const resetAudio = (audio: HTMLAudioElement | null) => {
4+
if (!audio) {
5+
return;
6+
}
7+
8+
audio.pause();
9+
audio.currentTime = 0;
10+
};
11+
12+
const useAudioPlayer = (audioUrl?: string) => {
13+
const [audio, setAudio] = useState<HTMLAudioElement | null>(null);
14+
15+
const clearAudio = useCallback(() => {
16+
setAudio((currentAudio) => {
17+
if (currentAudio) {
18+
resetAudio(currentAudio);
19+
}
20+
21+
return null;
22+
});
23+
}, []);
24+
25+
useEffect(() => {
26+
return () => {
27+
resetAudio(audio);
28+
};
29+
}, [audio]);
30+
31+
useEffect(() => {
32+
clearAudio();
33+
}, [audioUrl, clearAudio]);
34+
35+
const togglePlayback = useCallback(() => {
36+
if (!audioUrl) {
37+
return;
38+
}
39+
40+
setAudio((currentAudio) => {
41+
if (currentAudio) {
42+
if (!currentAudio.paused) {
43+
resetAudio(currentAudio);
44+
return null;
45+
}
46+
47+
currentAudio.currentTime = 0;
48+
const playPromise = currentAudio.play();
49+
if (playPromise) {
50+
void playPromise.catch(() => undefined);
51+
}
52+
53+
return currentAudio;
54+
}
55+
56+
const newAudio = new Audio(audioUrl);
57+
const playPromise = newAudio.play();
58+
if (playPromise) {
59+
void playPromise.catch(() => undefined);
60+
}
61+
62+
return newAudio;
63+
});
64+
}, [audioUrl]);
65+
66+
return { togglePlayback };
67+
};
68+
69+
export default useAudioPlayer;

0 commit comments

Comments
 (0)