Refactor video/chat

This commit is contained in:
tommyxchow 2022-01-23 20:13:48 -05:00
parent 80eefbc671
commit e98284be28
5 changed files with 141 additions and 131 deletions

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/screens/channel/chat/emote_menu/emote_menu.dart';
import 'package:frosty/screens/channel/chat/widgets/chat_bottom_bar.dart';
@ -13,6 +14,10 @@ class Chat extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Add a post-frame callback to scroll to bottom when rotating or entering full-screen.
SchedulerBinding.instance?.addPostFrameCallback((_) {
if (chatStore.scrollController.hasClients) chatStore.resumeScroll();
});
return Observer(
builder: (context) => Column(
children: [

View file

@ -21,14 +21,34 @@ abstract class _VideoStoreBase with Store {
late Timer updateTimer;
@readonly
var _overlayVisible = true;
var _paused = true;
@observable
var paused = true;
@readonly
var _overlayVisible = true;
@readonly
StreamTwitch? _streamInfo;
@computed
String get videoUrl => settingsStore.showOverlay
? 'https://player.twitch.tv/?channel=$userLogin&controls=false&muted=false&parent=frosty'
: 'https://player.twitch.tv/?channel=$userLogin&muted=false&parent=frosty';
Set<JavascriptChannel> get javascriptChannels => {
JavascriptChannel(
name: 'Pause',
onMessageReceived: (message) {
_paused = true;
},
),
JavascriptChannel(
name: 'Play',
onMessageReceived: (message) {
_paused = false;
},
),
};
final String userLogin;
final AuthStore authStore;
final SettingsStore settingsStore;
@ -46,13 +66,13 @@ abstract class _VideoStoreBase with Store {
@action
void handlePausePlay() {
try {
if (paused) {
if (_paused) {
controller?.runJavascript('document.getElementsByTagName("video")[0].play();');
} else {
controller?.runJavascript('document.getElementsByTagName("video")[0].pause();');
}
paused = !paused;
_paused = !_paused;
} catch (e) {
debugPrint(e.toString());
}
@ -91,11 +111,26 @@ abstract class _VideoStoreBase with Store {
overlayTimer = Timer(const Duration(seconds: 5), () => _overlayVisible = false);
}
@action
void handleToggleOverlay() {
if (settingsStore.toggleableOverlay) {
settingsStore.showOverlay = !settingsStore.showOverlay;
controller?.loadUrl(videoUrl);
}
}
void handleRefresh() async {
HapticFeedback.lightImpact();
controller?.reload();
}
FutureOr<NavigationDecision> handleNavigation(NavigationRequest navigation) {
if (navigation.url.startsWith('https://player.twitch.tv')) {
return NavigationDecision.navigate;
}
return NavigationDecision.prevent;
}
void initVideo() {
try {
controller?.runJavascript('document.getElementsByTagName("video")[0].addEventListener("pause", () => Pause.postMessage("video paused"));');

View file

@ -9,6 +9,31 @@ part of 'video_store.dart';
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
mixin _$VideoStore on _VideoStoreBase, Store {
Computed<String>? _$videoUrlComputed;
@override
String get videoUrl =>
(_$videoUrlComputed ??= Computed<String>(() => super.videoUrl,
name: '_VideoStoreBase.videoUrl'))
.value;
final _$_pausedAtom = Atom(name: '_VideoStoreBase._paused');
bool get paused {
_$_pausedAtom.reportRead();
return super._paused;
}
@override
bool get _paused => paused;
@override
set _paused(bool value) {
_$_pausedAtom.reportWrite(value, super._paused, () {
super._paused = value;
});
}
final _$_overlayVisibleAtom = Atom(name: '_VideoStoreBase._overlayVisible');
bool get overlayVisible {
@ -26,21 +51,6 @@ mixin _$VideoStore on _VideoStoreBase, Store {
});
}
final _$pausedAtom = Atom(name: '_VideoStoreBase.paused');
@override
bool get paused {
_$pausedAtom.reportRead();
return super.paused;
}
@override
set paused(bool value) {
_$pausedAtom.reportWrite(value, super.paused, () {
super.paused = value;
});
}
final _$_streamInfoAtom = Atom(name: '_VideoStoreBase._streamInfo');
StreamTwitch? get streamInfo {
@ -102,10 +112,21 @@ mixin _$VideoStore on _VideoStoreBase, Store {
}
}
@override
void handleToggleOverlay() {
final _$actionInfo = _$_VideoStoreBaseActionController.startAction(
name: '_VideoStoreBase.handleToggleOverlay');
try {
return super.handleToggleOverlay();
} finally {
_$_VideoStoreBaseActionController.endAction(_$actionInfo);
}
}
@override
String toString() {
return '''
paused: ${paused}
videoUrl: ${videoUrl}
''';
}
}

View file

@ -1,103 +1,27 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:frosty/screens/channel/stores/video_store.dart';
import 'package:frosty/screens/channel/video/video_overlay.dart';
import 'package:webview_flutter/webview_flutter.dart';
class Video extends StatelessWidget {
final String userLogin;
final FocusNode textFieldFocus;
final VideoStore videoStore;
const Video({
Key? key,
required this.userLogin,
required this.textFieldFocus,
required this.videoStore,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Observer(
builder: (context) => GestureDetector(
onLongPress:
videoStore.settingsStore.toggleableOverlay ? () => videoStore.settingsStore.showOverlay = !videoStore.settingsStore.showOverlay : null,
child: AspectRatio(
aspectRatio: 16 / 9,
child: Observer(
builder: (context) {
if (videoStore.settingsStore.showOverlay) {
return Stack(
children: [
WebView(
backgroundColor: Colors.black,
initialUrl: 'https://player.twitch.tv/?channel=$userLogin&controls=false&muted=false&parent=frosty',
javascriptMode: JavascriptMode.unrestricted,
allowsInlineMediaPlayback: true,
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
onWebViewCreated: (controller) => videoStore.controller = controller,
onPageFinished: (string) => videoStore.initVideo(),
navigationDelegate: (navigation) {
if (navigation.url.startsWith('https://player.twitch.tv')) {
return NavigationDecision.navigate;
}
return NavigationDecision.prevent;
},
javascriptChannels: {
JavascriptChannel(
name: 'Pause',
onMessageReceived: (message) {
videoStore.paused = true;
},
),
JavascriptChannel(
name: 'Play',
onMessageReceived: (message) {
videoStore.paused = false;
},
),
},
),
Observer(
builder: (_) {
if (videoStore.paused) return VideoOverlay(videoStore: videoStore);
return SizedBox.expand(
child: Observer(
builder: (_) => AnimatedOpacity(
opacity: videoStore.overlayVisible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 200),
child: ColoredBox(
color: const Color.fromRGBO(0, 0, 0, 0.5),
child: IgnorePointer(
ignoring: !videoStore.overlayVisible,
child: VideoOverlay(videoStore: videoStore),
),
),
),
),
);
},
)
],
);
}
return WebView(
backgroundColor: Colors.black,
initialUrl: 'https://player.twitch.tv/?channel=$userLogin&muted=false&parent=frosty',
javascriptMode: JavascriptMode.unrestricted,
allowsInlineMediaPlayback: true,
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
navigationDelegate: (navigation) {
if (navigation.url.startsWith('https://player.twitch.tv')) {
return NavigationDecision.navigate;
}
return NavigationDecision.prevent;
},
);
},
),
),
),
return WebView(
backgroundColor: Colors.black,
initialUrl: videoStore.videoUrl,
javascriptMode: JavascriptMode.unrestricted,
allowsInlineMediaPlayback: true,
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow,
onWebViewCreated: (controller) => videoStore.controller = controller,
onPageFinished: (string) => videoStore.initVideo(),
navigationDelegate: videoStore.handleNavigation,
javascriptChannels: videoStore.javascriptChannels,
);
}
}

View file

@ -7,19 +7,25 @@ import 'package:frosty/screens/channel/chat/chat.dart';
import 'package:frosty/screens/channel/stores/chat_store.dart';
import 'package:frosty/screens/channel/stores/video_store.dart';
import 'package:frosty/screens/channel/video/video.dart';
import 'package:frosty/screens/channel/video/video_overlay.dart';
import 'package:frosty/screens/settings/settings.dart';
import 'package:provider/provider.dart';
class VideoChat extends StatefulWidget {
final ChatStore chatStore;
const VideoChat({Key? key, required this.chatStore}) : super(key: key);
const VideoChat({
Key? key,
required this.chatStore,
}) : super(key: key);
@override
_VideoChatState createState() => _VideoChatState();
}
class _VideoChatState extends State<VideoChat> {
final _videoKey = GlobalKey();
@override
Widget build(BuildContext context) {
final chatStore = widget.chatStore;
@ -32,7 +38,13 @@ class _VideoChatState extends State<VideoChat> {
settingsStore: chatStore.settings,
);
final player = Video(
key: _videoKey,
videoStore: videoStore,
);
final video = GestureDetector(
onLongPress: videoStore.handleToggleOverlay,
onTap: () {
if (chatStore.assetsStore.showEmoteMenu) {
chatStore.assetsStore.showEmoteMenu = false;
@ -44,18 +56,39 @@ class _VideoChatState extends State<VideoChat> {
}
}
},
child: Video(
key: GlobalKey(),
userLogin: chatStore.channelName,
textFieldFocus: chatStore.textFieldFocusNode,
videoStore: videoStore,
child: Observer(
builder: (context) {
if (videoStore.settingsStore.showOverlay) {
return Stack(
children: [
player,
Observer(
builder: (_) {
if (videoStore.paused) return VideoOverlay(videoStore: videoStore);
return Observer(
builder: (_) => AnimatedOpacity(
opacity: videoStore.overlayVisible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 200),
child: ColoredBox(
color: const Color.fromRGBO(0, 0, 0, 0.5),
child: IgnorePointer(
ignoring: !videoStore.overlayVisible,
child: VideoOverlay(videoStore: videoStore),
),
),
),
);
},
)
],
);
}
return player;
},
),
);
final chat = Chat(
key: GlobalKey(),
chatStore: chatStore,
);
final chat = Chat(chatStore: chatStore);
final appBar = AppBar(
title: Text(
@ -90,16 +123,7 @@ class _VideoChatState extends State<VideoChat> {
bottom: false,
child: settingsStore.showVideo
? settingsStore.fullScreen
? Stack(
children: [
Visibility(
visible: false,
maintainState: true,
child: chat,
),
Center(child: video),
],
)
? video
: Row(
children: [
Flexible(
@ -136,14 +160,15 @@ class _VideoChatState extends State<VideoChat> {
Observer(
builder: (_) {
if (settingsStore.showVideo) {
return video;
return AspectRatio(
aspectRatio: 16 / 9,
child: video,
);
}
return appBar;
},
),
Expanded(
child: chat,
),
Expanded(child: chat),
],
),
);