Drop yourself into TΓΌrkiye's most famous places with AI, then share it.
Lokaly is a social photo app. You upload a photo of yourself, pick a city and a landmark, and AI realistically places you at that spot β keeping your face and appearance intact. The generated image lands in a social feed where it can be liked, commented on, and shared.
| Login | Google Sign-In |
|---|---|
| 1 Β· Select City | 2 Β· Select Place | 3 Β· Select Photo |
|---|---|---|
| Generating | Result | Native Share |
|---|---|---|
| Feed | Comments | Profile |
|---|---|---|
| Edit Profile | Settings (EN) | Settings (TR) |
|---|---|---|
- πΈ AI placement β upload your photo and get realistically placed at a famous Turkish landmark
- ποΈ 81 cities β landmark suggestions are fetched live per city via Gemini
- π€ Person-preserving generation β your face and appearance stay identical to the reference
- π§ Guided 4-step create flow β City β Place β Photo β Result with a progress bar
- β€οΈ Social feed β like, comment, and follow other users
- π€ Share β share generated photos through the native share sheet
- π€ Profiles β stats, post grid, editable username / bio / avatar
- π Theme β Light / Dark / System
- π Localization β Turkish & English (
easy_localization) - π Auth β email/password and Google Sign-In via Firebase
| Layer | Technology |
|---|---|
| Framework | Flutter (Dart SDK ^3.12) |
| Page state | flutter_bloc (Cubit) |
| Global state | provider (AppState) |
| Navigation | go_router |
| Extensions | kartal |
| Auth | firebase_auth + google_sign_in |
| Database | cloud_firestore |
| Storage | firebase_storage |
| AI (landmarks) | firebase_ai β Gemini 2.5 Flash |
| AI (image) | firebase_ai β Gemini 2.5 Flash Image |
| Images | image_picker, cached_network_image, flutter_image_compress |
| Sharing | share_plus |
| Localization | easy_localization |
| Fonts | google_fonts (Manrope) |
| Env | flutter_dotenv |
| Models | freezed + json_serializable |
| Unit tests | flutter_test, mockito, bloc_test |
| Integration tests | patrol |
- Flutter SDK (latest stable, Dart
^3.12) - A Firebase project with Authentication (Email + Google), Firestore, and Storage enabled
- Firebase AI (Gemini) enabled on the project
flutterfire_cliinstalled
git clone https://github.com/burakJs/Lokaly.git
cd lokaly
flutter pub getConfigure Firebase (generates firebase_options.dart and platform config):
dart pub global activate flutterfire_cli
flutterfire configureCreate a .env file in the project root (it is referenced as an asset in pubspec.yaml):
# Optional β only needed for Google Sign-In on Android.
# The Google "Web client" OAuth ID (serverClientId). Leave empty to rely on
# platform config (google-services.json / Info.plist).
GOOGLE_SERVER_CLIENT_ID=
# Gemini access is provided through the Firebase project (firebase_ai),
# so no separate Gemini API key is required at runtime.The app loads
.envon startup, so the file must exist (it can be empty).
Run code generation (freezed / json_serializable / mockito):
dart run build_runner build --delete-conflicting-outputsRun the app:
flutter run# Unit & widget tests
flutter test
# With coverage
flutter test --coverage
# Patrol integration / E2E tests (Android)
patrol test
# Patrol integration / E2E tests (iOS)
patrol test --iosThe codebase follows a core / feature / product layering convention.
lib/
βββ main.dart # App bootstrap: init + global providers + runApp
βββ app/ # LokalyApp (MaterialApp) + go_router config
βββ core/ # Infrastructure: theme, init, constants (UI-agnostic)
β βββ theme/ # AppColors, text styles, ThemeData (light/dark)
β βββ constants/ # App constants + Turkey cities
β βββ init/ # dotenv + Firebase initialization
βββ feature/ # One folder per screen: cubit/ (state) + view/ (UI)
β βββ splash/ # auth-state redirect
β βββ auth/ # login + register
β βββ feed/ # home feed
β βββ create/ # city β landmark β photo β generating β result
β βββ comments/ # realtime comments sheet
β βββ profile/ # profile + edit profile
β βββ post_detail/ # single post view
β βββ settings/ # theme + language + account
βββ product/ # Cross-feature shared code
βββ state/ # AppState (global ChangeNotifier)
βββ models/ # user / post / comment models
βββ services/ # auth, firestore, gemini, image gen, share, etc.
βββ widgets/ # shared widgets (logo, post card, badges...)
βββ navigation/ # route paths
test/ # Unit + widget + cubit tests (flutter_test)
integration_test/ # Patrol E2E tests
screenshots/ # Images used in this README
Two-layer state management:
- Page state β each screen owns a Cubit (
flutter_bloc) underfeature/<x>/cubit/. Holds screen-local concerns: form, loading, error, lists, selection. - Global state β
AppState(provider/ChangeNotifier) underproduct/state/. Holds app-wide concerns: theme mode, current user, locale.
View (feature/<x>/view)
β (BlocBuilder / BlocProvider)
Cubit (feature/<x>/cubit) ββ AppState (global, via provider)
β (constructor injection)
Services (product/services: Auth, Firestore, Gemini, Image, Share)
β
Firebase Β· Gemini (firebase_ai)
Rule: service calls happen inside Cubits, never in the view. Cubits receive their services via constructor injection so they can be mocked in tests.
users/{uid} β username, email, photoURL, bio,
followersCount, followingCount, createdAt
posts/{postId} β userId, userUsername, userPhotoURL,
imageURL, city, landmark,
likesCount, commentsCount, createdAt
posts/{postId}/likes/{uid} β createdAt
posts/{postId}/comments/{commentId} β userId, userUsername, text, createdAt
following/{uid}/userFollowing/{targetUid} β createdAt
followers/{uid}/userFollowers/{targetUid} β createdAt
Likes and follows are written via Firestore transactions so the denormalized counters stay consistent.
Built with Flutter Β· Firebase Β· Gemini.