Adding i18n use in ThemeScreen.tsx

This commit is contained in:
saimuelbr 2026-01-06 18:47:29 -03:00
parent c530619039
commit cb9ce2906f
7 changed files with 6108 additions and 6042 deletions

View file

@ -161,11 +161,10 @@ public class ReactExoplayerView extends FrameLayout implements
AdEvent.AdEventListener,
AdErrorEvent.AdErrorListener {
public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 0.5;
public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 1;
public static final double DEFAULT_MIN_BUFFER_MEMORY_RESERVE = 0;
private static final String TAG = "ReactExoplayerView";
private static final ExecutorService SHARED_EXECUTOR = Executors.newSingleThreadExecutor();
private static final CookieManager DEFAULT_COOKIE_MANAGER;
private static final int SHOW_PROGRESS = 1;
@ -212,7 +211,6 @@ public class ReactExoplayerView extends FrameLayout implements
private float audioVolume = 1f;
private int maxBitRate = 0;
private boolean hasDrmFailed = false;
private int drmRetryCount = 0;
private boolean isUsingContentResolution = false;
private boolean selectTrackWhenReady = false;
private final Handler mainHandler;
@ -245,7 +243,7 @@ public class ReactExoplayerView extends FrameLayout implements
private BufferingStrategy.BufferingStrategyEnum bufferingStrategy;
private boolean disableDisconnectError;
private boolean preventsDisplaySleepDuringVideoPlayback = true;
private float mProgressUpdateInterval = 1000.0f;
private float mProgressUpdateInterval = 250.0f;
protected boolean playInBackground = false;
private boolean mReportBandwidth = false;
private boolean controls = false;
@ -645,12 +643,9 @@ public class ReactExoplayerView extends FrameLayout implements
PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, this.enterPictureInPictureOnLeave);
}
if (!source.isLocalAssetFile() && !source.isAsset() && source.getBufferConfig().getCacheSize() > 0) {
long requestedCacheSize = source.getBufferConfig().getCacheSize();
long MAX_SAFE_CACHE_SIZE = 100L * 1024 * 1024;
long effectiveCacheSize = Math.min(requestedCacheSize, MAX_SAFE_CACHE_SIZE);
RNVSimpleCache.INSTANCE.setSimpleCache(
this.getContext(),
(int) effectiveCacheSize
source.getBufferConfig().getCacheSize()
);
useCache = true;
} else {
@ -659,10 +654,9 @@ public class ReactExoplayerView extends FrameLayout implements
if (playerNeedsSource) {
// Will force display of shutter view if needed
exoPlayerView.invalidateAspectRatio();
drmRetryCount = 0;
hasDrmFailed = false;
// DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread
SHARED_EXECUTOR.execute(() -> {
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(() -> {
// DRM initialization must run on a different thread
if (viewHasDropped && runningSource == source) {
return;
@ -733,7 +727,7 @@ public class ReactExoplayerView extends FrameLayout implements
DefaultRenderersFactory renderersFactory =
new DefaultRenderersFactory(getContext())
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER)
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF)
.setEnableDecoderFallback(true)
.forceEnableMediaCodecAsynchronousQueueing();
@ -857,10 +851,13 @@ public class ReactExoplayerView extends FrameLayout implements
MediaSource mediaSource = Objects.requireNonNullElse(mediaSourceWithAds, videoSource);
// wait for player to be set
if (player == null) {
DebugLog.w(TAG, "Player not ready yet, aborting source initialization");
playerNeedsSource = true;
return;
while (player == null) {
try {
wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
DebugLog.e(TAG, ex.toString());
}
}
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
@ -1487,7 +1484,8 @@ public class ReactExoplayerView extends FrameLayout implements
ArrayList<Track> textTracks = getTextTrackInfo();
if (source.getContentStartTime() != -1) {
SHARED_EXECUTOR.execute(() -> {
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(() -> {
// To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done
ArrayList<VideoTrack> videoTracks = getVideoTrackInfoFromManifest();
if (videoTracks != null) {
@ -1598,11 +1596,12 @@ public class ReactExoplayerView extends FrameLayout implements
// We need retry count to in case where minefest request fails from poor network conditions
@WorkerThread
private ArrayList<VideoTrack> getVideoTrackInfoFromManifest(int retryCount) {
ExecutorService es = Executors.newSingleThreadExecutor();
final DataSource dataSource = this.mediaDataSourceFactory.createDataSource();
final Uri sourceUri = source.getUri();
final long startTime = source.getContentStartTime() * 1000 - 100; // s -> ms with 100ms offset
Future<ArrayList<VideoTrack>> result = SHARED_EXECUTOR.submit(new Callable<ArrayList<VideoTrack>>() {
Future<ArrayList<VideoTrack>> result = es.submit(new Callable() {
final DataSource ds = dataSource;
final Uri uri = sourceUri;
final long startTimeUs = startTime * 1000; // ms -> us
@ -1649,6 +1648,7 @@ public class ReactExoplayerView extends FrameLayout implements
if (results == null && retryCount < 1) {
return this.getVideoTrackInfoFromManifest(++retryCount);
}
es.shutdown();
return results;
} catch (Exception e) {
DebugLog.w(TAG, "error in getVideoTrackInfoFromManifest handling request:" + e.getMessage());
@ -1939,15 +1939,12 @@ public class ReactExoplayerView extends FrameLayout implements
case PlaybackException.ERROR_CODE_DRM_UNSPECIFIED:
if (!hasDrmFailed) {
// When DRM fails to reach the app level certificate server it will fail with a source error so we assume that it is DRM related and try one more time
if (drmRetryCount < 1) {
drmRetryCount++;
hasDrmFailed = true;
playerNeedsSource = true;
updateResumePosition();
initializePlayer();
setPlayWhenReady(true);
return;
}
hasDrmFailed = true;
playerNeedsSource = true;
updateResumePosition();
initializePlayer();
setPlayWhenReady(true);
return;
}
break;
default:

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,7 @@ import ColorPicker from 'react-native-wheel-color-picker';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { colors } from '../styles/colors';
import { useTheme, Theme, DEFAULT_THEMES } from '../contexts/ThemeContext';
import { useTranslation } from 'react-i18next';
import { RootStackParamList } from '../navigation/AppNavigator';
import { useSettings } from '../hooks/useSettings';
import CustomAlert from '../components/CustomAlert';
@ -31,10 +32,10 @@ const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
// Theme categories for organization
const THEME_CATEGORIES = [
{ id: 'all', name: 'All Themes' },
{ id: 'dark', name: 'Dark Themes' },
{ id: 'colorful', name: 'Colorful' },
{ id: 'custom', name: 'My Themes' },
{ id: 'all', name: 'themes.categories.all' },
{ id: 'dark', name: 'themes.categories.dark' },
{ id: 'colorful', name: 'themes.categories.colorful' },
{ id: 'custom', name: 'themes.categories.custom' },
];
interface ThemeCardProps {
@ -114,12 +115,9 @@ interface FilterTabProps {
primaryColor: string;
}
const FilterTab: React.FC<FilterTabProps> = ({
category,
isActive,
onPress,
primaryColor
}) => (
const FilterTab: React.FC<FilterTabProps> = ({ category, isActive, onPress, primaryColor }) => {
const { t } = useTranslation();
return (
<TouchableOpacity
style={[
styles.filterTab,
@ -128,16 +126,12 @@ const FilterTab: React.FC<FilterTabProps> = ({
]}
onPress={onPress}
>
<Text
style={[
styles.filterTabText,
isActive && { color: '#FFFFFF' }
]}
>
{category.name}
</Text>
<Text style={[styles.filterTabText, isActive && { color: '#FFFFFF' }]}>
{t(category.name)}
</Text>
</TouchableOpacity>
);
);
}
type ColorKey = 'primary' | 'secondary' | 'darkBackground';
@ -146,6 +140,7 @@ interface ThemeColorEditorProps {
primary: string;
secondary: string;
darkBackground: string;
name?: string;
};
onSave: (colors: {
primary: string;
@ -171,7 +166,8 @@ const ThemeColorEditor: React.FC<ThemeColorEditorProps & {
setAlertActions,
setAlertVisible
}) => {
const [themeName, setThemeName] = useState('Custom Theme');
const { t } = useTranslation();
const [themeName, setThemeName] = useState(initialColors.name || '');
const [selectedColorKey, setSelectedColorKey] = useState<ColorKey>('primary');
const [themeColors, setThemeColors] = useState({
primary: initialColors.primary,
@ -188,12 +184,12 @@ const ThemeColorEditor: React.FC<ThemeColorEditorProps & {
const handleSave = () => {
if (!themeName.trim()) {
setAlertTitle('Invalid Name');
setAlertMessage('Please enter a valid theme name');
setAlertActions([{ label: 'OK', onPress: () => {} }]);
setAlertVisible(true);
return;
}
setAlertTitle(t('themes.invalid_name'));
setAlertMessage(t('themes.invalid_name_msg'));
setAlertActions([{ label: 'OK', onPress: () => {} }]);
setAlertVisible(true);
return;
}
onSave({
...themeColors,
name: themeName
@ -252,14 +248,14 @@ const ThemeColorEditor: React.FC<ThemeColorEditorProps & {
style={styles.editorTitleInput}
value={themeName}
onChangeText={setThemeName}
placeholder="Theme name"
placeholder={t('themes.placeholder_name')}
placeholderTextColor="rgba(255,255,255,0.5)"
/>
<TouchableOpacity
style={styles.editorSaveButton}
onPress={handleSave}
>
<Text style={styles.saveButtonText}>Save</Text>
<Text style={styles.saveButtonText}>{t('themes.save')}</Text>
</TouchableOpacity>
</View>
@ -276,7 +272,7 @@ const ThemeColorEditor: React.FC<ThemeColorEditorProps & {
]}
onPress={() => setSelectedColorKey('primary')}
>
<Text style={styles.colorButtonText}>Primary</Text>
<Text style={styles.colorButtonText}>{t('themes.primary')}</Text>
</TouchableOpacity>
<TouchableOpacity
@ -287,7 +283,7 @@ const ThemeColorEditor: React.FC<ThemeColorEditorProps & {
]}
onPress={() => setSelectedColorKey('secondary')}
>
<Text style={styles.colorButtonText}>Secondary</Text>
<Text style={styles.colorButtonText}>{t('themes.secondary')}</Text>
</TouchableOpacity>
<TouchableOpacity
@ -298,7 +294,7 @@ const ThemeColorEditor: React.FC<ThemeColorEditorProps & {
]}
onPress={() => setSelectedColorKey('darkBackground')}
>
<Text style={styles.colorButtonText}>Background</Text>
<Text style={styles.colorButtonText}>{t('themes.background')}</Text>
</TouchableOpacity>
</View>
</View>
@ -319,6 +315,7 @@ const ThemeColorEditor: React.FC<ThemeColorEditorProps & {
};
const ThemeScreen: React.FC = () => {
const { t } = useTranslation();
const {
currentTheme,
availableThemes,
@ -398,18 +395,18 @@ const ThemeScreen: React.FC = () => {
}, []);
const handleDeleteTheme = useCallback((theme: Theme) => {
setAlertTitle('Delete Theme');
setAlertMessage(`Are you sure you want to delete "${theme.name}"?`);
setAlertTitle(t('themes.delete_title'));
setAlertMessage(t('themes.delete_msg', { name: theme.name }));
setAlertActions([
{ label: 'Cancel', style: { color: '#888' }, onPress: () => {} },
{
label: 'Delete',
style: { color: currentTheme.colors.error },
onPress: () => deleteCustomTheme(theme.id),
},
]);
setAlertVisible(true);
}, [deleteCustomTheme, currentTheme.colors.error]);
{ label: t('themes.cancel'), style: { color: '#888' }, onPress: () => {} },
{
label: t('themes.delete'),
style: { color: currentTheme.colors.error },
onPress: () => deleteCustomTheme(theme.id),
},
]);
setAlertVisible(true);
}, [deleteCustomTheme, currentTheme.colors.error, t]);
const handleCreateTheme = useCallback(() => {
setEditingTheme(null);
@ -430,21 +427,19 @@ const ThemeScreen: React.FC = () => {
}
});
} else {
// Create new theme
addCustomTheme({
name: themeData.name || 'Custom Theme',
colors: {
...currentTheme.colors,
primary: themeData.primary,
secondary: themeData.secondary,
darkBackground: themeData.darkBackground,
}
});
}
setIsEditMode(false);
setEditingTheme(null);
}, [editingTheme, updateCustomTheme, addCustomTheme, currentTheme]);
addCustomTheme({
name: themeData.name || t('themes.default_custom_name'),
colors: {
...currentTheme.colors,
primary: themeData.primary,
secondary: themeData.secondary,
darkBackground: themeData.darkBackground,
}
});
}
setIsEditMode(false);
setEditingTheme(null);
}, [editingTheme, updateCustomTheme, addCustomTheme, currentTheme, t]);
const handleCancelEdit = useCallback(() => {
setIsEditMode(false);
@ -463,42 +458,19 @@ const ThemeScreen: React.FC = () => {
}
}, [isEditMode, handleCancelEdit]);
// Pass alert state to ThemeColorEditor
const ThemeColorEditorWithAlert = (props: any) => {
const handleSave = (themeName: string, themeColors: any, onSave: any) => {
if (!themeName.trim()) {
setAlertTitle('Invalid Name');
setAlertMessage('Please enter a valid theme name');
setAlertActions([{ label: 'OK', onPress: () => {} }]);
setAlertVisible(true);
return false;
}
onSave();
return true;
};
return (
<>
<ThemeColorEditor {...props} handleSave={handleSave} />
<CustomAlert
visible={alertVisible}
title={alertTitle}
message={alertMessage}
actions={alertActions}
onClose={() => setAlertVisible(false)}
/>
</>
);
};
/* const ThemeColorEditorWithAlert **/
if (isEditMode) {
const initialColors = editingTheme ? {
primary: editingTheme.colors.primary,
secondary: editingTheme.colors.secondary,
darkBackground: editingTheme.colors.darkBackground,
name: editingTheme.name,
} : {
primary: currentTheme.colors.primary,
secondary: currentTheme.colors.secondary,
darkBackground: currentTheme.colors.darkBackground,
name: ''
};
return (
@ -541,7 +513,7 @@ const ThemeScreen: React.FC = () => {
>
<MaterialIcons name="arrow-back" size={24} color={currentTheme.colors.text} />
<Text style={[styles.backText, { color: currentTheme.colors.text }]}>
Settings
{t('settings.title')}
</Text>
</TouchableOpacity>
@ -551,7 +523,7 @@ const ThemeScreen: React.FC = () => {
</View>
<Text style={[styles.headerTitle, { color: currentTheme.colors.text }]}>
App Themes
{t('themes.title')}
</Text>
{/* Category filter */}
@ -579,7 +551,7 @@ const ThemeScreen: React.FC = () => {
showsVerticalScrollIndicator={false}
>
<Text style={[styles.sectionTitle, { color: currentTheme.colors.textMuted }]}>
SELECT THEME
{t('themes.select_theme')}
</Text>
<View style={styles.themeGrid}>
@ -604,16 +576,18 @@ const ThemeScreen: React.FC = () => {
onPress={handleCreateTheme}
>
<MaterialIcons name="add" size={20} color="#FFFFFF" />
<Text style={styles.createButtonText}>Create Custom Theme</Text>
<Text style={styles.createButtonText}>
{t('themes.create_custom')}
</Text>
</TouchableOpacity>
<Text style={[styles.sectionTitle, { color: currentTheme.colors.textMuted, marginTop: 24 }]}>
OPTIONS
{t('themes.options')}
</Text>
<View style={styles.optionRow}>
<Text style={[styles.optionLabel, { color: currentTheme.colors.text }]}>
Use Dominant Color from Artwork
{t('themes.use_dominant')}
</Text>
<Switch
value={settings.useDominantBackgroundColor}