A crowdsourced app for finding condominium blocks inside the big
condominium projects of Addis Ababa (e.g. Koye Feche Project 16,
Block 56). Flutter on Android and Web for users; one Laravel
app (its own repo: condominium_finder_server, kept as a sibling
folder) is the entire backend — REST API + Filament admin panel — and
runs on any shared hosting plan.
- Guests can search, browse the map and navigate to any block — no login.
- Signed-in users can add projects, add blocks, confirm/report block locations, and suggest corrections ("block 15 is here, not there"). A block becomes Verified after 3 independent confirmations (you can't confirm your own).
- Moderation: new submissions and corrections are pending until an admin approves them in the admin panel — then they appear on the map. 3+ reports automatically pull a block's Verified badge for review.
- Trust & ranking: approved contributions earn points (project 10, block 5, correction 3, confirmation vote 1). At 50 points a user becomes trusted and skips the review queue; a leaderboard lives in the profile.
- No paid services: flutter_map + OSM/Stadia tiles (no map SDK billing), your existing shared hosting for the API and database, and external-app navigation via URL schemes (no Directions API).
| Layer | Choice |
|---|---|
| App | Flutter (Material 3), Riverpod 3 — Android + Web |
| Map | flutter_map 8 + OpenStreetMap tiles (Stadia Maps optional) |
| Backend | Laravel 13 (condominium_finder_server repo): REST API + Filament 5 admin panel |
| Database | SQLite locally (auto-created); MySQL on the server |
| Auth | Email/password + free Telegram bot sign-in (all through the API) |
| Offline | Hive cache of recently viewed projects/blocks + bundled demo data |
| Navigation | url_launcher handoff to the Google Maps app / web |
| Location | geolocator one-shot "my location" (no background tracking) |
Swiss-modernism utility palette chosen for a navigation tool: navy
#1E3A5F (brand), route blue #2563EB (interactive), green #059669
(reserved for the verified state), slate neutrals, full light + dark
themes, WCAG-AA contrast. Typography is Figtree (headings/labels) and
Noto Sans (body) with Noto Sans Ethiopic bundled so the Amharic UI
renders natively on every device. All fonts ship in the binary — no
runtime font fetching.
No MySQL, no config files. On its first request the server creates a SQLite database, migrates it, and seeds the 10 real Addis areas plus a local-only admin (admin@local / admin123).
# Terminal 1 — API + admin panel → http://localhost:8000/admin
cd ../condominium_finder_server && php artisan serve
# Terminal 2 — the app (env.json already points at localhost:8000/api)
flutter run --dart-define-from-file=env.json # Android
flutter run -d chrome --dart-define-from-file=env.json # WebTry the loop: register a user in the app → add a block (it shows "pending review") → approve it in the panel → it appears on the map.
No PHP installed at all? flutter run with no dart-defines still works
as a pure demo with bundled sample data.
- In cPanel, create a MySQL database + user, and make sure your domain has HTTPS (SSL/TLS Status → Run AutoSSL — it's free; Telegram sign-in and safe token transport need it). Requires PHP 8.3+ (MultiPHP Manager).
- Upload the
condominium_finder_serverfolder (includingvendor/) intopublic_html/. - Rename the included
.env.productionto.envand fill in the 6 marked values: your domain (withhttps://), the MySQL database/user/password from step 1, and your admin email + password. - Optional — Sign in with Telegram (free): message
@BotFather, send
/newbot, pick a name and username, and paste the token it replies with intoTELEGRAM_BOT_TOKEN=in the same.env. That's the entire setup — the bot username and webhook configure themselves, and the app shows the button automatically. Leave it empty to hide the button. - Open
https://your-domain.com/api/health— the database installs and seeds itself on this first load, your admin account included. You should see"status":"ok"(and"telegram":trueif you did step 4). The panel is athttps://your-domain.com/admin.
Re-uploading the server folder later is a safe deploy — all state lives in MySQL (schedule backups in cPanel: Backup → MySQL databases).
Point env.json at the live server first:
{ "API_BASE_URL": "https://your-domain.com/api", "STADIA_API_KEY": "" }(STADIA_API_KEY is optional — empty means plain OSM tiles.)
One-time release signing — required before sharing the APK or publishing to Play; without it builds are debug-signed and users can't install updates:
cd android
keytool -genkey -v -keystore upload-keystore.jks -keyalg RSA \
-keysize 2048 -validity 10000 -alias upload
cp key.properties.example key.properties # then fill in the paths/passwordsKeep upload-keystore.jks + passwords backed up somewhere safe (losing
them means you can never update the app); both files are gitignored.
Then build:
flutter build apk --release --dart-define-from-file=env.json # direct APK
flutter build appbundle --release --dart-define-from-file=env.json # Play StoreThe web build is served from the /app/ path on your domain, so it
must be built with that base href:
flutter build web --release --base-href=/app/ --dart-define-from-file=env.jsonUpload the contents of build/web/ to public_html/public/app/ and
set CONDO_WEB_APP_URL=https://your-domain.com/app in the server
.env. The app is then live at https://your-domain.com/app/.
https://your-domain.com/api/healthsaysok(+"telegram":trueif configured), and/adminaccepts your admin login.- Sign in with Telegram from a real phone: tap the button → bot chat opens → Start → you're signed in.
- Send yourself a password-reset email from the live site — shared
hosting mail sometimes lands in spam; if so, switch
MAIL_*in.envto your host's SMTP account. - Full loop: register → add a block (pending) → approve in
/admin→ it appears on the map.
- Geo without PostGIS: coordinates are
DECIMAL(10,7)lat/lng columns. Condominium and project footprints are free-form polygons drawn corner-by-corner in the app (older projects fall back to a generated hexagon); "which area contains this tap" is a client-side ray-casting test, innermost shape first — no spatial extension needed at this scale. Tapping a condominium highlights it and shows the project footprints nested inside it at any zoom. - Search:
GET /search?q=uses indexedLIKEmatching across project names, area names and block numbers, grouped in the UI into Projects and Blocks. Debounced 300 ms in the client. - Crowdsourcing integrity:
block_verifications/block_reportshaveUNIQUE(block_id, user_id)constraints (no double-voting); the API maintainsverified_countand flipsis_verifiedat 3+. Submitters can't confirm their own blocks, and 3+ reports pull the Verified badge until an admin reviews. - Moderation & trust:
projects/blockscarry apending/approved/rejectedstatus; public endpoints only serve approved rows. Approvals award points; at 50 points contributors become trusted and publish instantly. - Auth: bearer tokens (stored SHA-256-hashed, 90-day expiry) issued
by the API; passwords bcrypt-hashed. All write endpoints require
Authorization: Bearer. Login, register, and the password-reset endpoints are rate limited (per IP / email), and reset codes die after 5 wrong attempts. The moderation panel (/admin) only admits accounts withrole = admin. - Navigation:
google.navigation:/geo:intents on Android; the universal Google Maps URL in a new tab on web. Zero API calls. - Offline: last-fetched projects/blocks are cached in Hive and served with an "offline" banner when the network fails.
- Responsive: bottom sheets and stacked forms on phones; centered dialogs, a fixed-width search panel, and side-by-side map+form layouts from 720 px up. Verified on 360 px phones through desktop.
- Language: English / Amharic (አማርኛ) toggle, persisted locally.
lib/
core/ config (dart-define), theme + design tokens, breakpoints,
EN/AM strings, constants
models/ CondoProject, CondoBlock, SearchResult, UserProfile,
Leaderboard
data/ api_client (bearer token), repository (+cache/demo), Hive
services/ auth controller (email/Telegram), maps launcher,
location (GPS)
providers.dart Riverpod 3 providers
screens/ map, search panel, project/block sheets, add flows,
suggest-edit flow, auth, profile (+leaderboard)
assets/fonts/ Figtree, Noto Sans, Noto Sans Ethiopic (bundled)
The backend is its own repo, condominium_finder_server (sibling
folder, see its README): routes/api.php (the REST API consumed by the
app), app/Http/Controllers/Api/, app/Filament/ (admin panel),
database/migrations/ (auto-installing schema), and .env.production
(ready-to-upload server settings).
| Method & path | Auth | Purpose |
|---|---|---|
GET /projects |
– | approved projects with block counts |
GET /projects/{id}/blocks |
– | approved blocks of a project |
GET /blocks/{id} |
– | one approved block |
GET /search?q= |
– | fuzzy search, approved projects + blocks |
GET /leaderboard |
opt | top contributors (+ your rank when signed in) |
GET /privacy |
– | privacy policy page (link it in Play Store) |
POST /projects |
✓ | add project → pending unless trusted |
POST /blocks |
✓ | add block → pending unless trusted |
POST /blocks/{id}/verify |
✓ | confirm location (once, not your own) |
POST /blocks/{id}/report |
✓ | report incorrect (3+ pulls Verified) |
POST /blocks/{id}/suggest-edit |
✓ | propose corrected pin → review queue |
POST /auth/register /login /logout |
– / ✓ | email auth, returns bearer token (rate limited) |
POST /auth/forgot /reset |
– | 6-digit email reset code (attempt-capped) |
GET /auth/me · PATCH /me |
✓ | session profile / display name |
DELETE /me |
✓ | permanent account deletion |
GET /me/contributions |
✓ | my projects + blocks, incl. pending status |
POST /auth/telegram/start /poll |
– | Telegram bot sign-in (one-time code + poll) |