diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 810d5111..aab98261 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,12 +2,12 @@ + - diff --git a/android/sentry.properties b/android/sentry.properties index ae003a4b..5581e03d 100644 --- a/android/sentry.properties +++ b/android/sentry.properties @@ -1,4 +1,4 @@ defaults.url=https://sentry.io/ defaults.org=tapframe defaults.project=react-native -auth.token=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 +# Using SENTRY_AUTH_TOKEN environment variable \ No newline at end of file diff --git a/node_modules/react-native-video/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt b/node_modules/react-native-video/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt index 953eb59e..1ac0fd03 100644 --- a/node_modules/react-native-video/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt +++ b/node_modules/react-native-video/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt @@ -1,6 +1,5 @@ package com.brentvatne.common.api -import android.graphics.Color import com.brentvatne.common.toolbox.ReactBridgeUtils import com.facebook.react.bridge.ReadableMap @@ -23,17 +22,6 @@ class SubtitleStyle public constructor() { var subtitlesFollowVideo = true private set - // Extended styling (used by ExoPlayerView via Media3 SubtitleView) - // Stored as Android color ints to avoid parsing multiple times. - var textColor: Int? = null - private set - var backgroundColor: Int? = null - private set - var edgeType: String? = null - private set - var edgeColor: Int? = null - private set - companion object { private const val PROP_FONT_SIZE_TRACK = "fontSize" private const val PROP_PADDING_BOTTOM = "paddingBottom" @@ -43,21 +31,6 @@ class SubtitleStyle public constructor() { private const val PROP_OPACITY = "opacity" private const val PROP_SUBTITLES_FOLLOW_VIDEO = "subtitlesFollowVideo" - // Extended props (optional) - private const val PROP_TEXT_COLOR = "textColor" - private const val PROP_BACKGROUND_COLOR = "backgroundColor" - private const val PROP_EDGE_TYPE = "edgeType" - private const val PROP_EDGE_COLOR = "edgeColor" - - private fun parseColorOrNull(value: String?): Int? { - if (value.isNullOrBlank()) return null - return try { - Color.parseColor(value) - } catch (_: IllegalArgumentException) { - null - } - } - @JvmStatic fun parse(src: ReadableMap?): SubtitleStyle { val subtitleStyle = SubtitleStyle() @@ -68,13 +41,6 @@ class SubtitleStyle public constructor() { subtitleStyle.paddingRight = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_RIGHT, 0) subtitleStyle.opacity = ReactBridgeUtils.safeGetFloat(src, PROP_OPACITY, 1f) subtitleStyle.subtitlesFollowVideo = ReactBridgeUtils.safeGetBool(src, PROP_SUBTITLES_FOLLOW_VIDEO, true) - - // Extended styling - subtitleStyle.textColor = parseColorOrNull(ReactBridgeUtils.safeGetString(src, PROP_TEXT_COLOR, null)) - subtitleStyle.backgroundColor = parseColorOrNull(ReactBridgeUtils.safeGetString(src, PROP_BACKGROUND_COLOR, null)) - subtitleStyle.edgeType = ReactBridgeUtils.safeGetString(src, PROP_EDGE_TYPE, null) - subtitleStyle.edgeColor = parseColorOrNull(ReactBridgeUtils.safeGetString(src, PROP_EDGE_COLOR, null)) - return subtitleStyle } } diff --git a/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt b/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt index 2d3f8cae..bb945fe7 100644 --- a/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt +++ b/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt @@ -10,14 +10,11 @@ import android.widget.FrameLayout import android.widget.TextView import androidx.media3.common.Player import androidx.media3.common.Timeline -import androidx.media3.common.text.CueGroup import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.AspectRatioFrameLayout -import androidx.media3.ui.CaptionStyleCompat import androidx.media3.ui.DefaultTimeBar import androidx.media3.ui.PlayerView -import androidx.media3.ui.SubtitleView import com.brentvatne.common.api.ResizeMode import com.brentvatne.common.api.SubtitleStyle @@ -55,58 +52,15 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute resizeMode = androidx.media3.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT } - /** - * Subtitles rendered in a full-size overlay (NOT inside PlayerView's content frame). - * This keeps subtitles anchored in-place even when the video surface/content frame moves - * due to aspect ratio / resizeMode changes. - * - * Controlled by SubtitleStyle.subtitlesFollowVideo. - */ - private val overlaySubtitleView = SubtitleView(context).apply { - layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - visibility = View.GONE - // We control styling via SubtitleStyle; don't pull Android system caption defaults. - setApplyEmbeddedStyles(true) - setApplyEmbeddedFontSizes(true) - } - - private fun updateSubtitleRenderingMode() { - val internalSubtitleView = playerView.subtitleView - val followVideo = localStyle.subtitlesFollowVideo - val shouldShow = localStyle.opacity != 0.0f - - if (followVideo) { - internalSubtitleView?.visibility = if (shouldShow) View.VISIBLE else View.GONE - overlaySubtitleView.visibility = View.GONE - } else { - // Hard-disable PlayerView's internal subtitle view. PlayerView can recreate/toggle this view - // during resize/layout, so we re-assert this in multiple lifecycle points. - internalSubtitleView?.visibility = View.GONE - internalSubtitleView?.alpha = 0f - overlaySubtitleView.visibility = if (shouldShow) View.VISIBLE else View.GONE - overlaySubtitleView.alpha = 1f - } - } - init { // Add PlayerView with explicit layout parameters val playerViewLayoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) addView(playerView, playerViewLayoutParams) - // Add overlay subtitles above PlayerView (so it doesn't move with video content frame) - val subtitleOverlayLayoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - addView(overlaySubtitleView, subtitleOverlayLayoutParams) - // Add live badge with its own layout parameters val liveBadgeLayoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) liveBadgeLayoutParams.setMargins(16, 16, 16, 16) addView(liveBadge, liveBadgeLayoutParams) - - // PlayerView may internally recreate its subtitle view during relayouts (e.g. resizeMode changes). - // Ensure our rendering mode is re-applied whenever PlayerView lays out. - playerView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> - updateSubtitleRenderingMode() - } } fun setPlayer(player: ExoPlayer?) { @@ -126,10 +80,6 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute playerView.resizeMode = resizeMode } } - - // Re-assert subtitle rendering mode for the current style. - updateSubtitleRenderingMode() - applySubtitleStyle(localStyle) } fun getPlayerView(): PlayerView = playerView @@ -158,63 +108,23 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute } fun setSubtitleStyle(style: SubtitleStyle) { - localStyle = style - applySubtitleStyle(localStyle) - } - - private fun applySubtitleStyle(style: SubtitleStyle) { - updateSubtitleRenderingMode() - playerView.subtitleView?.let { subtitleView -> - // Important: - // Avoid inheriting Android system caption settings via setUserDefaultStyle(), - // because those can force a background/window that the app doesn't want. - val resolvedTextColor = style.textColor ?: CaptionStyleCompat.DEFAULT.foregroundColor - val resolvedBackgroundColor = style.backgroundColor ?: Color.TRANSPARENT - val resolvedEdgeColor = style.edgeColor ?: Color.BLACK + // Reset to defaults + subtitleView.setUserDefaultStyle() + subtitleView.setUserDefaultTextSize() - val resolvedEdgeType = when (style.edgeType?.lowercase()) { - "outline" -> CaptionStyleCompat.EDGE_TYPE_OUTLINE - "shadow" -> CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW - else -> CaptionStyleCompat.EDGE_TYPE_NONE - } - - // windowColor MUST be transparent to avoid the "caption window" background. - val captionStyle = CaptionStyleCompat( - resolvedTextColor, - resolvedBackgroundColor, - Color.TRANSPARENT, - resolvedEdgeType, - resolvedEdgeColor, - null - ) - subtitleView.setStyle(captionStyle) - - // Text size: if not provided, fall back to user default size. + // Apply custom styling if (style.fontSize > 0) { - // Use DIP so the value matches React Native's dp-based fontSize more closely. - // SP would multiply by system fontScale and makes "30" look larger than RN "30". - subtitleView.setFixedTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, style.fontSize.toFloat()) - } else { - subtitleView.setUserDefaultTextSize() + subtitleView.setFixedTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, style.fontSize.toFloat()) } - // Horizontal padding is still useful (safe area); vertical offset is handled via bottomPaddingFraction. subtitleView.setPadding( style.paddingLeft, style.paddingTop, style.paddingRight, - 0 + style.paddingBottom ) - // Bottom offset for *internal* subtitles: - // Use Media3 SubtitleView's bottomPaddingFraction (moves cues up) rather than raw view padding. - if (style.paddingBottom > 0 && playerView.height > 0) { - val fraction = (style.paddingBottom.toFloat() / playerView.height.toFloat()) - .coerceIn(0f, 0.9f) - subtitleView.setBottomPaddingFraction(fraction) - } - if (style.opacity != 0.0f) { subtitleView.alpha = style.opacity subtitleView.visibility = android.view.View.VISIBLE @@ -222,59 +132,7 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute subtitleView.visibility = android.view.View.GONE } } - - // Apply the same styling to the overlay subtitle view. - run { - val subtitleView = overlaySubtitleView - - val resolvedTextColor = style.textColor ?: CaptionStyleCompat.DEFAULT.foregroundColor - val resolvedBackgroundColor = style.backgroundColor ?: Color.TRANSPARENT - val resolvedEdgeColor = style.edgeColor ?: Color.BLACK - - val resolvedEdgeType = when (style.edgeType?.lowercase()) { - "outline" -> CaptionStyleCompat.EDGE_TYPE_OUTLINE - "shadow" -> CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW - else -> CaptionStyleCompat.EDGE_TYPE_NONE - } - - val captionStyle = CaptionStyleCompat( - resolvedTextColor, - resolvedBackgroundColor, - Color.TRANSPARENT, - resolvedEdgeType, - resolvedEdgeColor, - null - ) - subtitleView.setStyle(captionStyle) - - if (style.fontSize > 0) { - // Use DIP so the value matches React Native's dp-based fontSize more closely. - subtitleView.setFixedTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, style.fontSize.toFloat()) - } else { - subtitleView.setUserDefaultTextSize() - } - - subtitleView.setPadding( - style.paddingLeft, - style.paddingTop, - style.paddingRight, - 0 - ) - - // Bottom offset relative to the full view height (stable even when video content frame moves). - val h = height.takeIf { it > 0 } ?: subtitleView.height - if (style.paddingBottom > 0 && h > 0) { - val fraction = (style.paddingBottom.toFloat() / h.toFloat()) - .coerceIn(0f, 0.9f) - subtitleView.setBottomPaddingFraction(fraction) - } else { - subtitleView.setBottomPaddingFraction(0f) - } - - if (style.opacity != 0.0f) { - subtitleView.alpha = style.opacity - } - } + localStyle = style } fun setShutterColor(color: Int) { @@ -365,13 +223,6 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute } private val playerListener = object : Player.Listener { - override fun onCues(cueGroup: CueGroup) { - // Keep overlay subtitles in sync. This does NOT interfere with PlayerView's own subtitle rendering. - // When subtitlesFollowVideo=false, overlaySubtitleView is the visible one. - updateSubtitleRenderingMode() - overlaySubtitleView.setCues(cueGroup.cues) - } - override fun onTimelineChanged(timeline: Timeline, reason: Int) { playerView.post { playerView.requestLayout() @@ -433,9 +284,6 @@ class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: Attribute pendingResizeMode?.let { resizeMode -> playerView.resizeMode = resizeMode } - // Re-apply bottomPaddingFraction once we have a concrete height. - updateSubtitleRenderingMode() - applySubtitleStyle(localStyle) } } } diff --git a/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 72007063..e16ac96d 100644 --- a/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -228,8 +228,7 @@ public class ReactExoplayerView extends FrameLayout implements private ArrayList rootViewChildrenOriginalVisibility = new ArrayList(); /* - * When user is seeking first called is on onPositionDiscontinuity -> - * DISCONTINUITY_REASON_SEEK + * When user is seeking first called is on onPositionDiscontinuity -> DISCONTINUITY_REASON_SEEK * Then we set if to false when playback is back in onIsPlayingChanged -> true */ private boolean isSeeking = false; @@ -299,8 +298,7 @@ public class ReactExoplayerView extends FrameLayout implements lastPos = pos; lastBufferDuration = bufferedDuration; lastDuration = duration; - eventEmitter.onVideoProgress.invoke(pos, bufferedDuration, player.getDuration(), - getPositionInFirstPeriodMsForCurrentWindow(pos)); + eventEmitter.onVideoProgress.invoke(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos)); } } } @@ -318,7 +316,7 @@ public class ReactExoplayerView extends FrameLayout implements public double getPositionInFirstPeriodMsForCurrentWindow(long currentPosition) { Timeline.Window window = new Timeline.Window(); - if (!player.getCurrentTimeline().isEmpty()) { + if(!player.getCurrentTimeline().isEmpty()) { player.getCurrentTimeline().getWindow(player.getCurrentMediaItemIndex(), window); } return window.windowStartTimeMs + currentPosition; @@ -357,9 +355,9 @@ public class ReactExoplayerView extends FrameLayout implements LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); exoPlayerView = new ExoPlayerView(getContext()); - exoPlayerView.addOnLayoutChangeListener( - (View v, int l, int t, int r, int b, int ol, int ot, int or, int ob) -> PictureInPictureUtil - .applySourceRectHint(themedReactContext, pictureInPictureParamsBuilder, exoPlayerView)); + exoPlayerView.addOnLayoutChangeListener( (View v, int l, int t, int r, int b, int ol, int ot, int or, int ob) -> + PictureInPictureUtil.applySourceRectHint(themedReactContext, pictureInPictureParamsBuilder, exoPlayerView) + ); exoPlayerView.setLayoutParams(layoutParams); addView(exoPlayerView, 0, layoutParams); @@ -385,10 +383,8 @@ public class ReactExoplayerView extends FrameLayout implements public void onHostPause() { isInBackground = true; Activity activity = themedReactContext.getCurrentActivity(); - boolean isInPictureInPicture = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null - && activity.isInPictureInPictureMode(); - boolean isInMultiWindowMode = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null - && activity.isInMultiWindowMode(); + boolean isInPictureInPicture = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null && activity.isInPictureInPictureMode(); + boolean isInMultiWindowMode = Util.SDK_INT >= Build.VERSION_CODES.N && activity != null && activity.isInMultiWindowMode(); if (playInBackground || isInPictureInPicture || isInMultiWindowMode) { return; } @@ -407,7 +403,7 @@ public class ReactExoplayerView extends FrameLayout implements viewHasDropped = true; } - // BandwidthMeter.EventListener implementation + //BandwidthMeter.EventListener implementation @Override public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) { if (mReportBandwidth) { @@ -415,8 +411,7 @@ public class ReactExoplayerView extends FrameLayout implements eventEmitter.onVideoBandwidthUpdate.invoke(bitrate, 0, 0, null); } else { Format videoFormat = player.getVideoFormat(); - boolean isRotatedContent = videoFormat != null - && (videoFormat.rotationDegrees == 90 || videoFormat.rotationDegrees == 270); + boolean isRotatedContent = videoFormat != null && (videoFormat.rotationDegrees == 90 || videoFormat.rotationDegrees == 270); int width = videoFormat != null ? (isRotatedContent ? videoFormat.height : videoFormat.width) : 0; int height = videoFormat != null ? (isRotatedContent ? videoFormat.width : videoFormat.height) : 0; String trackId = videoFormat != null ? videoFormat.id : null; @@ -431,8 +426,7 @@ public class ReactExoplayerView extends FrameLayout implements * Toggling the visibility of the player control view */ private void togglePlayerControlVisibility() { - if (player == null) - return; + if (player == null) return; if (exoPlayerView.isControllerVisible()) { exoPlayerView.hideController(); } else { @@ -456,8 +450,7 @@ public class ReactExoplayerView extends FrameLayout implements } private void updateControllerConfig() { - if (exoPlayerView == null) - return; + if (exoPlayerView == null) return; exoPlayerView.setControllerShowTimeoutMs(5000); @@ -468,8 +461,7 @@ public class ReactExoplayerView extends FrameLayout implements } private void updateControllerVisibility() { - if (exoPlayerView == null) - return; + if (exoPlayerView == null) return; exoPlayerView.setUseController(controls && !controlsConfig.getHideFullscreen()); } @@ -477,7 +469,7 @@ public class ReactExoplayerView extends FrameLayout implements private void openSettings() { AlertDialog.Builder builder = new AlertDialog.Builder(themedReactContext); builder.setTitle(R.string.settings); - String[] settingsOptions = { themedReactContext.getString(R.string.playback_speed) }; + String[] settingsOptions = {themedReactContext.getString(R.string.playback_speed)}; builder.setItems(settingsOptions, (dialog, which) -> { if (which == 0) { showPlaybackSpeedOptions(); @@ -487,7 +479,7 @@ public class ReactExoplayerView extends FrameLayout implements } private void showPlaybackSpeedOptions() { - String[] speedOptions = { "0.5x", "1.0x", "1.5x", "2.0x" }; + String[] speedOptions = {"0.5x", "1.0x", "1.5x", "2.0x"}; AlertDialog.Builder builder = new AlertDialog.Builder(themedReactContext); builder.setTitle(R.string.select_playback_speed); @@ -505,10 +497,8 @@ public class ReactExoplayerView extends FrameLayout implements speed = 2.0f; break; default: - speed = 1.0f; - ; - } - ; + speed = 1.0f;; + }; setRateModifier(speed); }); builder.show(); @@ -520,30 +510,24 @@ public class ReactExoplayerView extends FrameLayout implements /** * Update the layout + * @param view view needs to update layout * - * @param view view needs to update layout - * - * This is a workaround for the open bug in react-native: ... + * This is a workaround for the open bug in react-native: ... */ private void reLayout(View view) { - if (view == null) - return; + if (view == null) return; view.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight()); } private void refreshControlsStyles() { - if (exoPlayerView == null || player == null || !controls) - return; + if (exoPlayerView == null || player == null || !controls) return; updateControllerVisibility(); } - // Note: The following methods for live content and button visibility are no - // longer needed - // as PlayerView handles controls automatically. Some functionality may need to - // be + // Note: The following methods for live content and button visibility are no longer needed + // as PlayerView handles controls automatically. Some functionality may need to be // reimplemented using PlayerView's APIs if custom behavior is required. private void reLayoutControls() { @@ -580,7 +564,6 @@ public class ReactExoplayerView extends FrameLayout implements private class RNVLoadControl extends DefaultLoadControl { private final int availableHeapInBytes; private final Runtime runtime; - public RNVLoadControl(DefaultAllocator allocator, BufferConfig config) { super(allocator, config.getMinBufferMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() @@ -591,7 +574,7 @@ public class ReactExoplayerView extends FrameLayout implements : DefaultLoadControl.DEFAULT_MAX_BUFFER_MS, config.getBufferForPlaybackMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() ? config.getBufferForPlaybackMs() - : DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS, + : DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS , config.getBufferForPlaybackAfterRebufferMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() ? config.getBufferForPlaybackAfterRebufferMs() : DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS, @@ -602,12 +585,10 @@ public class ReactExoplayerView extends FrameLayout implements : DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS, DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME); runtime = Runtime.getRuntime(); - ActivityManager activityManager = (ActivityManager) themedReactContext - .getSystemService(ThemedReactContext.ACTIVITY_SERVICE); - double maxHeap = config.getMaxHeapAllocationPercent() != BufferConfig.Companion - .getBufferConfigPropUnsetDouble() - ? config.getMaxHeapAllocationPercent() - : DEFAULT_MAX_HEAP_ALLOCATION_PERCENT; + ActivityManager activityManager = (ActivityManager) themedReactContext.getSystemService(ThemedReactContext.ACTIVITY_SERVICE); + double maxHeap = config.getMaxHeapAllocationPercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() + ? config.getMaxHeapAllocationPercent() + : DEFAULT_MAX_HEAP_ALLOCATION_PERCENT; availableHeapInBytes = (int) Math.floor(activityManager.getMemoryClass() * maxHeap * 1024 * 1024); } @@ -625,15 +606,13 @@ public class ReactExoplayerView extends FrameLayout implements } long usedMemory = runtime.totalMemory() - runtime.freeMemory(); long freeMemory = runtime.maxMemory() - usedMemory; - double minBufferMemoryReservePercent = source.getBufferConfig() - .getMinBufferMemoryReservePercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() - ? source.getBufferConfig().getMinBufferMemoryReservePercent() - : ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE; + double minBufferMemoryReservePercent = source.getBufferConfig().getMinBufferMemoryReservePercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() + ? source.getBufferConfig().getMinBufferMemoryReservePercent() + : ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE; long reserveMemory = (long) minBufferMemoryReservePercent * runtime.maxMemory(); long bufferedMs = bufferedDurationUs / (long) 1000; if (reserveMemory > freeMemory && bufferedMs > 2000) { - // We don't have enough memory in reserve so we stop buffering to allow other - // components to use it instead + // We don't have enough memory in reserve so we stop buffering to allow other components to use it instead return false; } if (runtime.freeMemory() == 0) { @@ -666,13 +645,13 @@ public class ReactExoplayerView extends FrameLayout implements // Initialize core configuration and listeners initializePlayerCore(self); pipListenerUnsubscribe = PictureInPictureUtil.addLifecycleEventListener(themedReactContext, this); - PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, - this.enterPictureInPictureOnLeave); + PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, this.enterPictureInPictureOnLeave); } if (!source.isLocalAssetFile() && !source.isAsset() && source.getBufferConfig().getCacheSize() > 0) { RNVSimpleCache.INSTANCE.setSimpleCache( this.getContext(), - source.getBufferConfig().getCacheSize()); + source.getBufferConfig().getCacheSize() + ); useCache = true; } else { useCache = false; @@ -680,8 +659,7 @@ public class ReactExoplayerView extends FrameLayout implements if (playerNeedsSource) { // Will force display of shutter view if needed exoPlayerView.invalidateAspectRatio(); - // DRM session manager creation must be done on a different thread to prevent - // crashes so we start a new thread + // DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread ExecutorService es = Executors.newSingleThreadExecutor(); es.execute(() -> { // DRM initialization must run on a different thread @@ -690,8 +668,7 @@ public class ReactExoplayerView extends FrameLayout implements } if (activity == null) { DebugLog.e(TAG, "Failed to initialize Player!, null activity"); - eventEmitter.onVideoError.invoke("Failed to initialize Player!", - new Exception("Current Activity is null!"), "1001"); + eventEmitter.onVideoError.invoke("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001"); return; } @@ -744,7 +721,8 @@ public class ReactExoplayerView extends FrameLayout implements DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); RNVLoadControl loadControl = new RNVLoadControl( allocator, - source.getBufferConfig()); + source.getBufferConfig() + ); long initialBitrate = source.getBufferConfig().getInitialBitrate(); if (initialBitrate > 0) { @@ -752,10 +730,11 @@ public class ReactExoplayerView extends FrameLayout implements this.bandwidthMeter = config.getBandwidthMeter(); } - DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext()) - .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER) - .setEnableDecoderFallback(true) - .forceEnableMediaCodecAsynchronousQueueing(); + DefaultRenderersFactory renderersFactory = + new DefaultRenderersFactory(getContext()) + .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF) + .setEnableDecoderFallback(true) + .forceEnableMediaCodecAsynchronousQueueing(); DefaultMediaSourceFactory mediaSourceFactory; @@ -764,13 +743,11 @@ public class ReactExoplayerView extends FrameLayout implements } else { mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory); - mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, - exoPlayerView.getPlayerView()); + mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView.getPlayerView()); } if (useCache && !disableCache) { - mediaSourceFactory - .setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); + mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); } player = new ExoPlayer.Builder(getContext(), renderersFactory) @@ -795,7 +772,7 @@ public class ReactExoplayerView extends FrameLayout implements player.setPlaybackParameters(params); changeAudioOutput(this.audioOutput); - if (showNotificationControls) { + if(showNotificationControls) { setupPlaybackService(); } } @@ -807,7 +784,8 @@ public class ReactExoplayerView extends FrameLayout implements Uri adTagUrl = adProps.getAdTagUrl(); if (adTagUrl != null) { // Create an AdsLoader. - ImaAdsLoader.Builder imaLoaderBuilder = new ImaAdsLoader.Builder(themedReactContext) + ImaAdsLoader.Builder imaLoaderBuilder = new ImaAdsLoader + .Builder(themedReactContext) .setAdEventListener(this) .setAdErrorListener(this); @@ -839,8 +817,7 @@ public class ReactExoplayerView extends FrameLayout implements } try { - // First check if there's a custom DRM manager registered through the plugin - // system + // First check if there's a custom DRM manager registered through the plugin system DRMManagerSpec drmManager = ReactNativeVideoManager.Companion.getInstance().getDRMManager(); if (drmManager == null) { // If no custom manager is registered, use the default implementation @@ -849,13 +826,11 @@ public class ReactExoplayerView extends FrameLayout implements DrmSessionManager drmSessionManager = drmManager.buildDrmSessionManager(uuid, drmProps); if (drmSessionManager == null) { - eventEmitter.onVideoError.invoke("Failed to build DRM session manager", - new Exception("DRM session manager is null"), "3007"); + eventEmitter.onVideoError.invoke("Failed to build DRM session manager", new Exception("DRM session manager is null"), "3007"); } // Allow plugins to override the DrmSessionManager - DrmSessionManager overriddenManager = ReactNativeVideoManager.Companion.getInstance() - .overrideDrmSessionManager(source, drmSessionManager); + DrmSessionManager overriddenManager = ReactNativeVideoManager.Companion.getInstance().overrideDrmSessionManager(source, drmSessionManager); return overriddenManager != null ? overriddenManager : drmSessionManager; } catch (UnsupportedDrmException ex) { // Unsupported DRM exceptions are handled by the calling method @@ -878,8 +853,7 @@ public class ReactExoplayerView extends FrameLayout implements } /// init DRM DrmSessionManager drmSessionManager = initializePlayerDrm(); - if (drmSessionManager == null && runningSource.getDrmProps() != null - && runningSource.getDrmProps().getDrmType() != null) { + if (drmSessionManager == null && runningSource.getDrmProps() != null && runningSource.getDrmProps().getDrmType() != null) { // Failed to initialize DRM session manager - cannot continue DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!"); return; @@ -936,8 +910,7 @@ public class ReactExoplayerView extends FrameLayout implements } catch (UnsupportedDrmException e) { int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME - ? R.string.error_drm_unsupported_scheme - : R.string.error_drm_unknown); + ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003"); } } @@ -982,8 +955,7 @@ public class ReactExoplayerView extends FrameLayout implements if (playbackServiceBinder != null) { playbackServiceBinder.getService().unregisterPlayer(player); } - } catch (Exception ignored) { - } + } catch (Exception ignored) {} playbackServiceBinder = null; } @@ -1015,22 +987,21 @@ public class ReactExoplayerView extends FrameLayout implements private void cleanupPlaybackService() { try { - if (player != null && playbackServiceBinder != null) { + if(player != null && playbackServiceBinder != null) { playbackServiceBinder.getService().unregisterPlayer(player); } playbackServiceBinder = null; - if (playbackServiceConnection != null) { + if(playbackServiceConnection != null) { themedReactContext.unbindService(playbackServiceConnection); } - } catch (Exception e) { + } catch(Exception e) { DebugLog.w(TAG, "Cloud not cleanup playback service"); } } - private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager, - long cropStartMs, long cropEndMs) { + private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager, long cropStartMs, long cropEndMs) { if (uri == null) { throw new IllegalStateException("Invalid video uri"); } @@ -1062,12 +1033,12 @@ public class ReactExoplayerView extends FrameLayout implements Uri adTagUrl = source.getAdsProps().getAdTagUrl(); if (adTagUrl != null) { mediaItemBuilder.setAdsConfiguration( - new MediaItem.AdsConfiguration.Builder(adTagUrl).build()); + new MediaItem.AdsConfiguration.Builder(adTagUrl).build() + ); } } - MediaItem.LiveConfiguration.Builder liveConfiguration = ConfigurationUtils - .getLiveConfiguration(source.getBufferConfig()); + MediaItem.LiveConfiguration.Builder liveConfiguration = ConfigurationUtils.getLiveConfiguration(source.getBufferConfig()); mediaItemBuilder.setLiveConfiguration(liveConfiguration.build()); MediaSource.Factory mediaSourceFactory; @@ -1079,26 +1050,29 @@ public class ReactExoplayerView extends FrameLayout implements drmProvider = new DefaultDrmSessionManagerProvider(); } + switch (type) { case CONTENT_TYPE_SS: - if (!BuildConfig.USE_EXOPLAYER_SMOOTH_STREAMING) { + if(!BuildConfig.USE_EXOPLAYER_SMOOTH_STREAMING) { DebugLog.e("Exo Player Exception", "Smooth Streaming is not enabled!"); throw new IllegalStateException("Smooth Streaming is not enabled!"); } mediaSourceFactory = new SsMediaSource.Factory( new DefaultSsChunkSource.Factory(mediaDataSourceFactory), - buildDataSourceFactory(false)); + buildDataSourceFactory(false) + ); break; case CONTENT_TYPE_DASH: - if (!BuildConfig.USE_EXOPLAYER_DASH) { + if(!BuildConfig.USE_EXOPLAYER_DASH) { DebugLog.e("Exo Player Exception", "DASH is not enabled!"); throw new IllegalStateException("DASH is not enabled!"); } mediaSourceFactory = new DashMediaSource.Factory( new DefaultDashChunkSource.Factory(mediaDataSourceFactory), - buildDataSourceFactory(false)); + buildDataSourceFactory(false) + ); break; case CONTENT_TYPE_HLS: if (!BuildConfig.USE_EXOPLAYER_HLS) { @@ -1113,14 +1087,13 @@ public class ReactExoplayerView extends FrameLayout implements } mediaSourceFactory = new HlsMediaSource.Factory( - dataSourceFactory) - .setAllowChunklessPreparation(source.getTextTracksAllowChunklessPreparation()); + dataSourceFactory + ).setAllowChunklessPreparation(source.getTextTracksAllowChunklessPreparation()); break; case CONTENT_TYPE_OTHER: if ("asset".equals(uri.getScheme())) { try { - DataSource.Factory assetDataSourceFactory = DataSourceUtil - .buildAssetDataSourceFactory(themedReactContext, uri); + DataSource.Factory assetDataSourceFactory = DataSourceUtil.buildAssetDataSourceFactory(themedReactContext, uri); mediaSourceFactory = new ProgressiveMediaSource.Factory(assetDataSourceFactory); } catch (Exception e) { throw new IllegalStateException("cannot open input file:" + uri); @@ -1128,10 +1101,12 @@ public class ReactExoplayerView extends FrameLayout implements } else if ("file".equals(uri.getScheme()) || !useCache) { mediaSourceFactory = new ProgressiveMediaSource.Factory( - mediaDataSourceFactory); + mediaDataSourceFactory + ); } else { mediaSourceFactory = new ProgressiveMediaSource.Factory( - RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); + RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true)) + ); } break; @@ -1150,19 +1125,20 @@ public class ReactExoplayerView extends FrameLayout implements if (cmcdConfigurationFactory != null) { mediaSourceFactory = mediaSourceFactory.setCmcdConfigurationFactory( - cmcdConfigurationFactory::createCmcdConfiguration); + cmcdConfigurationFactory::createCmcdConfiguration + ); } mediaSourceFactory = Objects.requireNonNullElse( ReactNativeVideoManager.Companion.getInstance() .overrideMediaSourceFactory(source, mediaSourceFactory, mediaDataSourceFactory), - mediaSourceFactory); + mediaSourceFactory + ); mediaItemBuilder.setStreamKeys(streamKeys); @Nullable - final MediaItem.Builder overridenMediaItemBuilder = ReactNativeVideoManager.Companion.getInstance() - .overrideMediaItemBuilder(source, mediaItemBuilder); + final MediaItem.Builder overridenMediaItemBuilder = ReactNativeVideoManager.Companion.getInstance().overrideMediaItemBuilder(source, mediaItemBuilder); MediaItem mediaItem = overridenMediaItemBuilder != null ? overridenMediaItemBuilder.build() @@ -1171,7 +1147,8 @@ public class ReactExoplayerView extends FrameLayout implements MediaSource mediaSource = mediaSourceFactory .setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(source.getMinLoadRetryCount())) + config.buildLoadErrorHandlingPolicy(source.getMinLoadRetryCount()) + ) .createMediaSource(mediaItem); if (cropStartMs >= 0 && cropEndMs >= 0) { @@ -1206,8 +1183,7 @@ public class ReactExoplayerView extends FrameLayout implements } } - MediaItem.SubtitleConfiguration.Builder configBuilder = new MediaItem.SubtitleConfiguration.Builder( - track.getUri()) + MediaItem.SubtitleConfiguration.Builder configBuilder = new MediaItem.SubtitleConfiguration.Builder(track.getUri()) .setId(trackId) .setMimeType(track.getType()) .setLabel(label) @@ -1218,8 +1194,7 @@ public class ReactExoplayerView extends FrameLayout implements configBuilder.setLanguage(track.getLanguage()); } - // Set selection flags - make first track default if no specific track is - // selected + // Set selection flags - make first track default if no specific track is selected if (trackIndex == 0 && (textTrackType == null || "disabled".equals(textTrackType))) { configBuilder.setSelectionFlags(C.SELECTION_FLAG_DEFAULT); } else { @@ -1229,12 +1204,10 @@ public class ReactExoplayerView extends FrameLayout implements MediaItem.SubtitleConfiguration subtitleConfiguration = configBuilder.build(); subtitleConfigurations.add(subtitleConfiguration); - DebugLog.d(TAG, - "Created subtitle configuration: " + trackId + " - " + label + " (" + track.getType() + ")"); + DebugLog.d(TAG, "Created subtitle configuration: " + trackId + " - " + label + " (" + track.getType() + ")"); trackIndex++; } catch (Exception e) { - DebugLog.e(TAG, - "Error creating SubtitleConfiguration for URI " + track.getUri() + ": " + e.getMessage()); + DebugLog.e(TAG, "Error creating SubtitleConfiguration for URI " + track.getUri() + ": " + e.getMessage()); } } @@ -1247,7 +1220,7 @@ public class ReactExoplayerView extends FrameLayout implements private void releasePlayer() { if (player != null) { - if (playbackServiceBinder != null) { + if(playbackServiceBinder != null) { playbackServiceBinder.getService().unregisterPlayer(player); themedReactContext.unbindService(playbackServiceConnection); } @@ -1303,8 +1276,7 @@ public class ReactExoplayerView extends FrameLayout implements case AudioManager.AUDIOFOCUS_LOSS: view.hasAudioFocus = false; view.eventEmitter.onAudioFocusChanged.invoke(false); - // FIXME this pause can cause issue if content doesn't have pause capability - // (can happen on live channel) + // FIXME this pause can cause issue if content doesn't have pause capability (can happen on live channel) if (activity != null) { activity.runOnUiThread(view::pausePlayback); } @@ -1325,12 +1297,16 @@ public class ReactExoplayerView extends FrameLayout implements if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { // Lower the volume if (!view.muted) { - activity.runOnUiThread(() -> view.player.setVolume(view.audioVolume * 0.8f)); + activity.runOnUiThread(() -> + view.player.setVolume(view.audioVolume * 0.8f) + ); } } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { // Raise it back to normal if (!view.muted) { - activity.runOnUiThread(() -> view.player.setVolume(view.audioVolume * 1)); + activity.runOnUiThread(() -> + view.player.setVolume(view.audioVolume * 1) + ); } } } @@ -1403,8 +1379,7 @@ public class ReactExoplayerView extends FrameLayout implements /** * Returns a new DataSource factory. * - * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener - * to the new + * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new * DataSource factory. * @return A new DataSource factory. */ @@ -1416,14 +1391,12 @@ public class ReactExoplayerView extends FrameLayout implements /** * Returns a new HttpDataSource factory. * - * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener - * to the new - * DataSource factory. + * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new + * DataSource factory. * @return A new HttpDataSource factory. */ private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) { - return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, - useBandwidthMeter ? bandwidthMeter : null, source.getHeaders()); + return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, useBandwidthMeter ? bandwidthMeter : null, source.getHeaders()); } // AudioBecomingNoisyListener implementation @@ -1440,13 +1413,11 @@ public class ReactExoplayerView extends FrameLayout implements @Override public void onEvents(@NonNull Player player, Player.Events events) { - if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) - || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) { + if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) { int playbackState = player.getPlaybackState(); boolean playWhenReady = player.getPlayWhenReady(); String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState="; - eventEmitter.onPlaybackRateChange - .invoke(playWhenReady && playbackState == ExoPlayer.STATE_READY ? 1.0f : 0.0f); + eventEmitter.onPlaybackRateChange.invoke(playWhenReady && playbackState == ExoPlayer.STATE_READY ? 1.0f : 0.0f); switch (playbackState) { case Player.STATE_IDLE: text += "idle"; @@ -1503,11 +1474,9 @@ public class ReactExoplayerView extends FrameLayout implements } /** - * The progress message handler will duplicate recursions of the - * onProgressMessage handler - * on change of player state from any state to STATE_READY with playWhenReady is - * true (when - * the video is not paused). This clears all existing messages. + * The progress message handler will duplicate recursions of the onProgressMessage handler + * on change of player state from any state to STATE_READY with playWhenReady is true (when + * the video is not paused). This clears all existing messages. */ private void clearProgressMessageHandler() { progressHandler.removeMessages(SHOW_PROGRESS); @@ -1526,8 +1495,7 @@ public class ReactExoplayerView extends FrameLayout implements setSelectedTextTrack(textTrackType, textTrackValue); } Format videoFormat = player.getVideoFormat(); - boolean isRotatedContent = videoFormat != null - && (videoFormat.rotationDegrees == 90 || videoFormat.rotationDegrees == 270); + boolean isRotatedContent = videoFormat != null && (videoFormat.rotationDegrees == 90 || videoFormat.rotationDegrees == 270); int width = videoFormat != null ? (isRotatedContent ? videoFormat.height : videoFormat.width) : 0; int height = videoFormat != null ? (isRotatedContent ? videoFormat.width : videoFormat.height) : 0; String trackId = videoFormat != null ? videoFormat.id : null; @@ -1536,19 +1504,18 @@ public class ReactExoplayerView extends FrameLayout implements long duration = player.getDuration(); long currentPosition = player.getCurrentPosition(); ArrayList audioTracks = getAudioTrackInfo(); - ArrayList textTracks = getTextTrackInfo(); + ArrayList textTracks = getTextTrackInfo(); if (source.getContentStartTime() != -1) { 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 + // To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done ArrayList videoTracks = getVideoTrackInfoFromManifest(); if (videoTracks != null) { isUsingContentResolution = true; } eventEmitter.onVideoLoad.invoke(duration, currentPosition, width, height, - audioTracks, textTracks, videoTracks, trackId); + audioTracks, textTracks, videoTracks, trackId ); updateSubtitleButtonVisibility(); }); @@ -1566,9 +1533,9 @@ public class ReactExoplayerView extends FrameLayout implements } private static boolean isTrackSelected(TrackSelection selection, TrackGroup group, - int trackIndex) { + int trackIndex){ return selection != null && selection.getTrackGroup() == group - && selection.indexOf(trackIndex) != C.INDEX_UNSET; + && selection.indexOf( trackIndex ) != C.INDEX_UNSET; } private ArrayList getAudioTrackInfo() { @@ -1586,6 +1553,7 @@ public class ReactExoplayerView extends FrameLayout implements TrackSelectionArray selectionArray = player.getCurrentTrackSelections(); TrackSelection selection = selectionArray.get(C.TRACK_TYPE_AUDIO); + for (int groupIndex = 0; groupIndex < groups.length; ++groupIndex) { TrackGroup group = groups.get(groupIndex); Format format = group.getFormat(0); @@ -1611,8 +1579,7 @@ public class ReactExoplayerView extends FrameLayout implements videoTrack.setHeight(format.height == Format.NO_VALUE ? 0 : format.height); videoTrack.setBitrate(format.bitrate == Format.NO_VALUE ? 0 : format.bitrate); videoTrack.setRotation(format.rotationDegrees); - if (format.codecs != null) - videoTrack.setCodecs(format.codecs); + if (format.codecs != null) videoTrack.setCodecs(format.codecs); videoTrack.setTrackId(format.id == null ? String.valueOf(trackIndex) : format.id); videoTrack.setIndex(trackIndex); return videoTrack; @@ -1649,8 +1616,7 @@ public class ReactExoplayerView extends FrameLayout implements return this.getVideoTrackInfoFromManifest(0); } - // We need retry count to in case where minefest request fails from poor network - // conditions + // We need retry count to in case where minefest request fails from poor network conditions @WorkerThread private ArrayList getVideoTrackInfoFromManifest(int retryCount) { ExecutorService es = Executors.newSingleThreadExecutor(); @@ -1665,20 +1631,18 @@ public class ReactExoplayerView extends FrameLayout implements public ArrayList call() { ArrayList videoTracks = new ArrayList<>(); - try { + try { DashManifest manifest = DashUtil.loadManifest(this.ds, this.uri); int periodCount = manifest.getPeriodCount(); for (int i = 0; i < periodCount; i++) { Period period = manifest.getPeriod(i); - for (int adaptationIndex = 0; adaptationIndex < period.adaptationSets - .size(); adaptationIndex++) { + for (int adaptationIndex = 0; adaptationIndex < period.adaptationSets.size(); adaptationIndex++) { AdaptationSet adaptation = period.adaptationSets.get(adaptationIndex); if (adaptation.type != C.TRACK_TYPE_VIDEO) { continue; } boolean hasFoundContentPeriod = false; - for (int representationIndex = 0; representationIndex < adaptation.representations - .size(); representationIndex++) { + for (int representationIndex = 0; representationIndex < adaptation.representations.size(); representationIndex++) { Representation representation = adaptation.representations.get(representationIndex); Format format = representation.format; if (isFormatSupported(format)) { @@ -1686,8 +1650,7 @@ public class ReactExoplayerView extends FrameLayout implements break; } hasFoundContentPeriod = true; - VideoTrack videoTrack = exoplayerVideoTrackToGenericVideoTrack(format, - representationIndex); + VideoTrack videoTrack = exoplayerVideoTrackToGenericVideoTrack(format, representationIndex); videoTracks.add(videoTrack); } } @@ -1717,16 +1680,12 @@ public class ReactExoplayerView extends FrameLayout implements return null; } - private Track exoplayerTrackToGenericTrack(Format format, int trackIndex, TrackSelection selection, - TrackGroup group) { + private Track exoplayerTrackToGenericTrack(Format format, int trackIndex, TrackSelection selection, TrackGroup group) { Track track = new Track(); track.setIndex(trackIndex); - if (format.sampleMimeType != null) - track.setMimeType(format.sampleMimeType); - if (format.language != null) - track.setLanguage(format.language); - if (format.label != null) - track.setTitle(format.label); + if (format.sampleMimeType != null) track.setMimeType(format.sampleMimeType); + if (format.language != null) track.setLanguage(format.language); + if (format.label != null) track.setTitle(format.label); track.setSelected(isTrackSelected(selection, group, trackIndex)); return track; } @@ -1796,8 +1755,7 @@ public class ReactExoplayerView extends FrameLayout implements track.setLanguage(format.language != null ? format.language : "unknown"); track.setTitle(format.label != null ? format.label : "Track " + (groupIndex + 1)); track.setSelected(false); // Don't report selection status - let PlayerView handle it - if (format.sampleMimeType != null) - track.setMimeType(format.sampleMimeType); + if (format.sampleMimeType != null) track.setMimeType(format.sampleMimeType); track.setBitrate(format.bitrate == Format.NO_VALUE ? 0 : format.bitrate); tracks.add(track); @@ -1828,10 +1786,8 @@ public class ReactExoplayerView extends FrameLayout implements Track textTrack = new Track(); textTrack.setIndex(textTracks.size()); - if (format.sampleMimeType != null) - textTrack.setMimeType(format.sampleMimeType); - if (format.language != null) - textTrack.setLanguage(format.language); + if (format.sampleMimeType != null) textTrack.setMimeType(format.sampleMimeType); + if (format.language != null) textTrack.setLanguage(format.language); boolean isExternal = format.id != null && format.id.startsWith("external-subtitle-"); @@ -1865,34 +1821,28 @@ public class ReactExoplayerView extends FrameLayout implements } @Override - public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, - @NonNull Player.PositionInfo newPosition, @Player.DiscontinuityReason int reason) { + public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, @Player.DiscontinuityReason int reason) { if (reason == Player.DISCONTINUITY_REASON_SEEK) { isSeeking = true; seekPosition = newPosition.positionMs; if (isUsingContentResolution) { - // We need to update the selected track to make sure that it still matches user - // selection if track list has changed in this period + // We need to update the selected track to make sure that it still matches user selection if track list has changed in this period setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); } } if (playerNeedsSource) { - // This will only occur if the user has performed a seek whilst in the error - // state. Update the - // resume position so that if the user then retries, playback will resume from - // the position to + // This will only occur if the user has performed a seek whilst in the error state. Update the + // resume position so that if the user then retries, playback will resume from the position to // which they seeked. updateResumePosition(); } if (isUsingContentResolution) { - // Discontinuity events might have a different track list so we update the - // selected track + // Discontinuity events might have a different track list so we update the selected track setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); selectTrackWhenReady = true; } - // When repeat is turned on, reaching the end of the video will not cause a - // state change + // When repeat is turned on, reaching the end of the video will not cause a state change // so we need to explicitly detect it. if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION && player.getRepeatMode() == Player.REPEAT_MODE_ONE) { @@ -1940,17 +1890,15 @@ public class ReactExoplayerView extends FrameLayout implements updateSubtitleButtonVisibility(); } + private boolean hasBuiltInTextTracks() { - if (player == null || trackSelector == null) - return false; + if (player == null || trackSelector == null) return false; MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo(); - if (info == null) - return false; + if (info == null) return false; int textRendererIndex = getTrackRendererIndex(C.TRACK_TYPE_TEXT); - if (textRendererIndex == C.INDEX_UNSET) - return false; + if (textRendererIndex == C.INDEX_UNSET) return false; TrackGroupArray groups = info.getTrackGroups(textRendererIndex); @@ -1970,12 +1918,11 @@ public class ReactExoplayerView extends FrameLayout implements } private void updateSubtitleButtonVisibility() { - if (exoPlayerView == null) - return; + if (exoPlayerView == null) return; boolean hasTextTracks = (source.getSideLoadedTextTracks() != null && - !source.getSideLoadedTextTracks().getTracks().isEmpty()) || - hasBuiltInTextTracks(); + !source.getSideLoadedTextTracks().getTracks().isEmpty()) || + hasBuiltInTextTracks(); exoPlayerView.setShowSubtitleButton(hasTextTracks); } @@ -1995,8 +1942,7 @@ public class ReactExoplayerView extends FrameLayout implements if (isPlaying && isSeeking) { eventEmitter.onVideoSeek.invoke(player.getCurrentPosition(), seekPosition); } - PictureInPictureUtil.applyPlayingStatus(themedReactContext, pictureInPictureParamsBuilder, - pictureInPictureReceiver, !isPlaying); + PictureInPictureUtil.applyPlayingStatus(themedReactContext, pictureInPictureParamsBuilder, pictureInPictureReceiver, !isPlaying); eventEmitter.onVideoPlaybackStateChanged.invoke(isPlaying, isSeeking); if (isPlaying) { @@ -2008,15 +1954,14 @@ public class ReactExoplayerView extends FrameLayout implements public void onPlayerError(@NonNull PlaybackException e) { String errorString = "ExoPlaybackException: " + PlaybackException.getErrorCodeName(e.errorCode); String errorCode = "2" + e.errorCode; - switch (e.errorCode) { + switch(e.errorCode) { case PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED: case PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED: case PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED: case PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR: 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 + // 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 hasDrmFailed = true; playerNeedsSource = true; updateResumePosition(); @@ -2098,16 +2043,14 @@ public class ReactExoplayerView extends FrameLayout implements boolean isSourceEqual = source.isEquals(this.source); hasDrmFailed = false; this.source = source; - final DataSource.Factory tmpMediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory( - this.themedReactContext, bandwidthMeter, - source.getHeaders()); + final DataSource.Factory tmpMediaDataSourceFactory = + DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter, + source.getHeaders()); @Nullable - final DataSource.Factory overriddenMediaDataSourceFactory = ReactNativeVideoManager.Companion.getInstance() - .overrideMediaDataSourceFactory(source, tmpMediaDataSourceFactory); + final DataSource.Factory overriddenMediaDataSourceFactory = ReactNativeVideoManager.Companion.getInstance().overrideMediaDataSourceFactory(source, tmpMediaDataSourceFactory); - this.mediaDataSourceFactory = Objects.requireNonNullElse(overriddenMediaDataSourceFactory, - tmpMediaDataSourceFactory); + this.mediaDataSourceFactory = Objects.requireNonNullElse(overriddenMediaDataSourceFactory, tmpMediaDataSourceFactory); if (source.getCmcdProps() != null) { CMCDConfig cmcdConfig = new CMCDConfig(source.getCmcdProps()); @@ -2126,7 +2069,6 @@ public class ReactExoplayerView extends FrameLayout implements clearSrc(); } } - public void clearSrc() { if (source.getUri() != null) { if (player != null) { @@ -2175,8 +2117,7 @@ public class ReactExoplayerView extends FrameLayout implements } public void disableTrack(int rendererIndex) { - if (trackSelector == null) - return; + if (trackSelector == null) return; DefaultTrackSelector.Parameters disableParameters = trackSelector.getParameters() .buildUpon() @@ -2186,8 +2127,7 @@ public class ReactExoplayerView extends FrameLayout implements } private void selectTextTrackInternal(String type, String value) { - if (player == null || trackSelector == null) - return; + if (player == null || trackSelector == null) return; DebugLog.d(TAG, "selectTextTrackInternal: type=" + type + ", value=" + value); @@ -2207,11 +2147,6 @@ public class ReactExoplayerView extends FrameLayout implements TrackGroupArray groups = info.getTrackGroups(textRendererIndex); boolean trackFound = false; - // react-native-video uses a flattened `textTracks` list on the JS side. - // For HLS/DASH, each TrackGroup often contains a single track at index 0, - // so comparing against `trackIndex` alone makes only the first subtitle selectable. - int flattenedIndex = 0; - for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { TrackGroup group = groups.get(groupIndex); for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { @@ -2224,28 +2159,25 @@ public class ReactExoplayerView extends FrameLayout implements isMatch = true; } else if ("index".equals(type)) { int targetIndex = ReactBridgeUtils.safeParseInt(value, -1); - if (targetIndex == flattenedIndex) { + if (targetIndex == trackIndex) { isMatch = true; } } - flattenedIndex++; - if (isMatch) { TrackSelectionOverride override = new TrackSelectionOverride(group, - java.util.Arrays.asList(trackIndex)); + java.util.Arrays.asList(trackIndex)); parametersBuilder.addOverride(override); trackFound = true; break; } } - if (trackFound) - break; + if (trackFound) break; } if (!trackFound) { DebugLog.w(TAG, "Text track not found for type=" + type + ", value=" + value + - ". Keeping current selection."); + ". Keeping current selection."); } } } @@ -2266,8 +2198,7 @@ public class ReactExoplayerView extends FrameLayout implements } public void setSelectedTrack(int trackType, String type, String value) { - if (player == null || trackSelector == null) - return; + if (player == null || trackSelector == null) return; if (controls) { return; @@ -2341,11 +2272,9 @@ public class ReactExoplayerView extends FrameLayout implements usingExactMatch = true; break; } else if (isUsingContentResolution) { - // When using content resolution rather than ads, we need to try and find the - // closest match if there is no exact match + // When using content resolution rather than ads, we need to try and find the closest match if there is no exact match if (closestFormat != null) { - if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height) - && format.height < height) { + if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height) && format.height < height) { // Higher quality match closestFormat = format; closestTrackIndex = j; @@ -2356,8 +2285,7 @@ public class ReactExoplayerView extends FrameLayout implements } } } - // This is a fallback if the new period contains only higher resolutions than - // the user has selected + // This is a fallback if the new period contains only higher resolutions than the user has selected if (closestFormat == null && isUsingContentResolution && !usingExactMatch) { // No close match found - so we pick the lowest quality int minHeight = Integer.MAX_VALUE; @@ -2380,8 +2308,8 @@ public class ReactExoplayerView extends FrameLayout implements } } else if (trackType == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default // Use system settings if possible - CaptioningManager captioningManager = (CaptioningManager) themedReactContext - .getSystemService(Context.CAPTIONING_SERVICE); + CaptioningManager captioningManager + = (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); if (captioningManager != null && captioningManager.isEnabled()) { groupIndex = getGroupIndexForDefaultLocale(groups); } @@ -2410,7 +2338,7 @@ public class ReactExoplayerView extends FrameLayout implements // With only one tracks we can't remove any tracks so attempt to play it anyway tracks = allTracks; } else { - tracks = new ArrayList<>(supportedFormatLength + 1); + tracks = new ArrayList<>(supportedFormatLength + 1); for (int k = 0; k < allTracks.size(); k++) { Format format = group.getFormat(k); if (isFormatSupported(format)) { @@ -2436,8 +2364,7 @@ public class ReactExoplayerView extends FrameLayout implements .setRendererDisabled(rendererIndex, false); // Clear existing overrides for this track type to avoid conflicts - // But be careful with audio tracks - don't clear unless explicitly selecting a - // different track + // But be careful with audio tracks - don't clear unless explicitly selecting a different track if (trackType != C.TRACK_TYPE_AUDIO || !type.equals("default")) { selectionParameters.clearOverridesOfType(selectionOverride.getType()); } @@ -2453,7 +2380,7 @@ public class ReactExoplayerView extends FrameLayout implements selectionParameters.setForceHighestSupportedBitrate(false); selectionParameters.setForceLowestBitrate(false); DebugLog.d(TAG, "Audio track selection: group=" + groupIndex + ", tracks=" + tracks + - ", override=" + selectionOverride); + ", override=" + selectionOverride); } trackSelector.setParameters(selectionParameters.build()); @@ -2484,7 +2411,7 @@ public class ReactExoplayerView extends FrameLayout implements } private int getGroupIndexForDefaultLocale(TrackGroupArray groups) { - if (groups.length == 0) { + if (groups.length == 0){ return C.INDEX_UNSET; } @@ -2505,8 +2432,7 @@ public class ReactExoplayerView extends FrameLayout implements public void setSelectedVideoTrack(String type, String value) { videoTrackType = type; videoTrackValue = value; - if (!loadVideoStarted) - setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); + if (!loadVideoStarted) setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); } public void setSelectedAudioTrack(String type, String value) { @@ -2537,11 +2463,9 @@ public class ReactExoplayerView extends FrameLayout implements } public void setEnterPictureInPictureOnLeave(boolean enterPictureInPictureOnLeave) { - this.enterPictureInPictureOnLeave = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N - && enterPictureInPictureOnLeave; + this.enterPictureInPictureOnLeave = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && enterPictureInPictureOnLeave; if (player != null) { - PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, - this.enterPictureInPictureOnLeave); + PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, this.enterPictureInPictureOnLeave); } } @@ -2549,14 +2473,12 @@ public class ReactExoplayerView extends FrameLayout implements eventEmitter.onPictureInPictureStatusChanged.invoke(isInPictureInPicture); if (fullScreenPlayerView != null && fullScreenPlayerView.isShowing()) { - if (isInPictureInPicture) - fullScreenPlayerView.hideWithoutPlayer(); + if (isInPictureInPicture) fullScreenPlayerView.hideWithoutPlayer(); return; } Activity currentActivity = themedReactContext.getCurrentActivity(); - if (currentActivity == null) - return; + if (currentActivity == null) return; View decorView = currentActivity.getWindow().getDecorView(); ViewGroup rootView = decorView.findViewById(android.R.id.content); @@ -2566,7 +2488,7 @@ public class ReactExoplayerView extends FrameLayout implements LayoutParams.MATCH_PARENT); if (isInPictureInPicture) { - ViewGroup parent = (ViewGroup) exoPlayerView.getParent(); + ViewGroup parent = (ViewGroup)exoPlayerView.getParent(); if (parent != null) { parent.removeView(exoPlayerView); } @@ -2592,12 +2514,10 @@ public class ReactExoplayerView extends FrameLayout implements public void enterPictureInPictureMode() { PictureInPictureParams _pipParams = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - ArrayList actions = PictureInPictureUtil.getPictureInPictureActions(themedReactContext, - isPaused, pictureInPictureReceiver); + ArrayList actions = PictureInPictureUtil.getPictureInPictureActions(themedReactContext, isPaused, pictureInPictureReceiver); pictureInPictureParamsBuilder.setActions(actions); if (player.getPlaybackState() == Player.STATE_READY) { - pictureInPictureParamsBuilder - .setAspectRatio(PictureInPictureUtil.calcPictureInPictureAspectRatio(player)); + pictureInPictureParamsBuilder.setAspectRatio(PictureInPictureUtil.calcPictureInPictureAspectRatio(player)); } _pipParams = pictureInPictureParamsBuilder.build(); } @@ -2606,15 +2526,13 @@ public class ReactExoplayerView extends FrameLayout implements public void exitPictureInPictureMode() { Activity currentActivity = themedReactContext.getCurrentActivity(); - if (currentActivity == null) - return; + if (currentActivity == null) return; View decorView = currentActivity.getWindow().getDecorView(); ViewGroup rootView = decorView.findViewById(android.R.id.content); if (!rootViewChildrenOriginalVisibility.isEmpty()) { - if (exoPlayerView.getParent().equals(rootView)) - rootView.removeView(exoPlayerView); + if (exoPlayerView.getParent().equals(rootView)) rootView.removeView(exoPlayerView); for (int i = 0; i < rootView.getChildCount(); i++) { rootView.getChildAt(i).setVisibility(rootViewChildrenOriginalVisibility.get(i)); } @@ -2712,7 +2630,7 @@ public class ReactExoplayerView extends FrameLayout implements if (playbackServiceConnection == null && showNotificationControls) { setupPlaybackService(); - } else if (!showNotificationControls && playbackServiceConnection != null) { + } else if(!showNotificationControls && playbackServiceConnection != null) { cleanupPlaybackService(); } } @@ -2741,13 +2659,12 @@ public class ReactExoplayerView extends FrameLayout implements } if (isFullscreen) { - fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, this, null, - new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - setFullscreen(false); - } - }, controlsConfig); + fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, this, null, new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + setFullscreen(false); + } + }, controlsConfig); eventEmitter.onVideoFullscreenPlayerWillPresent.invoke(); if (fullScreenPlayerView != null) { fullScreenPlayerView.show(); @@ -2784,8 +2701,7 @@ public class ReactExoplayerView extends FrameLayout implements } @Override - public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, - @NonNull Exception e) { + public void onDrmSessionManagerError(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId, @NonNull Exception e) { DebugLog.d("DRM Info", "onDrmSessionManagerError"); eventEmitter.onVideoError.invoke("onDrmSessionManagerError", e, "3002"); } @@ -2803,7 +2719,7 @@ public class ReactExoplayerView extends FrameLayout implements /** * Handling controls prop * - * @param controls Controls prop, if true enable controls, if false disable them + * @param controls Controls prop, if true enable controls, if false disable them */ public void setControls(boolean controls) { this.controls = controls; @@ -2812,7 +2728,7 @@ public class ReactExoplayerView extends FrameLayout implements // Additional configuration for proper touch handling if (controls) { exoPlayerView.setControllerAutoShow(true); - exoPlayerView.setControllerHideOnTouch(true); // Show controls on touch, don't hide + exoPlayerView.setControllerHideOnTouch(true); // Show controls on touch, don't hide exoPlayerView.setControllerShowTimeoutMs(5000); } } @@ -2845,7 +2761,8 @@ public class ReactExoplayerView extends FrameLayout implements Map errMap = Map.of( "message", error.getMessage(), "code", String.valueOf(error.getErrorCode()), - "type", String.valueOf(error.getErrorType())); + "type", String.valueOf(error.getErrorType()) + ); eventEmitter.onReceiveAdEvent.invoke("ERROR", errMap); handleDaiBackupStream(); @@ -2879,10 +2796,10 @@ public class ReactExoplayerView extends FrameLayout implements * @return The configured IMA server-side ad insertion AdsLoader */ private ImaServerSideAdInsertionMediaSource.AdsLoader createAdsLoader() { - ImaServerSideAdInsertionMediaSource.AdsLoader.Builder adsLoaderBuilder = new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder( - getContext(), exoPlayerView.getPlayerView()) - .setAdEventListener(this) - .setAdErrorListener(this); + ImaServerSideAdInsertionMediaSource.AdsLoader.Builder adsLoaderBuilder = + new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(getContext(), exoPlayerView.getPlayerView()) + .setAdEventListener(this) + .setAdErrorListener(this); return adsLoaderBuilder.build(); } @@ -2898,8 +2815,8 @@ public class ReactExoplayerView extends FrameLayout implements DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(getContext()); DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(dataSourceFactory); - ImaServerSideAdInsertionMediaSource.Factory adsMediaSourceFactory = new ImaServerSideAdInsertionMediaSource.Factory( - daiAdsLoader, mediaSourceFactory); + ImaServerSideAdInsertionMediaSource.Factory adsMediaSourceFactory = + new ImaServerSideAdInsertionMediaSource.Factory(daiAdsLoader, mediaSourceFactory); mediaSourceFactory.setServerSideAdInsertionMediaSourceFactory(adsMediaSourceFactory); @@ -2933,8 +2850,7 @@ public class ReactExoplayerView extends FrameLayout implements /** * Requests a DAI stream from Google IMA using the ExoPlayer IMA extension. * - * Builds an SSAI URI based on the provided parameters and sets it on the - * player. + * Builds an SSAI URI based on the provided parameters and sets it on the player. * Supports both VOD (contentSourceId + videoId) and Live (assetKey) streams. * * @param runningSource The source containing DAI properties @@ -2967,8 +2883,7 @@ public class ReactExoplayerView extends FrameLayout implements .build() .buildUpon(); } else { - throw new IllegalArgumentException( - "Either assetKey (for live) or contentSourceId+videoId (for VOD) must be provided"); + throw new IllegalArgumentException("Either assetKey (for live) or contentSourceId+videoId (for VOD) must be provided"); } Map adTagParameters = adsProps.getAdTagParameters(); @@ -2991,8 +2906,7 @@ public class ReactExoplayerView extends FrameLayout implements /** * Handles fallback to backup stream when DAI stream fails. * - * If a backup stream URI is available in the DAI properties, it cleans up DAI - * resources + * If a backup stream URI is available in the DAI properties, it cleans up DAI resources * and switches to the backup stream. * * @return true if backup stream was successfully used, false otherwise diff --git a/package-lock.json b/package-lock.json index bb70659e..3e95d031 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "crypto-js": "^4.2.0", "date-fns": "^4.1.0", "eventemitter3": "^5.0.1", - "expo": "^54", + "expo": "^54.0.33", "expo-application": "~7.0.7", "expo-auth-session": "~7.0.8", "expo-blur": "~15.0.7", @@ -132,12 +132,12 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -146,9 +146,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -185,13 +185,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -213,12 +213,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -229,17 +229,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -267,16 +267,16 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" + "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -305,13 +305,13 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -347,9 +347,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -373,14 +373,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -430,14 +430,14 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", - "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -543,12 +543,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -558,14 +558,14 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz", - "integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz", + "integrity": "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-decorators": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-decorators": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -641,12 +641,12 @@ } }, "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", - "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz", + "integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -668,12 +668,12 @@ } }, "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.27.1.tgz", - "integrity": "sha512-eBC/3KSekshx19+N40MzjWqJd7KTEdOoLesAfa4IDFI8eRz5a47i5Oszus6zG/cwIXN63YhgLOMSSNJx49sENg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.28.6.tgz", + "integrity": "sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -683,12 +683,12 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", - "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz", + "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -737,12 +737,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -884,14 +884,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -901,13 +901,13 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { @@ -918,12 +918,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", - "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -949,13 +949,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -985,13 +985,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1096,12 +1096,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", - "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1127,13 +1127,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1158,12 +1158,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1173,16 +1173,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1192,12 +1192,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1207,12 +1207,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1238,13 +1238,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1254,14 +1254,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1286,16 +1286,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", - "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-jsx": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1366,12 +1366,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1381,13 +1381,13 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz", - "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", @@ -1416,12 +1416,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1546,31 +1546,31 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -1597,9 +1597,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1700,114 +1700,6 @@ "node": ">=0.8.0" } }, - "node_modules/@expo/cli": { - "version": "54.0.19", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-54.0.19.tgz", - "integrity": "sha512-Za+Ena29uYkq2c1Lbh+r3VrooR/mW7c9dahoH4WvL1T9ttbfAeu7sJmCuWZo88bZ4bFsOpE5fYne71DK11iSrQ==", - "license": "MIT", - "dependencies": { - "@0no-co/graphql.web": "^1.0.8", - "@expo/code-signing-certificates": "^0.0.5", - "@expo/config": "~12.0.12", - "@expo/config-plugins": "~54.0.3", - "@expo/devcert": "^1.2.1", - "@expo/env": "~2.0.8", - "@expo/image-utils": "^0.8.8", - "@expo/json-file": "^10.0.8", - "@expo/metro": "~54.1.0", - "@expo/metro-config": "~54.0.11", - "@expo/osascript": "^2.3.8", - "@expo/package-manager": "^1.9.9", - "@expo/plist": "^0.4.8", - "@expo/prebuild-config": "^54.0.7", - "@expo/schema-utils": "^0.1.8", - "@expo/spawn-async": "^1.7.2", - "@expo/ws-tunnel": "^1.0.1", - "@expo/xcpretty": "^4.3.0", - "@react-native/dev-middleware": "0.81.5", - "@urql/core": "^5.0.6", - "@urql/exchange-retry": "^1.3.0", - "accepts": "^1.3.8", - "arg": "^5.0.2", - "better-opn": "~3.0.2", - "bplist-creator": "0.1.0", - "bplist-parser": "^0.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.3.0", - "compression": "^1.7.4", - "connect": "^3.7.0", - "debug": "^4.3.4", - "env-editor": "^0.4.1", - "expo-server": "^1.0.5", - "freeport-async": "^2.0.0", - "getenv": "^2.0.0", - "glob": "^13.0.0", - "lan-network": "^0.1.6", - "minimatch": "^9.0.0", - "node-forge": "^1.3.1", - "npm-package-arg": "^11.0.0", - "ora": "^3.4.0", - "picomatch": "^3.0.1", - "pretty-bytes": "^5.6.0", - "pretty-format": "^29.7.0", - "progress": "^2.0.3", - "prompts": "^2.3.2", - "qrcode-terminal": "0.11.0", - "require-from-string": "^2.0.2", - "requireg": "^0.2.2", - "resolve": "^1.22.2", - "resolve-from": "^5.0.0", - "resolve.exports": "^2.0.3", - "semver": "^7.6.0", - "send": "^0.19.0", - "slugify": "^1.3.4", - "source-map-support": "~0.5.21", - "stacktrace-parser": "^0.1.10", - "structured-headers": "^0.4.1", - "tar": "^7.5.2", - "terminal-link": "^2.1.1", - "undici": "^6.18.2", - "wrap-ansi": "^7.0.0", - "ws": "^8.12.1" - }, - "bin": { - "expo-internal": "build/bin/cli" - }, - "peerDependencies": { - "expo": "*", - "expo-router": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "expo-router": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/@expo/cli/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/cli/node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, "node_modules/@expo/code-signing-certificates": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.5.tgz", @@ -1819,13 +1711,13 @@ } }, "node_modules/@expo/config": { - "version": "12.0.12", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-12.0.12.tgz", - "integrity": "sha512-X2MW86+ulLpMGvdgfvpl2EOBAKUlwvnvoPwdaZeeyWufGopn1nTUeh4C9gMsplDaP1kXv9sLXVhOoUoX6g9PvQ==", + "version": "12.0.13", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-12.0.13.tgz", + "integrity": "sha512-Cu52arBa4vSaupIWsF0h7F/Cg//N374nYb7HAxV0I4KceKA7x2UXpYaHOL7EEYYvp7tZdThBjvGpVmr8ScIvaQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "~7.10.4", - "@expo/config-plugins": "~54.0.3", + "@expo/config-plugins": "~54.0.4", "@expo/config-types": "^54.0.10", "@expo/json-file": "^10.0.8", "deepmerge": "^4.3.1", @@ -2037,38 +1929,40 @@ } }, "node_modules/@expo/metro": { - "version": "54.1.0", - "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-54.1.0.tgz", - "integrity": "sha512-MgdeRNT/LH0v1wcO0TZp9Qn8zEF0X2ACI0wliPtv5kXVbXWI+yK9GyrstwLAiTXlULKVIg3HVSCCvmLu0M3tnw==", + "version": "54.2.0", + "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-54.2.0.tgz", + "integrity": "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w==", "license": "MIT", "dependencies": { - "metro": "0.83.2", - "metro-babel-transformer": "0.83.2", - "metro-cache": "0.83.2", - "metro-cache-key": "0.83.2", - "metro-config": "0.83.2", - "metro-core": "0.83.2", - "metro-file-map": "0.83.2", - "metro-resolver": "0.83.2", - "metro-runtime": "0.83.2", - "metro-source-map": "0.83.2", - "metro-transform-plugins": "0.83.2", - "metro-transform-worker": "0.83.2" + "metro": "0.83.3", + "metro-babel-transformer": "0.83.3", + "metro-cache": "0.83.3", + "metro-cache-key": "0.83.3", + "metro-config": "0.83.3", + "metro-core": "0.83.3", + "metro-file-map": "0.83.3", + "metro-minify-terser": "0.83.3", + "metro-resolver": "0.83.3", + "metro-runtime": "0.83.3", + "metro-source-map": "0.83.3", + "metro-symbolicate": "0.83.3", + "metro-transform-plugins": "0.83.3", + "metro-transform-worker": "0.83.3" } }, "node_modules/@expo/metro-config": { - "version": "54.0.11", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-54.0.11.tgz", - "integrity": "sha512-Bmht6VW9w6Wk49EFqkMzYpICV++Q3Kuqh2KygjH/e5mj/9wHSCWLkmJYmUn0XaOo4bm6BwOp/hO3r5YNKP3AeQ==", + "version": "54.0.14", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-54.0.14.tgz", + "integrity": "sha512-hxpLyDfOR4L23tJ9W1IbJJsG7k4lv2sotohBm/kTYyiG+pe1SYCAWsRmgk+H42o/wWf/HQjE5k45S5TomGLxNA==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", - "@expo/config": "~12.0.12", + "@expo/config": "~12.0.13", "@expo/env": "~2.0.8", "@expo/json-file": "~10.0.8", - "@expo/metro": "~54.1.0", + "@expo/metro": "~54.2.0", "@expo/spawn-async": "^1.7.2", "browserslist": "^4.25.0", "chalk": "^4.1.0", @@ -2117,6 +2011,330 @@ } } }, + "node_modules/@expo/metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "license": "MIT" + }, + "node_modules/@expo/metro/node_modules/hermes-estree": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", + "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "license": "MIT" + }, + "node_modules/@expo/metro/node_modules/hermes-parser": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", + "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.0" + } + }, + "node_modules/@expo/metro/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@expo/metro/node_modules/metro": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.3.tgz", + "integrity": "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "@babel/types": "^7.25.2", + "accepts": "^1.3.7", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.32.0", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.83.3", + "metro-cache": "0.83.3", + "metro-cache-key": "0.83.3", + "metro-config": "0.83.3", + "metro-core": "0.83.3", + "metro-file-map": "0.83.3", + "metro-resolver": "0.83.3", + "metro-runtime": "0.83.3", + "metro-source-map": "0.83.3", + "metro-symbolicate": "0.83.3", + "metro-transform-plugins": "0.83.3", + "metro-transform-worker": "0.83.3", + "mime-types": "^2.1.27", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-babel-transformer": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.3.tgz", + "integrity": "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.32.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-cache": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.3.tgz", + "integrity": "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==", + "license": "MIT", + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "https-proxy-agent": "^7.0.5", + "metro-core": "0.83.3" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-cache-key": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.3.tgz", + "integrity": "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-config": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.3.tgz", + "integrity": "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==", + "license": "MIT", + "dependencies": { + "connect": "^3.6.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.83.3", + "metro-cache": "0.83.3", + "metro-core": "0.83.3", + "metro-runtime": "0.83.3", + "yaml": "^2.6.1" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-core": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.3.tgz", + "integrity": "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.83.3" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-file-map": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.3.tgz", + "integrity": "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-minify-terser": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.3.tgz", + "integrity": "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-resolver": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.3.tgz", + "integrity": "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-runtime": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.3.tgz", + "integrity": "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-source-map": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.3.tgz", + "integrity": "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.83.3", + "nullthrows": "^1.1.1", + "ob1": "0.83.3", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-symbolicate": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.3.tgz", + "integrity": "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.83.3", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-transform-plugins": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.3.tgz", + "integrity": "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/metro-transform-worker": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.3.tgz", + "integrity": "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/types": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "metro": "0.83.3", + "metro-babel-transformer": "0.83.3", + "metro-cache": "0.83.3", + "metro-cache-key": "0.83.3", + "metro-minify-terser": "0.83.3", + "metro-source-map": "0.83.3", + "metro-transform-plugins": "0.83.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/ob1": { + "version": "0.83.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.3.tgz", + "integrity": "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=20.19.4" + } + }, + "node_modules/@expo/metro/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@expo/osascript": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.3.8.tgz", @@ -2131,9 +2349,9 @@ } }, "node_modules/@expo/package-manager": { - "version": "1.9.9", - "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.9.9.tgz", - "integrity": "sha512-Nv5THOwXzPprMJwbnXU01iXSrCp3vJqly9M4EJ2GkKko9Ifer2ucpg7x6OUsE09/lw+npaoUnHMXwkw7gcKxlg==", + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.9.10.tgz", + "integrity": "sha512-axJm+NOj3jVxep49va/+L3KkF3YW/dkV+RwzqUJedZrv4LeTqOG4rhrCaCPXHTvLqCTDKu6j0Xyd28N7mnxsGA==", "license": "MIT", "dependencies": { "@expo/json-file": "^10.0.8", @@ -2156,14 +2374,14 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "54.0.7", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-54.0.7.tgz", - "integrity": "sha512-cKqBsiwcFFzpDWgtvemrCqJULJRLDLKo2QMF74NusoGNpfPI3vQVry1iwnYLeGht02AeD3dvfhpqBczD3wchxA==", + "version": "54.0.8", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-54.0.8.tgz", + "integrity": "sha512-EA7N4dloty2t5Rde+HP0IEE+nkAQiu4A/+QGZGT9mFnZ5KKjPPkqSyYcRvP5bhQE10D+tvz6X0ngZpulbMdbsg==", "license": "MIT", "dependencies": { - "@expo/config": "~12.0.11", - "@expo/config-plugins": "~54.0.3", - "@expo/config-types": "^54.0.9", + "@expo/config": "~12.0.13", + "@expo/config-plugins": "~54.0.4", + "@expo/config-types": "^54.0.10", "@expo/image-utils": "^0.8.8", "@expo/json-file": "^10.0.8", "@react-native/normalize-colors": "0.81.5", @@ -2177,9 +2395,9 @@ } }, "node_modules/@expo/prebuild-config/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2236,29 +2454,19 @@ "license": "MIT" }, "node_modules/@expo/xcpretty": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.3.2.tgz", - "integrity": "sha512-ReZxZ8pdnoI3tP/dNnJdnmAk7uLT4FjsKDGW7YeDdvdOMz2XCQSmSCM9IWlrXuWtMF9zeSB6WJtEhCQ41gQOfw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.0.tgz", + "integrity": "sha512-o2qDlTqJ606h4xR36H2zWTywmZ3v3842K6TU8Ik2n1mfW0S580VHlt3eItVYdLYz+klaPp7CXqanja8eASZjRw==", "license": "BSD-3-Clause", "dependencies": { - "@babel/code-frame": "7.10.4", + "@babel/code-frame": "^7.20.0", "chalk": "^4.1.0", - "find-up": "^5.0.0", "js-yaml": "^4.1.0" }, "bin": { "excpretty": "build/cli.js" } }, - "node_modules/@expo/xcpretty/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, "node_modules/@gorhom/bottom-sheet": { "version": "5.2.8", "resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-5.2.8.tgz", @@ -2950,7 +3158,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -4740,13 +4948,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { @@ -4767,12 +4975,12 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" + "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -4845,9 +5053,9 @@ } }, "node_modules/babel-preset-expo": { - "version": "54.0.8", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-54.0.8.tgz", - "integrity": "sha512-3ZJ4Q7uQpm8IR/C9xbKhE/IUjGpLm+OIjF8YCedLgqoe/wN1Ns2wLT7HwG6ZXXb6/rzN8IMCiKFQ2F93qlN6GA==", + "version": "54.0.10", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-54.0.10.tgz", + "integrity": "sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.25.9", @@ -5583,12 +5791,12 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", - "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", "license": "MIT", "dependencies": { - "browserslist": "^4.28.0" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", @@ -6291,29 +6499,29 @@ "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" }, "node_modules/expo": { - "version": "54.0.29", - "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.29.tgz", - "integrity": "sha512-9C90gyOzV83y2S3XzCbRDCuKYNaiyCzuP9ketv46acHCEZn+QTamPK/DobdghoSiofCmlfoaiD6/SzfxDiHMnw==", + "version": "54.0.33", + "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz", + "integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==", "license": "MIT", "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "54.0.19", - "@expo/config": "~12.0.12", + "@expo/cli": "54.0.23", + "@expo/config": "~12.0.13", "@expo/config-plugins": "~54.0.4", "@expo/devtools": "0.1.8", "@expo/fingerprint": "0.15.4", - "@expo/metro": "~54.1.0", - "@expo/metro-config": "54.0.11", + "@expo/metro": "~54.2.0", + "@expo/metro-config": "54.0.14", "@expo/vector-icons": "^15.0.3", "@ungap/structured-clone": "^1.3.0", - "babel-preset-expo": "~54.0.8", - "expo-asset": "~12.0.11", - "expo-constants": "~18.0.12", + "babel-preset-expo": "~54.0.10", + "expo-asset": "~12.0.12", + "expo-constants": "~18.0.13", "expo-file-system": "~19.0.21", - "expo-font": "~14.0.10", + "expo-font": "~14.0.11", "expo-keep-awake": "~15.0.8", - "expo-modules-autolinking": "3.0.23", + "expo-modules-autolinking": "3.0.24", "expo-modules-core": "3.0.29", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", @@ -6353,13 +6561,13 @@ } }, "node_modules/expo-asset": { - "version": "12.0.11", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-12.0.11.tgz", - "integrity": "sha512-pnK/gQ5iritDPBeK54BV35ZpG7yeW5DtgGvJHruIXkyDT9BCoQq3i0AAxfcWG/e4eiRmTzAt5kNVYFJi48uo+A==", + "version": "12.0.12", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-12.0.12.tgz", + "integrity": "sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ==", "license": "MIT", "dependencies": { "@expo/image-utils": "^0.8.8", - "expo-constants": "~18.0.11" + "expo-constants": "~18.0.12" }, "peerDependencies": { "expo": "*", @@ -6418,12 +6626,12 @@ } }, "node_modules/expo-constants": { - "version": "18.0.12", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.12.tgz", - "integrity": "sha512-WzcKYMVNRRu4NcSzfIVRD5aUQFnSpTZgXFrlWmm19xJoDa4S3/PQNi6PNTBRc49xz9h8FT7HMxRKaC8lr0gflA==", + "version": "18.0.13", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz", + "integrity": "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ==", "license": "MIT", "dependencies": { - "@expo/config": "~12.0.12", + "@expo/config": "~12.0.13", "@expo/env": "~2.0.8" }, "peerDependencies": { @@ -6534,9 +6742,9 @@ } }, "node_modules/expo-font": { - "version": "14.0.10", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.10.tgz", - "integrity": "sha512-UqyNaaLKRpj4pKAP4HZSLnuDQqueaO5tB1c/NWu5vh1/LF9ulItyyg2kF/IpeOp0DeOLk0GY0HrIXaKUMrwB+Q==", + "version": "14.0.11", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", + "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", "license": "MIT", "peer": true, "dependencies": { @@ -6657,9 +6865,9 @@ } }, "node_modules/expo-modules-autolinking": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.23.tgz", - "integrity": "sha512-YZnaE0G+52xftjH5nsIRaWsoVBY38SQCECclpdgLisdbRY/6Mzo7ndokjauOv3mpFmzMZACHyJNu1YSAffQwTg==", + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.24.tgz", + "integrity": "sha512-TP+6HTwhL7orDvsz2VzauyQlXJcAWyU3ANsZ7JGL4DQu8XaZv/A41ZchbtAYLfozNA2Ya1Hzmhx65hXryBMjaQ==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -6856,6 +7064,144 @@ "react-native": "*" } }, + "node_modules/expo/node_modules/@expo/cli": { + "version": "54.0.23", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-54.0.23.tgz", + "integrity": "sha512-km0h72SFfQCmVycH/JtPFTVy69w6Lx1cHNDmfLfQqgKFYeeHTjx7LVDP4POHCtNxFP2UeRazrygJhlh4zz498g==", + "license": "MIT", + "dependencies": { + "@0no-co/graphql.web": "^1.0.8", + "@expo/code-signing-certificates": "^0.0.6", + "@expo/config": "~12.0.13", + "@expo/config-plugins": "~54.0.4", + "@expo/devcert": "^1.2.1", + "@expo/env": "~2.0.8", + "@expo/image-utils": "^0.8.8", + "@expo/json-file": "^10.0.8", + "@expo/metro": "~54.2.0", + "@expo/metro-config": "~54.0.14", + "@expo/osascript": "^2.3.8", + "@expo/package-manager": "^1.9.10", + "@expo/plist": "^0.4.8", + "@expo/prebuild-config": "^54.0.8", + "@expo/schema-utils": "^0.1.8", + "@expo/spawn-async": "^1.7.2", + "@expo/ws-tunnel": "^1.0.1", + "@expo/xcpretty": "^4.3.0", + "@react-native/dev-middleware": "0.81.5", + "@urql/core": "^5.0.6", + "@urql/exchange-retry": "^1.3.0", + "accepts": "^1.3.8", + "arg": "^5.0.2", + "better-opn": "~3.0.2", + "bplist-creator": "0.1.0", + "bplist-parser": "^0.3.1", + "chalk": "^4.0.0", + "ci-info": "^3.3.0", + "compression": "^1.7.4", + "connect": "^3.7.0", + "debug": "^4.3.4", + "env-editor": "^0.4.1", + "expo-server": "^1.0.5", + "freeport-async": "^2.0.0", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "lan-network": "^0.1.6", + "minimatch": "^9.0.0", + "node-forge": "^1.3.3", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "picomatch": "^3.0.1", + "pretty-bytes": "^5.6.0", + "pretty-format": "^29.7.0", + "progress": "^2.0.3", + "prompts": "^2.3.2", + "qrcode-terminal": "0.11.0", + "require-from-string": "^2.0.2", + "requireg": "^0.2.2", + "resolve": "^1.22.2", + "resolve-from": "^5.0.0", + "resolve.exports": "^2.0.3", + "semver": "^7.6.0", + "send": "^0.19.0", + "slugify": "^1.3.4", + "source-map-support": "~0.5.21", + "stacktrace-parser": "^0.1.10", + "structured-headers": "^0.4.1", + "tar": "^7.5.2", + "terminal-link": "^2.1.1", + "undici": "^6.18.2", + "wrap-ansi": "^7.0.0", + "ws": "^8.12.1" + }, + "bin": { + "expo-internal": "build/bin/cli" + }, + "peerDependencies": { + "expo": "*", + "expo-router": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo-router": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/@expo/code-signing-certificates": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz", + "integrity": "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==", + "license": "MIT", + "dependencies": { + "node-forge": "^1.3.3" + } + }, + "node_modules/expo/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/expo/node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/expo/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", @@ -7073,22 +7419,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/find-yarn-workspace-root": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", @@ -8512,9 +8842,9 @@ "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -8527,23 +8857,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", "cpu": [ "arm64" ], @@ -8561,9 +8891,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", "cpu": [ "arm64" ], @@ -8581,9 +8911,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", "cpu": [ "x64" ], @@ -8601,9 +8931,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", "cpu": [ "x64" ], @@ -8621,9 +8951,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", "cpu": [ "arm" ], @@ -8641,9 +8971,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", "cpu": [ "arm64" ], @@ -8661,9 +8991,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", "cpu": [ "arm64" ], @@ -8681,9 +9011,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", "cpu": [ "x64" ], @@ -8701,9 +9031,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", "cpu": [ "x64" ], @@ -8721,9 +9051,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", "cpu": [ "arm64" ], @@ -8741,9 +9071,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", "cpu": [ "x64" ], @@ -8775,21 +9105,6 @@ "uc.micro": "^1.0.1" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -9366,27 +9681,6 @@ "node": ">=0.10.0" } }, - "node_modules/metro/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -9680,9 +9974,9 @@ } }, "node_modules/npm-package-arg/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9974,21 +10268,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -10616,27 +10895,6 @@ "ws": "^7" } }, - "node_modules/react-devtools-core/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/react-dom": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", @@ -11995,9 +12253,9 @@ } }, "node_modules/send": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", - "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -12006,13 +12264,13 @@ "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -12042,6 +12300,26 @@ "node": ">= 0.8" } }, + "node_modules/send/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/send/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -12055,9 +12333,9 @@ } }, "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -12795,9 +13073,9 @@ "optional": true }, "node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -13631,16 +13909,16 @@ } }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=8.3.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index e5f102df..9a11649e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "crypto-js": "^4.2.0", "date-fns": "^4.1.0", "eventemitter3": "^5.0.1", - "expo": "^54", + "expo": "^54.0.33", "expo-application": "~7.0.7", "expo-auth-session": "~7.0.8", "expo-blur": "~15.0.7", diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index befff6bb..a3cfa9ea 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -45,6 +45,8 @@ export interface AppSettings { tmdbLanguagePreference: string; // Preferred language for TMDB logos (ISO 639-1 code) episodeLayoutStyle: 'vertical' | 'horizontal'; // Layout style for episode cards autoplayBestStream: boolean; // Automatically play the best available stream + autoplayPreferredQuality: string; // Preferred quality for autoplay (e.g., '4K', '1080p', '720p', '480p') + autoplayPreferredLanguage: string; // Preferred language for autoplay (e.g., 'English', 'Spanish', 'Any') // Local scraper settings scraperRepositoryUrl: string; // URL to the scraper repository enableLocalScrapers: boolean; // Enable/disable local scraper functionality @@ -135,6 +137,8 @@ export const DEFAULT_SETTINGS: AppSettings = { tmdbLanguagePreference: 'en', // Default to English episodeLayoutStyle: 'vertical', // Default to vertical layout for new installs autoplayBestStream: false, // Disabled by default for user choice + autoplayPreferredQuality: '1080p', // Default to 1080p + autoplayPreferredLanguage: 'Any', // Default to Any language // Local scraper defaults scraperRepositoryUrl: '', enableLocalScrapers: true, diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 33978f4f..c4ca4c0e 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -14,6 +14,7 @@ "try_again": "Try Again", "go_back": "Go Back", "settings": "Settings", + "any": "Any", "close": "Close", "enable": "Enable", "disable": "Disable", @@ -641,6 +642,9 @@ "chinese": "Chinese (Simplified)", "hindi": "Hindi", "serbian": "Serbian", + "russian": "Russian", + "japanese": "Japanese", + "korean": "Korean", "account": "Account", "content_discovery": "Content & Discovery", "appearance": "Appearance", @@ -1183,6 +1187,10 @@ "powered_by_introdb": "Powered by IntroDB", "autoplay_title": "Auto-play First Stream", "autoplay_desc": "Automatically start the first stream shown in the list.", + "preferred_quality_title": "Preferred Quality", + "preferred_quality_desc": "Select preferred quality for autoplay.", + "preferred_language_title": "Preferred Language", + "preferred_language_desc": "Select preferred language for autoplay.", "resume_title": "Always Resume", "resume_desc": "Skip the resume prompt and automatically continue where you left off (if less than 85% watched).", "engine_title": "Video Player Engine", diff --git a/src/screens/PlayerSettingsScreen.tsx b/src/screens/PlayerSettingsScreen.tsx index 93f4031d..9cb7ba5c 100644 --- a/src/screens/PlayerSettingsScreen.tsx +++ b/src/screens/PlayerSettingsScreen.tsx @@ -291,19 +291,150 @@ const PlayerSettingsScreen: React.FC = () => { {t('player.autoplay_desc')} - updateSetting('autoplayBestStream', value)} - trackColor={{ false: '#767577', true: currentTheme.colors.primary }} - thumbColor={settings.autoplayBestStream ? '#ffffff' : '#f4f3f4'} - ios_backgroundColor="#3e3e3e" - /> - - + updateSetting('autoplayBestStream', value)} + trackColor={{ false: '#767577', true: currentTheme.colors.primary }} + thumbColor={settings.autoplayBestStream ? '#ffffff' : '#f4f3f4'} + ios_backgroundColor="#3e3e3e" + /> + + + + {/* Preferred Quality for Autoplay */} + + + + + + + + {t('player.preferred_quality_title') || 'Preferred Quality'} + + + {t('player.preferred_quality_desc') || 'Select preferred quality for autoplay'} + + + + + {([ + { id: '4K', label: '4K' }, + { id: '1080p', label: '1080p' }, + { id: '720p', label: '720p' }, + { id: '480p', label: '480p' }, + ] as const).map((option) => ( + updateSetting('autoplayPreferredQuality', option.id)} + style={[ + styles.optionButton, + settings.autoplayPreferredQuality === option.id && { backgroundColor: currentTheme.colors.primary }, + ]} + > + + {option.label} + + + ))} + + - - - {/* Video Player Engine for Android */} + {/* Preferred Language for Autoplay */} + + + + + + + + {t('player.preferred_language_title') || 'Preferred Language'} + + + {t('player.preferred_language_desc') || 'Select preferred language for autoplay'} + + + + + {([ + { id: 'Any', label: t('common.any') || 'Any' }, + { id: 'English', label: t('settings.english') || 'English' }, + { id: 'Spanish', label: t('settings.spanish') || 'Spanish' }, + { id: 'French', label: t('settings.french') || 'French' }, + { id: 'German', label: t('settings.german') || 'German' }, + { id: 'Italian', label: t('settings.italian') || 'Italian' }, + { id: 'Portuguese', label: t('settings.portuguese') || 'Portuguese' }, + { id: 'Russian', label: t('settings.russian') || 'Russian' }, + { id: 'Hindi', label: t('settings.hindi') || 'Hindi' }, + { id: 'Chinese', label: t('settings.chinese') || 'Chinese' }, + { id: 'Japanese', label: t('settings.japanese') || 'Japanese' }, + { id: 'Korean', label: t('settings.korean') || 'Korean' }, + ] as const).map((option) => ( + updateSetting('autoplayPreferredLanguage', option.id)} + style={[ + styles.optionButton, + styles.optionButtonLanguage, + settings.autoplayPreferredLanguage === option.id && { backgroundColor: currentTheme.colors.primary }, + ]} + > + + {option.label} + + + ))} + + + + {/* Video Player Engine for Android */} {Platform.OS === 'android' && !settings.useExternalPlayer && ( <> @@ -653,6 +784,12 @@ const styles = StyleSheet.create({ paddingHorizontal: 52, gap: 8, }, + optionButtonsRowScroll: { + paddingHorizontal: 52, + gap: 8, + marginTop: 12, + paddingBottom: 4, + }, optionButton: { flex: 1, paddingVertical: 10, @@ -662,6 +799,10 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, + optionButtonLanguage: { + minWidth: 90, + flex: 0, + }, optionButtonWide: { flex: 1.5, }, diff --git a/src/screens/streams/useStreamsScreen.ts b/src/screens/streams/useStreamsScreen.ts index 62de8cb0..396228fe 100644 --- a/src/screens/streams/useStreamsScreen.ts +++ b/src/screens/streams/useStreamsScreen.ts @@ -246,20 +246,67 @@ export const useStreamsScreen = () => { if (allStreams.length === 0) return null; - // Sort primarily by provider priority, then respect the addon's internal order (originalIndex) - // This ensures if an addon lists 1080p before 4K, we pick 1080p - allStreams.sort((a, b) => { + // Map preferred quality to numeric value + const targetQuality = settings.autoplayPreferredQuality === '4K' ? 2160 : parseInt(settings.autoplayPreferredQuality, 10) || 1080; + const preferredLanguage = settings.autoplayPreferredLanguage; + + // 1. Try to find streams matching preferred language + let languageMatchedStreams = allStreams; + if (preferredLanguage && preferredLanguage !== 'Any') { + languageMatchedStreams = allStreams.filter(item => { + const streamLang = (item.stream.lang || '').toLowerCase(); + const prefLang = preferredLanguage.toLowerCase(); + // Match by name if lang is not set, or match by lang property + return streamLang === prefLang || + (item.stream.name || '').toLowerCase().includes(prefLang) || + (item.stream.title || '').toLowerCase().includes(prefLang) || + (item.stream.description || '').toLowerCase().includes(prefLang); + }); + } + + // 2. If no language match (and language wasn't 'Any'), just play the "first" stream + if (languageMatchedStreams.length === 0 && allStreams.length > 0) { + // Sort by provider priority and original index to find the "first" one + const sortedByPriority = [...allStreams].sort((a, b) => { + if (a.providerPriority !== b.providerPriority) return b.providerPriority - a.providerPriority; + return a.originalIndex - b.originalIndex; + }); + logger.log(`🎯 Autoplay: No language match for ${preferredLanguage}, playing first available stream.`); + return sortedByPriority[0].stream; + } + + if (languageMatchedStreams.length === 0) return null; + + // 3. Among language-matched streams, find the one closest to target quality + // Sort primarily by how close the stream quality is to the preferred quality + // If quality is identical, sort by provider priority and then addon's internal order + languageMatchedStreams.sort((a, b) => { + // Calculate absolute difference from target quality + // Note: 0 quality (unknown/auto) is treated as being far from any specific target + const qualityA = a.quality === 0 ? 0 : a.quality; + const qualityB = b.quality === 0 ? 0 : b.quality; + + const diffA = Math.abs(qualityA - targetQuality); + const diffB = Math.abs(qualityB - targetQuality); + + if (diffA !== diffB) return diffA - diffB; + + // Tie-break: if both are equally close (e.g. 720p and 1440p to 1080p) + // prefer the higher quality one + if (qualityA !== qualityB) return qualityB - qualityA; + if (a.providerPriority !== b.providerPriority) return b.providerPriority - a.providerPriority; return a.originalIndex - b.originalIndex; }); + const selected = languageMatchedStreams[0]; logger.log( - `🎯 Best stream selected: ${allStreams[0].stream.name || allStreams[0].stream.title} (Quality: ${allStreams[0].quality}p)` + `🎯 Best stream selected: ${selected.stream.name || selected.stream.title} (Lang: ${selected.stream.lang || 'unknown'}, Quality: ${selected.quality}p, Target: ${targetQuality}p)` ); - return allStreams[0].stream; + return selected.stream; }, - [filterByQuality, filterByLanguage] + [filterByQuality, filterByLanguage, settings.autoplayPreferredQuality, settings.autoplayPreferredLanguage] ); // Current episode