Skip to content

Commit

Permalink
refactor: example app
Browse files Browse the repository at this point in the history
  • Loading branch information
cybex-dev committed Nov 28, 2023
1 parent 3bd5841 commit 65db24b
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 181 deletions.
57 changes: 40 additions & 17 deletions example/lib/screens/ui_call_screen.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:twilio_voice_example/screens/widgets/on_call_widget.dart';

import 'widgets/call_actions.dart';
import 'widgets/call_features.dart';
import 'widgets/call_status.dart';
import 'widgets/permissions_block.dart';
import 'widgets/twilio_log.dart';

typedef PerformCall = Future<void> Function(String clientIdentifier);

class UICallScreen extends StatefulWidget {
final String userId;
final PerformCall onPerformCall;
final PerformCall? onCallToQueue;

const UICallScreen({Key? key, required this.userId, required this.onPerformCall}) : super(key: key);
const UICallScreen({Key? key, required this.userId, required this.onPerformCall, this.onCallToQueue}) : super(key: key);

@override
State<UICallScreen> createState() => _UICallScreenState();
Expand Down Expand Up @@ -70,26 +75,44 @@ class _UICallScreenState extends State<UICallScreen> {
],
),
const SizedBox(height: 10),
ElevatedButton(
child: const Text("Make Call"),
onPressed: () {
if (!_identifierKey.currentState!.validate()) {
return;
CallActions(
canCall: true,
onPerformCall: () {
final identifier = _identifierKey.currentState?.value;
if (identifier != null && identifier.isNotEmpty) {
widget.onPerformCall(identifier);
}
final identity = _controller.text;
widget.onPerformCall(identity);
},
),
const Divider(),
const Padding(
padding: EdgeInsets.all(8.0),
child: OnCallWidget(),
const SizedBox(height: 10),
const Card(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CallStatus(),
),
),
const Card(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CallControls(),
),
),
const Card(
child: Padding(
padding: EdgeInsets.all(8.0),
child: PermissionsBlock(),
),
),
const Divider(),
const Expanded(
child: PermissionsBlock(),
)
Expanded(
child: Column(
children: [
Text("Events (latest at top)", style: Theme.of(context).textTheme.titleLarge),
const TwilioLog(),
],
),
),
],
);
}
}
}
38 changes: 38 additions & 0 deletions example/lib/screens/widgets/call_actions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';

class CallActions extends StatelessWidget {
final bool canCall;
final VoidCallback? onPerformCall;

const CallActions({
super.key,
this.canCall = true,
this.onPerformCall,
});

@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if(canCall)
_CallAction(onPressed: onPerformCall, text: "Make Call"),
],
);
}
}

class _CallAction extends StatelessWidget {
final VoidCallback? onPressed;
final String text;

const _CallAction({super.key, required this.onPressed, required this.text});

@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: Text(text),
);
}
}
188 changes: 188 additions & 0 deletions example/lib/screens/widgets/call_features.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:twilio_voice/twilio_voice.dart';

import 'state_toggle.dart';

class CallControls extends StatefulWidget {
const CallControls({super.key});

@override
State<CallControls> createState() => _CallControlsState();
}

class _CallControlsState extends State<CallControls> {

late final StreamSubscription<CallEvent> _subscription;
final _events = <CallEvent>[];

//#region #region State Getters
bool _stateHold = false;

set stateHold(bool value) {
setState(() {
_stateHold = value;
});
}

bool _stateMute = false;

set stateMute(bool value) {
setState(() {
_stateMute = value;
});
}

bool _stateSpeaker = false;

set stateSpeaker(bool value) {
setState(() {
_stateSpeaker = value;
});
}

bool _stateBluetooth = false;

set stateBluetooth(bool value) {
setState(() {
_stateBluetooth = value;
});
}

//#endregion

final _tv = TwilioVoice.instance;
bool activeCall = false;

@override
void initState() {
super.initState();
_subscription = _tv.callEventsListener.listen((event) {
_events.add(event);
switch (event) {
case CallEvent.unhold:
case CallEvent.hold:
case CallEvent.unmute:
case CallEvent.mute:
case CallEvent.speakerOn:
case CallEvent.speakerOff:
case CallEvent.bluetoothOn:
case CallEvent.bluetoothOff:
_updateStates();
break;

case CallEvent.connected:
activeCall = true;
_updateStates();
break;

case CallEvent.callEnded:
activeCall = false;
_updateStates();
break;

case CallEvent.incoming:
case CallEvent.ringing:
case CallEvent.declined:
case CallEvent.answer:
case CallEvent.missedCall:
case CallEvent.returningCall:
case CallEvent.reconnecting:
case CallEvent.reconnected:
_updateStates();
break;

case CallEvent.log:
break;

case CallEvent.permission:
// Using app lifecycle states, we don't have to update permissions here - convenience only.
break;
}
});
_updateStates();
}

void _updateStates() {
// get all states from call
_tv.call.isMuted().then((value) => stateMute = value ?? false);
_tv.call.isHolding().then((value) => stateHold = value ?? false);
_tv.call.isOnSpeaker().then((value) => stateSpeaker = value ?? false);
_tv.call.isBluetoothOn().then((value) => stateBluetooth = value ?? false);
}

@override
Widget build(BuildContext context) {
return Column(
children: [
// state
Text("State", style: Theme.of(context).textTheme.titleLarge),

Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: StateToggle(
state: _stateMute,
icon: _stateMute ? Icons.mic : Icons.mic_off,
title: "Mute",
onTap: () => _tv.call.toggleMute(!_stateMute),
),
),
Expanded(
child: StateToggle(
state: _stateHold,
icon: Icons.pause,
title: "Hold",
iconColor: _stateHold ? Colors.orange : null,
onTap: () => _tv.call.holdCall(holdCall: !_stateHold),
),
),
Expanded(
child: StateToggle(
state: _stateSpeaker,
icon: Icons.volume_up,
title: "Speaker",
iconColor: _stateSpeaker ? Colors.green : null,
onTap: () => _tv.call.toggleSpeaker(!_stateSpeaker),
),
),
Expanded(
child: StateToggle(
state: _stateBluetooth,
icon: Icons.bluetooth,
title: "Bluetooth",
iconColor: _stateBluetooth ? Colors.blue : null,
onTap: () => _tv.call.toggleBluetooth(bluetoothOn: !_stateBluetooth),
),
),
],
),

Row(
children: [
Expanded(
child: StateToggle(
state: activeCall,
icon: Icons.call_end,
title: "Hangup",
iconColor: Colors.red,
onTap: activeCall ? () => _tv.call.hangUp() : null,
),
),
],
),

const SizedBox(height: 12),
]
);
}

@override
void dispose() {
_subscription.cancel();
super.dispose();
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import 'package:flutter/material.dart';
import 'package:twilio_voice/twilio_voice.dart';

class OnCallWidget extends StatefulWidget {
const OnCallWidget({super.key});
class CallStatus extends StatefulWidget {
const CallStatus({super.key});

@override
State<OnCallWidget> createState() => _OnCallWidgetState();
State<CallStatus> createState() => _CallStatusState();
}

class _OnCallWidgetState extends State<OnCallWidget> {
class _CallStatusState extends State<CallStatus> {
final List<CallEvent> _events = [];

/// Store only call-related events
Expand Down
Loading

0 comments on commit 65db24b

Please sign in to comment.