mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
Adding i18n use in ThemeScreen.tsx
This commit is contained in:
parent
c530619039
commit
cb9ce2906f
7 changed files with 6108 additions and 6042 deletions
|
|
@ -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
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Reference in a new issue