ZEGOCLOUD / zego_uikit_prebuilt_call_example_flutter

Call Kit is a prebuilt feature-rich call component, which enables you to build one-on-one and group voice/video calls into your app with only a few lines of code.
https://www.zegocloud.com
52 stars 24 forks source link

Handlers for custom buttons and custom call UI #6

Closed yokawaiik closed 7 months ago

yokawaiik commented 7 months ago

Hello, I need to make a custom UI, but at the moment it’s not clear how:

I have the following code now:

Code for video call with invitation

```dart import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:zego_uikit_prebuilt_call/zego_uikit_prebuilt_call.dart'; import 'package:zego_uikit_signaling_plugin/zego_uikit_signaling_plugin.dart'; import '../constants/videocalls_constants.dart'; import '../utils/get_doc_ref_by_doc_id.dart'; import '../actions/on_hang_up_confirmation_action.dart'; class VideocallsService { late final GlobalKey globalCallNavigatorKey; String? _userID; VideocallsService() { globalCallNavigatorKey = GlobalKey(); // To add duration for video call ZegoUIKitPrebuiltCallInvitationService() .setNavigatorKey(globalCallNavigatorKey); /// initialized ZegoUIKitPrebuiltCallInvitationService } void init({ required String userID, required String userName, }) { if (ZegoUIKitPrebuiltCallInvitationService().isInit) { return; } _userID = userID; /// initialized ZegoUIKitPrebuiltCallInvitationService /// when app's user is logged in or re-logged in /// We recommend calling this method as soon as the user logs in to your app. ZegoUIKitPrebuiltCallInvitationService().init( appID: VideocallsConstants.kAppID /*input your AppID*/, appSign: VideocallsConstants.kAppSign /*input your AppSign*/, userID: _userID!, userName: userName, plugins: [ ZegoUIKitSignalingPlugin(), ], notificationConfig: ZegoCallInvitationNotificationConfig( androidNotificationConfig: ZegoCallAndroidNotificationConfig( showFullScreen: true, ), ), requireConfig: (ZegoCallInvitationData data) { final config = ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall(); config.video = ZegoUIKitVideoConfig.preset540P(); // Modify your custom configurations here. config.duration = ZegoCallDurationConfig( // isVisible: true, isVisible: false, onDurationUpdate: (Duration duration) { if (duration.inSeconds >= VideocallsConstants.kHangUpCallAfterInSeconds) { ZegoUIKitPrebuiltCallController() // .hangUp(navigatorKey.currentState!.context); .hangUp(globalCallNavigatorKey.currentState!.context); } }, ); config.layout = ZegoLayout.pictureInPicture(); // todo: maybe use inviter - data.inviter. // Custom handlers // todo: add handlers config.bottomMenuBar = ZegoCallBottomMenuBarConfig( backgroundColor: Colors.white, hideAutomatically: false, hideByClick: false, buttons: List.empty(), // убрать стандартные кнопки extendButtons: [ ElevatedButton( style: ElevatedButton.styleFrom( fixedSize: const Size(60, 60), shape: const CircleBorder(), ), onPressed: () { // ZegoUIKitPrebuiltCallController() }, child: const Icon( FFIcons.kcamera, // Icon(FFIcons.kcameraslash), ), ), ElevatedButton( style: ElevatedButton.styleFrom( fixedSize: const Size(60, 60), shape: const CircleBorder(), ), onPressed: () {}, child: const Icon( FFIcons.kmicrophone, // Icon(FFIcons.kmicrophoneslash), ), ), ElevatedButton( style: ElevatedButton.styleFrom( fixedSize: const Size(60, 60), shape: const CircleBorder(), ), onPressed: () { ZegoUIKitPrebuiltCallController().hangUp( globalCallNavigatorKey.currentState!.context, ); }, child: const Icon( FFIcons.kphonedisconnect, ), ), ]); return config; }, uiConfig: ZegoCallInvitationUIConfig( declineButton: ZegoCallButtonUIConfig( visible: false, ), acceptButton: ZegoCallButtonUIConfig( visible: false, ), cancelButton: ZegoCallButtonUIConfig( visible: false, ), callingBackgroundBuilder: (context, size, info) { final callerRef = getDocRefByDocId(info.inviter.id, 'users'); debugPrint( '-- ZegoUIKitPrebuiltCallInvitationService -- callingBackgroundBuilder: ${callerRef.id}'); return VideocallLobbyWidget( userRef: callerRef, acceptButtonCallback: () => ZegoUIKitPrebuiltCallInvitationService().accept(), declineButtonCallback: () => ZegoUIKitPrebuiltCallInvitationService().reject(), ); }, ), events: ZegoUIKitPrebuiltCallEvents( onError: (ZegoUIKitError error) { debugPrint( '-- ZegoUIKitPrebuiltCallEvents -- onError: ${error.toString()}'); }, onHangUpConfirmation: (event, onHangUpConfirmation) async { final result = await onHangUpConfirmationAction( event.context, ); return result; }, onCallEnd: (event, onCallEnd) async { // todo: maybe not await await onCallEndAction( getDocRefByDocId(_userID!, 'users'), ); }, ), ); } /// on App's user logout void onUserLogout() { ZegoUIKitPrebuiltCallInvitationService().uninit(); } } ```

I'd like to get something near to image

I couldn’t find any information on how you can control sound and video through this package.

How can i to achieve to realize these features?

yoer commented 7 months ago
yoer commented 7 months ago

and you can add your countdown widget by ZegoUIKitPrebuiltCallConfig.foreground

yoer commented 7 months ago

containerRect: Specify the rect of the audio & video container. If not specified, it defaults to display full.

containerBuilder: Custom audio/video view. If you don't want to use the default view components, you can pass a custom component through this parameter. and if return null, will be display the default view

You can use containerBuilder and containerRect to draw anything you want in the specified area.

Like the layout below: img_v3_029r_f83a729e-2daf-4541-8830-611d7a719cag

// Flutter imports:
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';

// Package imports:
import 'package:zego_uikit_prebuilt_call/zego_uikit_prebuilt_call.dart';

class CallPage extends StatefulWidget {
  const CallPage({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => CallPageState();
}

class CallPageState extends State<CallPage> {
  final durationNotifier = ValueNotifier<Duration>(Duration.zero);

  double get bottomBarHeight => 100;

  double get topPadding => 100;

  double get hangUpButtonSize => 50;

  double get hangUpButtonPadding => 30;

  double get audioVideoViewSize {
    final size = MediaQuery.of(context).size;
    final audioVideoViewSize = size.width / 5 * 2;
    return audioVideoViewSize;
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: ZegoUIKitPrebuiltCall(
        appID: yourAppID /*input your AppID*/,
        appSign: yourAppSign /*input your AppSign*/,
        userID: 'userID',
        userName: 'userName',
        callID: 'callID',
        config: ZegoUIKitPrebuiltCallConfig.groupVideoCall()
          ..avatarBuilder = customAvatarBuilder
          ..background = Container(
            color: Colors.black,
          )
          ..foreground = foreground()
          ..turnOnCameraWhenJoining = false
          ..audioVideoView.showUserNameOnView = false
          ..audioVideoView.useVideoViewAspectFill = true
          ..audioVideoView.containerRect = () {
            final size = MediaQuery.of(context).size;
            final padding = MediaQuery.of(context).padding;
            final height = size.height - padding.bottom - padding.top;

            return Rect.fromLTWH(
              0,
              bottomBarHeight,
              size.width,
              height - topPadding - bottomBarHeight,
            );
          }
          ..audioVideoView.containerBuilder = (
            context,
            allUsers,
            audioVideoUsers,
            audioVideoViewCreator,
          ) {
            return Column(
              children: audioVideoUsers
                  .map((user) => audioVideoView(
                        user,
                        audioVideoViewCreator,
                      ))
                  .toList(),
            );
          }
          ..duration.isVisible = false
          ..duration.onDurationUpdate = (Duration duration) {
            durationNotifier.value = duration;
          }
          ..topMenuBar.isVisible = false
          ..bottomMenuBar.isVisible = false,
      ),
    );
  }

  Widget audioVideoView(
    ZegoUIKitUser user,
    ZegoAudioVideoView Function(ZegoUIKitUser) defaultViewCreator,
  ) {
    final size = MediaQuery.of(context).size;

    return SizedBox(
      width: size.width / 3 * 2,
      height: size.width / 3 * 2,
      child: Stack(
        children: [
          Positioned(
            left: 0,
            right: 0,
            child: ClipOval(
              child: SizedBox(
                width: audioVideoViewSize,
                height: audioVideoViewSize,
                child: avatarView(user),
              ),
            ),
          ),
          Positioned(
            left: 0,
            right: 0,
            bottom: 0,
            child: SizedBox(
              width: size.width,
              child: Text(
                user.name,
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                ),
                textAlign: TextAlign.center,
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget avatarView(ZegoUIKitUser user) {
    return CachedNetworkImage(
      imageUrl:
          'https://i.pinimg.com/736x/82/a1/74/82a174dc4b695259ba1ecb92204c6076.jpg',
      imageBuilder: (context, imageProvider) => Container(
        decoration: BoxDecoration(
          border: Border.all(color: Colors.white),
          shape: BoxShape.circle,
          image: DecorationImage(
            image: imageProvider,
            fit: BoxFit.contain,
          ),
        ),
      ),
      progressIndicatorBuilder: (context, url, downloadProgress) =>
          CircularProgressIndicator(value: downloadProgress.progress),
      errorWidget: (context, url, error) {
        return Container();
      },
    );
  }

  Widget foreground() {
    return Stack(
      children: [
        bottomBar(),
        hangUpBackground(),
        hangUpButton(),
        duration(),
      ],
    );
  }

  String durationFormatString(Duration elapsedTime) {
    final hours = elapsedTime.inHours;
    final minutes = elapsedTime.inMinutes.remainder(60);
    final seconds = elapsedTime.inSeconds.remainder(60);

    final minutesFormatString =
        '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
    return hours > 0
        ? '${hours.toString().padLeft(2, '0')}:$minutesFormatString'
        : minutesFormatString;
  }

  Widget duration() {
    final size = MediaQuery.of(context).size;
    const spacing = 150;

    return Positioned(
      bottom: bottomBarHeight + spacing,
      left: 0,
      right: 0,
      child: ValueListenableBuilder<Duration>(
        valueListenable: durationNotifier,
        builder: (context, duration, _) {
          return Center(
            child: Container(
              padding: const EdgeInsets.all(5),
              decoration: BoxDecoration(
                color: Colors.grey,
                borderRadius: BorderRadius.circular(5),
              ),
              child: Text(
                durationFormatString(duration),
                style: TextStyle(color: Colors.white),
              ),
            ),
          );
        },
      ),
    );
  }

  Widget bottomBar() {
    return Positioned(
      bottom: 0,
      left: 0,
      right: 0,
      child: Container(
        height: bottomBarHeight,
        decoration: BoxDecoration(color: Colors.lightBlue.withOpacity(0.5)),
        child: Stack(
          children: [
            bottomBarBackground(),
            microphoneButton(),
            moreButton(),
          ],
        ),
      ),
    );
  }

  Widget microphoneButton() {
    return Align(
      alignment: Alignment.bottomLeft,
      child: GestureDetector(
        onTap: () {
          ZegoUIKitPrebuiltCallController().audioVideo.microphone.switchState();
        },
        child: ValueListenableBuilder<bool>(
          valueListenable: ZegoUIKitPrebuiltCallController()
              .audioVideo
              .microphone
              .localStateNotifier,
          builder: (context, isMicrophoneOn, _) {
            return Container(
              width: 30.0,
              height: 30.0,
              margin: const EdgeInsets.all(5),
              decoration: BoxDecoration(
                color: Colors.grey,
                borderRadius: BorderRadius.circular(8.0),
              ),
              child: Icon(
                isMicrophoneOn ? Icons.mic : Icons.mic_off,
                color: Colors.white,
              ),
            );
          },
        ),
      ),
    );
  }

  Widget moreButton() {
    return Align(
      alignment: Alignment.bottomRight,
      child: GestureDetector(
        onTap: () {},
        child: Container(
          width: 30.0,
          height: 30.0,
          margin: const EdgeInsets.all(5),
          decoration: BoxDecoration(
            color: Colors.grey,
            borderRadius: BorderRadius.circular(8.0),
          ),
          child: const Icon(Icons.more, color: Colors.white),
        ),
      ),
    );
  }

  Widget bottomBarBackground() {
    final size = MediaQuery.of(context).size;

    return Align(
      alignment: Alignment.topCenter,
      child: Container(
        width: size.width,
        height: bottomBarHeight / 5 * 2,
        color: Colors.black,
      ),
    );
  }

  Widget hangUpBackground() {
    final size = MediaQuery.of(context).size;

    return Positioned(
      bottom: bottomBarHeight - hangUpButtonSize - hangUpButtonPadding,
      left: size.width / 2 - (hangUpButtonSize + hangUpButtonPadding) / 2,
      right: size.width / 2 - (hangUpButtonSize + hangUpButtonPadding) / 2,
      child: ClipOval(
        child: Container(
          width: hangUpButtonSize + hangUpButtonPadding,
          height: hangUpButtonSize + hangUpButtonPadding,
          color: Colors.black,
        ),
      ),
    );
  }

  Widget hangUpButton() {
    final size = MediaQuery.of(context).size;

    return Positioned(
      bottom: bottomBarHeight - hangUpButtonPadding * 2,
      left: size.width / 2 - (hangUpButtonSize / 2),
      right: size.width / 2 - (hangUpButtonSize / 2),
      child: ClipOval(
        child: GestureDetector(
          onTap: () {
            ZegoUIKitPrebuiltCallController().hangUp(
              context,
              showConfirmation: true,
            );
          },
          child: Container(
            width: hangUpButtonSize,
            height: hangUpButtonSize,
            decoration: const BoxDecoration(color: Colors.red),
            child: const Icon(Icons.phone, color: Colors.white),
          ),
        ),
      ),
    );
  }
}
yoer commented 7 months ago
yoer commented 7 months ago

please use version more than v4.8.0