Skip to main content

How to Create a Desktop Skype-like Chat Box in Flutter?

Desktop Skype is a software that enables users to make voice and video calls over the internet. The app also allows for instant messaging, file sharing, and calling landlines and mobiles. Desktop Skype is available for Windows, Mac, and Linux operating systems.

In this snippet, we will try to replicate Skype-like desktop UI in Flutter.

import 'dart:math';

import 'package:flutter/material.dart';

class Theme {
  static final Color primaryTextColor = Colors.grey.shade700;
  static final Color secondaryTextColor = Colors.grey;
}

class SkypePage extends StatefulWidget {
  @override
  _SkypePageState createState() => _SkypePageState();
}

class _SkypePageState extends State<SkypePage> {
  final double leftWidth = 300.0;

  Widget _leftCol() {
    return Padding(
      padding: const EdgeInsets.all(4.0),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              MainAvatarRow(),
            ],
          ),
          Row(
            children: [
              Flexible(
                flex: 8,
                child: TextField(
                  maxLines: 1,
                  decoration: InputDecoration(
                      contentPadding: EdgeInsets.all(0),
                      hintText: 'People, groups & messages',
                      prefixIcon: Icon(Icons.search),
                      border: OutlineInputBorder(
                          borderSide:
                              BorderSide(color: Theme.secondaryTextColor))),
                ),
              ),
              Flexible(
                  child: IconButton(
                      icon: Icon(Icons.dialpad_outlined), onPressed: () {}))
            ],
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              LabelButton(label: 'Chats', icon: Icons.message),
              LabelButton(label: 'Calls', icon: Icons.phone),
              LabelButton(label: 'Contacts', icon: Icons.contact_page_outlined),
              LabelButton(
                  label: 'Notifications',
                  icon: Icons.notification_important_outlined),
            ],
          ),
          Divider(),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              LabelButtonRow(
                  icon: Icons.video_call_outlined, label: 'Meet Now'),
              SizedBox(width: 4.0),
              LabelButtonRow(icon: Icons.note_add_outlined, label: 'New Chat')
            ],
          ),
          SizedBox(height: 8.0),
          Text(
            'CHATS',
            style: TextStyle(fontSize: 16.0, color: Theme.primaryTextColor),
          ),
          Expanded(
            child: ListView(
              children: [
                ...List.generate(15, (index) {
                  return ListTile(
                    contentPadding: EdgeInsets.zero,
                    title: Row(
                      children: [
                        AvatarRow(
                          name: 'Name ${index + 1}',
                          message: 'hello',
                        ),
                      ],
                    ),
                  );
                })
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _rightCol() {
    return Column(
      children: [
        Row(
          children: [
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('Name 1',
                    style: TextStyle(
                        fontSize: 18.0, color: Theme.primaryTextColor)),
                Row(
                  children: [
                    Text('Last seen days ago',
                        style: TextStyle(color: Theme.secondaryTextColor)),
                    Text(' | ',
                        style: TextStyle(color: Theme.secondaryTextColor)),
                    Icon(Icons.image, color: Theme.secondaryTextColor),
                    Text('Gallery',
                        style: TextStyle(color: Theme.secondaryTextColor)),
                    Text(' | ',
                        style: TextStyle(color: Theme.secondaryTextColor)),
                    Icon(Icons.search, color: Theme.secondaryTextColor),
                    Text('Find',
                        style: TextStyle(color: Theme.secondaryTextColor)),
                  ],
                ),
              ],
            ),
            Spacer(),
            RoundButton(icon: Icons.video_call_outlined),
            RoundButton(icon: Icons.call),
            RoundButton(icon: Icons.person_add),
          ],
        ),
        Divider(
          color: Theme.secondaryTextColor,
          thickness: 1,
        ),
        SingleChildScrollView(
          child: ChatBox(),
        ),
        Row(
          children: [
            Flexible(
              flex: 8,
              child: TextField(
                maxLines: 1,
                decoration: InputDecoration(
                    fillColor: Colors.grey,
                    contentPadding: EdgeInsets.all(0),
                    hintText: 'Type a message',
                    prefixIcon: Icon(Icons.emoji_emotions_outlined),
                    border: OutlineInputBorder(
                        borderSide:
                            BorderSide(color: Theme.secondaryTextColor))),
              ),
            ),
            Spacer(),
            Flexible(child: RoundButton(icon: Icons.folder_open)),
            Flexible(child: RoundButton(icon: Icons.contact_mail_outlined)),
            Flexible(child: RoundButton(icon: Icons.mic)),
            Flexible(child: RoundButton(icon: Icons.more_horiz_outlined)),
          ],
        )
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
              width: leftWidth,
              height: double.infinity,
              decoration: BoxDecoration(
                  color: Colors.grey.shade100,
                  border: Border(
                      right: BorderSide(
                    color: Theme.secondaryTextColor,
                    width: 1,
                  ))),
              child: _leftCol()),
          Expanded(
            child: Container(
                padding: EdgeInsets.all(16.0),
                height: double.infinity,
                color: Colors.white,
                child: _rightCol()),
          ),
        ],
      ),
    );
  }
}

class Avatar extends StatelessWidget {
  final String? image;
  final double? size;

  const Avatar({Key? key, this.size, this.image}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
        width: size ?? 36,
        height: size ?? 36,
        child: ClipRRect(
          borderRadius: BorderRadius.circular(36.0),
          child: Image.network(
            image ??
                'https://cdn.pixabay.com/photo/2014/11/30/14/11/cat-551554_960_720.jpg',
            fit: BoxFit.fill,
          ),
        ));
  }
}

class AvatarRow extends StatelessWidget {
  final Avatar? avatar;
  final String? name;
  final String? message;

  const AvatarRow({Key? key, this.name, this.message, this.avatar})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
        //padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            avatar ?? Avatar(),
            SizedBox(width: 16),
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(name ?? 'Name',
                    style: TextStyle(color: Theme.primaryTextColor)),
                Text(message ?? 'hello',
                    style: TextStyle(color: Theme.secondaryTextColor)),
              ],
            ),
            Spacer(),
            Text('${Random().nextInt(12) + 1}/${Random().nextInt(20) + 1}/2021',
                style: TextStyle(color: Theme.secondaryTextColor))
          ],
        ),
      ),
    );
  }
}

class MainAvatarRow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Avatar(
                image:
                    'https://cdn.pixabay.com/photo/2014/04/13/20/49/cat-323262__340.jpg'),
            SizedBox(width: 16),
            Text('TL Templates',
                style: TextStyle(color: Theme.primaryTextColor)),
            SizedBox(width: 16),
            Text('\$0.00',
                style:
                    TextStyle(color: Theme.primaryTextColor.withOpacity(0.5))),
            Spacer(),
            IconButton(icon: Icon(Icons.more_horiz_outlined), onPressed: () {})
          ],
        ),
      ),
    );
  }
}

class LabelButton extends StatelessWidget {
  final IconData? icon;
  final VoidCallback? onPressed;
  final String? label;

  const LabelButton({Key? key, @required this.icon, this.onPressed, this.label})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        IconButton(
            icon: Icon(icon!, color: Theme.primaryTextColor, size: 18.0),
            onPressed: onPressed ?? () {}),
        Text(
          label!,
          style: TextStyle(color: Theme.primaryTextColor),
        )
      ],
    );
  }
}

class LabelButtonRow extends StatelessWidget {
  final IconData? icon;
  final VoidCallback? onPressed;
  final String? label;

  const LabelButtonRow(
      {Key? key, @required this.icon, this.onPressed, this.label})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.zero,
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16.0),
      ),
      child: Row(
        children: [
          IconButton(
              icon: Icon(icon!, color: Theme.primaryTextColor, size: 18.0),
              onPressed: onPressed ?? () {}),
          Text(
            label!,
            style: TextStyle(color: Theme.primaryTextColor),
          ),
          IconButton(
              icon: Icon(Icons.keyboard_arrow_down_outlined,
                  color: Theme.primaryTextColor, size: 18.0),
              onPressed: onPressed ?? () {})
        ],
      ),
    );
  }
}

class RoundButton extends StatelessWidget {
  final IconData? icon;
  final VoidCallback? onPressed;

  const RoundButton({Key? key, @required this.icon, this.onPressed})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 0),
      child: Container(
        decoration:
            BoxDecoration(color: Colors.grey.shade100, shape: BoxShape.circle),
        child: IconButton(
            icon: Icon(icon!, color: Theme.primaryTextColor, size: 18.0),
            onPressed: onPressed ?? () {}),
      ),
    );
  }
}

class ChatBox extends StatefulWidget {
  @override
  _ChatBoxState createState() => _ChatBoxState();
}

class _ChatBoxState extends State<ChatBox> {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: MediaQuery.of(context).size.height - 150,
      padding: EdgeInsets.fromLTRB(36.0, 16.0, 36.0, 16.0),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              Avatar(),
              SizedBox(width: 8.0),
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Name, 8:30PM',
                    style: TextStyle(color: Theme.secondaryTextColor),
                  ),
                  Container(
                      padding: EdgeInsets.all(8.0),
                      decoration: BoxDecoration(
                          color: Colors.blueAccent,
                          borderRadius: BorderRadius.circular(8.0)),
                      child: Text('Hello\nMy test message')),
                ],
              ),
            ],
          ),
          SizedBox(height: 16.0),
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              Container(
                  padding: EdgeInsets.all(8.0),
                  decoration: BoxDecoration(
                      color: Colors.grey.shade200,
                      borderRadius: BorderRadius.circular(8.0)),
                  child: Text('Hello')),
            ],
          ),
        ],
      ),
    );
  }
}

By continuing to use the site, you agree to the use of cookies.