social_live_one_on_one_call 1.0.4 social_live_one_on_one_call: ^1.0.4 copied to clipboard
SDK for video/audio Calls
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),
),
);
}
}