cicare_rtc_flutter 1.0.2 copy "cicare_rtc_flutter: ^1.0.2" to clipboard
cicare_rtc_flutter: ^1.0.2 copied to clipboard

A professional RTC SDK for Flutter providing App-to-App and SIP calling with WhatsApp-style UI and FCM integration.

example/lib/main.dart

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:cicare_rtc_flutter/cicare_rtc_flutter.dart';
import 'screens/login_page.dart';
import 'screens/dashboard_page.dart';
import 'services/auth_service.dart';
import 'models/call_state.dart';

final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
final CicareRtcFlutter _sdk = CicareRtcFlutter();

// Global status for call tracking
final ValueNotifier<CallState> callStateNotifier = ValueNotifier(CallState());

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  await _handleIncomingCallMessage(message);
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  runApp(const ExampleApp());
}

class ExampleApp extends StatefulWidget {
  const ExampleApp({super.key});
  @override
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  @override
  void initState() {
    super.initState();
    _sdk.setAPI(
      baseUrl: "END POINT",
      token:   "TOKEN",
    );
    _setupMessaging();
    _setupRtcCallbacks();
  }

  void _setupRtcCallbacks() {
    CicareRtcFlutter.setOnCallStateChanged((state) {
      print('RTC: Call state changed: $state');
      // Update global state while preserving current callee info if applicable
      callStateNotifier.value = callStateNotifier.value.copyWith(
        status: _mapStringToStatus(state),
      );
    });

    CicareRtcFlutter.setOnCallError((code, message) {
      print('RTC: Call Error: [$code] $message');
      callStateNotifier.value = callStateNotifier.value.copyWith(
        status: CallStatus.error,
        errorMessage: message,
      );
    });
  }

  CallStatus _mapStringToStatus(String state) {
    switch (state.toUpperCase()) {
      case 'CALLING': return CallStatus.calling;
      case 'RINGING': return CallStatus.ringing;
      case 'CONNECTED': return CallStatus.connected;
      case 'DISCONNECTED': return CallStatus.ended;
      default: return CallStatus.idle;
    }
  }

  Future<void> _setupMessaging() async {
    try {
      await FirebaseMessaging.instance.requestPermission();
      
      FirebaseMessaging.instance.onTokenRefresh.listen((token) {
        AuthService.syncFcmToken();
      });

      FirebaseMessaging.onMessage.listen((msg) async {
        print('FCM: Foreground message: ${msg.data}');
        await _handleIncomingCallMessage(msg);
      });

      FirebaseMessaging.onMessageOpenedApp.listen(_handleIncomingCallMessage);
    } catch (e) {
      print('FCM: Setup error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: navigatorKey,
      title: 'CiCare SDK Call',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      routes: {
        '/': (c) => const _Startup(),
        '/login': (c) => const LoginPage(),
        '/dashboard': (c) => const DashboardPage(),
      },
    );
  }
}

Future<void> _handleIncomingCallMessage(RemoteMessage message) async {
  print("Incoming call");
  final data = Map<String, dynamic>.from(message.data);
  if (!_looksLikeIncomingCall(data)) {
    return;
  }

  final currentUser = await AuthService.getUser();
  final payload = _IncomingCallPayload.fromMessageData(data, currentUser?.id);
  if (payload == null) {
    print('FCM: Incoming call payload incomplete: $data');
    return;
  }

  try {
    await _sdk.showIncoming(
      callerId: payload.callerId,
      callerName: payload.callerName,
      callerAvatar: payload.callerAvatar,
      calleeId: payload.calleeId,
      calleeName: payload.calleeName,
      calleeAvatar: payload.calleeAvatar,
      checkSum: payload.checkSum,
      metaData: payload.metaData,
    );
  } catch (e) {
    print('FCM: Failed to show incoming call: $e');
  }
}

bool _looksLikeIncomingCall(Map<String, dynamic> data) {
  final event = (data['event'] ?? data['type'] ?? data['call_type'] ?? '')
      .toString()
      .toLowerCase();
  return event.contains('incoming') ||
      event.contains('call') ||
      data.containsKey('alert_data') ||
      data.containsKey('callerId') ||
      data.containsKey('caller_id');
}

class _IncomingCallPayload {
  const _IncomingCallPayload({
    required this.callerId,
    required this.callerName,
    required this.callerAvatar,
    required this.calleeId,
    required this.calleeName,
    required this.calleeAvatar,
    required this.checkSum,
    required this.metaData,
  });

  final String callerId;
  final String? callerName;
  final String? callerAvatar;
  final String calleeId;
  final String? calleeName;
  final String? calleeAvatar;
  final String checkSum;
  final Map<String, String> metaData;

  static _IncomingCallPayload? fromMessageData(
    Map<String, dynamic> data,
    String? fallbackCalleeId,
  ) {
    final metaData = _extractMetaData(data);
    final callerId = _firstString(data, ['callerId', 'caller_id', 'from_user_id']);
    final calleeId =
        _firstString(data, ['calleeId', 'callee_id', 'user_id', 'to_user_id']) ??
        fallbackCalleeId ?? "0";
    final checkSum = _firstString(data, ['checkSum', 'checksum', 'check_sum']) ?? "";

    if (callerId == null || !metaData.containsKey('alert_data')) {
      return null;
    }

    return _IncomingCallPayload(
      callerId: callerId,
      callerName: _firstString(data, ['callerName', 'caller_name', 'from_user_name']),
      callerAvatar: _firstString(data, ['callerAvatar', 'caller_avatar', 'from_user_avatar']),
      calleeId: calleeId,
      calleeName: _firstString(data, ['calleeName', 'callee_name', 'to_user_name']),
      calleeAvatar: _firstString(data, ['calleeAvatar', 'callee_avatar', 'to_user_avatar']),
      checkSum: checkSum,
      metaData: metaData,
    );
  }

  static Map<String, String> _extractMetaData(Map<String, dynamic> data) {
    final result = <String, String>{};

    final rawMeta = data['metaData'] ?? data['metadata'] ?? data['meta_data'];
    if (rawMeta is Map) {
      for (final entry in rawMeta.entries) {
        result[entry.key.toString()] = entry.value.toString();
      }
    } else if (rawMeta is String && rawMeta.isNotEmpty) {
      try {
        final decoded = jsonDecode(rawMeta);
        if (decoded is Map) {
          for (final entry in decoded.entries) {
            result[entry.key.toString()] = entry.value.toString();
          }
        }
      } catch (_) {}
    }

    for (final entry in data.entries) {
      if (entry.key.startsWith('meta_') && entry.value != null) {
        result[entry.key.substring(5)] = entry.value.toString();
      }
    }

    final alertData = _firstString(data, ['alert_data']);
    if (alertData != null) {
      result['alert_data'] = alertData;
    }

    return result;
  }

  static String? _firstString(Map<String, dynamic> data, List<String> keys) {
    for (final key in keys) {
      final value = data[key];
      if (value == null) continue;
      final text = value.toString().trim();
      if (text.isNotEmpty) return text;
    }
    return null;
  }
}

class _Startup extends StatefulWidget {
  const _Startup();
  @override
  State<_Startup> createState() => _StartupState();
}

class _StartupState extends State<_Startup> {
  @override
  void initState() {
    super.initState();
    _checkLogin();
  }

  Future<void> _checkLogin() async {
    final user = await AuthService.getUser();
    if (!mounted) return;
    
    if (user != null) {
      Navigator.of(context).pushReplacementNamed('/dashboard');
    } else {
      Navigator.of(context).pushReplacementNamed('/login');
    }
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(body: Center(child: CircularProgressIndicator()));
  }
}
1
likes
150
points
32
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A professional RTC SDK for Flutter providing App-to-App and SIP calling with WhatsApp-style UI and FCM integration.

Homepage
Repository
View/report issues

Topics

#rtc #voip #sip #call #fcm

License

MIT (license)

Dependencies

cupertino_icons, flutter, plugin_platform_interface

More

Packages that depend on cicare_rtc_flutter

Packages that implement cicare_rtc_flutter