Add initial support for PiP on Android (#154)
This commit is contained in:
parent
e91f231173
commit
402d75cc21
5 changed files with 105 additions and 95 deletions
|
|
@ -19,6 +19,7 @@
|
|||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/models/irc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class ChatModes extends StatelessWidget {
|
||||
final ROOMSTATE roomState;
|
||||
|
|
@ -11,10 +10,6 @@ class ChatModes extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final test = Intl.plural(2, other: 'test');
|
||||
|
||||
debugPrint(test);
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:floating/floating.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
|
|
@ -24,6 +25,8 @@ abstract class VideoStoreBase with Store {
|
|||
|
||||
Timer? sleepTimer;
|
||||
|
||||
final floating = Floating();
|
||||
|
||||
@observable
|
||||
var sleepHours = 0;
|
||||
|
||||
|
|
@ -184,7 +187,11 @@ abstract class VideoStoreBase with Store {
|
|||
|
||||
void requestPictureInPicture() {
|
||||
try {
|
||||
controller?.runJavascript('document.getElementsByTagName("video")[0].requestPictureInPicture();');
|
||||
if (Platform.isAndroid) {
|
||||
floating.enable();
|
||||
} else if (Platform.isIOS) {
|
||||
controller?.runJavascript('document.getElementsByTagName("video")[0].requestPictureInPicture();');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
|
|
@ -228,4 +235,9 @@ abstract class VideoStoreBase with Store {
|
|||
sleepTimer?.cancel();
|
||||
timeRemaining = const Duration();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
floating.dispose();
|
||||
sleepTimer?.cancel();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:floating/floating.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
|
@ -160,90 +161,94 @@ class _VideoChatState extends State<VideoChat> {
|
|||
],
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
body: OrientationBuilder(
|
||||
builder: (context, orientation) {
|
||||
// Scroll to bottom when summoning keyboard or rotating.
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_chatStore.scrollController.hasClients) _chatStore.scrollController.jumpTo(_chatStore.scrollController.position.maxScrollExtent);
|
||||
});
|
||||
return PiPSwitcher(
|
||||
floating: _videoStore.floating,
|
||||
childWhenEnabled: player,
|
||||
childWhenDisabled: Scaffold(
|
||||
body: OrientationBuilder(
|
||||
builder: (context, orientation) {
|
||||
// Scroll to bottom when summoning keyboard or rotating.
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_chatStore.scrollController.hasClients) _chatStore.scrollController.jumpTo(_chatStore.scrollController.position.maxScrollExtent);
|
||||
});
|
||||
|
||||
if (orientation == Orientation.landscape) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||
if (orientation == Orientation.landscape) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||
|
||||
return Observer(
|
||||
builder: (context) {
|
||||
final landscapeChat = AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: _chatStore.expandChat ? MediaQuery.of(context).size.width / 2 : MediaQuery.of(context).size.width * _chatStore.settings.chatWidth,
|
||||
curve: Curves.ease,
|
||||
color: _chatStore.settings.fullScreen
|
||||
? Colors.black.withOpacity(_chatStore.settings.fullScreenChatOverlayOpacity)
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: chat,
|
||||
);
|
||||
return Observer(
|
||||
builder: (context) {
|
||||
final landscapeChat = AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: _chatStore.expandChat ? MediaQuery.of(context).size.width / 2 : MediaQuery.of(context).size.width * _chatStore.settings.chatWidth,
|
||||
curve: Curves.ease,
|
||||
color: _chatStore.settings.fullScreen
|
||||
? Colors.black.withOpacity(_chatStore.settings.fullScreenChatOverlayOpacity)
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: chat,
|
||||
);
|
||||
|
||||
final overlayChat = Visibility(
|
||||
visible: settingsStore.fullScreenChatOverlay,
|
||||
maintainState: true,
|
||||
child: Theme(
|
||||
data: darkTheme,
|
||||
child: DefaultTextStyle(
|
||||
style: DefaultTextStyle.of(context).style.copyWith(color: Colors.white),
|
||||
child: landscapeChat,
|
||||
final overlayChat = Visibility(
|
||||
visible: settingsStore.fullScreenChatOverlay,
|
||||
maintainState: true,
|
||||
child: Theme(
|
||||
data: darkTheme,
|
||||
child: DefaultTextStyle(
|
||||
style: DefaultTextStyle.of(context).style.copyWith(color: Colors.white),
|
||||
child: landscapeChat,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
|
||||
return ColoredBox(
|
||||
color: settingsStore.showVideo ? Colors.black : Theme.of(context).scaffoldBackgroundColor,
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: settingsStore.showVideo
|
||||
? settingsStore.fullScreen
|
||||
? Stack(
|
||||
children: [
|
||||
player,
|
||||
if (settingsStore.showOverlay)
|
||||
Row(
|
||||
children: settingsStore.landscapeChatLeftSide
|
||||
? [overlayChat, Expanded(child: overlay)]
|
||||
: [Expanded(child: overlay), overlayChat],
|
||||
)
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
children:
|
||||
settingsStore.landscapeChatLeftSide ? [landscapeChat, Expanded(child: video)] : [Expanded(child: video), landscapeChat],
|
||||
)
|
||||
: Column(
|
||||
children: [appBar, Expanded(child: chat)],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
return ColoredBox(
|
||||
color: settingsStore.showVideo ? Colors.black : Theme.of(context).scaffoldBackgroundColor,
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: settingsStore.showVideo
|
||||
? settingsStore.fullScreen
|
||||
? Stack(
|
||||
children: [
|
||||
player,
|
||||
if (settingsStore.showOverlay)
|
||||
Row(
|
||||
children: settingsStore.landscapeChatLeftSide
|
||||
? [overlayChat, Expanded(child: overlay)]
|
||||
: [Expanded(child: overlay), overlayChat],
|
||||
)
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
children:
|
||||
settingsStore.landscapeChatLeftSide ? [landscapeChat, Expanded(child: video)] : [Expanded(child: video), landscapeChat],
|
||||
)
|
||||
: Column(
|
||||
children: [appBar, Expanded(child: chat)],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values,
|
||||
);
|
||||
}
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) {
|
||||
if (!settingsStore.showVideo) return appBar;
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values,
|
||||
);
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) {
|
||||
if (!settingsStore.showVideo) return appBar;
|
||||
|
||||
return AspectRatio(aspectRatio: 16 / 9, child: video);
|
||||
},
|
||||
),
|
||||
Expanded(child: chat),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
return AspectRatio(aspectRatio: 16 / 9, child: video);
|
||||
},
|
||||
),
|
||||
Expanded(child: chat),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -252,7 +257,7 @@ class _VideoChatState extends State<VideoChat> {
|
|||
void dispose() {
|
||||
_chatStore.dispose();
|
||||
|
||||
_videoStore.cancelSleepTimer();
|
||||
_videoStore.dispose();
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.manual,
|
||||
|
|
@ -614,7 +619,7 @@ class _VideoOverlay extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (Platform.isIOS && videoStore.settingsStore.pictureInPicture)
|
||||
if (videoStore.settingsStore.pictureInPicture)
|
||||
IconButton(
|
||||
tooltip: 'Picture-in-picture',
|
||||
icon: const Icon(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/screens/settings/stores/settings_store.dart';
|
||||
|
|
@ -34,14 +32,13 @@ class VideoSettings extends StatelessWidget {
|
|||
value: settingsStore.showOverlay,
|
||||
onChanged: settingsStore.showVideo ? (newValue) => settingsStore.showOverlay = newValue : null,
|
||||
),
|
||||
if (Platform.isIOS)
|
||||
SwitchListTile.adaptive(
|
||||
isThreeLine: true,
|
||||
title: const Text('Picture-in-picture Button (experimental)'),
|
||||
subtitle: const Text('Adds a button to enter PiP mode on the bottom right of the overlay (may cause freezes/crashes).'),
|
||||
value: settingsStore.pictureInPicture,
|
||||
onChanged: settingsStore.showVideo && settingsStore.showOverlay ? (newValue) => settingsStore.pictureInPicture = newValue : null,
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
isThreeLine: true,
|
||||
title: const Text('Picture-in-picture Button (experimental)'),
|
||||
subtitle: const Text('Adds a button to enter PiP mode on the bottom right of the overlay (may cause freezes/crashes).'),
|
||||
value: settingsStore.pictureInPicture,
|
||||
onChanged: settingsStore.showVideo && settingsStore.showOverlay ? (newValue) => settingsStore.pictureInPicture = newValue : null,
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
isThreeLine: true,
|
||||
title: const Text('Long-press player to toggle overlay'),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue