💄 Optimized call
This commit is contained in:
		@@ -66,6 +66,8 @@ class CallNotifier extends _$CallNotifier {
 | 
			
		||||
 | 
			
		||||
  Timer? _durationTimer;
 | 
			
		||||
 | 
			
		||||
  Room? get room => _room;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  CallState build() {
 | 
			
		||||
    // Subscribe to websocket updates
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ part of 'call.dart';
 | 
			
		||||
// RiverpodGenerator
 | 
			
		||||
// **************************************************************************
 | 
			
		||||
 | 
			
		||||
String _$callNotifierHash() => r'e04cea314c823e407d49fd616d90d77491232c12';
 | 
			
		||||
String _$callNotifierHash() => r'47eaba43aa2af1a107725998f4a34af2c94fbc55';
 | 
			
		||||
 | 
			
		||||
/// See also [CallNotifier].
 | 
			
		||||
@ProviderFor(CallNotifier)
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,6 @@ class AppRouter extends RootStackRouter {
 | 
			
		||||
          children: [
 | 
			
		||||
            AutoRoute(page: ChatListRoute.page, path: ''),
 | 
			
		||||
            AutoRoute(page: ChatRoomRoute.page, path: ':id'),
 | 
			
		||||
            AutoRoute(page: CallRoute.page, path: ':id/call'),
 | 
			
		||||
            AutoRoute(page: NewChatRoute.page, path: 'new'),
 | 
			
		||||
            AutoRoute(page: EditChatRoute.page, path: ':id/edit'),
 | 
			
		||||
            AutoRoute(page: ChatDetailRoute.page, path: ':id/detail'),
 | 
			
		||||
@@ -54,6 +53,7 @@ class AppRouter extends RootStackRouter {
 | 
			
		||||
    ),
 | 
			
		||||
    AutoRoute(page: PostComposeRoute.page, path: '/posts/compose'),
 | 
			
		||||
    AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'),
 | 
			
		||||
    AutoRoute(page: CallRoute.page, path: '/chat/:id/call'),
 | 
			
		||||
    AutoRoute(page: EventCalanderRoute.page, path: '/account/:name/calendar'),
 | 
			
		||||
    AutoRoute(
 | 
			
		||||
      page: CreatorHubShellRoute.page,
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import 'package:island/widgets/chat/call_button.dart';
 | 
			
		||||
import 'package:island/widgets/chat/call_overlay.dart';
 | 
			
		||||
import 'package:island/widgets/chat/call_participant_tile.dart';
 | 
			
		||||
import 'package:livekit_client/livekit_client.dart';
 | 
			
		||||
import 'package:material_symbols_icons/symbols.dart';
 | 
			
		||||
import 'package:styled_widget/styled_widget.dart';
 | 
			
		||||
 | 
			
		||||
@RoutePage()
 | 
			
		||||
@@ -32,37 +33,9 @@ class CallScreen extends HookConsumerWidget {
 | 
			
		||||
    final viewMode = useState<String>('grid');
 | 
			
		||||
 | 
			
		||||
    return AppScaffold(
 | 
			
		||||
      noBackground: false,
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: PageBackButton(
 | 
			
		||||
          onWillPop: () {
 | 
			
		||||
            showDialog<void>(
 | 
			
		||||
              context: context,
 | 
			
		||||
              builder: (context) {
 | 
			
		||||
                return AlertDialog(
 | 
			
		||||
                  content: const Text(
 | 
			
		||||
                    'Do you want to leave the call or leave it in background?',
 | 
			
		||||
                  ),
 | 
			
		||||
                  actions: [
 | 
			
		||||
                    TextButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        Navigator.of(context).pop();
 | 
			
		||||
                      },
 | 
			
		||||
                      child: const Text('In Background'),
 | 
			
		||||
                    ),
 | 
			
		||||
                    TextButton(
 | 
			
		||||
                      onPressed: () async {
 | 
			
		||||
                        Navigator.of(context).pop();
 | 
			
		||||
                        await callNotifier.disconnect();
 | 
			
		||||
                        callNotifier.dispose();
 | 
			
		||||
                      },
 | 
			
		||||
                      child: const Text('Leave'),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                );
 | 
			
		||||
              },
 | 
			
		||||
            );
 | 
			
		||||
          },
 | 
			
		||||
        ),
 | 
			
		||||
        leading: PageBackButton(),
 | 
			
		||||
        title: Column(
 | 
			
		||||
          crossAxisAlignment: CrossAxisAlignment.center,
 | 
			
		||||
          children: [
 | 
			
		||||
@@ -83,7 +56,7 @@ class CallScreen extends HookConsumerWidget {
 | 
			
		||||
            mainAxisAlignment: MainAxisAlignment.end,
 | 
			
		||||
            children: [
 | 
			
		||||
              IconButton(
 | 
			
		||||
                icon: Icon(Icons.grid_view),
 | 
			
		||||
                icon: Icon(Symbols.grid_view),
 | 
			
		||||
                tooltip: 'Grid View',
 | 
			
		||||
                onPressed: () => viewMode.value = 'grid',
 | 
			
		||||
                color:
 | 
			
		||||
@@ -92,7 +65,7 @@ class CallScreen extends HookConsumerWidget {
 | 
			
		||||
                        : null,
 | 
			
		||||
              ),
 | 
			
		||||
              IconButton(
 | 
			
		||||
                icon: Icon(Icons.view_agenda),
 | 
			
		||||
                icon: Icon(Symbols.view_agenda),
 | 
			
		||||
                tooltip: 'Stage View',
 | 
			
		||||
                onPressed: () => viewMode.value = 'stage',
 | 
			
		||||
                color:
 | 
			
		||||
 
 | 
			
		||||
@@ -431,7 +431,7 @@ class ChatListScreen extends HookConsumerWidget {
 | 
			
		||||
          Positioned(
 | 
			
		||||
            left: 0,
 | 
			
		||||
            right: 0,
 | 
			
		||||
            bottom: 0,
 | 
			
		||||
            bottom: getTabbedPadding(context).bottom + 8,
 | 
			
		||||
            child: const CallOverlayBar().padding(horizontal: 16, vertical: 12),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,28 @@ class TabsScreen extends HookConsumerWidget {
 | 
			
		||||
      builder: (context, child, _) {
 | 
			
		||||
        final tabsRouter = AutoTabsRouter.of(context);
 | 
			
		||||
 | 
			
		||||
        if (isWideScreen(context)) {
 | 
			
		||||
          return Row(
 | 
			
		||||
            children: [
 | 
			
		||||
              NavigationRail(
 | 
			
		||||
                destinations:
 | 
			
		||||
                    destinations
 | 
			
		||||
                        .map(
 | 
			
		||||
                          (e) => NavigationRailDestination(
 | 
			
		||||
                            icon: e.icon,
 | 
			
		||||
                            label: Text(e.label),
 | 
			
		||||
                          ),
 | 
			
		||||
                        )
 | 
			
		||||
                        .toList(),
 | 
			
		||||
                selectedIndex: tabsRouter.activeIndex,
 | 
			
		||||
                onDestinationSelected: tabsRouter.setActiveIndex,
 | 
			
		||||
              ),
 | 
			
		||||
              const VerticalDivider(width: 1),
 | 
			
		||||
              Expanded(child: child),
 | 
			
		||||
            ],
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Stack(
 | 
			
		||||
          children: [
 | 
			
		||||
            Positioned.fill(child: child),
 | 
			
		||||
 
 | 
			
		||||
@@ -25,14 +25,22 @@ EdgeInsets getTabbedPadding(
 | 
			
		||||
  double? top,
 | 
			
		||||
  double? bottom,
 | 
			
		||||
}) {
 | 
			
		||||
  final bottomPadding = bottom ?? MediaQuery.of(context).padding.bottom + 16;
 | 
			
		||||
  if (isWideScreen(context)) {
 | 
			
		||||
    return EdgeInsets.only(
 | 
			
		||||
      left: left ?? horizontal ?? 0,
 | 
			
		||||
      right: right ?? horizontal ?? 0,
 | 
			
		||||
      top: top ?? vertical ?? 0,
 | 
			
		||||
      bottom: bottom ?? vertical ?? 0,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  final effectiveBottom = bottom ?? vertical;
 | 
			
		||||
  return EdgeInsets.only(
 | 
			
		||||
    left: left ?? horizontal ?? 0,
 | 
			
		||||
    right: right ?? horizontal ?? 0,
 | 
			
		||||
    top: top ?? vertical ?? 0,
 | 
			
		||||
    bottom:
 | 
			
		||||
        bottom != null
 | 
			
		||||
            ? bottomPadding
 | 
			
		||||
        effectiveBottom != null
 | 
			
		||||
            ? effectiveBottom + MediaQuery.of(context).padding.bottom + 16
 | 
			
		||||
            : MediaQuery.of(context).padding.bottom + 16,
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,11 @@ import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:gap/gap.dart';
 | 
			
		||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
			
		||||
import 'package:island/pods/call.dart';
 | 
			
		||||
import 'package:island/pods/userinfo.dart';
 | 
			
		||||
import 'package:island/route.gr.dart';
 | 
			
		||||
import 'package:island/widgets/chat/call_participant_tile.dart';
 | 
			
		||||
import 'package:styled_widget/styled_widget.dart';
 | 
			
		||||
import 'package:livekit_client/livekit_client.dart';
 | 
			
		||||
import '../../widgets/content/sheet.dart';
 | 
			
		||||
 | 
			
		||||
class CallControlsBar extends HookConsumerWidget {
 | 
			
		||||
  const CallControlsBar({super.key});
 | 
			
		||||
@@ -17,76 +18,226 @@ class CallControlsBar extends HookConsumerWidget {
 | 
			
		||||
    final callState = ref.watch(callNotifierProvider);
 | 
			
		||||
    final callNotifier = ref.read(callNotifierProvider.notifier);
 | 
			
		||||
 | 
			
		||||
    final userInfo = ref.watch(userInfoProvider);
 | 
			
		||||
 | 
			
		||||
    final actionButtonStyle = ButtonStyle(
 | 
			
		||||
      minimumSize: const MaterialStatePropertyAll(Size(24, 24)),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return Card(
 | 
			
		||||
      margin: const EdgeInsets.only(left: 12, right: 12, top: 8),
 | 
			
		||||
    return Container(
 | 
			
		||||
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
        mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
        children: [
 | 
			
		||||
          Expanded(
 | 
			
		||||
            child: Row(
 | 
			
		||||
              children: [
 | 
			
		||||
                Builder(
 | 
			
		||||
                  builder: (context) {
 | 
			
		||||
                    if (callNotifier.localParticipant == null) {
 | 
			
		||||
                      return CircularProgressIndicator().center();
 | 
			
		||||
                    }
 | 
			
		||||
                    return SizedBox(
 | 
			
		||||
                      width: 40,
 | 
			
		||||
                      height: 40,
 | 
			
		||||
                      child:
 | 
			
		||||
                          SpeakingRippleAvatar(
 | 
			
		||||
                            isSpeaking:
 | 
			
		||||
                                callNotifier.localParticipant!.isSpeaking,
 | 
			
		||||
                            audioLevel:
 | 
			
		||||
                                callNotifier.localParticipant!.audioLevel,
 | 
			
		||||
                            pictureId: userInfo.value?.profile.picture?.id,
 | 
			
		||||
                            size: 36,
 | 
			
		||||
                          ).center(),
 | 
			
		||||
                    );
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          _buildCircularButtonWithDropdown(
 | 
			
		||||
            context: context,
 | 
			
		||||
            ref: ref,
 | 
			
		||||
            icon:
 | 
			
		||||
                callState.isCameraEnabled ? Icons.videocam : Icons.videocam_off,
 | 
			
		||||
            onPressed: () => callNotifier.toggleCamera(),
 | 
			
		||||
            backgroundColor: const Color(0xFF424242),
 | 
			
		||||
            hasDropdown: true,
 | 
			
		||||
            deviceType: 'videoinput',
 | 
			
		||||
          ),
 | 
			
		||||
          IconButton(
 | 
			
		||||
            icon: Icon(
 | 
			
		||||
              callState.isMicrophoneEnabled ? Icons.mic : Icons.mic_off,
 | 
			
		||||
            ),
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              callNotifier.toggleMicrophone();
 | 
			
		||||
            },
 | 
			
		||||
            style: actionButtonStyle,
 | 
			
		||||
          const Gap(16),
 | 
			
		||||
          _buildCircularButton(
 | 
			
		||||
            icon:
 | 
			
		||||
                callState.isScreenSharing
 | 
			
		||||
                    ? Icons.stop_screen_share
 | 
			
		||||
                    : Icons.screen_share,
 | 
			
		||||
            onPressed: () => callNotifier.toggleScreenShare(),
 | 
			
		||||
            backgroundColor: const Color(0xFF424242),
 | 
			
		||||
          ),
 | 
			
		||||
          IconButton(
 | 
			
		||||
            icon: Icon(
 | 
			
		||||
              callState.isCameraEnabled ? Icons.videocam : Icons.videocam_off,
 | 
			
		||||
            ),
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              callNotifier.toggleCamera();
 | 
			
		||||
            },
 | 
			
		||||
            style: actionButtonStyle,
 | 
			
		||||
          const Gap(16),
 | 
			
		||||
          _buildCircularButtonWithDropdown(
 | 
			
		||||
            context: context,
 | 
			
		||||
            ref: ref,
 | 
			
		||||
            icon: callState.isMicrophoneEnabled ? Icons.mic : Icons.mic_off,
 | 
			
		||||
            onPressed: () => callNotifier.toggleMicrophone(),
 | 
			
		||||
            backgroundColor: const Color(0xFF424242),
 | 
			
		||||
            hasDropdown: true,
 | 
			
		||||
            deviceType: 'audioinput',
 | 
			
		||||
          ),
 | 
			
		||||
          IconButton(
 | 
			
		||||
            icon: Icon(
 | 
			
		||||
              callState.isScreenSharing
 | 
			
		||||
                  ? Icons.stop_screen_share
 | 
			
		||||
                  : Icons.screen_share,
 | 
			
		||||
            ),
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              callNotifier.toggleScreenShare();
 | 
			
		||||
            },
 | 
			
		||||
            style: actionButtonStyle,
 | 
			
		||||
          const Gap(16),
 | 
			
		||||
          _buildCircularButton(
 | 
			
		||||
            icon: Icons.call_end,
 | 
			
		||||
            onPressed: () => callNotifier.disconnect(),
 | 
			
		||||
            backgroundColor: const Color(0xFFE53E3E),
 | 
			
		||||
            iconColor: Colors.white,
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ).padding(all: 16),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildCircularButton({
 | 
			
		||||
    required IconData icon,
 | 
			
		||||
    required VoidCallback onPressed,
 | 
			
		||||
    required Color backgroundColor,
 | 
			
		||||
    Color? iconColor,
 | 
			
		||||
  }) {
 | 
			
		||||
    return Container(
 | 
			
		||||
      width: 56,
 | 
			
		||||
      height: 56,
 | 
			
		||||
      decoration: BoxDecoration(color: backgroundColor, shape: BoxShape.circle),
 | 
			
		||||
      child: IconButton(
 | 
			
		||||
        icon: Icon(icon, color: iconColor ?? Colors.white, size: 24),
 | 
			
		||||
        onPressed: onPressed,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildCircularButtonWithDropdown({
 | 
			
		||||
    required BuildContext context,
 | 
			
		||||
    required WidgetRef ref,
 | 
			
		||||
    required IconData icon,
 | 
			
		||||
    required VoidCallback onPressed,
 | 
			
		||||
    required Color backgroundColor,
 | 
			
		||||
    required bool hasDropdown,
 | 
			
		||||
    Color? iconColor,
 | 
			
		||||
    String? deviceType, // 'videoinput' or 'audioinput'
 | 
			
		||||
  }) {
 | 
			
		||||
    return Stack(
 | 
			
		||||
      children: [
 | 
			
		||||
        Container(
 | 
			
		||||
          width: 56,
 | 
			
		||||
          height: 56,
 | 
			
		||||
          decoration: BoxDecoration(
 | 
			
		||||
            color: backgroundColor,
 | 
			
		||||
            shape: BoxShape.circle,
 | 
			
		||||
          ),
 | 
			
		||||
          child: IconButton(
 | 
			
		||||
            icon: Icon(icon, color: iconColor ?? Colors.white, size: 24),
 | 
			
		||||
            onPressed: onPressed,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        if (hasDropdown && deviceType != null)
 | 
			
		||||
          Positioned(
 | 
			
		||||
            bottom: 4,
 | 
			
		||||
            right: 4,
 | 
			
		||||
            child: GestureDetector(
 | 
			
		||||
              onTap: () => _showDeviceSelectionDialog(context, ref, deviceType),
 | 
			
		||||
              child: Container(
 | 
			
		||||
                width: 16,
 | 
			
		||||
                height: 16,
 | 
			
		||||
                decoration: BoxDecoration(
 | 
			
		||||
                  color: backgroundColor.withOpacity(0.8),
 | 
			
		||||
                  shape: BoxShape.circle,
 | 
			
		||||
                  border: Border.all(
 | 
			
		||||
                    color: Colors.white.withOpacity(0.3),
 | 
			
		||||
                    width: 0.5,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                child: Icon(
 | 
			
		||||
                  Icons.arrow_drop_down,
 | 
			
		||||
                  color: Colors.white,
 | 
			
		||||
                  size: 12,
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _showDeviceSelectionDialog(
 | 
			
		||||
    BuildContext context,
 | 
			
		||||
    WidgetRef ref,
 | 
			
		||||
    String deviceType,
 | 
			
		||||
  ) async {
 | 
			
		||||
    try {
 | 
			
		||||
      final devices = await Hardware.instance.enumerateDevices(
 | 
			
		||||
        type: deviceType,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (!context.mounted) return;
 | 
			
		||||
 | 
			
		||||
      showModalBottomSheet(
 | 
			
		||||
        context: context,
 | 
			
		||||
        builder: (BuildContext dialogContext) {
 | 
			
		||||
          return SheetScaffold(
 | 
			
		||||
            titleText: deviceType == 'videoinput'
 | 
			
		||||
                ? 'selectCamera'.tr()
 | 
			
		||||
                : 'selectMicrophone'.tr(),
 | 
			
		||||
            child: ListView.builder(
 | 
			
		||||
              itemCount: devices.length,
 | 
			
		||||
              itemBuilder: (context, index) {
 | 
			
		||||
                final device = devices[index];
 | 
			
		||||
                return ListTile(
 | 
			
		||||
                  title: Text(
 | 
			
		||||
                    device.label.isNotEmpty
 | 
			
		||||
                        ? device.label
 | 
			
		||||
                        : '${'device'.tr()} ${index + 1}',
 | 
			
		||||
                  ),
 | 
			
		||||
                  onTap: () {
 | 
			
		||||
                    Navigator.of(dialogContext).pop();
 | 
			
		||||
                    _switchDevice(context, ref, device, deviceType);
 | 
			
		||||
                  },
 | 
			
		||||
                );
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
      );
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if (context.mounted) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(
 | 
			
		||||
            content: Text('${'failedToEnumerateDevices'.tr()}: $e'),
 | 
			
		||||
            backgroundColor: Colors.red,
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _switchDevice(
 | 
			
		||||
    BuildContext context,
 | 
			
		||||
    WidgetRef ref,
 | 
			
		||||
    MediaDevice device,
 | 
			
		||||
    String deviceType,
 | 
			
		||||
  ) async {
 | 
			
		||||
    try {
 | 
			
		||||
      final callNotifier = ref.read(callNotifierProvider.notifier);
 | 
			
		||||
 | 
			
		||||
      if (deviceType == 'videoinput') {
 | 
			
		||||
        // Switch camera device
 | 
			
		||||
        final localParticipant = callNotifier.room?.localParticipant;
 | 
			
		||||
        final videoTrack =
 | 
			
		||||
            localParticipant?.videoTrackPublications.firstOrNull?.track;
 | 
			
		||||
 | 
			
		||||
        if (videoTrack is LocalVideoTrack) {
 | 
			
		||||
          await videoTrack.switchCamera(device.deviceId);
 | 
			
		||||
        }
 | 
			
		||||
      } else if (deviceType == 'audioinput') {
 | 
			
		||||
        // Switch microphone device
 | 
			
		||||
        final localParticipant = callNotifier.room?.localParticipant;
 | 
			
		||||
        final audioTrack =
 | 
			
		||||
            localParticipant?.audioTrackPublications.firstOrNull?.track;
 | 
			
		||||
 | 
			
		||||
        if (audioTrack is LocalAudioTrack) {
 | 
			
		||||
          // For audio devices, we need to restart the track with new device
 | 
			
		||||
          await audioTrack.restartTrack(
 | 
			
		||||
            AudioCaptureOptions(deviceId: device.deviceId),
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (context.mounted) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(
 | 
			
		||||
            content: Text(
 | 
			
		||||
              '${'switchedTo'.tr()} ${device.label.isNotEmpty ? device.label : 'selectedDevice'.tr()}',
 | 
			
		||||
            ),
 | 
			
		||||
            backgroundColor: Colors.green,
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if (context.mounted) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(
 | 
			
		||||
            content: Text('${'failedToSwitchDevice'.tr()}: $e'),
 | 
			
		||||
            backgroundColor: Colors.red,
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CallOverlayBar extends HookConsumerWidget {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user