diff --git a/patches/expo-video+1.2.4.patch b/patches/expo-video+1.2.4.patch index 0364dd63..13fe25ed 100644 --- a/patches/expo-video+1.2.4.patch +++ b/patches/expo-video+1.2.4.patch @@ -5,7 +5,7 @@ index 473f964..f37aff9 100644 @@ -41,6 +41,11 @@ sealed class PlayerEvent { override val name = "playToEnd" } - + + data class PlayerTimeRemainingChanged(val timeRemaining: Double): PlayerEvent() { + override val name = "timeRemainingChange" + override val arguments = arrayOf(timeRemaining) @@ -32,10 +32,10 @@ index 9905e13..47342ff 100644 setTimeBarInteractive(requireLinearPlayback) + setShowSubtitleButton(true) } - + @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) @@ -27,7 +28,8 @@ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) { - + @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) internal fun PlayerView.setFullscreenButtonVisibility(visible: Boolean) { - val fullscreenButton = findViewById(androidx.media3.ui.R.id.exo_fullscreen) @@ -80,6 +80,57 @@ index 0000000..0249e23 + } +} \ No newline at end of file +diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt +index 4b6c6d8..e20f51a 100644 +--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt ++++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt +@@ -1,5 +1,6 @@ + package expo.modules.video + ++import android.provider.MediaStore.Video + import androidx.annotation.OptIn + import androidx.media3.common.util.UnstableApi + import expo.modules.kotlin.AppContext +@@ -15,6 +16,8 @@ object VideoManager { + // Keeps track of all existing VideoPlayers, and whether they are attached to a VideoView + private var videoPlayersToVideoViews = mutableMapOf>() + ++ private var previouslyPlayingViews: MutableList? = null ++ + private lateinit var audioFocusManager: AudioFocusManager + + fun onModuleCreated(appContext: AppContext) { +@@ -69,16 +72,24 @@ object VideoManager { + return videoPlayersToVideoViews[videoPlayer]?.isNotEmpty() ?: false + } + +- fun onAppForegrounded() = Unit ++ fun onAppForegrounded() { ++ val previouslyPlayingViews = this.previouslyPlayingViews ?: return ++ for (videoView in previouslyPlayingViews) { ++ val player = videoView.videoPlayer?.player ?: continue ++ player.play() ++ } ++ this.previouslyPlayingViews = null ++ } + + fun onAppBackgrounded() { ++ val previouslyPlayingViews = mutableListOf() + for (videoView in videoViews.values) { +- if (videoView.videoPlayer?.staysActiveInBackground == false && +- !videoView.willEnterPiP && +- !videoView.isInFullscreen +- ) { +- videoView.videoPlayer?.player?.pause() ++ val player = videoView.videoPlayer?.player ?: continue ++ if (player.isPlaying) { ++ player.pause() ++ previouslyPlayingViews.add(videoView) + } + } ++ this.previouslyPlayingViews = previouslyPlayingViews + } + } diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt index ec3da2a..5a1397a 100644 --- a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt @@ -93,7 +144,7 @@ index ec3da2a..5a1397a 100644 + "onEnterFullscreen", + "onExitFullscreen" ) - + Prop("player") { view: VideoView, player: VideoPlayer -> diff --git a/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt index 58f00af..5ad8237 100644 @@ -101,7 +152,7 @@ index 58f00af..5ad8237 100644 +++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt @@ -1,5 +1,6 @@ package expo.modules.video - + +import ProgressTracker import android.content.Context import android.view.SurfaceView @@ -111,18 +162,18 @@ index 58f00af..5ad8237 100644 .setLooper(context.mainLooper) .build() + var progressTracker: ProgressTracker? = null - + val serviceConnection = PlaybackServiceConnection(WeakReference(player)) - + var playing by IgnoreSameSet(false) { new, old -> sendEvent(PlayerEvent.IsPlayingChanged(new, old)) + addOrRemoveProgressTracker() } - + var uncommittedSource: VideoSource? = source @@ -141,6 +144,9 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou } - + override fun close() { + this.progressTracker?.remove() + this.progressTracker = null @@ -133,7 +184,7 @@ index 58f00af..5ad8237 100644 @@ -228,7 +234,7 @@ class VideoPlayer(val context: Context, appContext: AppContext, source: VideoSou listeners.removeAll { it.get() == videoPlayerListener } } - + - private fun sendEvent(event: PlayerEvent) { + fun sendEvent(event: PlayerEvent) { // Emits to the native listeners @@ -173,7 +224,7 @@ index a951d80..3932535 100644 val onPictureInPictureStop by EventDispatcher() + val onEnterFullscreen by EventDispatcher() + val onExitFullscreen by EventDispatcher() - + var willEnterPiP: Boolean = false var isInFullscreen: Boolean = false @@ -154,6 +156,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap @@ -183,7 +234,7 @@ index a951d80..3932535 100644 + onEnterFullscreen(mapOf()) isInFullscreen = true } - + @@ -162,6 +165,7 @@ class VideoView(context: Context, appContext: AppContext) : ExpoView(context, ap val fullScreenButton: ImageButton = playerView.findViewById(androidx.media3.ui.R.id.exo_fullscreen) fullScreenButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter) @@ -191,9 +242,9 @@ index a951d80..3932535 100644 + this.onExitFullscreen(mapOf()) isInFullscreen = false } - + diff --git a/node_modules/expo-video/build/VideoPlayer.types.d.ts b/node_modules/expo-video/build/VideoPlayer.types.d.ts -index a09fcfe..65fe29a 100644 +index a09fcfe..5eac9e5 100644 --- a/node_modules/expo-video/build/VideoPlayer.types.d.ts +++ b/node_modules/expo-video/build/VideoPlayer.types.d.ts @@ -128,6 +128,8 @@ export type VideoPlayerEvents = { @@ -219,6 +270,132 @@ index cb9ca6d..ed8bb7e 100644 } //# sourceMappingURL=VideoView.types.d.ts.map \ No newline at end of file +diff --git a/node_modules/expo-video/ios/VideoManager.swift b/node_modules/expo-video/ios/VideoManager.swift +index 094a8b0..3f00525 100644 +--- a/node_modules/expo-video/ios/VideoManager.swift ++++ b/node_modules/expo-video/ios/VideoManager.swift +@@ -12,6 +12,7 @@ class VideoManager { + + private var videoViews = NSHashTable.weakObjects() + private var videoPlayers = NSHashTable.weakObjects() ++ private var previouslyPlayingPlayers: [VideoPlayer]? + + func register(videoPlayer: VideoPlayer) { + videoPlayers.add(videoPlayer) +@@ -33,63 +34,70 @@ class VideoManager { + for videoPlayer in videoPlayers.allObjects { + videoPlayer.setTracksEnabled(true) + } ++ ++ if let previouslyPlayingPlayers = self.previouslyPlayingPlayers { ++ previouslyPlayingPlayers.forEach { player in ++ player.pointer.play() ++ } ++ } + } + + func onAppBackgrounded() { ++ var previouslyPlayingPlayers: [VideoPlayer] = [] + for videoView in videoViews.allObjects { + guard let player = videoView.player else { + continue + } +- if player.staysActiveInBackground == true { +- player.setTracksEnabled(videoView.isInPictureInPicture) +- } else if !videoView.isInPictureInPicture { ++ if player.isPlaying { + player.pointer.pause() ++ previouslyPlayingPlayers.append(player) + } + } ++ self.previouslyPlayingPlayers = previouslyPlayingPlayers + } + + // MARK: - Audio Session Management + + internal func setAppropriateAudioSessionOrWarn() { +- let audioSession = AVAudioSession.sharedInstance() +- var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = [] +- +- let isAnyPlayerPlaying = videoPlayers.allObjects.contains { player in +- player.isPlaying +- } +- let areAllPlayersMuted = videoPlayers.allObjects.allSatisfy { player in +- player.isMuted +- } +- let needsPiPSupport = videoViews.allObjects.contains { view in +- view.allowPictureInPicture +- } +- let anyPlayerShowsNotification = videoPlayers.allObjects.contains { player in +- player.showNowPlayingNotification +- } +- // The notification won't be shown if we allow the audio to mix with others +- let shouldAllowMixing = (!isAnyPlayerPlaying || areAllPlayersMuted) && !anyPlayerShowsNotification +- let isOutputtingAudio = !areAllPlayersMuted && isAnyPlayerPlaying +- let shouldUpdateToAllowMixing = !audioSession.categoryOptions.contains(.mixWithOthers) && shouldAllowMixing +- +- if shouldAllowMixing { +- audioSessionCategoryOptions.insert(.mixWithOthers) +- } +- +- if isOutputtingAudio || needsPiPSupport || shouldUpdateToAllowMixing || anyPlayerShowsNotification { +- do { +- try audioSession.setCategory(.playback, mode: .moviePlayback) +- } catch { +- log.warn("Failed to set audio session category. This might cause issues with audio playback and Picture in Picture. \(error.localizedDescription)") +- } +- } +- +- // Make sure audio session is active if any video is playing +- if isAnyPlayerPlaying { +- do { +- try audioSession.setActive(true) +- } catch { +- log.warn("Failed to activate the audio session. This might cause issues with audio playback. \(error.localizedDescription)") +- } +- } ++// let audioSession = AVAudioSession.sharedInstance() ++// var audioSessionCategoryOptions: AVAudioSession.CategoryOptions = [] ++// ++// let isAnyPlayerPlaying = videoPlayers.allObjects.contains { player in ++// player.isPlaying ++// } ++// let areAllPlayersMuted = videoPlayers.allObjects.allSatisfy { player in ++// player.isMuted ++// } ++// let needsPiPSupport = videoViews.allObjects.contains { view in ++// view.allowPictureInPicture ++// } ++// let anyPlayerShowsNotification = videoPlayers.allObjects.contains { player in ++// player.showNowPlayingNotification ++// } ++// // The notification won't be shown if we allow the audio to mix with others ++// let shouldAllowMixing = (!isAnyPlayerPlaying || areAllPlayersMuted) && !anyPlayerShowsNotification ++// let isOutputtingAudio = !areAllPlayersMuted && isAnyPlayerPlaying ++// let shouldUpdateToAllowMixing = !audioSession.categoryOptions.contains(.mixWithOthers) && shouldAllowMixing ++// ++// if shouldAllowMixing { ++// audioSessionCategoryOptions.insert(.mixWithOthers) ++// } ++// ++// if isOutputtingAudio || needsPiPSupport || shouldUpdateToAllowMixing || anyPlayerShowsNotification { ++// do { ++// try audioSession.setCategory(.playback, mode: .moviePlayback) ++// } catch { ++// log.warn("Failed to set audio session category. This might cause issues with audio playback and Picture in Picture. \(error.localizedDescription)") ++// } ++// } ++// ++// // Make sure audio session is active if any video is playing ++// if isAnyPlayerPlaying { ++// do { ++// try audioSession.setActive(true) ++// } catch { ++// log.warn("Failed to activate the audio session. This might cause issues with audio playback. \(error.localizedDescription)") ++// } ++// } + } + } diff --git a/node_modules/expo-video/ios/VideoModule.swift b/node_modules/expo-video/ios/VideoModule.swift index c537a12..e4a918f 100644 --- a/node_modules/expo-video/ios/VideoModule.swift @@ -232,16 +409,16 @@ index c537a12..e4a918f 100644 + "onEnterFullscreen", + "onExitFullscreen" ) - + Prop("player") { (view, player: VideoPlayer?) in diff --git a/node_modules/expo-video/ios/VideoPlayer.swift b/node_modules/expo-video/ios/VideoPlayer.swift -index 3315b88..f482390 100644 +index 3315b88..733ab1f 100644 --- a/node_modules/expo-video/ios/VideoPlayer.swift +++ b/node_modules/expo-video/ios/VideoPlayer.swift @@ -185,6 +185,10 @@ internal final class VideoPlayer: SharedRef, Hashable, VideoPlayerObse safeEmit(event: "sourceChange", arguments: newVideoPlayerItem?.videoSource, oldVideoPlayerItem?.videoSource) } - + + func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) { + safeEmit(event: "timeRemainingChange", arguments: timeRemaining) + } @@ -250,7 +427,7 @@ index 3315b88..f482390 100644 if self.appContext != nil { self.emit(event: event, arguments: repeat each arguments) diff --git a/node_modules/expo-video/ios/VideoPlayerObserver.swift b/node_modules/expo-video/ios/VideoPlayerObserver.swift -index d289e26..de9a26f 100644 +index d289e26..ea4d96f 100644 --- a/node_modules/expo-video/ios/VideoPlayerObserver.swift +++ b/node_modules/expo-video/ios/VideoPlayerObserver.swift @@ -21,6 +21,7 @@ protocol VideoPlayerObserverDelegate: AnyObject { @@ -259,7 +436,7 @@ index d289e26..de9a26f 100644 func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status) + func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) } - + // Default implementations for the delegate @@ -33,6 +34,7 @@ extension VideoPlayerObserverDelegate { func onItemChanged(player: AVPlayer, oldVideoPlayerItem: VideoPlayerItem?, newVideoPlayerItem: VideoPlayerItem?) {} @@ -267,14 +444,14 @@ index d289e26..de9a26f 100644 func onPlayerItemStatusChanged(player: AVPlayer, oldStatus: AVPlayerItem.Status?, newStatus: AVPlayerItem.Status) {} + func onPlayerTimeRemainingChanged(player: AVPlayer, timeRemaining: Double) {} } - + // Wrapper used to store WeakReferences to the observer delegate @@ -91,6 +93,7 @@ class VideoPlayerObserver { private var playerVolumeObserver: NSKeyValueObservation? private var playerCurrentItemObserver: NSKeyValueObservation? private var playerIsMutedObserver: NSKeyValueObservation? + private var playerPeriodicTimeObserver: Any? - + // Current player item observers private var playbackBufferEmptyObserver: NSKeyValueObservation? @@ -152,6 +155,9 @@ class VideoPlayerObserver { @@ -285,16 +462,16 @@ index d289e26..de9a26f 100644 + player?.removeTimeObserver(playerPeriodicTimeObserver) + } } - + private func initializeCurrentPlayerItemObservers(player: AVPlayer, playerItem: AVPlayerItem) { @@ -270,6 +276,7 @@ class VideoPlayerObserver { - + if isPlaying != (player.timeControlStatus == .playing) { isPlaying = player.timeControlStatus == .playing + addPeriodicTimeObserverIfNeeded() } } - + @@ -310,4 +317,28 @@ class VideoPlayerObserver { } } @@ -329,12 +506,12 @@ index f4579e4..10c5908 100644 --- a/node_modules/expo-video/ios/VideoView.swift +++ b/node_modules/expo-video/ios/VideoView.swift @@ -41,6 +41,8 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate { - + let onPictureInPictureStart = EventDispatcher() let onPictureInPictureStop = EventDispatcher() + let onEnterFullscreen = EventDispatcher() + let onExitFullscreen = EventDispatcher() - + public override var bounds: CGRect { didSet { @@ -163,6 +165,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate { @@ -344,7 +521,7 @@ index f4579e4..10c5908 100644 + onEnterFullscreen() isFullscreen = true } - + @@ -179,6 +182,7 @@ public final class VideoView: ExpoView, AVPlayerViewControllerDelegate { if wasPlaying { self.player?.pointer.play() @@ -364,7 +541,7 @@ index aaf4b63..f438196 100644 + + timeRemainingChange(timeRemaining: number): void; }; - + /** diff --git a/node_modules/expo-video/src/VideoView.types.ts b/node_modules/expo-video/src/VideoView.types.ts index 29fe5db..e1fbf59 100644 diff --git a/patches/expo-video+1.2.4.patch.md b/patches/expo-video+1.2.4.patch.md index 689cf9a9..99c14c28 100644 --- a/patches/expo-video+1.2.4.patch.md +++ b/patches/expo-video+1.2.4.patch.md @@ -2,5 +2,17 @@ ## `expo-video` Patch -This patch adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on +### `onEnterFullScreen`/`onExitFullScreen` +Adds two props to `VideoView`: `onEnterFullscreen` and `onExitFullscreen` which do exactly what they say on the tin. + +### Removing audio session management + +This patch also removes the audio session management that Expo does on its own, as we handle audio session management +ourselves. + +### Pausing/playing on background/foreground + +Instead of handling the pausing/playing of videos in React, we'll handle them here. There's some logic that we do not +need (around PIP mode) that we can remove, and just pause any playing players on background and then resume them on +foreground. diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index 429a0607..d2e7ffc2 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -1,9 +1,14 @@ -import {Platform, StyleSheet} from 'react-native' +import {Platform, StyleSheet, ViewStyle} from 'react-native' import * as tokens from '#/alf/tokens' import {native, web} from '#/alf/util/platform' export const atoms = { + debug: { + borderColor: 'red', + borderWidth: 1, + }, + /* * Positioning */ @@ -55,6 +60,19 @@ export const atoms = { height: '100vh', }), + /** + * Used for the outermost components on screens, to ensure that they can fill + * the screen and extend beyond. + */ + util_screen_outer: [ + web({ + minHeight: '100vh', + }), + native({ + height: '100%', + }), + ] as ViewStyle, + /* * Theme-independent bg colors */ diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 765e890e..85457e12 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -136,3 +136,12 @@ export const GIF_FEATURED = (params: string) => `${GIF_SERVICE}/tenor/v2/featured?${params}` export const MAX_LABELERS = 20 + +export const SUPPORTED_MIME_TYPES = [ + 'video/mp4', + 'video/mpeg', + 'video/webm', + 'video/quicktime', +] as const + +export type SupportedMimeTypes = (typeof SUPPORTED_MIME_TYPES)[number] diff --git a/src/lib/custom-animations/CountWheel.tsx b/src/lib/custom-animations/CountWheel.tsx index 1a867671..2e435f7d 100644 --- a/src/lib/custom-animations/CountWheel.tsx +++ b/src/lib/custom-animations/CountWheel.tsx @@ -91,13 +91,15 @@ export function CountWheel({ likeCount, big, isLiked, + isToggle, }: { likeCount: number big?: boolean isLiked: boolean + isToggle: boolean }) { const t = useTheme() - const shouldAnimate = !useReducedMotion() + const shouldAnimate = !useReducedMotion() && isToggle const shouldRoll = decideShouldRoll(isLiked, likeCount) // Incrementing the key will cause the `Animated.View` to re-render, with the newly selected entering/exiting diff --git a/src/lib/custom-animations/CountWheel.web.tsx b/src/lib/custom-animations/CountWheel.web.tsx index 594117bf..78120b70 100644 --- a/src/lib/custom-animations/CountWheel.web.tsx +++ b/src/lib/custom-animations/CountWheel.web.tsx @@ -39,13 +39,15 @@ export function CountWheel({ likeCount, big, isLiked, + isToggle, }: { likeCount: number big?: boolean isLiked: boolean + isToggle: boolean }) { const t = useTheme() - const shouldAnimate = !useReducedMotion() + const shouldAnimate = !useReducedMotion() && isToggle const shouldRoll = decideShouldRoll(isLiked, likeCount) const countView = React.useRef(null) diff --git a/src/lib/custom-animations/LikeIcon.tsx b/src/lib/custom-animations/LikeIcon.tsx index f5802ecc..ee3d413e 100644 --- a/src/lib/custom-animations/LikeIcon.tsx +++ b/src/lib/custom-animations/LikeIcon.tsx @@ -71,13 +71,15 @@ const circle2Keyframe = new Keyframe({ export function AnimatedLikeIcon({ isLiked, big, + isToggle, }: { isLiked: boolean big?: boolean + isToggle: boolean }) { const t = useTheme() const size = big ? 22 : 18 - const shouldAnimate = !useReducedMotion() + const shouldAnimate = !useReducedMotion() && isToggle return ( diff --git a/src/lib/custom-animations/LikeIcon.web.tsx b/src/lib/custom-animations/LikeIcon.web.tsx index 6dc94c29..ef330bc6 100644 --- a/src/lib/custom-animations/LikeIcon.web.tsx +++ b/src/lib/custom-animations/LikeIcon.web.tsx @@ -41,13 +41,15 @@ const circle2Keyframe = [ export function AnimatedLikeIcon({ isLiked, big, + isToggle, }: { isLiked: boolean big?: boolean + isToggle: boolean }) { const t = useTheme() const size = big ? 22 : 18 - const shouldAnimate = !useReducedMotion() + const shouldAnimate = !useReducedMotion() && isToggle const prevIsLiked = React.useRef(isLiked) const likeIconRef = React.useRef(null) diff --git a/src/lib/media/video/compress.ts b/src/lib/media/video/compress.ts index 79c58f5d..e783a843 100644 --- a/src/lib/media/video/compress.ts +++ b/src/lib/media/video/compress.ts @@ -30,5 +30,5 @@ export async function compressVideo( const info = await getVideoMetaData(compressed) - return {uri: compressed, size: info.size, mimeType: `video/${info.extension}`} + return {uri: compressed, size: info.size, mimeType: `video/mp4`} } diff --git a/src/locale/locales/pt-BR/messages.po b/src/locale/locales/pt-BR/messages.po index aa6258cf..33b48a0c 100644 --- a/src/locale/locales/pt-BR/messages.po +++ b/src/locale/locales/pt-BR/messages.po @@ -10,7 +10,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "PO-Revision-Date: 2024-05-13 11:41\n" "Last-Translator: fabiohcnobre\n" -"Language-Team: maisondasilva, MightyLoggor, gildaswise, gleydson, faeriarum, fabiohcnobre\n" +"Language-Team: maisondasilva, MightyLoggor, gildaswise, gleydson, faeriarum, fabiohcnobre, garccez\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/screens/Messages/List/ChatListItem.tsx:120 @@ -32,7 +32,7 @@ msgstr "{0, plural, one {{formattedCount} outro} other {{formattedCount} outros} #: src/components/moderation/LabelsOnMe.tsx:55 msgid "{0, plural, one {# label has been placed on this account} other {# labels have been placed on this account}}" -msgstr "" +msgstr "{0, plural, one {# rótulo foi colocado nesta conta} other {# rótulos foram colocado nesta conta}}" #: src/components/moderation/LabelsOnMe.tsx:61 #~ msgid "{0, plural, one {# label has been placed on this content} other {# labels has been placed on this content}}" @@ -40,15 +40,15 @@ msgstr "" #: src/components/moderation/LabelsOnMe.tsx:61 msgid "{0, plural, one {# label has been placed on this content} other {# labels have been placed on this content}}" -msgstr "" +msgstr "{0, plural, one {# rótulo foi colocado neste conteúdo} other {# rótulos foram colocado neste conteúdo}}" #: src/view/com/util/post-ctrls/RepostButton.tsx:68 msgid "{0, plural, one {# repost} other {# reposts}}" -msgstr "{0, plural, one {# repost} other {# reposts}}" +msgstr "{0, plural, one {# repostagem} other {# repostagens}}" #: src/components/KnownFollowers.tsx:179 #~ msgid "{0, plural, one {and # other} other {and # others}}" -#~ msgstr "" +#~ msgstr "{0, plural, one {e # outro} other {e # outros}" #: src/components/ProfileHoverCard/index.web.tsx:398 #: src/screens/Profile/Header/Metrics.tsx:23 @@ -75,11 +75,11 @@ msgstr "{0, plural, one {Curtido por # usuário} other {Curtido por # usuários} #: src/screens/Profile/Header/Metrics.tsx:59 msgid "{0, plural, one {post} other {posts}}" -msgstr "{0, plural, one {post} other {posts}}" +msgstr "{0, plural, one {postagem} other {postagens}}" #: src/view/com/post-thread/PostThreadItem.tsx:413 msgid "{0, plural, one {quote} other {quotes}}" -msgstr "" +msgstr "{0, plural, one {citação} other {citações}}" #: src/view/com/util/post-ctrls/PostCtrls.tsx:233 msgid "{0, plural, one {Reply (# reply)} other {Reply (# replies)}}" @@ -125,7 +125,7 @@ msgstr "Os feeds e pessoas favoritas de {0} - junte-se a mim!" #: src/screens/StarterPack/Wizard/StepDetails.tsx:47 msgid "{0}'s starter pack" -msgstr "O kit inicial de {0}" +msgstr "O pacote inicial de {0}" #: src/components/LabelingServiceCard/index.tsx:71 msgid "{count, plural, one {Liked by # user} other {Liked by # users}}" @@ -133,27 +133,27 @@ msgstr "{count, plural, one {Curtido por # usuário} other {Curtido por # usuár #: src/lib/hooks/useTimeAgo.ts:69 msgid "{diff, plural, one {day} other {days}}" -msgstr "" +msgstr "{diff, plural, one {dia} other {dias}}" #: src/lib/hooks/useTimeAgo.ts:64 msgid "{diff, plural, one {hour} other {hours}}" -msgstr "" +msgstr "{diff, plural, one {hora} other {horas}}" #: src/lib/hooks/useTimeAgo.ts:59 msgid "{diff, plural, one {minute} other {minutes}}" -msgstr "" +msgstr "{diff, plural, one {minuto} other {minutos}}" #: src/lib/hooks/useTimeAgo.ts:75 msgid "{diff, plural, one {month} other {months}}" -msgstr "" +msgstr "{diff, plural, one {mês} other {meses}}" #: src/lib/hooks/useTimeAgo.ts:54 msgid "{diffSeconds, plural, one {second} other {seconds}}" -msgstr "" +msgstr "{diffSeconds, plural, one {segundo} other {segundos}}" #: src/screens/StarterPack/Wizard/index.tsx:174 msgid "{displayName}'s Starter Pack" -msgstr "O Kit Inicial de {displayName}" +msgstr "O Pacote Inicial de {displayName}" #: src/screens/SignupQueued.tsx:207 msgid "{estimatedTimeHrs, plural, one {hour} other {hours}}" @@ -184,15 +184,15 @@ msgstr "{numUnreadNotifications} não lidas" #: src/components/NewskieDialog.tsx:116 msgid "{profileName} joined Bluesky {0} ago" -msgstr "" +msgstr "{profileName} juntou-se ao Bluesky há {0}" #: src/components/NewskieDialog.tsx:111 msgid "{profileName} joined Bluesky using a starter pack {0} ago" -msgstr "" +msgstr "{profileName} juntou-se ao Bluesky utilizando um pacote inicial há {0}" #: src/view/screens/PreferencesFollowingFeed.tsx:67 #~ msgid "{value, plural, =0 {Show all replies} one {Show replies with at least # like} other {Show replies with at least # likes}}" -#~ msgstr "{value, plural, =0 {Show all replies} one {Show replies with at least # like} other {Show replies with at least # likes}}" +#~ msgstr "{value, plural, =0 {Mostrar todas as respostas} one {Show replies with at least # curtida} other {Show replies with at least # curtidas}}" #: src/components/WhoCanReply.tsx:296 #~ msgid "<0/> members" @@ -200,21 +200,21 @@ msgstr "" #: src/screens/StarterPack/Wizard/index.tsx:485 #~ msgid "<0>{0} and<1> <2>{1} are included in your starter pack" -#~ msgstr "" +#~ msgstr "<0>{0} e<1> <2>{1} estão incluídos no seu pacote inicial" #: src/screens/StarterPack/Wizard/index.tsx:466 msgctxt "profiles" msgid "<0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}} are included in your starter pack" -msgstr "<0>{0}, <1>{1}, e {2, plural, one {# outro} other {# outros}} estão incluídos no seu kit inicial" +msgstr "<0>{0}, <1>{1}, e {2, plural, one {# outro} other {# outros}} estão incluídos no seu pacote inicial" #: src/screens/StarterPack/Wizard/index.tsx:519 msgctxt "feeds" msgid "<0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}} are included in your starter pack" -msgstr "<0>{0}, <1>{1}, e {2, plural, one {# outro} other {# outros}} estão incluídos no seu kit inicial" +msgstr "<0>{0}, <1>{1}, e {2, plural, one {# outro} other {# outros}} estão incluídos no seu pacote inicial" #: src/screens/StarterPack/Wizard/index.tsx:497 #~ msgid "<0>{0}, <1>{1}, and {2} {3, plural, one {other} other {others}} are included in your starter pack" -#~ msgstr "" +#~ msgstr "<0>{0}, <1>{1}, e {2} {3, plural, one {outro} other {outros}} estão incluídos no seu pacote inicial" #: src/view/shell/Drawer.tsx:109 msgid "<0>{0} {1, plural, one {follower} other {followers}}" @@ -226,7 +226,7 @@ msgstr "<0>{0} {1, plural, one {seguindo} other {seguindo}}" #: src/screens/StarterPack/Wizard/index.tsx:507 msgid "<0>{0} and<1> <2>{1} are included in your starter pack" -msgstr "<0>{0} e<1> <2>{1} estão incluídos no seu kit inicial" +msgstr "<0>{0} e<1> <2>{1} estão incluídos no seu pacote inicial" #: src/view/shell/Drawer.tsx:96 #~ msgid "<0>{0} following" @@ -234,7 +234,7 @@ msgstr "<0>{0} e<1> <2>{1} estão incluídos no seu kit inicial" #: src/screens/StarterPack/Wizard/index.tsx:500 msgid "<0>{0} is included in your starter pack" -msgstr "<0>{0} está incluído no seu kit inicial" +msgstr "<0>{0} está incluído no seu pacote inicial" #: src/components/WhoCanReply.tsx:274 msgid "<0>{0} members" @@ -267,7 +267,7 @@ msgstr "<0>Não se aplica. Este aviso só funciona para posts com mídia." #: src/screens/StarterPack/Wizard/index.tsx:457 msgid "<0>You and<1> <2>{0} are included in your starter pack" -msgstr "<0>Você e<1> <2>{0} estão incluídos no seu kit inicial" +msgstr "<0>Você e<1> <2>{0} estão incluídos no seu pacote inicial" #: src/screens/Profile/Header/Handle.tsx:50 msgid "⚠Invalid Handle" @@ -279,7 +279,7 @@ msgstr "24 horas" #: src/screens/Login/LoginForm.tsx:266 msgid "2FA Confirmation" -msgstr "Confirmação do 2FA" +msgstr "Confirmação de 2FA" #: src/components/dialogs/MutedWords.tsx:232 msgid "30 days" @@ -381,7 +381,7 @@ msgstr "Adicione mais {0} para continuar" #: src/components/StarterPack/Wizard/WizardListCard.tsx:59 msgid "Add {displayName} to starter pack" -msgstr "Adicione {displayName} ao kit inicial" +msgstr "Adicione {displayName} ao pacote inicial" #: src/view/com/modals/SelfLabel.tsx:57 msgid "Add a content warning" @@ -435,7 +435,7 @@ msgstr "Adicionar palavras/tags silenciadas" #: src/screens/StarterPack/Wizard/index.tsx:197 #~ msgid "Add people to your starter pack that you think others will enjoy following" -#~ msgstr "Adicione pessoas ao seu kit inicial que você acha que outros gostarão de seguir" +#~ msgstr "Adicione pessoas ao seu pacote inicial que você acha que outros gostarão de seguir" #: src/screens/Home/NoFeedsPinned.tsx:99 msgid "Add recommended feeds" @@ -443,7 +443,7 @@ msgstr "Utilizar feeds recomendados" #: src/screens/StarterPack/Wizard/index.tsx:488 msgid "Add some feeds to your starter pack!" -msgstr "Adicione alguns feeds ao seu kit inicial!" +msgstr "Adicione alguns feeds ao seu pacote inicial!" #: src/screens/Feeds/NoFollowingFeed.tsx:41 msgid "Add the default feed of only people you follow" @@ -589,7 +589,7 @@ msgstr "Ocorreu um erro" #: src/components/StarterPack/ProfileStarterPacks.tsx:315 msgid "An error occurred while generating your starter pack. Want to try again?" -msgstr "Ocorreu um erro ao gerar seu kit inicial. Quer tentar novamente?" +msgstr "Ocorreu um erro ao gerar seu pacote inicial. Quer tentar novamente?" #: src/view/com/util/post-embeds/VideoEmbed.tsx:69 #: src/view/com/util/post-embeds/VideoEmbed.web.tsx:150 @@ -665,7 +665,7 @@ msgstr "GIF animado" #: src/lib/moderation/useReportOptions.ts:33 msgid "Anti-Social Behavior" -msgstr "Comportamento anti-social" +msgstr "Comportamento Anti-Social" #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:54 msgid "Anybody can interact" @@ -758,7 +758,7 @@ msgstr "Tem certeza de que deseja excluir esta mensagem? A mensagem será exclu #: src/screens/StarterPack/StarterPackScreen.tsx:621 msgid "Are you sure you want to delete this starter pack?" -msgstr "Tem certeza de que deseja excluir este kit inicial?" +msgstr "Tem certeza de que deseja excluir este pacote inicial?" #: src/components/dms/ConvoMenu.tsx:189 #~ msgid "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for other participants." @@ -887,11 +887,11 @@ msgstr "Contas bloqueadas não podem te responder, mencionar ou interagir com vo #: src/view/screens/ModerationBlockedAccounts.tsx:117 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours." -msgstr "Contas bloqueadas não podem te responder, mencionar ou interagir com você. Você não verá o conteúdo deles e eles serão impedidos de ver o seu." +msgstr "Contas bloqueadas não podem responder, mencionar ou interagir com você. Você não verá o conteúdo deles e eles serão impedidos de ver o seu." #: src/view/com/post-thread/PostThread.tsx:412 msgid "Blocked post." -msgstr "Post bloqueado." +msgstr "Postagem bloqueada." #: src/screens/Profile/Sections/Labels.tsx:173 msgid "Blocking does not prevent this labeler from placing labels on your account." @@ -899,7 +899,7 @@ msgstr "Bloquear não previne este rotulador de rotular a sua conta." #: src/view/screens/ProfileList.tsx:741 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." -msgstr "Bloqueios são públicos. Contas bloqueadas não podem te responder, mencionar ou interagir com você." +msgstr "Bloqueios são públicos. Contas bloqueadas não podem responder, mencionar ou interagir com você." #: src/view/com/profile/ProfileMenu.tsx:357 msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." @@ -939,11 +939,11 @@ msgstr "Bluesky é melhor com amigos!" #: src/components/StarterPack/ProfileStarterPacks.tsx:282 msgid "Bluesky will choose a set of recommended accounts from people in your network." -msgstr "O Bluesky escolherá um conjunto de contas recomendadas de pessoas em sua rede." +msgstr "O Bluesky escolherá um conjunto de contas recomendadas dentre as pessoas em sua rede." #: src/screens/Moderation/index.tsx:567 msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private." -msgstr "O Bluesky não mostrará seu perfil e publicações para usuários desconectados. Outros aplicativos podem não honrar esta solicitação. Isso não torna a sua conta privada." +msgstr "O Bluesky não mostrará seu perfil e publicações para usuários desconectados. Outros aplicativos podem não respeitar esta solicitação. Isto não torna a sua conta privada." #: src/lib/moderation/useLabelBehaviorDescription.ts:53 msgid "Blur images" @@ -1021,7 +1021,7 @@ msgstr "Câmera" #: src/view/com/modals/AddAppPasswords.tsx:180 msgid "Can only contain letters, numbers, spaces, dashes, and underscores. Must be at least 4 characters long, but no more than 32 characters long." -msgstr "Só pode conter letras, números, espaços, traços e sublinhados. Deve ter pelo menos 4 caracteres, mas não mais de 32 caracteres." +msgstr "Só pode conter letras, números, espaços, riscas e subtraços. Deve ter pelo menos 4 caracteres, mas não mais de 32 caracteres." #: src/components/Menu/index.tsx:235 #: src/components/Prompt.tsx:119 @@ -1092,7 +1092,7 @@ msgstr "Cancela a abertura do link" #: src/view/com/modals/VerifyEmail.tsx:160 msgid "Change" -msgstr "Trocar" +msgstr "Alterar" #: src/view/screens/Settings/index.tsx:341 msgctxt "action" @@ -1123,7 +1123,7 @@ msgstr "Alterar Senha" #: src/view/com/composer/select-language/SuggestedLanguage.tsx:73 msgid "Change post language to {0}" -msgstr "Trocar idioma do post para {0}" +msgstr "Alterar idioma do post para {0}" #: src/view/com/modals/ChangeEmail.tsx:104 msgid "Change Your Email" @@ -1294,7 +1294,7 @@ msgstr "Clique para desabilitar as citações desta publicação." #: src/components/dialogs/PostInteractionSettingsDialog.tsx:304 msgid "Click to enable quote posts of this post." -msgstr "Clique para habilitar citações desta publicação." +msgstr "Clique para habilitar as citações desta publicação." #: src/components/dms/MessageItem.tsx:231 msgid "Click to retry failed message" @@ -1306,7 +1306,7 @@ msgstr "Clima e tempo" #: src/components/dms/ChatEmptyPill.tsx:39 msgid "Clip 🐴 clop 🐴" -msgstr "" +msgstr "Tchic 🐴 tloc 🐴" #: src/components/dialogs/GifSelect.ios.tsx:250 #: src/components/dialogs/GifSelect.tsx:270 @@ -1378,7 +1378,7 @@ msgstr "Fecha o editor de post e descarta o rascunho" #: src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx:37 msgid "Closes viewer for header image" -msgstr "Fechar o visualizador de banner" +msgstr "Fecha o visualizador de banner" #: src/view/com/notifications/FeedItem.tsx:269 msgid "Collapse list of users" @@ -1676,17 +1676,17 @@ msgstr "Criar uma nova conta do Bluesky" #: src/components/StarterPack/QrCodeDialog.tsx:154 msgid "Create a QR code for a starter pack" -msgstr "Crie o QR code para um kit inicial" +msgstr "Crie o QR code para um pacote inicial" #: src/components/StarterPack/ProfileStarterPacks.tsx:165 #: src/components/StarterPack/ProfileStarterPacks.tsx:259 #: src/Navigation.tsx:368 msgid "Create a starter pack" -msgstr "Crie um kit inicial" +msgstr "Crie um pacote inicial" #: src/components/StarterPack/ProfileStarterPacks.tsx:246 msgid "Create a starter pack for me" -msgstr "Crie um kit inicial para mim" +msgstr "Crie um pacote inicial para mim" #: src/screens/Signup/index.tsx:99 msgid "Create Account" @@ -1861,16 +1861,16 @@ msgstr "Excluir minha conta…" #: src/view/com/util/forms/PostDropdownBtn.tsx:609 #: src/view/com/util/forms/PostDropdownBtn.tsx:611 msgid "Delete post" -msgstr "Excluir post" +msgstr "Excluir postagem" #: src/screens/StarterPack/StarterPackScreen.tsx:567 #: src/screens/StarterPack/StarterPackScreen.tsx:723 msgid "Delete starter pack" -msgstr "Excluir kit inicial" +msgstr "Excluir pacote inicial" #: src/screens/StarterPack/StarterPackScreen.tsx:618 msgid "Delete starter pack?" -msgstr "Excluir kit inicial?" +msgstr "Excluir pacote inicial?" #: src/view/screens/ProfileList.tsx:718 msgid "Delete this list?" @@ -1878,7 +1878,7 @@ msgstr "Excluir esta lista?" #: src/view/com/util/forms/PostDropdownBtn.tsx:624 msgid "Delete this post?" -msgstr "Excluir este post?" +msgstr "Excluir esta postagem?" #: src/view/com/util/post-embeds/QuoteEmbed.tsx:85 msgid "Deleted" @@ -1886,7 +1886,7 @@ msgstr "Excluído" #: src/view/com/post-thread/PostThread.tsx:398 msgid "Deleted post." -msgstr "Post excluído." +msgstr "Postagem excluída." #: src/view/screens/Settings/index.tsx:857 msgid "Deletes the chat declaration record" @@ -2208,7 +2208,7 @@ msgstr "Editar Perfil" #: src/screens/StarterPack/StarterPackScreen.tsx:554 msgid "Edit starter pack" -msgstr "Editar kit incial" +msgstr "Editar pacote inicial" #: src/view/com/modals/CreateOrEditList.tsx:234 msgid "Edit User List" @@ -2228,7 +2228,7 @@ msgstr "Editar sua descrição" #: src/Navigation.tsx:373 msgid "Edit your starter pack" -msgstr "Editar kit inicial" +msgstr "Editar pacote inicial" #: src/screens/Onboarding/index.tsx:31 #: src/screens/Onboarding/state.ts:86 @@ -2945,7 +2945,7 @@ msgstr "Galeria" #: src/components/StarterPack/ProfileStarterPacks.tsx:279 msgid "Generate a starter pack" -msgstr "Gere um kit inicial" +msgstr "Gere um pacote inicial" #: src/view/shell/Drawer.tsx:350 msgid "Get help" @@ -3374,7 +3374,7 @@ msgstr "Convites: 1 disponível" #: src/components/StarterPack/ShareDialog.tsx:97 msgid "Invite people to this starter pack!" -msgstr "Convide pessoas para este kit inicial!" +msgstr "Convide pessoas para este pacote inicial!" #: src/screens/StarterPack/Wizard/StepDetails.tsx:35 msgid "Invite your friends to follow your favorite feeds and people" @@ -3390,7 +3390,7 @@ msgstr "Convites, mas pessoais" #: src/screens/StarterPack/Wizard/index.tsx:452 msgid "It's just you right now! Add more people to your starter pack by searching above." -msgstr "É só você por enquanto! Adicione mais pessoas ao seu kit inicial pesquisando acima." +msgstr "É só você por enquanto! Adicione mais pessoas ao seu pacote inicial pesquisando acima." #: src/view/com/auth/SplashScreen.web.tsx:164 msgid "Jobs" @@ -4111,7 +4111,7 @@ msgstr "Novo" #: src/screens/Messages/List/index.tsx:331 #: src/screens/Messages/List/index.tsx:338 msgid "New chat" -msgstr "Novo chat" +msgstr "Nova conversa" #: src/components/dms/NewMessagesPill.tsx:92 msgid "New messages" @@ -4132,7 +4132,7 @@ msgstr "Nova Senha" #: src/view/com/feeds/FeedPage.tsx:147 msgctxt "action" msgid "New post" -msgstr "Novo post" +msgstr "Postar" #: src/view/screens/Feeds.tsx:580 #: src/view/screens/Notifications.tsx:228 @@ -4142,12 +4142,12 @@ msgstr "Novo post" #: src/view/screens/ProfileList.tsx:276 #: src/view/shell/desktop/LeftNav.tsx:278 msgid "New post" -msgstr "Novo post" +msgstr "Postar" #: src/view/shell/desktop/LeftNav.tsx:284 msgctxt "action" msgid "New Post" -msgstr "Novo Post" +msgstr "Postar" #: src/components/NewskieDialog.tsx:83 msgid "New user info dialog" @@ -4517,7 +4517,7 @@ msgstr "Abrir opções do post" #: src/screens/StarterPack/StarterPackScreen.tsx:540 msgid "Open starter pack menu" -msgstr "Abra o menu do kit inicial" +msgstr "Abra o menu do pacote inicial" #: src/view/screens/Settings/index.tsx:826 #: src/view/screens/Settings/index.tsx:836 @@ -4927,11 +4927,11 @@ msgstr "Postar" #: src/view/com/post-thread/PostThread.tsx:480 msgctxt "description" msgid "Post" -msgstr "Post" +msgstr "Postar" #: src/view/com/post-thread/PostThreadItem.tsx:196 msgid "Post by {0}" -msgstr "Post por {0}" +msgstr "Postado por {0}" #: src/Navigation.tsx:199 #: src/Navigation.tsx:206 @@ -4942,21 +4942,21 @@ msgstr "Post por @{0}" #: src/view/com/util/forms/PostDropdownBtn.tsx:174 msgid "Post deleted" -msgstr "Post excluído" +msgstr "Postagem excluída" #: src/view/com/post-thread/PostThread.tsx:212 msgid "Post hidden" -msgstr "Post oculto" +msgstr "Postagem oculta" #: src/components/moderation/ModerationDetailsDialog.tsx:106 #: src/lib/moderation/useModerationCauseDescription.ts:104 msgid "Post Hidden by Muted Word" -msgstr "Post Escondido por Palavra Silenciada" +msgstr "Postagem Escondida por Palavra Silenciada" #: src/components/moderation/ModerationDetailsDialog.tsx:109 #: src/lib/moderation/useModerationCauseDescription.ts:113 msgid "Post Hidden by You" -msgstr "Post Escondido por Você" +msgstr "Postagem Escondida por Você" #: src/components/dialogs/PostInteractionSettingsDialog.tsx:283 msgid "Post interaction settings" @@ -4968,25 +4968,25 @@ msgstr "Idioma do post" #: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:75 msgid "Post Languages" -msgstr "Idiomas do Post" +msgstr "Idiomas da Postagem" #: src/view/com/post-thread/PostThread.tsx:207 #: src/view/com/post-thread/PostThread.tsx:219 msgid "Post not found" -msgstr "Post não encontrado" +msgstr "Postagem não encontrada" #: src/components/TagMenu/index.tsx:267 msgid "posts" -msgstr "posts" +msgstr "postagens" #: src/screens/StarterPack/StarterPackScreen.tsx:173 #: src/view/screens/Profile.tsx:209 msgid "Posts" -msgstr "Posts" +msgstr "Postagens" #: src/components/dialogs/MutedWords.tsx:89 #~ msgid "Posts can be muted based on their text, their tags, or both." -#~ msgstr "Posts podem ser silenciados baseados no seu conteúdo, tags ou ambos." +#~ msgstr "Postagens podem ser silenciadas baseados no seu conteúdo, tags ou ambos." #: src/components/dialogs/MutedWords.tsx:115 msgid "Posts can be muted based on their text, their tags, or both. We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." @@ -4994,7 +4994,7 @@ msgstr "As postagens podem ser silenciadas com base em seu texto, suas tags ou a #: src/view/com/posts/FeedErrorMessage.tsx:68 msgid "Posts hidden" -msgstr "Posts ocultados" +msgstr "Postagens ocultadas" #: src/view/com/modals/LinkWarning.tsx:60 msgid "Potentially Misleading Link" @@ -5137,7 +5137,7 @@ msgstr "Citar post" #: src/view/com/modals/Repost.tsx:71 #~ msgctxt "action" #~ msgid "Quote Post" -#~ msgstr "Citar Post" +#~ msgstr "Citar Postagem" #: src/view/com/util/forms/PostDropdownBtn.tsx:302 msgid "Quote post was re-attached" @@ -5505,7 +5505,7 @@ msgstr "Denunciar post" #: src/screens/StarterPack/StarterPackScreen.tsx:593 #: src/screens/StarterPack/StarterPackScreen.tsx:596 msgid "Report starter pack" -msgstr "Denunciar kit inicial" +msgstr "Denunciar pacote inicial" #: src/components/ReportDialog/SelectReportOptionView.tsx:43 msgid "Report this content" @@ -5531,7 +5531,7 @@ msgstr "Denunciar este post" #: src/components/ReportDialog/SelectReportOptionView.tsx:59 msgid "Report this starter pack" -msgstr "Denunciar este kit inicial" +msgstr "Denunciar este pacote inicial" #: src/components/ReportDialog/SelectReportOptionView.tsx:47 msgid "Report this user" @@ -6195,15 +6195,15 @@ msgstr "Compartilhar QR code" #: src/screens/StarterPack/StarterPackScreen.tsx:404 msgid "Share this starter pack" -msgstr "Compartilhar este kit inicial" +msgstr "Compartilhar este pacote inicial" #: src/components/StarterPack/ShareDialog.tsx:100 msgid "Share this starter pack and help people join your community on Bluesky." -msgstr "Compartilhe este kit inicial e ajude as pessoas a se juntarem à sua comunidade no Bluesky." +msgstr "Compartilhe este pacote inicial e ajude as pessoas a se juntarem à sua comunidade no Bluesky." #: src/components/dms/ChatEmptyPill.tsx:34 msgid "Share your favorite feed!" -msgstr "Compartilhe este kit inicial e ajude as pessoas a se juntarem à sua comunidade no Bluesky." +msgstr "Compartilhe este Pacote Inicial e ajude as pessoas a se juntarem à sua comunidade no Bluesky." #: src/Navigation.tsx:251 msgid "Shared Preferences Tester" @@ -6277,7 +6277,7 @@ msgstr "Mostrar respostas silenciadas" #: src/view/screens/PreferencesFollowingFeed.tsx:154 msgid "Show Posts from My Feeds" -msgstr "Mostrar Posts dos Meus Feeds" +msgstr "Mostrar Postagens dos Meus Feeds" #: src/view/screens/PreferencesFollowingFeed.tsx:118 msgid "Show Quote Posts" @@ -6427,12 +6427,12 @@ msgstr "autenticado como @{0}" #: src/view/com/notifications/FeedItem.tsx:222 msgid "signed up with your starter pack" -msgstr "se inscreveu com seu kit inicial" +msgstr "se inscreveu com seu pacote inicial" #: src/screens/StarterPack/StarterPackLandingScreen.tsx:306 #: src/screens/StarterPack/StarterPackLandingScreen.tsx:313 msgid "Signup without a starter pack" -msgstr "Inscreva-se sem um kit inicial" +msgstr "Inscreva-se sem um pacote inicial" #: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:102 msgid "Similar accounts" @@ -6548,23 +6548,23 @@ msgstr "Início da integração da sua janela. Não retroceda. Em vez disso, ava #: src/Navigation.tsx:363 #: src/screens/StarterPack/Wizard/index.tsx:182 msgid "Starter Pack" -msgstr "Kit Inicial" +msgstr "Pacote Inicial" #: src/components/StarterPack/StarterPackCard.tsx:73 msgid "Starter pack by {0}" -msgstr "Kit inicial por {0}" +msgstr "Pacote inicial por {0}" #: src/screens/StarterPack/StarterPackScreen.tsx:703 msgid "Starter pack is invalid" -msgstr "Kit inicial é inválido" +msgstr "Pacote inicial é inválido" #: src/view/screens/Profile.tsx:214 msgid "Starter Packs" -msgstr "Kits Iniciais" +msgstr "Pacotes Iniciais" #: src/components/StarterPack/ProfileStarterPacks.tsx:238 msgid "Starter packs let you easily share your favorite feeds and people with your friends." -msgstr "Kits iniciais permitem que você compartilhe facilmente seus feeds e pessoas favoritas com seus amigos." +msgstr "Pacotes iniciais permitem que você compartilhe facilmente seus feeds e pessoas favoritas com seus amigos." #: src/view/screens/Settings/index.tsx:862 #~ msgid "Status page" @@ -6776,7 +6776,7 @@ msgstr "Este identificador de usuário já está sendo usado." #: src/screens/StarterPack/Wizard/index.tsx:105 #: src/screens/StarterPack/Wizard/index.tsx:113 msgid "That starter pack could not be found." -msgstr "Esse kit inicial não pôde ser encontrado." +msgstr "Esse pacote inicial não pôde ser encontrado." #: src/view/com/post-thread/PostQuotes.tsx:129 msgid "That's all, folks!" @@ -6852,7 +6852,7 @@ msgstr "Vídeo selecionado é maior que 100 MB." #: src/screens/StarterPack/StarterPackScreen.tsx:713 msgid "The starter pack that you are trying to view is invalid. You may delete this starter pack instead." -msgstr "O kit inicial que você está tentando visualizar é inválido. Você pode excluir este kit inicial em vez disso." +msgstr "O pacote inicial que você está tentando visualizar é inválido. Você pode excluir este pacote inicial em vez disso." #: src/view/screens/Support.tsx:36 msgid "The support form has been moved. If you need help, please <0/> or visit {HELP_DESK_URL} to get in touch with us." @@ -7884,7 +7884,7 @@ msgstr "Do que você gosta?" #: src/screens/StarterPack/Wizard/StepDetails.tsx:42 msgid "What do you want to call your starter pack?" -msgstr "Como você quer chamar seu kit inicial?" +msgstr "Como você quer chamar seu pacote inicial?" #: src/view/com/auth/SplashScreen.tsx:40 #: src/view/com/auth/SplashScreen.web.tsx:86 @@ -7994,7 +7994,7 @@ msgstr "Sim, desativar" #: src/screens/StarterPack/StarterPackScreen.tsx:649 msgid "Yes, delete this starter pack" -msgstr "Sim, exclua este kit inicial" +msgstr "Sim, exclua este pacote inicial" #: src/view/com/util/forms/PostDropdownBtn.tsx:692 msgid "Yes, detach" @@ -8048,7 +8048,7 @@ msgstr "Você pode alterar isso a qualquer momento." #: src/screens/Messages/Settings.tsx:111 msgid "You can continue ongoing conversations regardless of which setting you choose." -msgstr "Você pode continuar conversas em andamento, independentemente da configuração que escolher." +msgstr "Você pode continuar conversando, independentemente da configuração que escolher." #: src/screens/Login/index.tsx:158 #: src/screens/Login/PasswordUpdatedForm.tsx:33 @@ -8156,7 +8156,7 @@ msgstr "Você chegou ao fim" #: src/components/StarterPack/ProfileStarterPacks.tsx:235 msgid "You haven't created a starter pack yet!" -msgstr "Você ainda não criou um kit inicial!" +msgstr "Você ainda não criou um pacote inicial!" #: src/components/dialogs/MutedWords.tsx:398 msgid "You haven't muted any words or tags yet" @@ -8177,7 +8177,7 @@ msgstr "Você pode contestar estes rótulos se você acha que estão errados." #: src/screens/StarterPack/Wizard/State.tsx:79 msgid "You may only add up to {STARTER_PACK_MAX_SIZE} profiles" -msgstr "Você pode adicionar no máximo {STARTER_PACK_MAX_SIZE} perfis ao seu kit inicial" +msgstr "Você pode adicionar no máximo {STARTER_PACK_MAX_SIZE} perfis ao seu Pacote Inicial" #: src/screens/StarterPack/Wizard/State.tsx:97 msgid "You may only add up to 3 feeds" @@ -8201,7 +8201,7 @@ msgstr "Você precisa ter no mínimo 13 anos de idade para se cadastrar." #: src/components/StarterPack/ProfileStarterPacks.tsx:306 msgid "You must be following at least seven other people to generate a starter pack." -msgstr "Você deve estar seguindo pelo menos sete outras pessoas para gerar um kit inicial." +msgstr "Você deve estar seguindo pelo menos sete outras pessoas para gerar um pacote inicial." #: src/components/StarterPack/QrCodeDialog.tsx:60 msgid "You must grant access to your photo library to save a QR code" @@ -8388,4 +8388,4 @@ msgstr "Sua denúncia será enviada para o serviço de moderação do Bluesky" #: src/screens/Signup/index.tsx:148 msgid "Your user handle" -msgstr "Seu identificador de usuário" +msgstr "Seu identificador de usuário" \ No newline at end of file diff --git a/src/screens/Post/PostLikedBy.tsx b/src/screens/Post/PostLikedBy.tsx index eab9e2d2..ea522488 100644 --- a/src/screens/Post/PostLikedBy.tsx +++ b/src/screens/Post/PostLikedBy.tsx @@ -27,7 +27,7 @@ export const PostLikedByScreen = ({route}: Props) => { ) return ( - + diff --git a/src/screens/Post/PostQuotes.tsx b/src/screens/Post/PostQuotes.tsx index 4a06639f..0d59418f 100644 --- a/src/screens/Post/PostQuotes.tsx +++ b/src/screens/Post/PostQuotes.tsx @@ -27,7 +27,7 @@ export const PostQuotesScreen = ({route}: Props) => { ) return ( - + diff --git a/src/screens/Post/PostRepostedBy.tsx b/src/screens/Post/PostRepostedBy.tsx index 2a8ef1e0..f8c058ff 100644 --- a/src/screens/Post/PostRepostedBy.tsx +++ b/src/screens/Post/PostRepostedBy.tsx @@ -27,7 +27,7 @@ export const PostRepostedByScreen = ({route}: Props) => { ) return ( - + diff --git a/src/state/queries/video/util.ts b/src/state/queries/video/util.ts index 898f1736..e019848a 100644 --- a/src/state/queries/video/util.ts +++ b/src/state/queries/video/util.ts @@ -1,6 +1,8 @@ import {useMemo} from 'react' import {AtpAgent} from '@atproto/api' +import {SupportedMimeTypes} from '#/lib/constants' + const UPLOAD_ENDPOINT = 'https://video.bsky.app/' export const createVideoEndpointUrl = ( @@ -25,7 +27,7 @@ export function useVideoAgent() { }, []) } -export function mimeToExt(mimeType: string) { +export function mimeToExt(mimeType: SupportedMimeTypes | (string & {})) { switch (mimeType) { case 'video/mp4': return 'mp4' @@ -33,6 +35,8 @@ export function mimeToExt(mimeType: string) { return 'webm' case 'video/mpeg': return 'mpeg' + case 'video/quicktime': + return 'mov' default: throw new Error(`Unsupported mime type: ${mimeType}`) } diff --git a/src/state/queries/video/video.ts b/src/state/queries/video/video.ts index 87f31564..5e36ce35 100644 --- a/src/state/queries/video/video.ts +++ b/src/state/queries/video/video.ts @@ -5,6 +5,7 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {QueryClient, useQuery, useQueryClient} from '@tanstack/react-query' +import {SUPPORTED_MIME_TYPES, SupportedMimeTypes} from '#/lib/constants' import {logger} from '#/logger' import {isWeb} from '#/platform/detection' import {ServerError, VideoTooLargeError} from 'lib/media/video/errors' @@ -175,19 +176,19 @@ export function useUploadVideo({ }) const selectVideo = (asset: ImagePickerAsset) => { - switch (getMimeType(asset)) { - case 'video/mp4': - case 'video/mpeg': - case 'video/webm': - dispatch({ - type: 'SetAsset', - asset, - }) - onSelectVideo(asset) - break - default: - throw new Error(_(msg`Unsupported video type: ${getMimeType(asset)}`)) + // compression step on native converts to mp4, so no need to check there + if (isWeb) { + const mimeType = getMimeType(asset) + if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) { + throw new Error(_(msg`Unsupported video type: ${mimeType}`)) + } } + + dispatch({ + type: 'SetAsset', + asset, + }) + onSelectVideo(asset) } const clearVideo = () => { diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index bad6ccfe..d712605d 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -562,7 +562,9 @@ let Feed = ({ desktopFixedHeightOffset ? desktopFixedHeightOffset : true } initialNumToRender={initialNumToRenderOverride ?? initialNumToRender} - windowSize={11} + windowSize={9} + maxToRenderPerBatch={5} + updateCellsBatchingPeriod={40} onItemSeen={feedFeedback.onItemSeen} /> diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 6a58a562..de73103c 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -106,8 +106,7 @@ let PostCtrls = ({ [t], ) as StyleProp - const likeValue = post.viewer?.like ? 1 : 0 - const nextExpectedLikeValue = React.useRef(likeValue) + const [isToggleLikeIcon, setIsToggleLikeIcon] = React.useState(false) const onPressToggleLike = React.useCallback(async () => { if (isBlocked) { @@ -119,8 +118,8 @@ let PostCtrls = ({ } try { + setIsToggleLikeIcon(true) if (!post.viewer?.like) { - nextExpectedLikeValue.current = 1 playHaptic() sendInteraction({ item: post.uri, @@ -130,7 +129,6 @@ let PostCtrls = ({ captureAction(ProgressGuideAction.Like) await queueLike() } else { - nextExpectedLikeValue.current = 0 await queueUnlike() } } catch (e: any) { @@ -317,11 +315,16 @@ let PostCtrls = ({ } accessibilityHint="" hitSlop={POST_CTRL_HITSLOP}> - + diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index ca9dc1c0..c61cda68 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -11,6 +11,7 @@ import { AppBskyEmbedImages, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, + AppBskyEmbedVideo, AppBskyFeedDefs, AppBskyFeedPost, ModerationDecision, @@ -180,12 +181,17 @@ export function QuoteEmbed({ if (allowNestedQuotes) { return e } else { - if (AppBskyEmbedImages.isView(e) || AppBskyEmbedExternal.isView(e)) { + if ( + AppBskyEmbedImages.isView(e) || + AppBskyEmbedExternal.isView(e) || + AppBskyEmbedVideo.isView(e) + ) { return e } else if ( AppBskyEmbedRecordWithMedia.isView(e) && (AppBskyEmbedImages.isView(e.media) || - AppBskyEmbedExternal.isView(e.media)) + AppBskyEmbedExternal.isView(e.media) || + AppBskyEmbedVideo.isView(e.media)) ) { return e.media } diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx index 59f9d9f9..b9c0a99a 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -5,12 +5,9 @@ import {VideoPlayer, VideoView} from 'expo-video' import {AppBskyEmbedVideo} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useIsFocused} from '@react-navigation/native' import {HITSLOP_30} from '#/lib/constants' -import {useAppState} from '#/lib/hooks/useAppState' import {clamp} from '#/lib/numbers' -import {logger} from '#/logger' import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext' import {atoms as a, useTheme} from '#/alf' import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' @@ -29,26 +26,7 @@ export function VideoEmbedInnerNative({ const {_} = useLingui() const {player} = useActiveVideoNative() const ref = useRef(null) - const isScreenFocused = useIsFocused() - const isAppFocused = useAppState() - - useEffect(() => { - try { - if (isAppFocused === 'active' && isScreenFocused && !player.playing) { - PlatformInfo.setAudioCategory(AudioCategory.Ambient) - PlatformInfo.setAudioActive(false) - player.muted = true - player.play() - } else if (player.playing) { - player.pause() - } - } catch (err) { - logger.error( - 'Failed to play/pause while backgrounding/switching screens', - {safeMessage: err}, - ) - } - }, [isAppFocused, player, isScreenFocused]) + const [isFullscreen, setIsFullscreen] = useState(false) const enterFullscreen = useCallback(() => { ref.current?.enterFullscreen() @@ -69,18 +47,23 @@ export function VideoEmbedInnerNative({ player={player} style={[a.flex_1, a.rounded_sm]} contentFit="contain" - nativeControls={true} + nativeControls={isFullscreen} accessibilityIgnoresInvertColors onEnterFullscreen={() => { PlatformInfo.setAudioCategory(AudioCategory.Playback) PlatformInfo.setAudioActive(true) player.muted = false + setIsFullscreen(true) }} onExitFullscreen={() => { PlatformInfo.setAudioCategory(AudioCategory.Ambient) PlatformInfo.setAudioActive(false) player.muted = true - if (!player.playing) player.play() + player.playbackRate = 1 + if (!player.playing) { + player.play() + } + setIsFullscreen(false) }} accessibilityLabel={ embed.alt ? _(msg`Video: ${embed.alt}`) : _(msg`Video`) diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index e9cbf5d0..d9e075e7 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -86,6 +86,7 @@ export function PostEmbeds({ return } + // starter pack embed if (AppBskyGraphDefs.isStarterPackViewBasic(embed.record)) { return } @@ -178,6 +179,8 @@ export function PostEmbeds({ ) } + // video embed + // = if (AppBskyEmbedVideo.isView(embed)) { return ( diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx index 68447bd7..3a01edff 100644 --- a/src/view/screens/ProfileFollowers.tsx +++ b/src/view/screens/ProfileFollowers.tsx @@ -25,7 +25,7 @@ export const ProfileFollowersScreen = ({route}: Props) => { ) return ( - + diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx index 7cc10ffd..762a84a3 100644 --- a/src/view/screens/ProfileFollows.tsx +++ b/src/view/screens/ProfileFollows.tsx @@ -25,7 +25,7 @@ export const ProfileFollowsScreen = ({route}: Props) => { ) return ( - +