Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/workflows/build-apk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Build APK

# Builds the Aether Android APK on every push/PR to main and on demand.
# Pushing a tag like `v1.0.0` (or a manual run with "release") also publishes
# the APKs to a GitHub Release.
#
# Supabase credentials are injected at build time via --dart-define and must be
# configured as repository secrets (Settings -> Secrets and variables -> Actions):
# SUPABASE_URL, SUPABASE_ANON_KEY

on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
workflow_dispatch:
inputs:
release:
description: 'Attach APKs to a GitHub Release'
type: boolean
default: false

env:
FLUTTER_VERSION: '3.22.2'

jobs:
build:
name: Build release APK
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: stable
cache: true

- name: Install dependencies
run: flutter pub get

- name: Generate code (freezed / riverpod / isar / json)
run: dart run build_runner build --delete-conflicting-outputs

- name: Analyze
run: dart analyze

- name: Test
run: flutter test

- name: Build split-per-ABI release APKs
run: |
flutter build apk --release --split-per-abi \
--dart-define=SUPABASE_URL="${{ secrets.SUPABASE_URL }}" \
--dart-define=SUPABASE_ANON_KEY="${{ secrets.SUPABASE_ANON_KEY }}"

- name: Build universal release APK
run: |
flutter build apk --release \
--dart-define=SUPABASE_URL="${{ secrets.SUPABASE_URL }}" \
--dart-define=SUPABASE_ANON_KEY="${{ secrets.SUPABASE_ANON_KEY }}"

- name: Upload APK artifacts
uses: actions/upload-artifact@v4
with:
name: aether-apk
path: build/app/outputs/flutter-apk/*.apk
if-no-files-found: error
retention-days: 30

- name: Publish to GitHub Release
if: startsWith(github.ref, 'refs/tags/v') || inputs.release
uses: softprops/action-gh-release@v2
with:
files: build/app/outputs/flutter-apk/*.apk
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
240 changes: 173 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,126 +1,232 @@
# Aether
<div align="center">

> Modern social communication with customizable identity, music presence, and secure messaging.
# ✦ Aether

Aether is a username-based social messenger — **not** a WhatsApp clone. It combines
Signal-Protocol end-to-end encrypted messaging with expressive, customizable profiles,
Instagram-style stories, Discord-style notes, and Spotify-style music presence, all wrapped
in a glassmorphism UI on a pure AMOLED dark base.
### Modern social communication with customizable identity, music presence, and secure messaging.

Built with Flutter + Supabase. Created by **(Itz_Me) / KairoCloud**.
Aether is a **username-based social messenger** — *not* a WhatsApp clone. It fuses
Signal-Protocol end-to-end encrypted chat with expressive customizable profiles,
Instagram-style stories, Discord-style notes, and Spotify-style music presence —
all wrapped in a glassmorphism UI on a pure-AMOLED dark base.

[![Build APK](https://github.com/KairoCloud/Aether/actions/workflows/build-apk.yml/badge.svg)](https://github.com/KairoCloud/Aether/actions/workflows/build-apk.yml)
![Flutter](https://img.shields.io/badge/Flutter-3.22-02569B?logo=flutter&logoColor=white)
![Dart](https://img.shields.io/badge/Dart-3.4-0175C2?logo=dart&logoColor=white)
![Supabase](https://img.shields.io/badge/Supabase-Postgres-3FCF8E?logo=supabase&logoColor=white)
![E2EE](https://img.shields.io/badge/E2EE-Signal%20Protocol-7C3AED)
![Platform](https://img.shields.io/badge/Platform-Android%20%7C%20iOS-0A0A0F)

Crafted by **(Itz_Me) / KairoCloud**

</div>

---

## Features

- **End-to-end encrypted messaging** — Signal Protocol (`libsignal_protocol_dart`), X3DH session
setup, one-time prekey replenishment. Ciphertext only ever leaves the device; private keys
live in secure storage; decrypted plaintext is cached locally in Isar.
- **Realtime chat** — per-chat Supabase channel: live message inserts, typing indicators, read
receipts (✓ / ✓✓ / blue ✓✓), reactions, replies, pin, edit, delete, cursor pagination.
- **Voice messages** — hold-to-record with live waveform + slide-to-cancel; playback with
waveform seekbar and 1×/1.5×/2× speed.
- **Media** — image / video / file sharing with full-screen preview.
- **Profiles** — parallax banner, avatar with animated music ring, badges, bio, social links,
8 accent presets + custom hex (propagates through the whole app).
- **Friends** — search (with mutual counts), requests, accept/block, privacy-aware visibility.
- **Stories** — image & text stories, full-screen viewer (animated progress bars, hold-to-pause,
swipe-to-dismiss, reactions, "seen by"), 24h auto-expiry.
- **Notes** — 60-char status notes with optional music, 24h expiry, home strip.
- **Music presence** — manual status with animated equalizer + album art.
- **Notifications** — FCM push + local notifications, presence, unread badges.
## Table of contents

- [Highlights](#highlights)
- [Feature tour](#feature-tour)
- [Download the APK](#download-the-apk)
- [Tech stack](#tech-stack)
- [Architecture](#architecture)
- [Getting started](#getting-started)
- [Building locally](#building-locally)
- [CI / APK workflow](#ci--apk-workflow)
- [Quality gates & conventions](#quality-gates--conventions)
- [Security model](#security-model)
- [Roadmap & known limitations](#roadmap--known-limitations)
- [License](#license)

---

## Highlights

🔒 **End-to-end encrypted** — real Signal Protocol, not a hand-rolled cipher.
🎨 **Yours to theme** — 8 accent presets + custom hex that re-skins the entire app live.
🎵 **Music presence** — animated equalizer + album art, on your profile and your notes.
📖 **Stories & notes** — full-screen viewer and 60-char status notes, both auto-expiring at 24h.
🪟 **Glassmorphism** — consistent blurred-glass surfaces over a `#0A0A0F` AMOLED base.
⚡ **Realtime** — messages, typing, and read receipts arrive without a refresh.

---

## Feature tour

| Area | What you get |
|---|---|
| **Messaging** | Signal Protocol E2EE · realtime delivery · typing indicators · read receipts (✓ / ✓✓ / blue ✓✓) · reactions, replies, pin, edit, delete · 30-message cursor pagination |
| **Voice** | Hold-to-record with live waveform + slide-to-cancel · playback with waveform seekbar and 1× / 1.5× / 2× speed |
| **Media** | Image / video / file sharing with full-screen, zoomable previews |
| **Profiles** | Parallax banner · avatar with animated music ring · badges · bio · social links · accent color that propagates app-wide |
| **Friends** | Debounced search with mutual counts · requests · accept / decline / block · privacy-aware visibility |
| **Stories** | Image & text stories · animated progress bars · tap-to-navigate · hold-to-pause · swipe-to-dismiss · reactions · "seen by" · 24h expiry |
| **Notes** | 60-char status with optional music · home strip · expand sheet with equalizer |
| **Music status** | Manual song/artist/art entry · animated equalizer · blurred album-art backdrop |
| **Notifications** | FCM push + local notifications · presence · unread badges |

---

## Download the APK

CI builds release APKs on every push to `main`.

- **Latest build:** open the [**Build APK** workflow](https://github.com/KairoCloud/Aether/actions/workflows/build-apk.yml) → pick the most recent run → download the `aether-apk` artifact.
- **Tagged releases:** push a `v*` tag (e.g. `v1.0.0`) and the APKs are attached to a [GitHub Release](https://github.com/KairoCloud/Aether/releases) automatically.

> The workflow produces per-ABI APKs (`arm64-v8a`, `armeabi-v7a`, `x86_64`) plus a universal APK.
> For most modern phones, install the **arm64-v8a** one.

---

## Tech stack

| Layer | Choice |
|---|---|
| Framework | Flutter (stable) |
| Framework | Flutter (stable, 3.22+) |
| State | `flutter_riverpod` + `riverpod_annotation` (codegen) |
| Routing | `go_router` |
| Routing | `go_router` with auth + profile guards |
| Models | `freezed` + `json_serializable` |
| Local DB | `isar` (Signal stores + plaintext cache) |
| Backend | Supabase (Postgres + Auth + Realtime + Storage + Edge Functions) |
| Local DB | `isar` — Signal stores + decrypted-text cache |
| Backend | Supabase — Postgres · Auth · Realtime · Storage · Edge Functions |
| Encryption | `libsignal_protocol_dart` |
| Push | `firebase_messaging` + `flutter_local_notifications` |
| Audio | `just_audio` (playback), `record` (capture) |
| Audio | `just_audio` (playback) · `record` (capture) |
| Fonts | `Sora` (headings) + `DM Sans` (body) via `google_fonts` |

---

## Architecture

Feature-first Clean Architecture. Each feature has `data/` (repositories, datasources),
`domain/` (entities, repository interfaces), and `presentation/` (screens, widgets, providers).
Shared building blocks live under `lib/core/` (theme, constants, widgets, services, utils, db).
Feature-first **Clean Architecture**. Every feature owns its `data/` (repositories,
datasources), `domain/` (entities, repository interfaces), and `presentation/`
(screens, widgets, Riverpod providers). Shared building blocks live in `lib/core/`.

```
lib/
├── core/ # theme, constants, widgets, services (supabase, encryption, media,
│ # notifications), db (isar), utils, animations
├── models/ # Freezed data models (AppUser, Message, Story, Note, ...)
├── providers/ # global Riverpod providers (auth, user, theme, preferences)
├── features/ # auth, home, chats, friends, profile, social, notifications, settings
└── routes/ # go_router config + guards
├── core/
│ ├── animations/ transitions + reusable effect configs
│ ├── constants/ colors, sizes, strings, route names
│ ├── db/ Isar collections + service
│ ├── services/ supabase, encryption, media, notifications, storage
│ ├── theme/ AMOLED theme + AetherColors ThemeExtension (accent)
│ ├── utils/ validators, color/hex, debounce, formatters
│ └── widgets/ 14 reusable glassmorphism components
├── models/ Freezed models (AppUser, Message, Story, Note, ...)
├── providers/ global providers (auth, user, theme, preferences)
├── features/ auth · home · chats · friends · profile · social
│ · notifications · settings · media
└── routes/ go_router config + guards

supabase/
├── migrations/ # schema, RLS, RPCs, storage policies
└── functions/ # send-notification, clean-expired (Deno edge functions)
├── migrations/ schema · indexes · RLS · RPCs · storage policies (0001–0008)
└── functions/ send-notification · clean-expired (Deno edge functions)
```

---

## Getting started

### Prerequisites
- Flutter SDK (3.22+)
- A Supabase project
- (Optional) A Firebase project for push notifications
- Flutter SDK **3.22+**
- A **Supabase** project
- *(Optional)* A **Firebase** project for push notifications

### 1. Install dependencies
### 1 · Backend
Run the SQL migrations in `supabase/migrations/` **in order** (`0001` → `0008`) against your
Supabase project, then deploy the edge functions in `supabase/functions/` and set their secrets
(`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, and `FIREBASE_SERVICE_ACCOUNT` for push).

### 2 · Dependencies & codegen
```bash
flutter pub get
dart run build_runner build --delete-conflicting-outputs
```

### 2. Apply the database schema
Run the SQL migrations in `supabase/migrations/` in order (`0001` → `0008`) against your
Supabase project, then deploy the edge functions in `supabase/functions/`.

### 3. Configure secrets (never hardcoded)
Provide Supabase credentials at build/run time via `--dart-define`:
### 3 · Run (secrets are never hardcoded)
```bash
flutter run \
--dart-define=SUPABASE_URL=https://YOUR_PROJECT.supabase.co \
--dart-define=SUPABASE_ANON_KEY=YOUR_ANON_KEY
```

### 4. (Optional) Push notifications
Add your platform Firebase config (`google-services.json` / `GoogleService-Info.plist` and
`firebase_options.dart`). Without it, notifications are silently disabled — the app still runs.
### 4 · Push notifications *(optional)*
Add your platform Firebase config (`google-services.json` / `GoogleService-Info.plist` and a
generated `firebase_options.dart`). Without it, notifications are silently disabled and the app
still runs.

---

## Quality gates
## Building locally

```bash
dart analyze # 0 issues
flutter test # unit tests for models, mappers, utils
# Universal release APK
flutter build apk --release \
--dart-define=SUPABASE_URL=... --dart-define=SUPABASE_ANON_KEY=...

# Smaller, per-architecture APKs
flutter build apk --release --split-per-abi \
--dart-define=SUPABASE_URL=... --dart-define=SUPABASE_ANON_KEY=...
```
Output lands in `build/app/outputs/flutter-apk/`.

---

## CI / APK workflow

`.github/workflows/build-apk.yml` runs on every push/PR to `main`, on `v*` tags, and on manual
dispatch. Each run:

1. sets up Java 17 + Flutter 3.22.2,
2. installs deps and runs **codegen**,
3. gates on `dart analyze` + `flutter test`,
4. builds per-ABI **and** universal release APKs,
5. uploads them as the `aether-apk` artifact, and
6. on a `v*` tag (or a manual run with *release* checked) attaches them to a GitHub Release.

**Required repository secrets** (Settings → Secrets and variables → Actions):

| Secret | Purpose |
|---|---|
| `SUPABASE_URL` | Your Supabase project URL |
| `SUPABASE_ANON_KEY` | Supabase anon/publishable key |

> Builds are **unsigned** (debug-signed) by default. To ship a Play Store build, add a release
> keystore + signing config and wire it into `android/app/build.gradle`.

---

## Quality gates & conventions

```bash
dart analyze # 0 issues
flutter test # unit tests: models, RPC mappers, validators, hashing
```

Project conventions (enforced): sound null-safety, Freezed on every model, `@riverpod` providers,
constants in `lib/core/constants/`, `ListView.builder` for dynamic lists, 300-line file cap,
no plaintext logging, no custom crypto.
Enforced throughout: sound null-safety · `@freezed` on every model · `@riverpod` providers ·
constants in `lib/core/constants/` · `ListView.builder` for dynamic lists · **300-line file cap** ·
`const` everywhere allowed · **no plaintext logging** · **no custom crypto**.

---

## Security model

- **Text messages are end-to-end encrypted.** Supabase only ever stores ciphertext; the identity
private key never leaves `flutter_secure_storage`; decrypted text is cached on-device in Isar
(the Signal ratchet only allows decrypting a message once).
- **Row Level Security** is enabled on **every** table. Cross-table checks (chat membership,
friendship, blocks) go through `SECURITY DEFINER` helper functions to prevent policy recursion.
- **One-time prekeys** are claimed atomically server-side and replenished when low.
- **No secrets in source** — all credentials are injected via `--dart-define` / CI secrets.

---

## Security notes
## Roadmap & known limitations

- **Text messages are end-to-end encrypted.** Supabase stores ciphertext only.
- **Row Level Security** is enabled on every table; cross-table checks use `SECURITY DEFINER`
helper functions to prevent policy recursion.
- **Known limitations** (tracked for future work): media blobs are transport-encrypted (HTTPS +
RLS) but not E2E-encrypted; group messaging is not yet E2E (1:1 only); presence has no
background teardown/heartbeat.
- 📎 **Media is not E2E-encrypted** — blobs are transport-encrypted (HTTPS + RLS), text is E2EE.
- 👥 **1:1 E2EE only** — group sender-key encryption is not yet implemented.
- 🟢 **Presence has no background teardown** — online status is set on app open; a heartbeat /
lifecycle teardown is pending.
- 🔔 **Push requires Firebase config** — disabled gracefully until added.

---

Expand Down
Loading