social_live_one_on_one_call 1.0.4 copy "social_live_one_on_one_call: ^1.0.4" to clipboard
social_live_one_on_one_call: ^1.0.4 copied to clipboard

SDK for video/audio Calls

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:social_live_one_on_one_call/social_live_one_on_one_call.dart';
import 'package:social_live_one_on_one_call/services/base_entity.dart';
import 'package:flutter_call_sdk_sample/call_ui/call_shared_state.dart';
import 'package:flutter_call_sdk_sample/call_ui/in_call_view.dart';
import 'package:flutter_call_sdk_sample/call_ui/loader_view.dart';
import 'package:flutter_call_sdk_sample/network_manager.dart';
import 'package:loader_overlay/loader_overlay.dart';
import 'package:permission_handler/permission_handler.dart';

import 'package:flutter_foreground_service/flutter_foreground_service.dart';

void main() {
  runApp(MyApp());
  startForegroundService();
}

void startForegroundService() async {
  ForegroundService().start();
  debugPrint("Started service");
}

class MyApp extends StatelessWidget {
  MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: MyHomePage(title: 'Social Live Call'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  static const defaultAppId = "<Your AppId here>";

  String registeredPhoneNumber = "";
  String myToken = "";
  String appId = "";
  String callId = "";

  bool sdkStarted = false;

  final TextEditingController _myAccountEditController = TextEditingController();
  final TextEditingController _recipientEditController = TextEditingController();
  bool registered = false;
  bool registerDisabled = false;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    appId = defaultAppId;
  }

  @override
  Widget build(BuildContext context) {
    return LoaderView(
      loadingText: "Loading...",
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: GestureDetector(
            onDoubleTap: () {
              handleStopSDK();
            },
            child: Text(
              widget.title,
              textAlign: TextAlign.center,
            ),
          ),
        ),
        body: Stack(
          children: [
            Positioned(
              left: 10,
              right: 10,
              top: 20,
              height: 80,
              child: buildGuideLabel(),
            ),
            Positioned(
              top: 100,
              bottom: registered ? 80 : 0,
              left: 0,
              right: 0,
              child: SingleChildScrollView(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    buildMainFeature(),
                  ],
                ),
              ),
            ),
            Positioned(
              left: 10,
              right: 10,
              bottom: 50,
              height: 80,
              child: registered ? buildAccountDisplay() : const SizedBox.shrink(),
            ),
          ],
        ),
      ),

    );
  }

  Widget build2(BuildContext context) {
    final accountDisplay = "Your account: $registeredPhoneNumber";
    return LoaderView(
      loadingText: "Loading...",
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: GestureDetector(
            onDoubleTap: () {
              handleStopSDK();
            },
            child: Text(
              widget.title,
            ),
          ),
        ),
        body: Center(
          child: SingleChildScrollView(
            // physics: const BouncingScrollPhysics(),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.center,
              children:[
                buildMyNumberTextField(),
                buildRegisterButton(),

                registered ? Container(
                  alignment: Alignment.center,
                  margin: const EdgeInsets.only(top: 10, bottom: 0),
                  child: Text(
                    accountDisplay,
                    textAlign: TextAlign.center,
                    style: TextStyle(
                      fontSize: 24.0,
                      fontWeight: FontWeight.bold,
                      color : Colors.black,
                    ),
                  ),
                ) : const SizedBox.shrink(),
                registered ? Container(
                  margin: const EdgeInsets.only(top: 20, bottom: 20),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      buildStartSdkButton(),
                    ],
                  ),
                ) : const SizedBox.shrink(),

                sdkStarted ? buildRecipientTextField() : const SizedBox.shrink(),

                sdkStarted ? buildMakeCallButton() : const SizedBox.shrink(),

                sdkStarted? const Text(
                  "Or just wait for other to call in",
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 20.0,
                    fontWeight: FontWeight.bold,
                    color : Colors.black,
                  ),
                ) : const SizedBox.shrink(),

                sdkStarted ? const SizedBox(height: 50) : const SizedBox.shrink(),
                sdkStarted ? buildStopButton() : const SizedBox.shrink(),

              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget buildGuideLabel() {
    var guideStr = "";
    if (registered == false) {
      guideStr = "Registration";
    } else {
      guideStr = "Make a call / Receive a call";
    }
    return Text(
      guideStr,
      textAlign: TextAlign.center,
      style: const TextStyle(
        fontSize: 24.0,
        fontWeight: FontWeight.bold,
        color: Colors.black,
      ),
    );
  }

  Widget buildMainFeature() {
    if (registered == false) {
      return Container(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            buildMyNumberTextField(),
            buildRegisterButton(),
          ],
        ),
      );
    } else {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          buildRecipientTextField(),
          buildMakeCallButton(),
        ],
      );
    }
  }

  Widget buildAccountDisplay() {
    final accountDisplay = "Your account: $registeredPhoneNumber";
    return Container(
      alignment: Alignment.center,
      margin: const EdgeInsets.only(top: 10, bottom: 0),
      child: Text(
        accountDisplay,
        textAlign: TextAlign.center,
        style: const TextStyle(
          fontSize: 24.0,
          fontWeight: FontWeight.bold,
          color : Colors.black,
        ),
      ),
    );
  }

  void _showLoading() {
    context.loaderOverlay.show();
  }

  void _hideLoading() {
    context.loaderOverlay.hide();
  }
}

extension MyHomePageStateRegister on MyHomePageState {
  Widget buildMyNumberTextField() {
    return Container(
      margin: const EdgeInsets.only(left: 20, right: 20, top: 20, bottom: 10),
      child: TextField(
        textAlign: TextAlign.center,
        keyboardType: TextInputType.number,
        enabled: !registerDisabled,
        controller: _myAccountEditController,
        decoration: InputDecoration(
          labelText: 'Enter number to register',
        ),
      ),
    );
  }

  Widget buildRegisterButton() {
    var buttonColor = Colors.black;
    if (registerDisabled == true) {
      buttonColor = Colors.grey.shade400;
    }
    return Container(
      margin: const EdgeInsets.only(top: 20, bottom: 20),
      child: TextButton(
        onPressed: () {
          if (registerDisabled == false) {
            handleRegister();
          }
        },
        style: TextButton.styleFrom(
          padding: EdgeInsets.all(10.0),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10.0),
            side: BorderSide(color: buttonColor, width: 1.0),
          ),
        ),
        child: Container(
          padding: const EdgeInsets.only(left: 20, right: 20),
          child: Text(
            'Register',
            style: TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
              color: buttonColor,
            ),
          ),
        ),
      ),
    );
  }

  void closeKeyboard() {
    FocusManager.instance.primaryFocus?.unfocus();
  }

  void disableRegister() {

  }

  void handleRegister() async {
    closeKeyboard();
    _showLoading();
    final myNumber = _myAccountEditController.text.toLowerCase().trim();
    if (myNumber.isNotEmpty) {
      final phoneToken = await NetworkUtils().createToken(number: myNumber, appId: appId);
      if (phoneToken != null) {
        _hideLoading();
        registeredPhoneNumber = myNumber;
        myToken = phoneToken;
        registered = true;
        requestPermissions();

        Future.delayed(const Duration(milliseconds: 10), () {
          handleStartSDK();
        });
        if (mounted) {
          setState(() {

          });
        }
        return;
      }
    } else {
      if (mounted) {
        NetworkUtils.showAlertDialog(context: context, alertMsg: "Please enter a valid number.");
      }
      _hideLoading();
      return;
    }
    if (mounted) {
      NetworkUtils.showAlertDialog(context: context, alertMsg: "Cannot register, please try again.");
    }
    _hideLoading();
  }

  Future<void> requestPermissions() async {
    print('requestPermissions');
    // final microphonePermission = Permission.microphone;
    // if (await microphonePermission.isGranted == false) {
    //   await microphonePermission.request();
    // }
    // final cameraPermission = Permission.camera;
    // if (await cameraPermission.isGranted == false) {
    //   await cameraPermission.request();
    // }

    final microphoneStatus = await Permission.microphone.request();
    if (microphoneStatus != PermissionStatus.granted) {
      // Handle permission denied case for microphone
      print('microphoneStatus = $microphoneStatus');
      if (mounted) {
        NetworkUtils.showAlertDialog(context: context,
            alertMsg: "Please go to Settings and turn on Microphone permission.");
      }
    }
    // final cameraStatus = await Permission.camera.request();
    // if (cameraStatus != PermissionStatus.granted) {
    //   // Handle permission denied case for camera
    //   print('cameraStatus = $microphoneStatus');
    //   if (mounted) {
    //     NetworkUtils.showAlertDialog(context: context,
    //         alertMsg: "Please go to Settings and turn on Camera permission.");
    //   }
    // }
  }
}


extension MyHomePageStateBuild on MyHomePageState {
  Widget buildRecipientTextField() {
    return Container(
      margin: const EdgeInsets.only(left: 20, right: 20, top: 20, bottom: 10),
      child: TextField(
        textAlign: TextAlign.center,
        keyboardType: TextInputType.number,
        controller: _recipientEditController,
        decoration: InputDecoration(
          labelText: 'Enter a number to call',
        ),
      ),
    );
  }

  Widget buildMakeCallButton() {
    return Container(
      margin: const EdgeInsets.only(top: 20, bottom: 20),
      child: TextButton(
        onPressed: () {
          handleMakeCall();
        },
        style: TextButton.styleFrom(
          padding: EdgeInsets.all(10.0),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10.0),
            side: BorderSide(color: Colors.blue, width: 1.0),
          ),
        ),
        child: Container(
          padding: const EdgeInsets.only(left: 20, right: 20),
          child: Text(
            'Call',
            style: TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
              color: Colors.blue.shade600,
            ),
          ),
        ),
      ),
    );
  }

  Widget buildReceiveCallButton() {
    return Container(
      margin: const EdgeInsets.only(top: 20, bottom: 20),
      child: TextButton(
        onPressed: () {
          handleReceiveCall(callId: callId);
        },
        style: TextButton.styleFrom(
          padding: EdgeInsets.all(10.0),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10.0),
            side: BorderSide(color: Colors.blue, width: 1.0),
          ),
        ),
        child: Container(
          padding: const EdgeInsets.only(left: 20, right: 20),
          child: Text(
            'Receive Call',
            style: TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
              color: Colors.blue.shade600,
            ),
          ),
        ),
      ),
    );
  }

  Widget buildStartSdkButton() {
    Color buttonColor = Colors.red;
    if (registerDisabled == true) {
      buttonColor = Colors.grey.shade400;
    }
    return TextButton(
      onPressed: () {
        if (registerDisabled == false) {
          handleStartSDK();
        }
      },
      style: TextButton.styleFrom(
        padding: EdgeInsets.all(10.0),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10.0),
          side: BorderSide(color: buttonColor, width: 1.0),
        ),
      ),
      child: Text(
        'Start SDK',
        style: TextStyle(
          fontSize: 20.0,
          fontWeight: FontWeight.bold,
          color: buttonColor,
        ),
      ),
    );
  }

  Widget buildStopButton() {
    return TextButton(
      onPressed: () {
        handleStopSDK();
      },
      style: TextButton.styleFrom(
        padding: EdgeInsets.all(10.0),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10.0),
          side: BorderSide(color: Colors.green, width: 1.0),
        ),
      ),
      child: Text(
        'Stop SDK',
        style: TextStyle(
          fontSize: 20.0,
          fontWeight: FontWeight.bold,
          color: Colors.green.shade600,
        ),
      ),
    );
  }
}

extension MyHomePageStateHandler on MyHomePageState {
  void handleStopSDK() async {
    await CallSDK().stopSDK(completion: () {
      if (mounted) {
        sdkStarted = false;
        registerDisabled = false;
        registered = false;
        setState(() {

        });
      }
    });
  }

  Future<void> handleStartSDK() async {
    CallSDK().startSDK(appId: appId, token: myToken, callEvent: (event, data) {
      handleCallEvent(event, data);
    }, completion: (result) {
      if (mounted) {
        if (result == -1) {
          NetworkUtils.showAlertDialog(context: context, alertMsg: "Start SDK failed, cannot get config");
          sdkStarted = false;
          registerDisabled = false;
        } else if (result == -2) {
          NetworkUtils.showAlertDialog(context: context, alertMsg: "Start SDK failed, cannot connect socket");
          sdkStarted = false;
          registerDisabled = false;
        } else {
          sdkStarted = true;
          registerDisabled = true;
          setState(() {

          });
        }
      }
    });
  }

  void handleMakeCall() async {
    closeKeyboard();
    final microphoneStatus = await Permission.microphone.request();
    if (microphoneStatus.isGranted == false) {
      if (mounted) {
        NetworkUtils.showAlertDialog(
          context: context,
          alertMsg: "Cannot make call, please allow microphone.",
        );
      }
      return;
    }
    _showLoading();
    final calleeNumber = _recipientEditController.text.toLowerCase().trim();
    if (calleeNumber.isNotEmpty) {
      final newCallId = await NetworkUtils().createCall(appId: appId, callerNumber: registeredPhoneNumber, calleeNumber: calleeNumber);
      if (newCallId != null) {
        _hideLoading();
        CallSDK().startCall(callId: newCallId, callerName: registeredPhoneNumber, calleeName: calleeNumber, completion: () {
          if (mounted) {
            goToCallScreen(newRole: "caller", inputCallId: newCallId, calleeNumber: calleeNumber, callerNumber: registeredPhoneNumber);
          }
        });
        return;
      }
    } else {
      NetworkUtils.showAlertDialog(context: context, alertMsg: "Please enter a number");
    }
    _hideLoading();
  }

  void handleReceiveCall({required String callId, String? callerNumber}) async {
    CallSDK().startCall(callId: callId, callerName: callerNumber, calleeName: registeredPhoneNumber, completion: () {
      goToCallScreen(
        newRole: "callee",
        inputCallId: callId,
        callerNumber: callerNumber,
        calleeNumber: registeredPhoneNumber,
      );
    });
  }

  void handleCallEvent(CallEvent event, Map<String,dynamic>? data) {
    print('handleCallEvent event = $event, data = $data');
    if (event == CallEvent.connecting) {
      CallSharedState().addViewDelegateEvent(eventName: "connecting");
    } else if (event == CallEvent.connected) {
      CallSharedState().addViewDelegateEvent(eventName: "connected");
    } else if (event == CallEvent.ended) {
      CallSharedState().addViewDelegateEvent(eventName: "ended");
    } else if (event == CallEvent.audio) {
      CallSharedState().addViewDelegateEvent(eventName: "update");
    } else if (event == CallEvent.notify) {
      if (data != null) {
        final inputCallId = BaseEntity.stringValue(data, "call_id");
        final callerName = BaseEntity.stringValue(data, "caller_name");
        if (inputCallId != null) {
          handleReceiveCall(callId: inputCallId, callerNumber: callerName);
        }
      }
    } else if (event == CallEvent.busy) {
      if (data != null) {
        CallSharedState().addViewDelegateEvent(eventName: "busy", info: data);
      }
    }
  }

  void goToCallScreen({required String newRole, String? inputCallId, String? calleeNumber, String? callerNumber}) {
    var usedCallId = callId;
    if (inputCallId != null) {
      usedCallId = inputCallId;
    }
    Navigator.push(
      context,
      PageRouteBuilder(
        pageBuilder: (x, y, z) => InCallView(
            role: newRole,
            callId: usedCallId,
            calleeNumber: calleeNumber ?? "Callee",
            callerNumber: callerNumber ?? "Caller",
        ),
        transitionDuration: const Duration(milliseconds: 100),
        transitionsBuilder: (a, b, c, d) =>
            FadeTransition(opacity: b, child: d),
      ),
    );
  }
}