惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
Cisco Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
IT之家
IT之家
博客园 - 【当耐特】
V
V2EX
博客园_首页
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
G
Google Developers Blog
The Last Watchdog
The Last Watchdog
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 司徒正美
N
Netflix TechBlog - Medium
F
Fortinet All Blogs
Know Your Adversary
Know Your Adversary
S
Schneier on Security
V
Vulnerabilities – Threatpost
T
The Exploit Database - CXSecurity.com
Vercel News
Vercel News
量子位
G
GRAHAM CLULEY
T
Threatpost
D
Darknet – Hacking Tools, Hacker News & Cyber Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cybersecurity and Infrastructure Security Agency CISA
S
Security @ Cisco Blogs
B
Blog
Stack Overflow Blog
Stack Overflow Blog
T
Tor Project blog
A
About on SuperTechFans
博客园 - 叶小钗
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
月光博客
月光博客
S
Securelist
博客园 - 聂微东
Cloudbric
Cloudbric
N
News and Events Feed by Topic
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
H
Help Net Security
N
News | PayPal Newsroom
P
Privacy & Cybersecurity Law Blog
Schneier on Security
Schneier on Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
W
WeLiveSecurity
Martin Fowler
Martin Fowler
K
Kaspersky official blog
S
Security Affairs
TaoSecurity Blog
TaoSecurity Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
SignalApp UI Home Screen but with Flutter
Mathieu Kerjouan · 2026-06-23 · via DEV Community

He who can copy can do.
-- Leonardo Da Vinci

My UI skill is very low, I'm not really good with colors or themes and it's always a pain to design a full interface by myself... In short: I'm not an artist and it needs to be fixed. SignalApp is one of my main messenger, the interface is simple and functional. The application source code is also available on Github, and the Android application is using Material3. I think it can be a good target.

SignalApp Screenshot from appgefahren.de

Trying to reproduce it completely will take a while, only a small subset of the interface will be re-implemented, the home and the user chat screens, both can be seen from the screenshot above.

Home Screen

Good artists copy, great artists steal.
-- Pablo Picasso

The Home screen will be the entry-point of the application, where the active conversations can be seen by the user. It is also the place where SignalApp can be configured, when tapping on the User avatar on the top left of the application, a Drawer menu should open. This menu is not displayed on the screenshot, then, only an empty one will be created.

Top part of the Home Screen

The first easily identifiable element of the application is the top bar. I will assume this one can be created with the help of the AppBar class, where the leading parameter will contain the user picture (rounded). This element can be created with a CircleAvatar class. The title parameter will display the text "Signal" in bold, a TextStyle class can be set properly inside the Text class style attribute. The parameters actions will be set with 2 icons, one icon will represent a camera with Icons.camera and the last one will represent a pencil using the pencil constant. Finally, the background color of this element is grey (#fafafa).

AppBar homeBar(BuildContext context) {
  return AppBar(
    backgroundColor: Colors.grey[50],
    titleTextStyle: TextStyle(
      color: Colors.black,
      fontWeight: FontWeight.bold
    ),
    leading: Padding(
      padding: EdgeInsets.all(8.0),
      child: CircleAvatar(
        backgroundColor: Colors.amber,
        child: Text('AH')
      ),
    ),
    title: Center(child: Text('SignalCopyCat')),
    actions: [
      IconButton(
        onPressed: () {}, 
        icon: Icon(Icons.camera_alt_outlined)
      ),
      IconButton(
        onPressed: () {},
        icon: Icon(Icons.edit_outlined)
      ),
    ],
  );
}

The top Bar using by Signal is not the most challenging part of the design, at least, when we don't have state to manage. The homeBar() function created above is returning an AppBar() object with some custom parameters.

The backgroundColor parameter is set to grey. The titleTextStyle parameter has been set with a custom Color and FontWeight, this should be applied on the rest of the widgets instantiated below this bar.

The leading parameter (the right part of the bar) is using a Padding widget to reduce the side of its child element with the help of an EdgeInsets object. The offset configured (8.0) is applied on all sides, thanks to the EdgeInsets.all() constructor. Its only child is CircleAvatar widget containing only the initials of the users as a Text() widget.

The title parameter is only containing the title of the application as in a Text() object, centered via a Center() widget.

The actions parameter is using a list of IconButton widget, which one set with an Icon(). The number of icons available with Material is astonishing... I took me a moment to find what I was looking for, Icons.camera_alt_outlined for the camera and Icons.edit_outlined for the pencil. Other icons can also be used via the CubertinoIcons class (usually designed for iOS though).

No callback functions have been set in the onPressed parameters, this is not the goal of this article.

Middle and bottom part of the Home screen

The next big part of the Home screen is taking the remaining place. This section can be scrolled. It is made of many similar elements probably derived from a ListTile class. Those are split in 3 part, from the left to the right, the first part is an icon containing a picture of a remote user where we can reuse the CircleAvatar class. The second part contains the name of the user (in bold) above the last messages (if any). The last part contains the date of the last message received (with an attachment or an acknowledgement if any). When the user is tapping on one of these elements, it is redirected to a Chat Screen. The background color of this part of the application is white (#ffffff).

String initials(String str) {
  return str
    .split(" ")
    .map((x) => x.isEmpty ? "?" : x[0])
    .take(2)
    .join("");
}

Firstly, I think creating a function to convert a first name and last name (or any kind of string) to their initials can be helpful for the next part. This is a quick and dirty implementation, the string must be sanitized first and only characters (including UTF8 characters) should be used. It will do the job for know though, it could be nice to see what kind of algorithm SignalApp is using, I think the getAbbreviation() method from org.thoughtcrime.securesms.util.NameUtil is the one we are looking for.

ListTile tile({
  String? title,
  String? subtitle,
  String? trailing,
  MaterialColor? avatarColor,
  String? notifications,
  bool? received,
}) {
  return ListTile(
    leading: Badge(
      label: Text(notifications ?? "0"),
      isLabelVisible: notifications == null ? false : true,
      textColor: Colors.white,
      backgroundColor: Colors.blue,
      child: CircleAvatar(
        backgroundColor: avatarColor ?? Colors.amber,
        child: Container(
          decoration: BoxDecoration(shape: BoxShape.circle),
          child: Text(title == null ? "??" : initials(title)),
        ),
      ),
    ),
    title: Flex(
      direction: Axis.horizontal,
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Align(
          alignment: Alignment.topLeft,
          child: Text(
            title ?? "John Smith",
            style: TextStyle(fontWeight: FontWeight.bold, fontFamily: "Roboto")
            )
          ),
        Align(
          alignment: Alignment.topRight,
          child: Text(
            trailing ?? "11:11 AM",
            style: notifications != null 
              ? TextStyle(fontWeight: FontWeight.bold, fontSize: 12, fontFamily: "Roboto")
              : TextStyle(fontWeight: FontWeight.normal, fontSize: 12, fontFamily: "Roboto"),
          ),
        ),
      ],
    ),
    subtitle: Flex(
      direction: Axis.horizontal,
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Expanded(
          flex: 1,
          child: Text(
            subtitle ?? "Last message...",
            overflow: TextOverflow.ellipsis,
            style: notifications != null 
              ? TextStyle(fontWeight: FontWeight.bold, fontSize: 14, fontFamily: "Roboto")
              : TextStyle(fontWeight: FontWeight.normal, fontSize:14, fontFamily: "Roboto")
          ),
        ),
        received == true
            ? Icon(Icons.library_add_check_rounded, size: 16)
            : Container(),
      ],
    ),
    onTap: () {},
  );
}

Another function helper called tile() will help us to generate each ListTile() objects. This function can take 6 optional arguments, if those are not set, some default values are used instead.

The leading parameter is containing the user avatar, material gives us the Badge() object to deal with that. This one contains a label (the blue circle containing the number of notifications). I tried to add a white border on this label, without success, perhaps my future me will fix that. If there is no notification, the isLabelVisible parameter can be set to false, in this case, the label will be removed. The only child is a CircleAvatar using a specific BoxDecoration to produce a circle via BoxShape.circle. By default, the initials of the title variable will be used.

Regarding badges, 2 modules can probably fix most of my issues here:

  • info_label: A high-performance Flutter label widget built on CustomPainter. Background, border, text, and overlay indicators are painted directly on canvas for minimal widget overhead;

  • badges: A package for creating badges. Badges can be used for an additional marker for any widget, e.g. show a number of items in a shopping cart.

The title parameter was configured to set the user name on the left and the date on the right. I thought at first I could use the trailing parameter to do that, but it was kinda messy, and way more complex. Anyway, a Flex widget is used to let us have a flexible positioning across the elements. Indeed, we want the widgets on an horizontal Axis (see Axis.horizontal) with a space between them (see MainAxisAlignment.spaceBetween). Then the 2 widgets children can be aligned using the Alignment.topLeft and Alignment.topRight constants. Nothing smart here, both of them are Text() widgets.

The subtitle is using the same hack than the title parameter previously described. An element to left (last message from the remote user) and another one to the right (message ack).

onTap is not configured yet, but the idea when someone is tapping on one tile, he will be redirected to the chat screen.

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

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        tile(
          avatarColor: Colors.blueGrey,
          title: "Tina Ukuku",
          subtitle: "You set disappearing message time to 1...",
          avatarText: "TU",
        ),
        tile(
          avatarColor: Colors.indigo,
          title: "Chairman Meow",
          subtitle: "Missed call",
          avatarText: "CM",
          notifications: "2",
        ),
        tile(
          avatarColor: Colors.lightGreen,
          title: "Myles Larson",
          subtitle: "🎤 Voice Message",
          trailing: "11:07 AM",
          notifications: "7",
        ),
        tile(
          avatarColor: Colors.lime,
          title: "Cat Chat 🐈 🐱",
          subtitle: "This is the instruction manual. 📎 Attachment",
          trailing: "11:02 AM",
        ),
        tile(
          avatarColor: Colors.blueGrey,
          title: "Ali Smith",
          subtitle: "📷 Attachment",
          trailing: "10:38 AM",
          received: true,
        ),
        tile(
          avatarColor: Colors.indigo,
          title: "Kirk Family",
          subtitle: "Happy birthday to you. Happy birthday to you!",
          trailing: "9:13 AM",
          notifications: "1",
        ),
        tile(
          avatarColor: Colors.lightGreen,
          title: "Jordan B.",
          subtitle: "Sticker Message",
          received: true,
        ),
        tile(
          avatarColor: Colors.lime,
          title: "Sunsets 🌅",
          subtitle: "View-once media",
          trailing: "TUE",
          received: true,
        ),
        tile(
          avatarColor: Colors.blueGrey,
          title: "🧗‍♂️ Rock Climbers",
          subtitle: "Which route should we take?",
          trailing: "TUE",
        ),
        tile(
          avatarColor: Colors.indigo,
          title: "Nikki R.",
          subtitle: "Thanks! What a wonderful message to read in the morning!",
          trailing: "TUE",
        ),
        tile(
          avatarColor: Colors.lightGreen,
          title: "Weather Forecasts",
          subtitle: "Raining all day 📷 Attachment",
          trailing: "MON",
        ),
        tile(avatarColor: Colors.lime),
        tile(avatarColor: Colors.blueGrey),
        tile(avatarColor: Colors.indigo),
        tile(avatarColor: Colors.lightGreen),
        tile(avatarColor: Colors.lime),
      ],
    );
  }
}

Now our tile() function is ready, we can create our Conversations() object containing the list of user chats. This part creates a ListView object, containing a list of ListTile created by our helper function. This part seems complex, but only one function is used many time to generated content and mimic the screenshot.

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

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: homeBar(context),
      body: Conversations()
    );
  }
}

Everything looks good, we then mix everything together inside an Home class returning a Scaffold() object.

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SignalCopyCat',
      home: Home(),
      theme: ThemeData(
        fontFamily: "Roboto"
      ),
      themeMode: ThemeMode.light,
      debugShowCheckedModeBanner: false,
    );
  }
}

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

Finally, the last step, we can create also our application using a MaterialApp() object.

SignalApp UI Home Screen but with Flutter

Above, a screenshot of this application from the Android emulator. The images/pictures are missing on purpose, I was not able to found the "correct" images. As you can see, the emojis are not correctly rendered as well. Still few bugs to fix, but I did that in few hours.

I forgot something though, a big part of my code is using custom fonts (Roboto). Fonts are kind of assets and must be downloaded then configured in the project. The Use a custom font article on the official Flutter documentation is doing a great job to explain that.

Conclusion

I’ve been imitated so well, I’ve heard people copy my mistakes.
-- Jimi Hendrix

This is not the perfect 1:1 reproduction of the SignalApp Home screen... I still need more experience with Flutter. The terms used are also new to me, finding one icon similar to the one from the screenshot is taking time for example. The infinite amount of solution to display something is also time consuming. Here a list of few resources I checked during this experience.

Anyway, copying and reproducing is a great way to learn. It also train the mind to decompose every applications in small pieces. The same could have been done for Briar, SimpleX or Telegram applications, all of them are open-source and available on Github, Gitlab or somewhere else. Some of them are supporting mobile, desktop and web platform, in short, they are great targets. To summarize, if you want to learn how to design UI, try to recreate the design and the style of your favorite application.

The next step? Why not create an open-source library in Dart/Flutter exposing these styles? Perhaps one day...

Happy Hack and Have fun!


Cover Image by Compagnons on Unsplash