💫 Animated call overlay
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:animations/animations.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@@ -319,29 +320,46 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
// Default to true (preview mode) so user sees video immediately after joining
|
// Default to true (preview mode) so user sees video immediately after joining
|
||||||
final isExpanded = useState(true);
|
final isExpanded = useState(true);
|
||||||
|
|
||||||
// If connected, show active call UI
|
Widget child;
|
||||||
if (callState.isConnected) {
|
if (callState.isConnected) {
|
||||||
return _buildActiveCallOverlay(
|
child = _buildActiveCallOverlay(
|
||||||
context,
|
context,
|
||||||
ref,
|
ref,
|
||||||
callState,
|
callState,
|
||||||
callNotifier,
|
callNotifier,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
);
|
);
|
||||||
|
} else if (ongoingCall.value != null) {
|
||||||
|
child = _buildJoinPrompt(context, ref);
|
||||||
|
} else {
|
||||||
|
child = const SizedBox.shrink(key: ValueKey('empty'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not connected but there is an ongoing call, show join prompt
|
return AnimatedSize(
|
||||||
if (ongoingCall.value != null) {
|
duration: const Duration(milliseconds: 150),
|
||||||
return _buildJoinPrompt(context, ref);
|
curve: Curves.easeInOut,
|
||||||
}
|
alignment: Alignment.topCenter,
|
||||||
|
child: AnimatedSwitcher(
|
||||||
return const SizedBox.shrink();
|
duration: const Duration(milliseconds: 150),
|
||||||
|
layoutBuilder: (currentChild, previousChildren) {
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
children: <Widget>[
|
||||||
|
...previousChildren,
|
||||||
|
if (currentChild != null) currentChild,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildJoinPrompt(BuildContext context, WidgetRef ref) {
|
Widget _buildJoinPrompt(BuildContext context, WidgetRef ref) {
|
||||||
final isLoading = useState(false);
|
final isLoading = useState(false);
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
|
key: const ValueKey('join_prompt'),
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -441,13 +459,16 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
: value,
|
: value,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (lastSpeaker == null) return const SizedBox.shrink();
|
if (lastSpeaker == null) {
|
||||||
|
return const SizedBox.shrink(key: ValueKey('active_waiting'));
|
||||||
|
}
|
||||||
|
|
||||||
final userInfo = ref.watch(userInfoProvider).value!;
|
final userInfo = ref.watch(userInfoProvider).value!;
|
||||||
|
|
||||||
// Preview Mode (Expanded)
|
// Preview Mode (Expanded)
|
||||||
if (isExpanded.value) {
|
if (isExpanded.value) {
|
||||||
return Card(
|
return Card(
|
||||||
|
key: const ValueKey('active_expanded'),
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -461,21 +482,23 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(formatDuration(callState.duration)).bold(),
|
Text(formatDuration(callState.duration)).bold(),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
OpenContainer(
|
||||||
|
closedElevation: 0,
|
||||||
|
closedColor: Colors.transparent,
|
||||||
|
openColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
middleColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
openBuilder: (context, action) => CallScreen(room: room),
|
||||||
|
closedBuilder:
|
||||||
|
(context, openContainer) => IconButton(
|
||||||
visualDensity: const VisualDensity(
|
visualDensity: const VisualDensity(
|
||||||
horizontal: -4,
|
horizontal: -4,
|
||||||
vertical: -4,
|
vertical: -4,
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.fullscreen),
|
icon: const Icon(Icons.fullscreen),
|
||||||
onPressed: () {
|
onPressed: openContainer,
|
||||||
Navigator.of(context).push(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => CallScreen(room: room),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
tooltip: 'Full Screen',
|
tooltip: 'Full Screen',
|
||||||
),
|
),
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
visualDensity: const VisualDensity(
|
visualDensity: const VisualDensity(
|
||||||
horizontal: -4,
|
horizontal: -4,
|
||||||
@@ -504,6 +527,7 @@ class CallOverlayBar extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Compact Mode
|
// Compact Mode
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
key: const ValueKey('active_collapsed'),
|
||||||
onTap: () => isExpanded.value = true,
|
onTap: () => isExpanded.value = true,
|
||||||
child: Card(
|
child: Card(
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
|
|||||||
Reference in New Issue
Block a user