diff --git a/.gitignore b/.gitignore index 02f5ec62..fb2527ac 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ *.iml .vscode/ +# App config (contains API keys, passed via --dart-define-from-file) +apps/mobile_app/config/*.json + # macOS .DS_Store **/.DS_Store diff --git a/apps/mobile_app/config/development.json b/apps/mobile_app/config/development.json deleted file mode 100644 index 9c4863ac..00000000 --- a/apps/mobile_app/config/development.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "env": "development", - "apiBaseUrl": "https://api-neki-b2b.neki.es/gateway/api/", - "apiOrigin": "https://neki-b2b.neki.es", - "wsUrl": "wss://api-neki-b2b.neki.es/websocket", - "juphoonAppKey": "9efcf2d889dc8a0320925096" -} \ No newline at end of file diff --git a/apps/mobile_app/config/production.json b/apps/mobile_app/config/production.json deleted file mode 100644 index d6b3934f..00000000 --- a/apps/mobile_app/config/production.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "env": "production", - "apiBaseUrl": "https://api-platform.savefamily.app/gateway/api/", - "apiOrigin": "https://platform.savefamily.app", - "wsUrl": "wss://api-platform.savefamily.app/websocket", - "juphoonAppKey": "9efcf2d889dc8a0320925096" -} diff --git a/apps/mobile_app/config/staging.json b/apps/mobile_app/config/staging.json deleted file mode 100644 index d20530de..00000000 --- a/apps/mobile_app/config/staging.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "env": "staging", - "apiBaseUrl": "https://api-platform.pre.savefamilygps.net/gateway/api/", - "apiOrigin": "https://platform.pre.savefamilygps.net", - "wsUrl": "wss://api-platform.pre.savefamilygps.net/websocket", - "juphoonAppKey": "9efcf2d889dc8a0320925096" -} \ No newline at end of file diff --git a/VIDEOCALL_INTEGRATION.md b/apps/mobile_app/docs/videocall-integration.md similarity index 100% rename from VIDEOCALL_INTEGRATION.md rename to apps/mobile_app/docs/videocall-integration.md diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/data/datasources/videocall_signaling_datasource_impl.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/data/datasources/videocall_signaling_datasource_impl.dart index 4a1afe44..7b4fe069 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/data/datasources/videocall_signaling_datasource_impl.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/data/datasources/videocall_signaling_datasource_impl.dart @@ -13,15 +13,12 @@ class VideocallSignalingDatasourceImpl implements VideocallSignalingDatasource { required String appAccount, required String roomNumber, }) async { - // TODO: Implement when backend API spec is available - // await _repository.post('/devices/$deviceId/videocall/initiate', body: {...}); throw UnimplementedError( 'Backend signaling API not yet available. Waiting for endpoint spec.'); } @override Future cancelCall({required String deviceId}) async { - // TODO: Implement when backend API spec is available throw UnimplementedError( 'Backend signaling API not yet available. Waiting for endpoint spec.'); } @@ -31,14 +28,12 @@ class VideocallSignalingDatasourceImpl implements VideocallSignalingDatasource { required String deviceId, required String roomNumber, }) async { - // TODO: Implement when backend API spec is available throw UnimplementedError( 'Backend signaling API not yet available. Waiting for endpoint spec.'); } @override Future getRoomParticipantCount({required String roomNumber}) async { - // TODO: Implement when backend API spec is available throw UnimplementedError( 'Backend signaling API not yet available. Waiting for endpoint spec.'); } @@ -49,7 +44,6 @@ class VideocallSignalingDatasourceImpl implements VideocallSignalingDatasource { required int count, required int type, }) async { - // TODO: Implement when backend API spec is available throw UnimplementedError( 'Backend signaling API not yet available. Waiting for endpoint spec.'); } diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/domain/entities/videocall_error.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/domain/entities/videocall_error.dart index 6713d448..b34b5be4 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/domain/entities/videocall_error.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/domain/entities/videocall_error.dart @@ -3,6 +3,7 @@ enum VideocallErrorEvent { authentication, callStart, callAnswer, + missedCall, network, cameraPermission, microphonePermission, diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/group_call_controller.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/group_call_controller.dart index 781b8942..dd9a5af2 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/group_call_controller.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/group_call_controller.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:get_it/get_it.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:videocall_sdk/videocall_sdk.dart'; @@ -28,8 +27,8 @@ class GroupCallController extends _$GroupCallController { @override GroupCallState build() { - _channelService = GetIt.I(); - _deviceService = GetIt.I(); + _channelService = ref.read(videocallChannelServiceProvider); + _deviceService = ref.read(videocallDeviceServiceProvider); ref.onDispose(_disposeSubscriptions); _subscribeToStreams(); diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/group_call_controller.g.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/group_call_controller.g.dart index b0774944..5cd2f170 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/group_call_controller.g.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/group_call_controller.g.dart @@ -42,7 +42,7 @@ final class GroupCallControllerProvider } String _$groupCallControllerHash() => - r'c8528072cb1b6d2bb15d50b199405938e7d3acd0'; + r'0d3c5bd234ef3ed76b0b0f7666ddb73c8b98be55'; abstract class _$GroupCallController extends $Notifier { GroupCallState build(); diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/videocall_controller.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/videocall_controller.dart index 46447a5c..6cf749ee 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/videocall_controller.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/videocall_controller.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:get_it/get_it.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:sf_shared/sf_shared.dart'; import 'package:videocall_sdk/videocall_sdk.dart' hide VideocallState; @@ -25,10 +24,10 @@ class VideocallController extends _$VideocallController { @override VideocallState build() { - _manager = GetIt.I(); - _callService = GetIt.I(); - _deviceService = GetIt.I(); - _client = GetIt.I(); + _manager = ref.read(videocallManagerProvider); + _callService = ref.read(videocallCallServiceProvider); + _deviceService = ref.read(videocallDeviceServiceProvider); + _client = ref.read(videocallClientProvider); final device = ref.read(selectedDeviceProvider).value; final deviceId = device?.identificator ?? ''; @@ -79,18 +78,17 @@ class VideocallController extends _$VideocallController { void _subscribeToStreams() { _callAddSub = _callService.callItemAddStream.listen(_onCallItemAdd); - _callUpdateSub = - _callService.callItemUpdateStream.listen(_onCallItemUpdate); - _callRemoveSub = - _callService.callItemRemoveStream.listen(_onCallItemRemove); + _callUpdateSub = _callService.callItemUpdateStream.listen( + _onCallItemUpdate, + ); + _callRemoveSub = _callService.callItemRemoveStream.listen( + _onCallItemRemove, + ); _missedCallSub = _callService.missedCallStream.listen(_onMissedCall); _clientStateSub = _client.stateStream.listen(_onClientStateChange); } - Future login({ - required String userId, - required String password, - }) async { + Future login({required String userId, required String password}) async { final ok = await _client.login(userId: userId, password: password); if (!ref.mounted) return false; if (!ok) { @@ -103,8 +101,9 @@ class VideocallController extends _$VideocallController { Future startCall(String remoteUserId) async { final device = ref.read(selectedDeviceProvider).value; - final targetUserId = - remoteUserId.isNotEmpty ? remoteUserId : 'w_${device?.imei ?? ''}'; + final targetUserId = remoteUserId.isNotEmpty + ? remoteUserId + : 'w_${device?.imei ?? ''}'; state = state.copyWith( remoteUserId: targetUserId, @@ -252,7 +251,7 @@ class VideocallController extends _$VideocallController { if (!ref.mounted) return; state = state.copyWith(currentCall: item); - final isTalking = item.state.name == 'talking'; + final isTalking = item.isTalking; if (isTalking && state.screenMode != VideocallScreenMode.inCall) { state = state.copyWith( @@ -293,9 +292,7 @@ class VideocallController extends _$VideocallController { void _onMissedCall(VideocallItem item) { if (!ref.mounted) return; - state = state.copyWith( - errorEvent: VideocallErrorEvent.callStart, - ); + state = state.copyWith(errorEvent: VideocallErrorEvent.missedCall); } void _onClientStateChange(VideocallClientState clientState) { @@ -319,5 +316,10 @@ class VideocallController extends _$VideocallController { _callRemoveSub?.cancel(); _missedCallSub?.cancel(); _clientStateSub?.cancel(); + if (state.screenMode != VideocallScreenMode.idle) { + _callService.hangUp(); + _deviceService.stopAudio(); + _deviceService.stopCamera(); + } } } diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/videocall_controller.g.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/videocall_controller.g.dart index 045d7653..0419f618 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/videocall_controller.g.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/providers/videocall_controller.g.dart @@ -42,7 +42,7 @@ final class VideocallControllerProvider } String _$videocallControllerHash() => - r'2fc967b9e44ca13586fd36e0d3eaab1014d52821'; + r'910674a27ecef98e5df917d193f97fca1c60ac02'; abstract class _$VideocallController extends $Notifier { VideocallState build(); diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/videocall_screen.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/videocall_screen.dart index a3e1c68d..eac54c3a 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/videocall_screen.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/videocall_screen.dart @@ -42,15 +42,14 @@ class _VideocallScreenState extends ConsumerState { (_, next) { if (next == null) return; final key = switch (next) { - VideocallErrorEvent.sdkInitialization || - VideocallErrorEvent.authentication || - VideocallErrorEvent.callStart || - VideocallErrorEvent.callAnswer || - VideocallErrorEvent.cameraPermission || - VideocallErrorEvent.microphonePermission || - VideocallErrorEvent.network || - VideocallErrorEvent.generic => - I18n.errorGeneric, + VideocallErrorEvent.sdkInitialization => I18n.videocallErrorSdkInit, + VideocallErrorEvent.authentication => I18n.videocallErrorAuth, + VideocallErrorEvent.callStart => I18n.videocallErrorCallStart, + VideocallErrorEvent.callAnswer => I18n.videocallErrorCallAnswer, + VideocallErrorEvent.missedCall => I18n.videocallErrorMissedCall, + VideocallErrorEvent.cameraPermission => I18n.videocallErrorCamera, + VideocallErrorEvent.microphonePermission => I18n.videocallErrorMic, + VideocallErrorEvent.network || VideocallErrorEvent.generic => I18n.errorGeneric, }; showErrorDialog(context, key); vm.clearError(); @@ -109,7 +108,7 @@ class _IdleView extends StatelessWidget { CircularProgressIndicator(color: colorScheme.onSurface), const SizedBox(height: 16), Text( - 'Inicializando SDK...', + context.translate(I18n.videocallInitializingSdk), style: TextStyle(color: colorScheme.onSurface.withValues(alpha: 0.7)), ), ], @@ -125,7 +124,7 @@ class _IdleView extends StatelessWidget { Icon(Icons.videocam, color: colorScheme.onSurface, size: 64), const SizedBox(height: 24), Text( - 'Videollamada', + context.translate(I18n.videocallTitle), style: TextStyle( color: colorScheme.onSurface, fontSize: 24, @@ -137,7 +136,7 @@ class _IdleView extends StatelessWidget { controller: controller, style: TextStyle(color: colorScheme.onSurface), decoration: InputDecoration( - hintText: 'User ID del destinatario', + hintText: context.translate(I18n.videocallRecipientUserId), hintStyle: TextStyle(color: colorScheme.onSurface.withValues(alpha: 0.38)), enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: colorScheme.onSurface.withValues(alpha: 0.24)), @@ -162,7 +161,7 @@ class _IdleView extends StatelessWidget { vm.startCall(userId); }, icon: const Icon(Icons.videocam), - label: const Text('Iniciar videollamada'), + label: Text(context.translate(I18n.videocallStart)), style: ElevatedButton.styleFrom( backgroundColor: context.sfColors.legacyPrimary, foregroundColor: colorScheme.onPrimary, @@ -185,7 +184,7 @@ class _IdleView extends StatelessWidget { ), if (state.localUserId.isNotEmpty) Text( - 'Logged in as: ${state.localUserId}', + context.translate(I18n.videocallLoggedInAs, args: {'userId': state.localUserId}), style: TextStyle(color: colorScheme.onSurface.withValues(alpha: 0.38), fontSize: 12), ), ], @@ -252,7 +251,7 @@ class _InCallView extends StatelessWidget { Icon(Icons.person, color: colorScheme.onSurface.withValues(alpha: 0.24), size: 96), const SizedBox(height: 8), Text( - 'Esperando video remoto...', + context.translate(I18n.videocallWaitingRemoteVideo), style: TextStyle(color: colorScheme.onSurface.withValues(alpha: 0.38)), ), ], diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/call_controls_widget.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/call_controls_widget.dart index 1df3ef61..556c6395 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/call_controls_widget.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/call_controls_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sf_localizations/sf_localizations.dart'; class CallControlsWidget extends StatelessWidget { const CallControlsWidget({ @@ -29,7 +30,7 @@ class CallControlsWidget extends StatelessWidget { children: [ _ControlButton( icon: isMicEnabled ? Icons.mic : Icons.mic_off, - label: isMicEnabled ? 'Mic On' : 'Mic Off', + label: context.translate(isMicEnabled ? I18n.videocallMicOn : I18n.videocallMicOff), isActive: isMicEnabled, onPressed: onToggleMic, ), @@ -37,19 +38,19 @@ class CallControlsWidget extends StatelessWidget { icon: isSpeakerEnabled ? Icons.volume_up : Icons.volume_off, - label: isSpeakerEnabled ? 'Speaker' : 'Earpiece', + label: context.translate(isSpeakerEnabled ? I18n.videocallSpeaker : I18n.videocallEarpiece), isActive: isSpeakerEnabled, onPressed: onToggleSpeaker, ), _ControlButton( icon: Icons.cameraswitch, - label: isFrontCamera ? 'Front' : 'Back', + label: context.translate(isFrontCamera ? I18n.videocallCameraFront : I18n.videocallCameraBack), isActive: true, onPressed: onSwitchCamera, ), _ControlButton( icon: Icons.call_end, - label: 'Hang Up', + label: context.translate(I18n.videocallHangUp), isActive: false, isDestructive: true, onPressed: onHangUp, diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/call_status_indicator.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/call_status_indicator.dart index 8b517a1d..730a2fa1 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/call_status_indicator.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/call_status_indicator.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sf_localizations/sf_localizations.dart'; import '../../domain/entities/videocall_error.dart'; @@ -17,8 +18,8 @@ class CallStatusIndicator extends StatelessWidget { @override Widget build(BuildContext context) { final (text, showCancel) = switch (screenMode) { - VideocallScreenMode.outgoing => ('Llamando a $remoteUserId...', true), - VideocallScreenMode.incoming => ('$remoteUserId te está llamando', false), + VideocallScreenMode.outgoing => (context.translate(I18n.videocallCalling, args: {'userId': remoteUserId}), true), + VideocallScreenMode.incoming => (context.translate(I18n.videocallUserCalling, args: {'userId': remoteUserId}), false), _ => ('', false), }; diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/incoming_call_overlay.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/incoming_call_overlay.dart index 51f1f185..665feb09 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/incoming_call_overlay.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/incoming_call_overlay.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sf_localizations/sf_localizations.dart'; class IncomingCallOverlay extends StatelessWidget { const IncomingCallOverlay({ @@ -20,56 +21,54 @@ class IncomingCallOverlay extends StatelessWidget { return Container( color: colorScheme.surface, - child: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Spacer(), - Icon( - isVideo ? Icons.videocam : Icons.call, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + Icon( + isVideo ? Icons.videocam : Icons.call, + color: colorScheme.onSurface, + size: 64, + ), + const SizedBox(height: 24), + Text( + context.translate(isVideo ? I18n.videocallIncomingVideo : I18n.videocallIncomingAudio), + style: TextStyle( + color: colorScheme.onSurface.withValues(alpha: 0.7), + fontSize: 16, + ), + ), + const SizedBox(height: 8), + Text( + callerUserId, + style: TextStyle( color: colorScheme.onSurface, - size: 64, + fontSize: 28, + fontWeight: FontWeight.bold, ), - const SizedBox(height: 24), - Text( - isVideo ? 'Videollamada entrante' : 'Llamada entrante', - style: TextStyle( - color: colorScheme.onSurface.withValues(alpha: 0.7), - fontSize: 16, - ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.only(bottom: 64), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _ActionButton( + icon: Icons.call_end, + color: colorScheme.error, + label: context.translate(I18n.videocallReject), + onPressed: onReject, + ), + _ActionButton( + icon: isVideo ? Icons.videocam : Icons.call, + color: Colors.green, + label: context.translate(I18n.videocallAccept), + onPressed: onAccept, + ), + ], ), - const SizedBox(height: 8), - Text( - callerUserId, - style: TextStyle( - color: colorScheme.onSurface, - fontSize: 28, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - Padding( - padding: const EdgeInsets.only(bottom: 64), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _ActionButton( - icon: Icons.call_end, - color: colorScheme.error, - label: 'Rechazar', - onPressed: onReject, - ), - _ActionButton( - icon: isVideo ? Icons.videocam : Icons.call, - color: Colors.green, - label: 'Aceptar', - onPressed: onAccept, - ), - ], - ), - ), - ], - ), + ), + ], ), ); } diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/participant_grid_widget.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/participant_grid_widget.dart index caff1d0d..3750bd66 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/participant_grid_widget.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/participant_grid_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sf_localizations/sf_localizations.dart'; import '../../domain/entities/videocall_participant.dart'; import 'participant_tile_widget.dart'; @@ -14,10 +15,12 @@ class ParticipantGridWidget extends StatelessWidget { @override Widget build(BuildContext context) { if (participants.isEmpty) { - return const Center( + return Center( child: Text( - 'Esperando participantes...', - style: TextStyle(color: Colors.white70), + context.translate(I18n.videocallWaitingParticipants), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), + ), ), ); } diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/participant_tile_widget.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/participant_tile_widget.dart index 0715540d..b5caa58f 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/participant_tile_widget.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/participant_tile_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:sf_localizations/sf_localizations.dart'; import '../../domain/entities/videocall_participant.dart'; import 'video_view_widget.dart'; @@ -52,7 +53,7 @@ class ParticipantTileWidget extends StatelessWidget { child: Icon(Icons.mic_off, color: colorScheme.error, size: 14), ), Text( - participant.isSelf ? 'Tú' : participant.userId, + participant.isSelf ? context.translate(I18n.videocallYou) : participant.userId, style: TextStyle(color: colorScheme.onSurface, fontSize: 12), ), ], diff --git a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/video_view_widget.dart b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/video_view_widget.dart index ad29525e..7b7b5d31 100644 --- a/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/video_view_widget.dart +++ b/modules/legacy/modules/device_management/lib/src/features/videocall/presentation/widgets/video_view_widget.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:videocall_sdk/videocall_sdk.dart'; -class VideoViewWidget extends StatefulWidget { +class VideoViewWidget extends StatelessWidget { const VideoViewWidget({ super.key, required this.canvas, @@ -17,52 +17,27 @@ class VideoViewWidget extends StatefulWidget { final double height; final BoxFit fit; - @override - State createState() => _VideoViewWidgetState(); -} - -class _VideoViewWidgetState extends State { - Widget? _videoWidget; - - @override - void initState() { - super.initState(); - _initVideoView(); - } - - @override - void didUpdateWidget(VideoViewWidget oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.canvas.videoCanvasId != widget.canvas.videoCanvasId) { - _initVideoView(); - } - } - - Future _initVideoView() async { - final Widget videoWidget; + Future _buildVideoView() { if (Platform.isIOS) { - videoWidget = await widget.canvas.getIOSVideoView( - (viewId) {}, - widget.width, - widget.height, - ); - } else { - videoWidget = await widget.canvas.getAndroidVideoView(); - } - if (mounted) { - setState(() => _videoWidget = videoWidget); + return canvas.getIOSVideoView((_) {}, width, height); } + return canvas.getAndroidVideoView(); } @override Widget build(BuildContext context) { return SizedBox( - width: widget.width, - height: widget.height, - child: _videoWidget ?? - const Center( + width: width, + height: height, + child: FutureBuilder( + future: _buildVideoView(), + builder: (context, snapshot) { + if (snapshot.hasData) return snapshot.data!; + return const Center( child: CircularProgressIndicator(strokeWidth: 2), - ), + ); + }, + ), ); } } diff --git a/packages/sf_localizations/assets/l10n/de.json b/packages/sf_localizations/assets/l10n/de.json index 373e887b..8b15033e 100644 --- a/packages/sf_localizations/assets/l10n/de.json +++ b/packages/sf_localizations/assets/l10n/de.json @@ -683,6 +683,34 @@ "locationRevealSkip": "Überspringen", "locationDeleteGeofenceConfirm": "Möchten Sie diese sichere Zone wirklich löschen?", "locationDeleteFrequentPlaceConfirm": "Möchten Sie diesen häufigen Ort wirklich löschen?", + "videocallTitle": "Videoanruf", + "videocallInitializingSdk": "SDK wird initialisiert...", + "videocallRecipientUserId": "Benutzer-ID des Empfängers", + "videocallStart": "Videoanruf starten", + "videocallLoggedInAs": "Angemeldet als: {userId}", + "videocallCalling": "{userId} wird angerufen...", + "videocallUserCalling": "{userId} ruft dich an", + "videocallIncomingVideo": "Eingehender Videoanruf", + "videocallIncomingAudio": "Eingehender Anruf", + "videocallReject": "Ablehnen", + "videocallAccept": "Annehmen", + "videocallMicOn": "Mikro an", + "videocallMicOff": "Mikro aus", + "videocallSpeaker": "Lautsprecher", + "videocallEarpiece": "Hörer", + "videocallCameraFront": "Vorne", + "videocallCameraBack": "Hinten", + "videocallHangUp": "Auflegen", + "videocallWaitingParticipants": "Warten auf Teilnehmer...", + "videocallWaitingRemoteVideo": "Warten auf Remote-Video...", + "videocallYou": "Du", + "videocallErrorSdkInit": "Fehler beim Initialisieren des Videoanrufs", + "videocallErrorAuth": "Authentifizierungsfehler beim Videoanruf", + "videocallErrorCallStart": "Fehler beim Starten des Anrufs", + "videocallErrorCallAnswer": "Fehler beim Annehmen des Anrufs", + "videocallErrorCamera": "Kameraberechtigung erforderlich", + "videocallErrorMic": "Mikrofonberechtigung erforderlich", + "videocallErrorMissedCall": "Verpasster Anruf", "positionUpdated": "Letzte verfügbare Position aktualisiert", "locationMapStyleLight": "Hell", "locationMapStyleDark": "Dunkel", @@ -757,7 +785,7 @@ "frequentPlaceCreated": "Häufiger Ort erstellt", "frequentPlaceUpdated": "Häufiger Ort aktualisiert", "frequentPlaceDeleted": "Häufiger Ort gelöscht", - "errorGeneric": "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.", + "errorGeneric": "Ein unerwarteter Fehler ist aufgetreten.", "pullDownToRetry": "Zum Wiederholen nach unten ziehen", "errorGeofenceCreate": "Die Sicherheitszone konnte nicht erstellt werden", "errorGeofenceUpdate": "Die Sicherheitszone konnte nicht aktualisiert werden", @@ -1060,6 +1088,5 @@ "errorSessionExpired": "Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.", "errorValidation": "Die eingegebenen Daten sind ungültig.", "errorScaRequired": "Eine zusätzliche Authentifizierung ist erforderlich.", - "errorDeviceNotOwned": "Du hast keine Berechtigung, auf dieses Gerät zuzugreifen.", - "errorGeneric": "Ein unerwarteter Fehler ist aufgetreten." + "errorDeviceNotOwned": "Du hast keine Berechtigung, auf dieses Gerät zuzugreifen." } diff --git a/packages/sf_localizations/assets/l10n/en.json b/packages/sf_localizations/assets/l10n/en.json index 9efb1c4a..91bd48b9 100755 --- a/packages/sf_localizations/assets/l10n/en.json +++ b/packages/sf_localizations/assets/l10n/en.json @@ -863,6 +863,34 @@ "locationRevealSkip": "Skip", "locationDeleteGeofenceConfirm": "Are you sure you want to delete this safe zone?", "locationDeleteFrequentPlaceConfirm": "Are you sure you want to delete this frequent place?", + "videocallTitle": "Video Call", + "videocallInitializingSdk": "Initializing SDK...", + "videocallRecipientUserId": "Recipient User ID", + "videocallStart": "Start video call", + "videocallLoggedInAs": "Logged in as: {userId}", + "videocallCalling": "Calling {userId}...", + "videocallUserCalling": "{userId} is calling you", + "videocallIncomingVideo": "Incoming video call", + "videocallIncomingAudio": "Incoming call", + "videocallReject": "Reject", + "videocallAccept": "Accept", + "videocallMicOn": "Mic On", + "videocallMicOff": "Mic Off", + "videocallSpeaker": "Speaker", + "videocallEarpiece": "Earpiece", + "videocallCameraFront": "Front", + "videocallCameraBack": "Back", + "videocallHangUp": "Hang Up", + "videocallWaitingParticipants": "Waiting for participants...", + "videocallWaitingRemoteVideo": "Waiting for remote video...", + "videocallYou": "You", + "videocallErrorSdkInit": "Error initializing video call", + "videocallErrorAuth": "Video call authentication error", + "videocallErrorCallStart": "Error starting the call", + "videocallErrorCallAnswer": "Error answering the call", + "videocallErrorCamera": "Camera permission required", + "videocallErrorMic": "Microphone permission required", + "videocallErrorMissedCall": "Missed call", "positionUpdated": "Updated to latest available position", "locationMapStyleLight": "Light", "locationMapStyleDark": "Dark", @@ -931,7 +959,7 @@ "frequentPlaceCreated": "Frequent place created", "frequentPlaceUpdated": "Frequent place updated", "frequentPlaceDeleted": "Frequent place deleted", - "errorGeneric": "Something went wrong. Please try again.", + "errorGeneric": "An unexpected error occurred.", "pullDownToRetry": "Pull down to retry", "errorGeofenceCreate": "Could not create the safety zone", "errorGeofenceUpdate": "Could not update the safety zone", @@ -1060,6 +1088,5 @@ "errorSessionExpired": "Your session has expired. Please sign in again.", "errorValidation": "The information you entered is not valid.", "errorScaRequired": "Additional authentication is required to continue.", - "errorDeviceNotOwned": "You don't have permission to access this device.", - "errorGeneric": "An unexpected error occurred." + "errorDeviceNotOwned": "You don't have permission to access this device." } diff --git a/packages/sf_localizations/assets/l10n/es.json b/packages/sf_localizations/assets/l10n/es.json index 484fac88..eb276004 100644 --- a/packages/sf_localizations/assets/l10n/es.json +++ b/packages/sf_localizations/assets/l10n/es.json @@ -864,6 +864,34 @@ "locationRevealSkip": "Saltar", "locationDeleteGeofenceConfirm": "¿Seguro que quieres eliminar esta zona segura?", "locationDeleteFrequentPlaceConfirm": "¿Seguro que quieres eliminar este lugar frecuente?", + "videocallTitle": "Videollamada", + "videocallInitializingSdk": "Inicializando SDK...", + "videocallRecipientUserId": "User ID del destinatario", + "videocallStart": "Iniciar videollamada", + "videocallLoggedInAs": "Conectado como: {userId}", + "videocallCalling": "Llamando a {userId}...", + "videocallUserCalling": "{userId} te está llamando", + "videocallIncomingVideo": "Videollamada entrante", + "videocallIncomingAudio": "Llamada entrante", + "videocallReject": "Rechazar", + "videocallAccept": "Aceptar", + "videocallMicOn": "Mic On", + "videocallMicOff": "Mic Off", + "videocallSpeaker": "Altavoz", + "videocallEarpiece": "Auricular", + "videocallCameraFront": "Frontal", + "videocallCameraBack": "Trasera", + "videocallHangUp": "Colgar", + "videocallWaitingParticipants": "Esperando participantes...", + "videocallWaitingRemoteVideo": "Esperando video remoto...", + "videocallYou": "Tú", + "videocallErrorSdkInit": "Error al inicializar videollamada", + "videocallErrorAuth": "Error de autenticación de videollamada", + "videocallErrorCallStart": "Error al iniciar la llamada", + "videocallErrorCallAnswer": "Error al contestar la llamada", + "videocallErrorCamera": "Se requiere permiso de cámara", + "videocallErrorMic": "Se requiere permiso de micrófono", + "videocallErrorMissedCall": "Llamada perdida", "positionUpdated": "Última posición disponible actualizada", "locationMapStyleLight": "Claro", "locationMapStyleDark": "Oscuro", @@ -932,7 +960,7 @@ "frequentPlaceCreated": "Lugar frecuente creado", "frequentPlaceUpdated": "Lugar frecuente actualizado", "frequentPlaceDeleted": "Lugar frecuente eliminado", - "errorGeneric": "Algo salió mal. Inténtalo de nuevo.", + "errorGeneric": "Ha ocurrido un error inesperado.", "pullDownToRetry": "Desliza hacia abajo para reintentar", "errorGeofenceCreate": "No se pudo crear la zona de seguridad", "errorGeofenceUpdate": "No se pudo actualizar la zona de seguridad", @@ -1060,6 +1088,5 @@ "errorSessionExpired": "Tu sesión ha caducado. Vuelve a iniciar sesión.", "errorValidation": "Los datos introducidos no son válidos.", "errorScaRequired": "Se requiere autenticación adicional para continuar.", - "errorDeviceNotOwned": "No tienes permiso para acceder a este dispositivo.", - "errorGeneric": "Ha ocurrido un error inesperado." + "errorDeviceNotOwned": "No tienes permiso para acceder a este dispositivo." } diff --git a/packages/sf_localizations/assets/l10n/fr.json b/packages/sf_localizations/assets/l10n/fr.json index ea059ac8..d0f20f01 100644 --- a/packages/sf_localizations/assets/l10n/fr.json +++ b/packages/sf_localizations/assets/l10n/fr.json @@ -683,6 +683,34 @@ "locationRevealSkip": "Passer", "locationDeleteGeofenceConfirm": "Voulez-vous supprimer cette zone sûre ?", "locationDeleteFrequentPlaceConfirm": "Voulez-vous supprimer ce lieu fréquent ?", + "videocallTitle": "Appel vidéo", + "videocallInitializingSdk": "Initialisation du SDK...", + "videocallRecipientUserId": "ID utilisateur du destinataire", + "videocallStart": "Démarrer l'appel vidéo", + "videocallLoggedInAs": "Connecté en tant que : {userId}", + "videocallCalling": "Appel de {userId}...", + "videocallUserCalling": "{userId} vous appelle", + "videocallIncomingVideo": "Appel vidéo entrant", + "videocallIncomingAudio": "Appel entrant", + "videocallReject": "Rejeter", + "videocallAccept": "Accepter", + "videocallMicOn": "Micro activé", + "videocallMicOff": "Micro désactivé", + "videocallSpeaker": "Haut-parleur", + "videocallEarpiece": "Écouteur", + "videocallCameraFront": "Avant", + "videocallCameraBack": "Arrière", + "videocallHangUp": "Raccrocher", + "videocallWaitingParticipants": "En attente de participants...", + "videocallWaitingRemoteVideo": "En attente de la vidéo distante...", + "videocallYou": "Vous", + "videocallErrorSdkInit": "Erreur d'initialisation de l'appel vidéo", + "videocallErrorAuth": "Erreur d'authentification de l'appel vidéo", + "videocallErrorCallStart": "Erreur lors du démarrage de l'appel", + "videocallErrorCallAnswer": "Erreur lors de la réponse à l'appel", + "videocallErrorCamera": "Autorisation de la caméra requise", + "videocallErrorMic": "Autorisation du microphone requise", + "videocallErrorMissedCall": "Appel manqué", "positionUpdated": "Dernière position disponible mise à jour", "locationMapStyleLight": "Clair", "locationMapStyleDark": "Sombre", @@ -757,7 +785,7 @@ "frequentPlaceCreated": "Lieu fréquent créé", "frequentPlaceUpdated": "Lieu fréquent mis à jour", "frequentPlaceDeleted": "Lieu fréquent supprimé", - "errorGeneric": "Une erreur est survenue. Veuillez réessayer.", + "errorGeneric": "Une erreur inattendue s'est produite.", "pullDownToRetry": "Tirez vers le bas pour réessayer", "errorGeofenceCreate": "Impossible de créer la zone de sécurité", "errorGeofenceUpdate": "Impossible de mettre à jour la zone de sécurité", @@ -1060,6 +1088,5 @@ "errorSessionExpired": "Votre session a expiré. Veuillez vous reconnecter.", "errorValidation": "Les données saisies ne sont pas valides.", "errorScaRequired": "Une authentification supplémentaire est requise pour continuer.", - "errorDeviceNotOwned": "Vous n'avez pas l'autorisation d'accéder à cet appareil.", - "errorGeneric": "Une erreur inattendue s'est produite." + "errorDeviceNotOwned": "Vous n'avez pas l'autorisation d'accéder à cet appareil." } diff --git a/packages/sf_localizations/assets/l10n/it.json b/packages/sf_localizations/assets/l10n/it.json index 0f3d13af..dc76aa18 100644 --- a/packages/sf_localizations/assets/l10n/it.json +++ b/packages/sf_localizations/assets/l10n/it.json @@ -683,6 +683,34 @@ "locationRevealSkip": "Salta", "locationDeleteGeofenceConfirm": "Sei sicuro di voler eliminare questa zona sicura?", "locationDeleteFrequentPlaceConfirm": "Sei sicuro di voler eliminare questo luogo frequente?", + "videocallTitle": "Videochiamata", + "videocallInitializingSdk": "Inizializzazione SDK...", + "videocallRecipientUserId": "ID utente del destinatario", + "videocallStart": "Avvia videochiamata", + "videocallLoggedInAs": "Connesso come: {userId}", + "videocallCalling": "Chiamata a {userId}...", + "videocallUserCalling": "{userId} ti sta chiamando", + "videocallIncomingVideo": "Videochiamata in arrivo", + "videocallIncomingAudio": "Chiamata in arrivo", + "videocallReject": "Rifiuta", + "videocallAccept": "Accetta", + "videocallMicOn": "Micro attivo", + "videocallMicOff": "Micro disattivato", + "videocallSpeaker": "Altoparlante", + "videocallEarpiece": "Auricolare", + "videocallCameraFront": "Anteriore", + "videocallCameraBack": "Posteriore", + "videocallHangUp": "Riaggancia", + "videocallWaitingParticipants": "In attesa di partecipanti...", + "videocallWaitingRemoteVideo": "In attesa del video remoto...", + "videocallYou": "Tu", + "videocallErrorSdkInit": "Errore nell'inizializzazione della videochiamata", + "videocallErrorAuth": "Errore di autenticazione della videochiamata", + "videocallErrorCallStart": "Errore nell'avvio della chiamata", + "videocallErrorCallAnswer": "Errore nel rispondere alla chiamata", + "videocallErrorCamera": "Permesso fotocamera richiesto", + "videocallErrorMic": "Permesso microfono richiesto", + "videocallErrorMissedCall": "Chiamata persa", "positionUpdated": "Ultima posizione disponibile aggiornata", "locationMapStyleLight": "Chiaro", "locationMapStyleDark": "Scuro", @@ -757,7 +785,7 @@ "frequentPlaceCreated": "Luogo frequente creato", "frequentPlaceUpdated": "Luogo frequente aggiornato", "frequentPlaceDeleted": "Luogo frequente eliminato", - "errorGeneric": "Qualcosa è andato storto. Riprova.", + "errorGeneric": "Si è verificato un errore imprevisto.", "pullDownToRetry": "Trascina verso il basso per riprovare", "errorGeofenceCreate": "Impossibile creare la zona di sicurezza", "errorGeofenceUpdate": "Impossibile aggiornare la zona di sicurezza", @@ -1060,6 +1088,5 @@ "errorSessionExpired": "La tua sessione è scaduta. Accedi di nuovo.", "errorValidation": "I dati inseriti non sono validi.", "errorScaRequired": "È richiesta un'autenticazione aggiuntiva per continuare.", - "errorDeviceNotOwned": "Non hai il permesso di accedere a questo dispositivo.", - "errorGeneric": "Si è verificato un errore imprevisto." + "errorDeviceNotOwned": "Non hai il permesso di accedere a questo dispositivo." } diff --git a/packages/sf_localizations/assets/l10n/pt.json b/packages/sf_localizations/assets/l10n/pt.json index 79340238..7ffcacca 100644 --- a/packages/sf_localizations/assets/l10n/pt.json +++ b/packages/sf_localizations/assets/l10n/pt.json @@ -683,6 +683,34 @@ "locationRevealSkip": "Saltar", "locationDeleteGeofenceConfirm": "Tens a certeza que queres eliminar esta zona segura?", "locationDeleteFrequentPlaceConfirm": "Tens a certeza que queres eliminar este lugar frequente?", + "videocallTitle": "Videochamada", + "videocallInitializingSdk": "A inicializar SDK...", + "videocallRecipientUserId": "ID do utilizador destinatário", + "videocallStart": "Iniciar videochamada", + "videocallLoggedInAs": "Conectado como: {userId}", + "videocallCalling": "A ligar para {userId}...", + "videocallUserCalling": "{userId} está a ligar-te", + "videocallIncomingVideo": "Videochamada recebida", + "videocallIncomingAudio": "Chamada recebida", + "videocallReject": "Rejeitar", + "videocallAccept": "Aceitar", + "videocallMicOn": "Micro ligado", + "videocallMicOff": "Micro desligado", + "videocallSpeaker": "Altifalante", + "videocallEarpiece": "Auricular", + "videocallCameraFront": "Frontal", + "videocallCameraBack": "Traseira", + "videocallHangUp": "Desligar", + "videocallWaitingParticipants": "A aguardar participantes...", + "videocallWaitingRemoteVideo": "A aguardar vídeo remoto...", + "videocallYou": "Tu", + "videocallErrorSdkInit": "Erro ao inicializar videochamada", + "videocallErrorAuth": "Erro de autenticação da videochamada", + "videocallErrorCallStart": "Erro ao iniciar a chamada", + "videocallErrorCallAnswer": "Erro ao atender a chamada", + "videocallErrorCamera": "Permissão de câmara necessária", + "videocallErrorMic": "Permissão de microfone necessária", + "videocallErrorMissedCall": "Chamada perdida", "positionUpdated": "Última posição disponível atualizada", "locationMapStyleLight": "Claro", "locationMapStyleDark": "Escuro", @@ -757,7 +785,7 @@ "frequentPlaceCreated": "Local frequente criado", "frequentPlaceUpdated": "Local frequente atualizado", "frequentPlaceDeleted": "Local frequente eliminado", - "errorGeneric": "Algo correu mal. Tente novamente.", + "errorGeneric": "Ocorreu um erro inesperado.", "pullDownToRetry": "Deslize para baixo para tentar novamente", "errorGeofenceCreate": "Não foi possível criar a zona de segurança", "errorGeofenceUpdate": "Não foi possível atualizar a zona de segurança", @@ -1060,6 +1088,5 @@ "errorSessionExpired": "A tua sessão expirou. Inicia sessão novamente.", "errorValidation": "Os dados introduzidos não são válidos.", "errorScaRequired": "É necessária autenticação adicional para continuar.", - "errorDeviceNotOwned": "Não tens permissão para aceder a este dispositivo.", - "errorGeneric": "Ocorreu um erro inesperado." + "errorDeviceNotOwned": "Não tens permissão para aceder a este dispositivo." } diff --git a/packages/sf_localizations/lib/src/generated/i18n.dart b/packages/sf_localizations/lib/src/generated/i18n.dart index 0960ee2e..76effcff 100755 --- a/packages/sf_localizations/lib/src/generated/i18n.dart +++ b/packages/sf_localizations/lib/src/generated/i18n.dart @@ -1006,6 +1006,34 @@ class I18n { static const String verifyAccount = 'verifyAccount'; static const String vibrationOnly = 'vibrationOnly'; static const String videoCall = 'videoCall'; + static const String videocallAccept = 'videocallAccept'; + static const String videocallCalling = 'videocallCalling'; + static const String videocallCameraBack = 'videocallCameraBack'; + static const String videocallCameraFront = 'videocallCameraFront'; + static const String videocallEarpiece = 'videocallEarpiece'; + static const String videocallErrorAuth = 'videocallErrorAuth'; + static const String videocallErrorCallAnswer = 'videocallErrorCallAnswer'; + static const String videocallErrorCallStart = 'videocallErrorCallStart'; + static const String videocallErrorCamera = 'videocallErrorCamera'; + static const String videocallErrorMic = 'videocallErrorMic'; + static const String videocallErrorMissedCall = 'videocallErrorMissedCall'; + static const String videocallErrorSdkInit = 'videocallErrorSdkInit'; + static const String videocallHangUp = 'videocallHangUp'; + static const String videocallIncomingAudio = 'videocallIncomingAudio'; + static const String videocallIncomingVideo = 'videocallIncomingVideo'; + static const String videocallInitializingSdk = 'videocallInitializingSdk'; + static const String videocallLoggedInAs = 'videocallLoggedInAs'; + static const String videocallMicOff = 'videocallMicOff'; + static const String videocallMicOn = 'videocallMicOn'; + static const String videocallRecipientUserId = 'videocallRecipientUserId'; + static const String videocallReject = 'videocallReject'; + static const String videocallSpeaker = 'videocallSpeaker'; + static const String videocallStart = 'videocallStart'; + static const String videocallTitle = 'videocallTitle'; + static const String videocallUserCalling = 'videocallUserCalling'; + static const String videocallWaitingParticipants = 'videocallWaitingParticipants'; + static const String videocallWaitingRemoteVideo = 'videocallWaitingRemoteVideo'; + static const String videocallYou = 'videocallYou'; static const String volumeAlarm = 'volumeAlarm'; static const String volumeControl = 'volumeControl'; static const String volumeHint = 'volumeHint'; diff --git a/packages/videocall_sdk/lib/src/models/videocall_item.dart b/packages/videocall_sdk/lib/src/models/videocall_item.dart index c2eac430..52f186c5 100644 --- a/packages/videocall_sdk/lib/src/models/videocall_item.dart +++ b/packages/videocall_sdk/lib/src/models/videocall_item.dart @@ -18,6 +18,8 @@ class VideocallItem { final bool uploadVideoStreamSelf; final bool uploadVideoStreamOther; + bool get isTalking => state == VideocallState.talking; + VideocallItem copyWith({ String? userId, bool? isVideo,