Flutter: Create Audio and Group Call App UI

Flutter Design UI App audio group call

In this blog, I am going to introduce Audio and Group Call UI. Along with learning the awesome Audio and Group Call UI implementation in Flutter, we will also learn how its coding workflows and structures work.


How to Create a New Future Project

First, we need to create a new Flutter project. For that, make sure that you've installed the Flutter SDK and other Flutter app development-related requirements.

If everything is properly set up, then in order to create a project we can simply run the following command in our desired local directory:

flutter create flutter-calling-app-ui

After we've set up the project, we can navigate inside the project directory and execute the following command in the terminal to run the project in either an available emulator or an actual device:

flutter run

First, we need to make some simple configurations to the default boilerplate code in the main.dart file. We'll remove some default code and add the simple MaterialApp pointing to the remove Container and Call AudioCall or GroupCall page for now:


import 'package:callingui/screens/audiocall/audiocall.dart';
import 'package:callingui/screens/groupcall/groupcall.dart';
import 'package:flutter/material.dart';

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: AudioCall(),
    );
  }
}


Create a constants.dart and sizeConfig.dart file in inner lib folder 

constants.dart

import 'package:flutter/material.dart';

const dBackgroundColor = Color(0xFF091C40);
const dSecondaryColor = Color(0xFF606060);
const dRedColor = Color(0xFFFF1E46);

sizeConfig.dart

import 'package:flutter/material.dart';

class SizeConfig{
  static MediaQueryData _mediaQueryData = new MediaQueryData();
  static double screenWidth = 0;
   static double screenHeight = 0;
  static double defaultSize = 0;
  static Orientation? orientation;


  void init(BuildContext context){
    _mediaQueryData = MediaQuery.of(context);
    screenWidth = _mediaQueryData.size.width;
    screenHeight = _mediaQueryData.size.height;
    orientation  =  _mediaQueryData.orientation;
  }
}


double getProportionScreenHeight(double inputHeight){
  double screenHeight = SizeConfig.screenHeight;
  return (inputHeight / 896.0)* screenHeight;
}

double getProportionScreenWidth(double inputHeight){
  double screenWidth = SizeConfig.screenWidth;
  return (inputHeight / 414.0)* screenWidth;
}


class VerticalSapcing extends StatelessWidget {
  final double of;
  const VerticalSapcing({Key? key, this.of = 20 }):super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: getProportionScreenHeight(of),
    );
  }
}


class HorizontalSapcing extends StatelessWidget {
  final double of;
  const HorizontalSapcing({Key? key, this.of = 20 }):super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: getProportionScreenWidth(of),
    );
  }
}


Before Create body.dart, dialuserpic.dart andgroupbody.dart stateful widget  file in widget folder, then inside of it with the following code:


body.dart

import 'package:callingui/constants.dart';
import 'package:callingui/sizeConfig.dart';
import 'package:flutter/material.dart';

class Body extends StatelessWidget {
 
  @override
  Widget build(BuildContext context) {
    return Stack(
      fit: StackFit.expand,
      children: [
        Image.asset('assets/images/3.jpg', fit: BoxFit.cover),
        DecoratedBox(
          decoration: BoxDecoration(color: Colors.black.withOpacity(0.3))
        ),

       Padding(
         padding: const EdgeInsets.all(20.0),
         child: SafeArea(child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
              Text("Jimmy Ben", style: Theme.of(context).textTheme.headline3?.copyWith(color: Colors.white)),
              VerticalSapcing(of: 10),
              Text("Incoming 00.40".toUpperCase(), style: TextStyle(color: Colors.white60)),
              Spacer(),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                   SizedBox(
                    height: getProportionScreenHeight(64),
                    width: getProportionScreenWidth(64),
                    child: ElevatedButton(
                      style: ButtonStyle(
                        backgroundColor: MaterialStateProperty.all<Color>(Colors.white),
                        shape:  MaterialStateProperty.all<RoundedRectangleBorder>(
                          RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(100),
                            side: BorderSide(color: Colors.white)
                          )
                        )
                      ),
                      onPressed: (){},
                      child: Icon(Icons.mic_none_outlined,  color: Colors.black,),
                    ),
                  ),
                  SizedBox(
                    height: getProportionScreenHeight(64),
                    width: getProportionScreenWidth(64),
                    child: ElevatedButton(
                      style: ButtonStyle(
                        backgroundColor: MaterialStateProperty.all<Color>(dRedColor),
                        shape:  MaterialStateProperty.all<RoundedRectangleBorder>(
                          RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(100),
                            side: BorderSide(color: dRedColor)
                          )
                        )
                      ),
                      onPressed: (){},
                      child: Icon(Icons.call_end_sharp),
                    ),
                  ),
                  SizedBox(
                    height: getProportionScreenHeight(64),
                    width: getProportionScreenWidth(64),
                    child: ElevatedButton(
                      style: ButtonStyle(
                        backgroundColor: MaterialStateProperty.all<Color>(Colors.white),
                        shape:  MaterialStateProperty.all<RoundedRectangleBorder>(
                          RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(100),
                            side: BorderSide(color: Colors.white)
                          )
                        )
                      ),
                      onPressed: (){},
                      child: Icon(Icons.volume_up, color: Colors.black,),
                    ),
                  )
                ],
              )
             
          ],
         )),
       )
      ],
    );
  }
}


dialuserpic.dart


import 'package:callingui/sizeConfig.dart';
import 'package:flutter/material.dart';

class DialUserPic extends StatelessWidget {
  final double size;
  final String image;
  const DialUserPic({Key? key, this.size = 1, required this.image }):super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(30/250 * size),
      height: getProportionScreenHeight(size),
      width: getProportionScreenWidth(size),
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        gradient: RadialGradient(
          colors: [Colors.white.withOpacity(0.02), Colors.white.withOpacity(0.05)],
          stops: [0.5, 1]
        )
      ),
      child: CircleAvatar(
        radius: 100,
        backgroundImage: AssetImage(image),
      )
    );
  }
}


groupbody.dart

import 'package:callingui/constants.dart';
import 'package:callingui/sizeConfig.dart';
import 'package:callingui/widgets/dialuserpic.dart';
import 'package:flutter/material.dart';

class GroupBody extends StatelessWidget {
  
  List<Map<String, dynamic>> callingData = [
    {
      "isCalling":false,
      "name":"User 1",
      "image":"assets/images/1.jpg"
    },
    {
      "isCalling":true,
      "name":"User 2",
      "image":"assets/images/2.jpg"
    },
    {
      "isCalling":false,
      "name":"User 3",
      "image":"assets/images/3.jpg"
    }
    ,{
      "isCalling":false,
      "name":"User 4",
      "image":"assets/images/4.jpg"
    }
  ];

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: EdgeInsets.zero,
      itemCount: callingData.length,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        childAspectRatio: 0.49,
        crossAxisCount: 2,
        // mainAxisSpacing: 20,
        // crossAxisSpacing: 20
      ),
      itemBuilder:(context, index) => callingData[index]["isCalling"] 
      ? Container(
          width: SizeConfig.screenWidth/2,
          child: AspectRatio(
            aspectRatio: 0.53,
            child: Container(
              color: dBackgroundColor,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  DialUserPic(
                    size: 250,
                    image: callingData[index]["image"],
                  ),
                  VerticalSapcing(of: 10),
                  Text(callingData[index]["name"], style: TextStyle(fontSize: 18, color: Colors.white)),
                  VerticalSapcing(of: 5),
                  Text("Calling...", style: TextStyle(color: Colors.white))
                ],
              ),
            ),
          ),
        ):
        Image.asset(callingData[index]["image"], fit: BoxFit.cover,)
     
    );
    
  }
}


Before Create audiocall.dart stateful widget  file in screens folder like screens/audiocall/audiocall.dart, then inside of it with the following code:

import 'package:callingui/sizeConfig.dart';
import 'package:callingui/widgets/body.dart';
import 'package:flutter/material.dart';

class AudioCall extends StatelessWidget {
 

  @override
  Widget build(BuildContext context) {
    SizeConfig().init(context);
    return Scaffold(
      body: Body(),
    );
  }
}


Before Create groupcall.dart stateful widget  file in screens folder like screens/groupcall/groupcall.dart, then inside of it with the following code:

import 'package:callingui/constants.dart';
import 'package:callingui/sizeConfig.dart';
import 'package:callingui/widgets/groupbody.dart';
import 'package:flutter/material.dart';

class GroupCall extends StatelessWidget {
  const GroupCall({super.key});

  @override
  Widget build(BuildContext context) {
    SizeConfig().init(context);
    return Scaffold(
      body: GroupBody(),
      bottomNavigationBar: Container(
        color: dBackgroundColor,
        child: SafeArea(
          child: Padding(
            padding: const EdgeInsets.only(top: 10),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              
               children: [
                 SizedBox(
                      height: getProportionScreenHeight(64),
                      width: getProportionScreenWidth(64),
                      child: ElevatedButton(
                        style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all<Color>(dRedColor),
                          shape:  MaterialStateProperty.all<RoundedRectangleBorder>(
                            RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(100),
                              side: BorderSide(color: dRedColor)
                            )
                          )
                        ),
                        onPressed: (){},
                        child: Icon(Icons.call_end_sharp),
                      ),
                    ),
                     SizedBox(
                      height: getProportionScreenHeight(64),
                      width: getProportionScreenWidth(64),
                      child: ElevatedButton(
                        style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all<Color>(Colors.white),
                          shape:  MaterialStateProperty.all<RoundedRectangleBorder>(
                            RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(100),
                              // side: BorderSide(color: Colors.white)
                            )
                          )
                        ),
                        onPressed: (){},
                        child: Icon(Icons.mic_none_outlined,  color: Colors.black,),
                      ),
                    ),
                   
                    SizedBox(
                      height: getProportionScreenHeight(64),
                      width: getProportionScreenWidth(64),
                      child: ElevatedButton(
                        style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all<Color>(Colors.white),
                          shape:  MaterialStateProperty.all<RoundedRectangleBorder>(
                            RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(100),
                              // side: BorderSide(color: Colors.white)
                            )
                          )
                        ),
                        onPressed: (){},
                        child: Icon(Icons.volume_up, color: Colors.black,),
                      ),
                    ),
                    SizedBox(
                      height: getProportionScreenHeight(64),
                      width: getProportionScreenWidth(64),
                      child: ElevatedButton(
                        style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all<Color>(Colors.white),
                          shape:  MaterialStateProperty.all<RoundedRectangleBorder>(
                            RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(100),
                              // side: BorderSide(color: Colors.white)
                            )
                          )
                        ),
                        onPressed: (){},
                        child: Icon(Icons.videocam_outlined, color: Colors.black,),
                      ),
                    ),
                    SizedBox(
                      height: getProportionScreenHeight(64),
                      width: getProportionScreenWidth(64),
                      child: ElevatedButton(
                        style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all<Color>(Colors.white),
                          shape:  MaterialStateProperty.all<RoundedRectangleBorder>(
                            RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(100),
                            )
                          )
                        ),
                        onPressed: (){},
                        child: Icon(Icons.refresh_rounded, color: Colors.black,),
                      ),
                    )
                  ],
            ),
          ),
        ),
      ),
    );
  }
}

Output: