Yapp is a full-featured, real-time chat application built with Flutter and Supabase. It supports one-on-one messaging, group chats, live voice and video calls via Agora, a built-in AI assistant powered by Google Gemini, rich media sharing, and a polished glassmorphism UI with light/dark theme support.
- Features
- Tech Stack
- Architecture
- Project Structure
- Screens & Navigation
- State Management
- Backend — Supabase
- Edge Functions
- AI Assistant (Yapp AI)
- Voice & Video Calls (Agora)
- Local Storage & Offline Support
- UI & Theming
- Key Dependencies
- Getting Started
- Environment & Secrets
- Real-time one-on-one and group conversations powered by Supabase Realtime
- Text messages, emoji picker, file attachments, and image sharing
- Voice message recording and playback (via
record+just_audio) - Message delivery receipts: sent, delivered, and read status
- Message forwarding and per-message deletion
- Long-press context menu for message actions
- Date separators in chat history
- Inline media preview and file opening via
open_filex
- Create groups with a custom name and selected participants
- Group info screen with member management
- Groups shown in a dedicated tab separate from direct chats
- One-on-one audio and video calling using the Agora RTC Engine v6
- Secure Agora token generation via a Supabase Edge Function (server-side, not client-side)
- Incoming call screen with ringtone playback (
flutter_ringtone_player) - Call history log in the Calls tab
- Call signaling via Supabase Realtime channels
- Dedicated AI chat screen with a custom avatar
- Powered by Google Gemini 3.5 Flash via a Supabase Edge Function
- Server-Sent Events (SSE) streaming — responses appear word by word with a typewriter effect
- Full conversation history stored in Supabase (
ai_messagestable), loaded on session start - Context window: last 12 messages retrieved server-side to prevent prompt injection
- Clear chat history with confirmation dialog
- Greeting message shown when chat is empty
- Email/password sign-up and sign-in
- Forgot password and password reset flows (deep link / OTP)
- Session persistence via Hive local storage
- Splash screen that routes to onboarding, login, or home based on session state
- Avatar selection from a built-in asset library
- Display name and bio editing
- Profile settings screen accessible from the home app bar
- New Chat screen with full user search
- Block/unblock users (
block_service.dart) - Contact info screen showing shared conversation details
connectivity_plusdetects network state changes- Offline message cache via Hive (
chatCachebox) OfflineServiceguards network-dependent operations- Conversations refresh automatically when connectivity is restored
| Layer | Technology |
|---|---|
| Framework | Flutter 3 (Dart SDK ^3.12.2) |
| Backend-as-a-Service | Supabase (Auth, Database, Storage, Realtime, Edge Functions) |
| AI Model | Google Gemini 3.5 Flash (via Edge Function) |
| Voice/Video Calls | Agora RTC Engine 6.5.3 |
| State Management | Provider 6 (ChangeNotifier) |
| Local Storage | Hive 2 + Hive Flutter |
| HTTP Client | http package + native Dart HttpClient for SSE |
| UI Extras | Liquid Glass Widgets, Google Fonts, Lottie, Smooth Page Indicator |
| Icons | Hugeicons, Cupertino Icons, Material Icons |
| Image Handling | cached_network_image, image_picker |
| File Handling | file_picker, open_filex, path_provider |
| Audio | record (recording), just_audio (playback) |
| Notifications | flutter_ringtone_player (incoming calls) |
| Edge Function Runtime | Deno (TypeScript) |
| Agora Token Library | agora-token@2.0.3 (npm, in Edge Function) |
| Linting | flutter_lints |
| App Icon | flutter_launcher_icons |
The app follows a layered architecture:
UI (Screens & Widgets)
↕
Providers (State)
↕
Services (Logic)
↕
Supabase / Agora / Local Storage
- Screens are pure UI — they read from providers and dispatch actions.
- Providers hold all mutable state and orchestrate service calls.
- Services are stateless classes that wrap Supabase queries, Agora SDK calls, and Hive operations.
- Models are immutable data classes (
UserModel,MessageModel,ConversationModel,AiMessageModel).
chat_app_flutter/
├── lib/
│ ├── main.dart # Entry point: provider setup, routing, Supabase + Hive init
│ ├── config/
│ │ └── supabase_config.dart # Supabase URL, anon key, Agora App ID
│ ├── models/
│ │ ├── user_model.dart
│ │ ├── message_model.dart
│ │ ├── conversation_model.dart
│ │ └── ai_message_model.dart
│ ├── providers/
│ │ ├── auth_provider.dart # Auth state, login/logout/signup
│ │ ├── chat_provider.dart # Conversations, messages, groups, realtime
│ │ ├── ai_chat_provider.dart # AI chat state, typewriter effect, history
│ │ ├── call_provider.dart # Voice/video call lifecycle, Agora engine
│ │ └── theme_provider.dart # Light/dark theme, custom theme builder
│ ├── services/
│ │ ├── supabase_auth_service.dart
│ │ ├── chat_service.dart
│ │ ├── conversation_service.dart
│ │ ├── message_service.dart
│ │ ├── ai_chat_service.dart # Calls Edge Function, parses SSE stream
│ │ ├── agora_call_service.dart # Agora SDK wrapper, token fetch
│ │ ├── block_service.dart
│ │ ├── receipt_service.dart # Delivery + read receipts
│ │ ├── local_cache_service.dart # Hive-backed message cache
│ │ ├── media_cache_service.dart
│ │ └── offline_service.dart # Connectivity checking
│ ├── screens/
│ │ ├── splash_screen.dart
│ │ ├── onboarding_screen.dart
│ │ ├── login_screen.dart
│ │ ├── signup_screen.dart
│ │ ├── forgot_password_screen.dart
│ │ ├── reset_password_screen.dart
│ │ ├── home_screen.dart # 4-tab main screen (Chats, Groups, Calls, Settings)
│ │ ├── chat_room_screen.dart
│ │ ├── ai_chat_screen.dart
│ │ ├── calls_screen.dart
│ │ ├── new_chat_screen.dart
│ │ ├── create_group_screen.dart
│ │ ├── contact_info_screen.dart
│ │ ├── group_info_screen.dart
│ │ ├── profile_settings_screen.dart
│ │ └── settings_screen.dart
│ └── widgets/
│ ├── ai/
│ │ ├── ai_message_bubble.dart
│ │ └── ai_typing_indicator.dart
│ ├── calling/
│ │ ├── call_screen.dart
│ │ ├── video_call_screen.dart
│ │ └── incoming_call_dialog.dart
│ ├── chat/
│ │ ├── message_bubble.dart
│ │ ├── message_input_bar.dart
│ │ ├── message_info_sheet.dart
│ │ └── date_separator.dart
│ ├── common/
│ │ ├── abstract_background.dart # Animated gradient background
│ │ ├── glass_app_bar.dart # Frosted glass app bar
│ │ ├── glass_bottom_bar.dart # Frosted glass tab bar
│ │ ├── glass_container.dart
│ │ ├── glass_search_bar.dart
│ │ ├── avatar_helper.dart
│ │ └── neon_button.dart
│ └── home/
│ ├── conversation_tile.dart
│ └── empty_state.dart
├── supabase/
│ ├── config.toml
│ └── functions/
│ ├── chat-with-nova/
│ │ └── index.ts # Gemini AI Edge Function with SSE streaming
│ └── generate-agora-token/
│ └── index.ts # Agora RTC token generator
├── assets/
│ ├── avatars/ # Built-in avatar images
│ ├── onboarding/ # Onboarding screen illustrations
│ ├── animations/ # Lottie animation files
│ ├── yapp_ai_avatar.png
│ ├── yapp-logo.png
│ ├── yapp-logo-light-mode.png
│ ├── yapp_ai_full.png
│ ├── yapp-app-icon.png
│ ├── light-mode-bg.png
│ └── dark-mode-bg.png
└── pubspec.yaml
The app uses named routes registered in main.dart with a global NavigatorKey shared with CallProvider so incoming calls can push routes without a BuildContext.
| Route | Screen | Description |
|---|---|---|
/ (home) |
SplashScreen |
Checks session and redirects |
/onboarding |
OnboardingScreen |
First-launch walkthrough with page indicator |
/login |
LoginScreen |
Email/password sign-in |
/signup |
SignupScreen |
Registration with avatar picker |
/forgotPassword |
ForgotPasswordScreen |
Sends password reset email |
/resetPassword |
ResetPasswordScreen |
OTP or deep-link password reset |
/home |
HomeScreen |
4-tab hub: Chats, Groups, Calls, Settings |
| (push) | ChatRoomScreen |
Individual or group message thread |
| (push) | AiChatScreen |
Yapp AI assistant conversation |
| (push) | NewChatScreen |
User search to start a direct chat |
| (push) | CreateGroupScreen |
Multi-select contacts + group name |
| (push) | ContactInfoScreen |
View a contact's profile |
| (push) | GroupInfoScreen |
Group name, members, leave/delete |
| (push) | ProfileSettingsScreen |
Edit own name, bio, avatar |
/call |
CallScreen |
Active audio call UI |
/video-call |
VideoCallScreen |
Active video call UI |
/incoming-call |
IncomingCallScreen |
Incoming call with accept/decline |
The HomeScreen is the main hub after login. It uses a TabController with four tabs rendered via TabBarView:
- Chats — Direct conversations list with search, pull-to-refresh, swipe/long-press to delete
- Groups — Group conversations list (same pattern as Chats)
- Calls — Call history log
- Settings — App settings including theme toggle
A custom glass bottom bar handles tab switching. Floating action buttons appear on the Chats and Groups tabs (new chat / create group) and a persistent AI FAB provides one-tap access to Yapp AI from anywhere.
The app uses the provider package (ChangeNotifier + MultiProvider). Five providers are registered at the root:
Wraps SupabaseAuthService. Manages:
- Current user object and session state
- Login, signup, logout, password reset
refreshUser()for pulling updated profile data
The largest provider. Manages:
- Conversation list loading and Supabase Realtime subscription
- Per-conversation message loading and realtime message streaming
- Sending text, voice, images, and files
- Message deletion, forwarding, read receipts
- Group creation and management
- Offline cache read/write via
LocalCacheService - Block state integration
Manages the AI chat lifecycle:
- Loads chat history from Supabase on first open
- Calls
AiChatService.streamMessage()which consumes the Edge Function's SSE stream - Drives a typewriter effect (14ms timer per character) to animate streaming text
- Persists chat state between screen navigations (
_historyLoadedguard) - States:
idle,loading,thinking,error
Full call lifecycle:
- Listens to Supabase Realtime
call_signalsfor incoming invites - Initiates outgoing calls: creates a call session record, fetches an Agora token from the Edge Function, joins the Agora channel
- Manages Agora
RtcEngineevents (join, leave, remote user streams) - Navigates to call/incoming-call screens via the global navigator key
- Plays/stops ringtones
- Writes call history records
- Stores and persists the user's preferred theme mode (light/dark/system)
- Provides
buildLightTheme()andbuildDarkTheme()using Google Fonts and custom color schemes
Supabase provides the entire backend: authentication, PostgreSQL database, file storage, realtime, and Edge Functions.
| Table | Purpose |
|---|---|
profiles |
User display name, avatar URL, bio |
conversations |
Conversation metadata, is_group flag |
conversation_participants |
Junction table: user ↔ conversation, with status (active/left) |
messages |
All chat messages with sender_id, conversation_id, type, content, file_url |
message_receipts |
Per-user delivery and read timestamps |
call_sessions |
Call records with type, status, timestamps |
call_signals |
Realtime signaling payloads for call invite/accept/decline/end |
blocked_users |
Block relationships between users |
ai_messages |
AI chat history per user (role, content, created_at) |
- Conversations and messages use Supabase Realtime channel subscriptions
- Call signaling uses a dedicated Realtime channel filtered by user ID
- Subscriptions are started in
HomeScreen.initState()and torn down indispose()
- Profile avatars and media attachments are stored in Supabase Storage buckets
MediaCacheServicehandles local caching of downloaded media
Both Edge Functions are written in TypeScript and run on the Deno runtime inside Supabase.
Handles all Yapp AI conversations.
Flow:
- Authenticates the request using the Supabase JWT from the
Authorizationheader - Validates the message (max 8,000 characters)
- Fetches the last 12 messages from the
ai_messagestable to build conversation context — server-side, preventing history spoofing - Sends the request to Google Gemini 3.5 Flash via its streaming REST API (
alt=sse) - Forwards each SSE chunk back to the Flutter client immediately as it arrives
- When the stream ends, saves both the user message and the complete AI reply to
ai_messagesin a single insert
Gemini config:
- Model:
gemini-3.5-flash - Max output tokens: 1,024
- Temperature: 0.7
- System instruction: sets the assistant persona as "Yapp", a friendly mobile chat companion
Required secrets: GEMINI_API_KEY, SUPABASE_URL, SUPABASE_ANON_KEY
Issues signed Agora RTC tokens for voice and video calls.
Flow:
- Authenticates the caller via Supabase JWT
- Validates
conversationId,sessionId, andcallType(audio|video) from the request body - Verifies the caller is an active participant of the given conversation and that it is not a group (calls are one-on-one only)
- Constructs the channel name as
call_<sessionId>server-side so clients cannot request tokens for arbitrary channels - Builds a token using
agora-token@2.0.3(RtcTokenBuilder.buildTokenWithUid) with a 1-hour expiry - Returns
{ token, uid, appId }to the client
Required secrets: AGORA_APP_ID, AGORA_APP_CERTIFICATE, SUPABASE_URL, SUPABASE_ANON_KEY
Yapp AI is a built-in assistant accessible via a floating action button on the home screen.
- Persona: "Yapp" — warm, concise, conversational; suited to a mobile chat context
- Model: Google Gemini 3.5 Flash
- Streaming: SSE stream parsed character by character with a 14ms typewriter timer for smooth text animation
- History: Stored in the
ai_messagesSupabase table, loaded on screen open, persisted across sessions - Context: The Edge Function always reads conversation history from the database, not from the client payload, preventing prompt injection or history manipulation
- Clear Chat: Users can permanently delete their AI conversation history
- Greeting: A welcome message is injected locally when history is empty
The calling system uses the Agora RTC Engine SDK (agora_rtc_engine: 6.5.3).
Outgoing call:
CallProvidercreates acall_sessionrecord in Supabase- Calls
generate-agora-tokenEdge Function to get a signed token - Broadcasts a call invite signal via Supabase Realtime to the recipient
- Navigates to
CallScreenorVideoCallScreen - Joins the Agora channel using the token
Incoming call:
CallProviderlistens on a Supabase Realtime channel filtered to the current user- On invite signal, plays a ringtone and navigates to
IncomingCallScreen - On accept: fetches a token, joins the Agora channel
- On decline: sends a decline signal, stops ringtone
Call End:
- Either party can end the call; a signal is broadcast and both sides leave the Agora channel
- Call duration and status are updated in the
call_sessionstable
CallScreen— audio call with mute, speaker toggle, end callVideoCallScreen— video call with local/remote video renders, camera flip, muteIncomingCallScreen— caller info, accept/decline buttons, ringtone
Two Hive boxes are opened at startup:
authBox— persists the user sessionchatCache— caches conversation and message data for offline reading
LocalCacheService reads from the cache when the device is offline and writes to it when data is fetched online.
OfflineService uses connectivity_plus to check for network access. Navigation actions that require the network (loading conversations, refreshing profile) are guarded and retry automatically when connectivity is restored.
The entire UI is built around a glass/blur aesthetic using the liquid_glass_widgets package, which is initialized at app startup. Key components:
AbstractBackground— animated gradient background rendered behind all screensGlassAppBar— frosted glass top bar with user avatar and app titleGlassBottomBar— frosted glass tab bar with icon + label tabsGlassContainer— reusable frosted glass card/containerGlassSearchBar— frosted glass search input
- Fully supports light mode and dark mode, switchable from the Settings tab
ThemeProviderbuilds both themes with customColorSchemevalues and Google Fonts typography- Background images:
light-mode-bg.png/dark-mode-bg.png - Onboarding uses
smooth_page_indicatorandlottieanimations - Icons from the
hugeiconspackage throughout the app
assets/
├── avatars/ # Pre-built avatar selection for user profiles
├── onboarding/ # Onboarding screen illustrations
├── animations/ # Lottie JSON animation files
├── yapp_ai_avatar.png
├── yapp-logo.png
├── yapp-logo-light-mode.png
├── yapp_ai_full.png
├── yapp-app-icon.png
├── light-mode-bg.png
└── dark-mode-bg.png
| Package | Version | Purpose |
|---|---|---|
supabase_flutter |
^2.17.1 | Auth, database, storage, realtime |
provider |
^6.1.5+1 | State management |
agora_rtc_engine |
6.5.3 | Voice and video calls |
hive + hive_flutter |
^2.2.3 / ^1.1.0 | Local storage and offline cache |
liquid_glass_widgets |
^0.29.1 | Glassmorphism UI components |
google_fonts |
^8.2.1 | Typography |
lottie |
^3.1.2 | Animated illustrations |
cached_network_image |
^3.4.1 | Efficient remote image loading |
emoji_picker_flutter |
^4.4.0 | Emoji keyboard |
record |
^7.1.1 | Voice message recording |
just_audio |
^0.10.5 | Audio playback |
flutter_ringtone_player |
^4.0.0+4 | Incoming call ringtones |
file_picker |
^8.1.7 | Attach files from device |
image_picker |
^1.1.2 | Pick images from gallery/camera |
open_filex |
^4.3.0 | Open downloaded files |
url_launcher |
^6.3.1 | Launch URLs |
permission_handler |
^11.3.1 | Microphone, camera, storage permissions |
connectivity_plus |
^6.1.0 | Network connectivity detection |
timeago |
^3.7.1 | Human-readable timestamps |
intl |
^0.20.3 | Date/time formatting |
uuid |
^4.5.3 | UUID generation |
crypto |
^3.0.3 | Cryptographic utilities |
smooth_page_indicator |
^2.0.1 | Onboarding page dots |
hugeicons |
^1.0.0 | Icon library |
shared_preferences |
^2.5.5 | Lightweight key-value persistence |
email_validator |
^3.0.0 | Email format validation |
http |
^1.3.0 | HTTP requests (SSE streaming) |
path_provider |
^2.1.5 | Platform file paths |
flutter_launcher_icons |
^0.13.1 | App icon generation |
- Flutter SDK (Dart ^3.12.2)
- A Supabase project with the required tables and RLS policies
- A Google Gemini API key
- An Agora account with an App ID and App Certificate
-
Clone the repo
git clone <repo-url> cd chat_app_flutter flutter pub get
-
Configure Supabase
Update
lib/config/supabase_config.dartwith your project URL and anon key:class SupabaseConfig { static const supabaseUrl = 'https://your-project.supabase.co'; static const supabasePublishableKey = 'your-anon-key'; static const agoraAppId = 'your-agora-app-id'; }
-
Deploy Edge Functions
supabase functions deploy chat-with-nova supabase functions deploy generate-agora-token
-
Set Supabase Secrets
supabase secrets set GEMINI_API_KEY=your-gemini-key supabase secrets set AGORA_APP_ID=your-agora-app-id supabase secrets set AGORA_APP_CERTIFICATE=your-agora-certificate
-
Run the app
flutter run
| Secret | Location | Used By |
|---|---|---|
GEMINI_API_KEY |
Supabase Secrets | chat-with-nova Edge Function |
AGORA_APP_ID |
Supabase Secrets + supabase_config.dart |
generate-agora-token Edge Function + Flutter app |
AGORA_APP_CERTIFICATE |
Supabase Secrets | generate-agora-token Edge Function |
| Supabase URL + Anon Key | supabase_config.dart |
Flutter app initialization |
Note: Never commit
supabase_config.dartwith real credentials to a public repository. Use environment injection or a.envfile approach for production builds.