362 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			362 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:flutter/material.dart';
 | 
						|
import 'package:flutter_hbb/common.dart';
 | 
						|
import 'package:provider/provider.dart';
 | 
						|
 | 
						|
import '../../consts.dart';
 | 
						|
import '../../desktop/widgets/tabbar_widget.dart';
 | 
						|
import '../../models/chat_model.dart';
 | 
						|
import '../../models/model.dart';
 | 
						|
import 'chat_page.dart';
 | 
						|
 | 
						|
class DraggableChatWindow extends StatelessWidget {
 | 
						|
  const DraggableChatWindow(
 | 
						|
      {Key? key,
 | 
						|
      this.position = Offset.zero,
 | 
						|
      required this.width,
 | 
						|
      required this.height,
 | 
						|
      required this.chatModel})
 | 
						|
      : super(key: key);
 | 
						|
 | 
						|
  final Offset position;
 | 
						|
  final double width;
 | 
						|
  final double height;
 | 
						|
  final ChatModel chatModel;
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return Draggable(
 | 
						|
        checkKeyboard: true,
 | 
						|
        position: position,
 | 
						|
        width: width,
 | 
						|
        height: height,
 | 
						|
        builder: (context, onPanUpdate) {
 | 
						|
          return isIOS
 | 
						|
              ? ChatPage(chatModel: chatModel)
 | 
						|
              : Scaffold(
 | 
						|
                  resizeToAvoidBottomInset: false,
 | 
						|
                  appBar: CustomAppBar(
 | 
						|
                    onPanUpdate: onPanUpdate,
 | 
						|
                    appBar: isDesktop
 | 
						|
                        ? _buildDesktopAppBar(context)
 | 
						|
                        : _buildMobileAppBar(context),
 | 
						|
                  ),
 | 
						|
                  body: ChatPage(chatModel: chatModel),
 | 
						|
                );
 | 
						|
        });
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildMobileAppBar(BuildContext context) {
 | 
						|
    return Container(
 | 
						|
      color: Theme.of(context).colorScheme.primary,
 | 
						|
      height: 50,
 | 
						|
      child: Row(
 | 
						|
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
						|
        children: [
 | 
						|
          Padding(
 | 
						|
              padding: const EdgeInsets.symmetric(horizontal: 15),
 | 
						|
              child: Text(
 | 
						|
                translate("Chat"),
 | 
						|
                style: const TextStyle(
 | 
						|
                    color: Colors.white,
 | 
						|
                    fontFamily: 'WorkSans',
 | 
						|
                    fontWeight: FontWeight.bold,
 | 
						|
                    fontSize: 20),
 | 
						|
              )),
 | 
						|
          Row(
 | 
						|
            crossAxisAlignment: CrossAxisAlignment.center,
 | 
						|
            children: [
 | 
						|
              IconButton(
 | 
						|
                  onPressed: () {
 | 
						|
                    chatModel.hideChatWindowOverlay();
 | 
						|
                  },
 | 
						|
                  icon: const Icon(Icons.keyboard_arrow_down)),
 | 
						|
              IconButton(
 | 
						|
                  onPressed: () {
 | 
						|
                    chatModel.hideChatWindowOverlay();
 | 
						|
                    chatModel.hideChatIconOverlay();
 | 
						|
                  },
 | 
						|
                  icon: const Icon(Icons.close))
 | 
						|
            ],
 | 
						|
          )
 | 
						|
        ],
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  Widget _buildDesktopAppBar(BuildContext context) {
 | 
						|
    return Container(
 | 
						|
      decoration: BoxDecoration(
 | 
						|
          border: Border(
 | 
						|
              bottom: BorderSide(
 | 
						|
                  color: Theme.of(context).hintColor.withOpacity(0.4)))),
 | 
						|
      height: 38,
 | 
						|
      child: Row(
 | 
						|
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
						|
        children: [
 | 
						|
          Padding(
 | 
						|
              padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
 | 
						|
              child: Row(children: [
 | 
						|
                Icon(Icons.chat_bubble_outline,
 | 
						|
                    size: 20, color: Theme.of(context).colorScheme.primary),
 | 
						|
                SizedBox(width: 6),
 | 
						|
                Text(translate("Chat"))
 | 
						|
              ])),
 | 
						|
          Padding(
 | 
						|
              padding: EdgeInsets.all(2),
 | 
						|
              child: ActionIcon(
 | 
						|
                message: 'Close',
 | 
						|
                icon: IconFont.close,
 | 
						|
                onTap: chatModel.hideChatWindowOverlay,
 | 
						|
                isClose: true,
 | 
						|
                boxSize: 32,
 | 
						|
              ))
 | 
						|
        ],
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
 | 
						|
  final GestureDragUpdateCallback onPanUpdate;
 | 
						|
  final Widget appBar;
 | 
						|
 | 
						|
  const CustomAppBar(
 | 
						|
      {Key? key, required this.onPanUpdate, required this.appBar})
 | 
						|
      : super(key: key);
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return GestureDetector(onPanUpdate: onPanUpdate, child: appBar);
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Size get preferredSize => const Size.fromHeight(kToolbarHeight);
 | 
						|
}
 | 
						|
 | 
						|
/// floating buttons of back/home/recent actions for android
 | 
						|
class DraggableMobileActions extends StatelessWidget {
 | 
						|
  DraggableMobileActions(
 | 
						|
      {this.position = Offset.zero,
 | 
						|
      this.onBackPressed,
 | 
						|
      this.onRecentPressed,
 | 
						|
      this.onHomePressed,
 | 
						|
      this.onHidePressed,
 | 
						|
      required this.width,
 | 
						|
      required this.height});
 | 
						|
 | 
						|
  final Offset position;
 | 
						|
  final double width;
 | 
						|
  final double height;
 | 
						|
  final VoidCallback? onBackPressed;
 | 
						|
  final VoidCallback? onHomePressed;
 | 
						|
  final VoidCallback? onRecentPressed;
 | 
						|
  final VoidCallback? onHidePressed;
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return Draggable(
 | 
						|
        position: position,
 | 
						|
        width: width,
 | 
						|
        height: height,
 | 
						|
        builder: (_, onPanUpdate) {
 | 
						|
          return GestureDetector(
 | 
						|
              onPanUpdate: onPanUpdate,
 | 
						|
              child: Card(
 | 
						|
                  color: Colors.transparent,
 | 
						|
                  shadowColor: Colors.transparent,
 | 
						|
                  child: Container(
 | 
						|
                    decoration: BoxDecoration(
 | 
						|
                        color: MyTheme.accent.withOpacity(0.4),
 | 
						|
                        borderRadius: BorderRadius.all(Radius.circular(15))),
 | 
						|
                    child: Row(
 | 
						|
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
 | 
						|
                      children: [
 | 
						|
                        IconButton(
 | 
						|
                            color: Colors.white,
 | 
						|
                            onPressed: onBackPressed,
 | 
						|
                            splashRadius: kDesktopIconButtonSplashRadius,
 | 
						|
                            icon: const Icon(Icons.arrow_back)),
 | 
						|
                        IconButton(
 | 
						|
                            color: Colors.white,
 | 
						|
                            onPressed: onHomePressed,
 | 
						|
                            splashRadius: kDesktopIconButtonSplashRadius,
 | 
						|
                            icon: const Icon(Icons.home)),
 | 
						|
                        IconButton(
 | 
						|
                            color: Colors.white,
 | 
						|
                            onPressed: onRecentPressed,
 | 
						|
                            splashRadius: kDesktopIconButtonSplashRadius,
 | 
						|
                            icon: const Icon(Icons.more_horiz)),
 | 
						|
                        const VerticalDivider(
 | 
						|
                          width: 0,
 | 
						|
                          thickness: 2,
 | 
						|
                          indent: 10,
 | 
						|
                          endIndent: 10,
 | 
						|
                        ),
 | 
						|
                        IconButton(
 | 
						|
                            color: Colors.white,
 | 
						|
                            onPressed: onHidePressed,
 | 
						|
                            splashRadius: kDesktopIconButtonSplashRadius,
 | 
						|
                            icon: const Icon(Icons.keyboard_arrow_down)),
 | 
						|
                      ],
 | 
						|
                    ),
 | 
						|
                  )));
 | 
						|
        });
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class Draggable extends StatefulWidget {
 | 
						|
  const Draggable(
 | 
						|
      {Key? key,
 | 
						|
      this.checkKeyboard = false,
 | 
						|
      this.checkScreenSize = false,
 | 
						|
      this.position = Offset.zero,
 | 
						|
      required this.width,
 | 
						|
      required this.height,
 | 
						|
      required this.builder})
 | 
						|
      : super(key: key);
 | 
						|
 | 
						|
  final bool checkKeyboard;
 | 
						|
  final bool checkScreenSize;
 | 
						|
  final Offset position;
 | 
						|
  final double width;
 | 
						|
  final double height;
 | 
						|
  final Widget Function(BuildContext, GestureDragUpdateCallback) builder;
 | 
						|
 | 
						|
  @override
 | 
						|
  State<StatefulWidget> createState() => _DraggableState();
 | 
						|
}
 | 
						|
 | 
						|
class _DraggableState extends State<Draggable> {
 | 
						|
  late Offset _position;
 | 
						|
  bool _keyboardVisible = false;
 | 
						|
  double _saveHeight = 0;
 | 
						|
  double _lastBottomHeight = 0;
 | 
						|
 | 
						|
  @override
 | 
						|
  void initState() {
 | 
						|
    super.initState();
 | 
						|
    _position = widget.position;
 | 
						|
  }
 | 
						|
 | 
						|
  void onPanUpdate(DragUpdateDetails d) {
 | 
						|
    final offset = d.delta;
 | 
						|
    final size = MediaQuery.of(context).size;
 | 
						|
    double x = 0;
 | 
						|
    double y = 0;
 | 
						|
 | 
						|
    if (_position.dx + offset.dx + widget.width > size.width) {
 | 
						|
      x = size.width - widget.width;
 | 
						|
    } else if (_position.dx + offset.dx < 0) {
 | 
						|
      x = 0;
 | 
						|
    } else {
 | 
						|
      x = _position.dx + offset.dx;
 | 
						|
    }
 | 
						|
 | 
						|
    if (_position.dy + offset.dy + widget.height > size.height) {
 | 
						|
      y = size.height - widget.height;
 | 
						|
    } else if (_position.dy + offset.dy < 0) {
 | 
						|
      y = 0;
 | 
						|
    } else {
 | 
						|
      y = _position.dy + offset.dy;
 | 
						|
    }
 | 
						|
    setState(() {
 | 
						|
      _position = Offset(x, y);
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  checkScreenSize() {}
 | 
						|
 | 
						|
  checkKeyboard() {
 | 
						|
    final bottomHeight = MediaQuery.of(context).viewInsets.bottom;
 | 
						|
    final currentVisible = bottomHeight != 0;
 | 
						|
 | 
						|
    // save
 | 
						|
    if (!_keyboardVisible && currentVisible) {
 | 
						|
      _saveHeight = _position.dy;
 | 
						|
    }
 | 
						|
 | 
						|
    // reset
 | 
						|
    if (_lastBottomHeight > 0 && bottomHeight == 0) {
 | 
						|
      setState(() {
 | 
						|
        _position = Offset(_position.dx, _saveHeight);
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    // onKeyboardVisible
 | 
						|
    if (_keyboardVisible && currentVisible) {
 | 
						|
      final sumHeight = bottomHeight + widget.height;
 | 
						|
      final contextHeight = MediaQuery.of(context).size.height;
 | 
						|
      if (sumHeight + _position.dy > contextHeight) {
 | 
						|
        final y = contextHeight - sumHeight;
 | 
						|
        setState(() {
 | 
						|
          _position = Offset(_position.dx, y);
 | 
						|
        });
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    _keyboardVisible = currentVisible;
 | 
						|
    _lastBottomHeight = bottomHeight;
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    if (widget.checkKeyboard) {
 | 
						|
      checkKeyboard();
 | 
						|
    }
 | 
						|
    if (widget.checkKeyboard) {
 | 
						|
      checkScreenSize();
 | 
						|
    }
 | 
						|
    return Positioned(
 | 
						|
        top: _position.dy,
 | 
						|
        left: _position.dx,
 | 
						|
        width: widget.width,
 | 
						|
        height: widget.height,
 | 
						|
        child: widget.builder(context, onPanUpdate));
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class QualityMonitor extends StatelessWidget {
 | 
						|
  static const textStyle = TextStyle(color: MyTheme.grayBg);
 | 
						|
  final QualityMonitorModel qualityMonitorModel;
 | 
						|
  QualityMonitor(this.qualityMonitorModel);
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) => ChangeNotifierProvider.value(
 | 
						|
      value: qualityMonitorModel,
 | 
						|
      child: Consumer<QualityMonitorModel>(
 | 
						|
          builder: (context, qualityMonitorModel, child) => Positioned(
 | 
						|
              top: 10,
 | 
						|
              right: 10,
 | 
						|
              child: qualityMonitorModel.show
 | 
						|
                  ? Container(
 | 
						|
                      padding: const EdgeInsets.all(8),
 | 
						|
                      color: MyTheme.canvasColor.withAlpha(120),
 | 
						|
                      child: Column(
 | 
						|
                        crossAxisAlignment: CrossAxisAlignment.start,
 | 
						|
                        children: [
 | 
						|
                          Text(
 | 
						|
                            "Speed: ${qualityMonitorModel.data.speed ?? ''}",
 | 
						|
                            style: textStyle,
 | 
						|
                          ),
 | 
						|
                          Text(
 | 
						|
                            "FPS: ${qualityMonitorModel.data.fps ?? ''}",
 | 
						|
                            style: textStyle,
 | 
						|
                          ),
 | 
						|
                          Text(
 | 
						|
                            "Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
 | 
						|
                            style: textStyle,
 | 
						|
                          ),
 | 
						|
                          Text(
 | 
						|
                            "Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
 | 
						|
                            style: textStyle,
 | 
						|
                          ),
 | 
						|
                          Text(
 | 
						|
                            "Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
 | 
						|
                            style: textStyle,
 | 
						|
                          ),
 | 
						|
                        ],
 | 
						|
                      ),
 | 
						|
                    )
 | 
						|
                  : const SizedBox.shrink())));
 | 
						|
}
 |