diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6d307fa3..88874273 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -168,6 +168,8 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) + - native_device_orientation (0.0.1): + - Flutter - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -202,6 +204,7 @@ DEPENDENCIES: - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - in_app_review (from `.symlinks/plugins/in_app_review/ios`) + - native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -256,6 +259,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_secure_storage/ios" in_app_review: :path: ".symlinks/plugins/in_app_review/ios" + native_device_orientation: + :path: ".symlinks/plugins/native_device_orientation/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: @@ -301,6 +306,7 @@ SPEC CHECKSUMS: GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 in_app_review: 436034b18594851a7328d7f1c2ed5ec235b79cfc nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + native_device_orientation: 42487b19c552fb5a6a2b51e61b93366f628113df package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 diff --git a/lib/screens/channel/channel.dart b/lib/screens/channel/channel.dart index 049d9289..4a9a8f97 100644 --- a/lib/screens/channel/channel.dart +++ b/lib/screens/channel/channel.dart @@ -10,12 +10,12 @@ import 'package:frosty/screens/channel/video/stream_info_bar.dart'; import 'package:frosty/screens/channel/video/video.dart'; import 'package:frosty/screens/channel/video/video_overlay.dart'; import 'package:frosty/screens/channel/video/video_store.dart'; -import 'package:frosty/screens/settings/stores/settings_store.dart'; import 'package:frosty/theme.dart'; import 'package:frosty/utils/context_extensions.dart'; import 'package:frosty/widgets/blurred_container.dart'; import 'package:frosty/widgets/draggable_divider.dart'; import 'package:frosty/widgets/frosty_notification.dart'; +import 'package:native_device_orientation/native_device_orientation.dart'; import 'package:provider/provider.dart'; import 'package:simple_pip_mode/actions/pip_actions_layout.dart'; import 'package:simple_pip_mode/pip_widget.dart'; @@ -540,19 +540,38 @@ class _VideoChatState extends State ), ], ) - : SafeArea( - bottom: false, - left: - settingsStore.landscapeCutout != - LandscapeCutoutType.left && - settingsStore.landscapeCutout != - LandscapeCutoutType.both, - right: - settingsStore.landscapeCutout != - LandscapeCutoutType.right && - settingsStore.landscapeCutout != - LandscapeCutoutType.both, - child: LayoutBuilder( + : NativeDeviceOrientationReader( + useSensor: true, + builder: (context) { + final orientation = + NativeDeviceOrientationReader.orientation( + context, + ); + + // Determine which side to fill based on setting + final bool fillLeft; + final bool fillRight; + + if (settingsStore.landscapeFillAllEdges) { + fillLeft = true; + fillRight = true; + } else { + // Auto mode: fill the physical bottom side (opposite of notch) + // landscapeLeft = notch on left → fill right + // landscapeRight = notch on right → fill left + fillLeft = + orientation == + NativeDeviceOrientation.landscapeRight; + fillRight = + orientation == + NativeDeviceOrientation.landscapeLeft; + } + + return SafeArea( + bottom: false, + left: !fillLeft, + right: !fillRight, + child: LayoutBuilder( builder: (context, constraints) { final availableWidth = constraints.maxWidth; final chatWidth = _chatStore.expandChat @@ -646,6 +665,8 @@ class _VideoChatState extends State ); }, ), + ); + }, ) : SafeArea(child: chat), ); diff --git a/lib/screens/settings/chat_settings.dart b/lib/screens/settings/chat_settings.dart index 9bc24f19..604a8160 100644 --- a/lib/screens/settings/chat_settings.dart +++ b/lib/screens/settings/chat_settings.dart @@ -232,17 +232,14 @@ class _ChatSettingsState extends State { onChanged: (newValue) => settingsStore.landscapeForceVerticalChat = newValue, ), - SettingsListSelect( - title: 'Fill notch side', - subtitle: - 'Overrides and fills the available space in devices with a display notch.', - selectedOption: - landscapeCutoutNames[settingsStore.landscapeCutout.index], - options: landscapeCutoutNames, - onChanged: (newValue) => settingsStore.landscapeCutout = - LandscapeCutoutType.values[landscapeCutoutNames.indexOf( - newValue, - )], + SettingsListSwitch( + title: 'Fill all edges', + subtitle: const Text( + 'Fills both edges instead of just the bottom edge.', + ), + value: settingsStore.landscapeFillAllEdges, + onChanged: (newValue) => + settingsStore.landscapeFillAllEdges = newValue, ), SettingsListSlider( title: 'Chat overlay opacity', diff --git a/lib/screens/settings/stores/settings_store.dart b/lib/screens/settings/stores/settings_store.dart index 6c56c8e3..3a04ab3a 100644 --- a/lib/screens/settings/stores/settings_store.dart +++ b/lib/screens/settings/stores/settings_store.dart @@ -135,7 +135,7 @@ abstract class _SettingsStoreBase with Store { // Landscape mode defaults static const defaultLandscapeChatLeftSide = false; static const defaultLandscapeForceVerticalChat = false; - static const defaultLandscapeCutout = LandscapeCutoutType.none; + static const defaultLandscapeFillAllEdges = false; static const defaultChatWidth = 0.2; static const defaultFullScreenChatOverlayOpacity = 0.5; @@ -231,9 +231,9 @@ abstract class _SettingsStoreBase with Store { @observable var landscapeForceVerticalChat = defaultLandscapeForceVerticalChat; - @JsonKey(defaultValue: defaultLandscapeCutout) + @JsonKey(defaultValue: defaultLandscapeFillAllEdges) @observable - var landscapeCutout = defaultLandscapeCutout; + var landscapeFillAllEdges = defaultLandscapeFillAllEdges; @JsonKey(defaultValue: defaultChatWidth) @observable @@ -321,7 +321,7 @@ abstract class _SettingsStoreBase with Store { landscapeChatLeftSide = defaultLandscapeChatLeftSide; landscapeForceVerticalChat = defaultLandscapeForceVerticalChat; - landscapeCutout = defaultLandscapeCutout; + landscapeFillAllEdges = defaultLandscapeFillAllEdges; chatWidth = defaultChatWidth; fullScreenChatOverlayOpacity = defaultFullScreenChatOverlayOpacity; @@ -398,6 +398,3 @@ const timestampNames = ['Disabled', '12-hour', '24-hour']; enum TimestampType { disabled, twelve, twentyFour } -const landscapeCutoutNames = ['None', 'Left', 'Right', 'Both']; - -enum LandscapeCutoutType { none, left, right, both } diff --git a/lib/screens/settings/stores/settings_store.g.dart b/lib/screens/settings/stores/settings_store.g.dart index bcf06d8a..5561cfc1 100644 --- a/lib/screens/settings/stores/settings_store.g.dart +++ b/lib/screens/settings/stores/settings_store.g.dart @@ -49,12 +49,7 @@ SettingsStore _$SettingsStoreFromJson( ..landscapeChatLeftSide = json['landscapeChatLeftSide'] as bool? ?? false ..landscapeForceVerticalChat = json['landscapeForceVerticalChat'] as bool? ?? false - ..landscapeCutout = - $enumDecodeNullable( - _$LandscapeCutoutTypeEnumMap, - json['landscapeCutout'], - ) ?? - LandscapeCutoutType.none + ..landscapeFillAllEdges = json['landscapeFillAllEdges'] as bool? ?? false ..chatWidth = (json['chatWidth'] as num?)?.toDouble() ?? 0.2 ..fullScreenChatOverlayOpacity = (json['fullScreenChatOverlayOpacity'] as num?)?.toDouble() ?? 0.5 @@ -89,56 +84,55 @@ SettingsStore _$SettingsStoreFromJson( .toList() ?? []; -Map _$SettingsStoreToJson( - SettingsStore instance, -) => { - 'themeType': _$ThemeTypeEnumMap[instance.themeType]!, - 'accentColor': instance.accentColor, - 'showThumbnails': instance.showThumbnails, - 'largeStreamCard': instance.largeStreamCard, - 'launchUrlExternal': instance.launchUrlExternal, - 'showVideo': instance.showVideo, - 'defaultToHighestQuality': instance.defaultToHighestQuality, - 'useTextureRendering': instance.useTextureRendering, - 'showOverlay': instance.showOverlay, - 'toggleableOverlay': instance.toggleableOverlay, - 'showLatency': instance.showLatency, - 'badgeScale': instance.badgeScale, - 'emoteScale': instance.emoteScale, - 'messageScale': instance.messageScale, - 'messageSpacing': instance.messageSpacing, - 'fontSize': instance.fontSize, - 'showDeletedMessages': instance.showDeletedMessages, - 'showChatMessageDividers': instance.showChatMessageDividers, - 'timestampType': _$TimestampTypeEnumMap[instance.timestampType]!, - 'autoSyncChatDelay': instance.autoSyncChatDelay, - 'chatDelay': instance.chatDelay, - 'highlightFirstTimeChatter': instance.highlightFirstTimeChatter, - 'showUserNotices': instance.showUserNotices, - 'emoteMenuButtonOnLeft': instance.emoteMenuButtonOnLeft, - 'landscapeChatLeftSide': instance.landscapeChatLeftSide, - 'landscapeForceVerticalChat': instance.landscapeForceVerticalChat, - 'landscapeCutout': _$LandscapeCutoutTypeEnumMap[instance.landscapeCutout]!, - 'chatWidth': instance.chatWidth, - 'fullScreenChatOverlayOpacity': instance.fullScreenChatOverlayOpacity, - 'autocomplete': instance.autocomplete, - 'showTwitchEmotes': instance.showTwitchEmotes, - 'showTwitchBadges': instance.showTwitchBadges, - 'show7TVEmotes': instance.show7TVEmotes, - 'showBTTVEmotes': instance.showBTTVEmotes, - 'showBTTVBadges': instance.showBTTVBadges, - 'showFFZEmotes': instance.showFFZEmotes, - 'showFFZBadges': instance.showFFZBadges, - 'showRecentMessages': instance.showRecentMessages, - 'persistChatTabs': instance.persistChatTabs, - 'secondaryTabs': instance.secondaryTabs, - 'mutedWords': instance.mutedWords, - 'matchWholeWord': instance.matchWholeWord, - 'shareCrashLogsAndAnalytics': instance.shareCrashLogsAndAnalytics, - 'fullScreen': instance.fullScreen, - 'fullScreenChatOverlay': instance.fullScreenChatOverlay, - 'pinnedChannelIds': instance.pinnedChannelIds, -}; +Map _$SettingsStoreToJson(SettingsStore instance) => + { + 'themeType': _$ThemeTypeEnumMap[instance.themeType]!, + 'accentColor': instance.accentColor, + 'showThumbnails': instance.showThumbnails, + 'largeStreamCard': instance.largeStreamCard, + 'launchUrlExternal': instance.launchUrlExternal, + 'showVideo': instance.showVideo, + 'defaultToHighestQuality': instance.defaultToHighestQuality, + 'useTextureRendering': instance.useTextureRendering, + 'showOverlay': instance.showOverlay, + 'toggleableOverlay': instance.toggleableOverlay, + 'showLatency': instance.showLatency, + 'badgeScale': instance.badgeScale, + 'emoteScale': instance.emoteScale, + 'messageScale': instance.messageScale, + 'messageSpacing': instance.messageSpacing, + 'fontSize': instance.fontSize, + 'showDeletedMessages': instance.showDeletedMessages, + 'showChatMessageDividers': instance.showChatMessageDividers, + 'timestampType': _$TimestampTypeEnumMap[instance.timestampType]!, + 'autoSyncChatDelay': instance.autoSyncChatDelay, + 'chatDelay': instance.chatDelay, + 'highlightFirstTimeChatter': instance.highlightFirstTimeChatter, + 'showUserNotices': instance.showUserNotices, + 'emoteMenuButtonOnLeft': instance.emoteMenuButtonOnLeft, + 'landscapeChatLeftSide': instance.landscapeChatLeftSide, + 'landscapeForceVerticalChat': instance.landscapeForceVerticalChat, + 'landscapeFillAllEdges': instance.landscapeFillAllEdges, + 'chatWidth': instance.chatWidth, + 'fullScreenChatOverlayOpacity': instance.fullScreenChatOverlayOpacity, + 'autocomplete': instance.autocomplete, + 'showTwitchEmotes': instance.showTwitchEmotes, + 'showTwitchBadges': instance.showTwitchBadges, + 'show7TVEmotes': instance.show7TVEmotes, + 'showBTTVEmotes': instance.showBTTVEmotes, + 'showBTTVBadges': instance.showBTTVBadges, + 'showFFZEmotes': instance.showFFZEmotes, + 'showFFZBadges': instance.showFFZBadges, + 'showRecentMessages': instance.showRecentMessages, + 'persistChatTabs': instance.persistChatTabs, + 'secondaryTabs': instance.secondaryTabs, + 'mutedWords': instance.mutedWords, + 'matchWholeWord': instance.matchWholeWord, + 'shareCrashLogsAndAnalytics': instance.shareCrashLogsAndAnalytics, + 'fullScreen': instance.fullScreen, + 'fullScreenChatOverlay': instance.fullScreenChatOverlay, + 'pinnedChannelIds': instance.pinnedChannelIds, + }; const _$ThemeTypeEnumMap = { ThemeType.system: 'system', @@ -152,13 +146,6 @@ const _$TimestampTypeEnumMap = { TimestampType.twentyFour: 'twentyFour', }; -const _$LandscapeCutoutTypeEnumMap = { - LandscapeCutoutType.none: 'none', - LandscapeCutoutType.left: 'left', - LandscapeCutoutType.right: 'right', - LandscapeCutoutType.both: 'both', -}; - // ************************************************************************** // StoreGenerator // ************************************************************************** @@ -658,22 +645,26 @@ mixin _$SettingsStore on _SettingsStoreBase, Store { ); } - late final _$landscapeCutoutAtom = Atom( - name: '_SettingsStoreBase.landscapeCutout', + late final _$landscapeFillAllEdgesAtom = Atom( + name: '_SettingsStoreBase.landscapeFillAllEdges', context: context, ); @override - LandscapeCutoutType get landscapeCutout { - _$landscapeCutoutAtom.reportRead(); - return super.landscapeCutout; + bool get landscapeFillAllEdges { + _$landscapeFillAllEdgesAtom.reportRead(); + return super.landscapeFillAllEdges; } @override - set landscapeCutout(LandscapeCutoutType value) { - _$landscapeCutoutAtom.reportWrite(value, super.landscapeCutout, () { - super.landscapeCutout = value; - }); + set landscapeFillAllEdges(bool value) { + _$landscapeFillAllEdgesAtom.reportWrite( + value, + super.landscapeFillAllEdges, + () { + super.landscapeFillAllEdges = value; + }, + ); } late final _$chatWidthAtom = Atom( @@ -1136,7 +1127,7 @@ showUserNotices: ${showUserNotices}, emoteMenuButtonOnLeft: ${emoteMenuButtonOnLeft}, landscapeChatLeftSide: ${landscapeChatLeftSide}, landscapeForceVerticalChat: ${landscapeForceVerticalChat}, -landscapeCutout: ${landscapeCutout}, +landscapeFillAllEdges: ${landscapeFillAllEdges}, chatWidth: ${chatWidth}, fullScreenChatOverlayOpacity: ${fullScreenChatOverlayOpacity}, autocomplete: ${autocomplete}, diff --git a/pubspec.lock b/pubspec.lock index 19197629..599ea649 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -784,6 +784,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.4" + native_device_orientation: + dependency: "direct main" + description: + name: native_device_orientation + sha256: "711aabfd7c67396f6562437cba078d17291f8b7c454c2fbc9739c9d7141b041d" + url: "https://pub.dev" + source: hosted + version: "2.1.0" nested: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3fac52cc..004fa6a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -70,6 +70,7 @@ dependencies: dio: ^5.9.0 app_links: ^7.0.0 extended_text_field: ^16.0.2 + native_device_orientation: ^2.1.0 dev_dependencies: flutter_test: