A small, standalone cross-platform app demonstrating our mobile engineering: Flutter + Dart, Clean Architecture, BLoC / Cubit, GetIt + injectable for dependency injection, and Freezed + fpdart for immutable state and explicit error handling — the Flutter counterpart of Showcase.Android and Showcase.iOS.
It recreates the same Hero and Portfolio content shown on Showcase.NextJS, rebuilt for phones with a bottom navigation bar and an auto-advancing portfolio pager — no backend, no credentials.
Android (Pixel 9)
| Splash | Home | Portfolio |
|---|---|---|
![]() |
![]() |
![]() |
iOS (iPhone 16 Pro)
| Splash | Home | Portfolio |
|---|---|---|
![]() |
![]() |
![]() |
| Android | iOS |
|---|---|
![]() |
![]() |
Click a preview for the full-quality video.
- Splash — fades in the brand logo and tagline on the brand-green background, then auto-navigates to Home.
- Home — logo with an animated "Powered by AI" sign, headline, subtitle, and a CTA that opens artificialss.ai/portfolio in the browser.
- Portfolio — a starry black-sky background with a
PageViewthat auto-advances every 2 seconds through 7 projects (including this repo), each with an illustratedCustomPaintermockup and a tap-through link.
Clean Architecture in three layers; BLoC/Cubit on top:
lib/
├── core/
│ ├── di/ # GetIt + injectable (injection.dart, app_module.dart, generated config)
│ ├── error/ # Failure — Freezed union, the Left side of Either<Failure, T>
│ ├── usecase/ # UseCase<T, Params> contract
│ ├── services/ # UrlLauncherService → Either<Failure, Unit>
│ ├── theme/ # Brand color tokens ported from the Next.js design system
│ └── widgets/ # Logo (+ animated sign), SpaceBackground
└── features/
├── splash/presentation/ # SplashCubit + SplashPage
├── home/presentation/ # HomeCubit + HomePage
├── main/presentation/ # NavigationCubit + bottom-nav shell
└── portfolio/
├── domain/ # PortfolioItem (Equatable), repository interface, GetPortfolioItems use case
├── data/ # Mock data source + PortfolioRepositoryImpl (TaskEither.tryCatch)
└── presentation/ # PortfolioBloc (+ Freezed state), page, pager, card, mockups
- Domain is pure Dart — no Flutter imports. Use cases and repositories return
Future<Either<Failure, T>>(fpdart), so errors are values, not thrown exceptions. - State is immutable.
PortfolioBloc(event-driven) emits a Freezed union —loading | success | failure— and the UI switches over it exhaustively. Simpler flows (splash, home, tab navigation) useCubits. - DI is
injectableannotations on top of GetIt;build_runnergenerates the wiring. - Animation timing (the 2-second auto-advance) lives in the widget —
PageControlleris a UI concern, not business logic. - The 4k-path logo is pre-compiled to a
.vecasset (vector_graphics_compiler), so it isn't parsed at runtime.
| Layer | Technology |
|---|---|
| Framework | Flutter 3.47 / Dart |
| State | flutter_bloc (Bloc + Cubit), equatable |
| DI | get_it + injectable |
| Immutability / errors | freezed, fpdart (Either, TaskEither) |
| Tests | bloc_test, mocktail, integration_test |
No environment variables, API keys, analytics, or backend of any kind — the portfolio list is a static, deterministic mock data source, matching the other Showcase.* repos. External links open in the system browser.
flutter pub get
dart run build_runner build # generates injectable + freezed code
flutter run # on any Android emulator or iOS simulator
flutter test # unit + bloc testsEvery screenshot and video in docs/screenshots/ was captured with no human touching a device. The app
drives itself (iOS) or is driven over adb (Android) while the platform's own screen recorder runs, and the
raw footage is then trimmed and encoded by script. The whole pipeline is in tool/.
The walkthrough: splash → Home → tap Portfolio → pager auto-advances through all 7 projects → tap Home.
| Step | iOS | Android |
|---|---|---|
| Device | iPhone 16 Pro simulator | Pixel 9 emulator (API 36) |
| Driver | integration_test/demo_test.dart taps the nav bar and waits on the real clock (LiveTestWidgetsFlutterBindingFramePolicy.fullyLive) |
adb shell input tap against the installed release build |
| Recorder | xcrun simctl io <device> recordVideo |
adb shell screenrecord |
| Script | tool/record_ios.sh |
tool/record_android.sh |
Then tool/process_video.sh turns the raw footage into the final assets with
ffmpeg: it trims to the first frame of the splash, scales to 540 px wide, and exports demo.mp4 (H.264) plus
a palette-optimised demo.gif. Screenshots are single full-resolution frames pulled from the same raw video.
# iOS — boot ONE simulator first
xcrun simctl boot <udid>
tool/record_ios.sh <udid> /tmp/ios_raw.mov
tool/process_video.sh /tmp/ios_raw.mov <start-s> <duration-s> docs/screenshots/ios
# Android — boot ONE emulator first
tool/record_android.sh emulator-5554 /tmp/android_raw.mp4
tool/process_video.sh /tmp/android_raw.mp4 <start-s> <duration-s> docs/screenshots/androidThings learned along the way (so the next re-record is quick):
- Run one emulator/simulator at a time. Two at once exhausts memory; Android's low-memory killer then drops the test connection mid-run and the emulator can render a black screen.
- Trim with filters, not
-ss. Simulator and emulator recordings are variable-frame-rate, so seeking by timestamp lands on the wrong frame;fps=30,trim=…keeps the timeline consistent. - Record Android from a release build. A debug build sits on the native launch screen for 20 s+ on a cold emulator; release starts in about a second.
- A widget test can't wait with
Future.delayed(fake clock) — the iOS driver pumps frames in a loop withtester.runAsyncso animations play at true speed. - The recording scripts reproduce the exact commands used to produce the committed assets; they have not been
re-run end-to-end as scripts, so expect to adjust the
<start-s>trim point and, on Android, the tap coordinates for other screen sizes.
MIT — see LICENSE. Free to use, modify, and distribute.
Built with Flutter by Artificialss







