Refactor video/chat
This commit is contained in:
parent
80eefbc671
commit
e98284be28
5 changed files with 141 additions and 131 deletions
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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"));');
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue