class GetDrawUseCase {
final DrawRepository repo;
Future<Either<Failure, Draw>> call(String id) =>
repo.getDrawById(id);
} abstract class DrawRepository {
Future<Either<Failure, Draw>> getDrawById(String id);
Future<Either<Failure, void>> saveDraw(Draw draw);
Stream<List<Draw>> watchAllDraws();
} export const generateInterpretation =
onCall(async (request) => {
const { uid } = request.auth;
const { cards, intention } = request.data;
await validateQuota(uid);
const result = await geminiAI.generate({
cards, intention, lang: "en"
});
await incrementQuota(uid);
return { interpretation: result };
}); MultiBlocProvider(
providers: [
BlocProvider(create: (_) => AuthCubit(
signIn: sl(), signOut: sl(), watchAuth: sl(),
)),
BlocProvider(create: (_) => DrawCubit(
getDraw: sl(), createDraw: sl(),
)),
BlocProvider(create: (_) => JournalCubit(
getEntries: sl(), deleteEntry: sl(),
)),
],
child: const App(),
) class Draw extends Equatable {
final String id;
final List<TarotCard> cards;
final DateTime createdAt;
final Interpretation? interpretation;
} class DrawRemoteDataSource {
final FirebaseFirestore _firestore;
Future<DrawModel> getDraw(String id) async {
final doc = await _firestore
.collection('users')
.doc(userId)
.collection('draws')
.doc(id)
.get();
return DrawModel.fromFirestore(doc);
}
} GoRouter router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: 'draw/:id',
builder: (context, state) =>
DrawPage(id: state.pathParameters['id']!),
),
],
),
],
); match /users/{userId}/draws/{drawId} {
allow read: if request.auth.uid == userId;
allow create: if request.auth.uid == userId
&& request.resource.data.keys().hasAll(
['cards', 'intention', 'createdAt']
);
allow delete: if request.auth.uid == userId;
} class DrawCubit extends Cubit<DrawState> {
final GetDrawUseCase _getDraw;
final CreateDrawUseCase _createDraw;
Future<void> loadDraw(String id) async {
emit(DrawLoading());
final result = await _getDraw(id);
result.fold(
(failure) => emit(DrawError(failure)),
(draw) => emit(DrawLoaded(draw)),
);
}
} sealed class DrawState extends Equatable {}
class DrawInitial extends DrawState {}
class DrawLoading extends DrawState {}
class DrawLoaded extends DrawState {
final Draw draw;
}
class DrawError extends DrawState {
final Failure failure;
} class DrawRepositoryImpl implements DrawRepository {
final DrawRemoteDataSource remote;
final NetworkInfo networkInfo;
@override
Future<Either<Failure, Draw>> getDrawById(String id) async {
try {
final model = await remote.getDraw(id);
return Right(model.toEntity());
} on FirebaseException catch (e) {
return Left(ServerFailure(e.message));
}
}
} abstract class DrawRepository {
Future<Either<Failure, Draw>> getDrawById(String id);
Future<Either<Failure, void>> saveDraw(Draw draw);
Stream<List<Draw>> watchAllDraws();
} export const generateInterpretation =
onCall(async (request) => {
const { uid } = request.auth;
const { cards, intention } = request.data;
await validateQuota(uid);
const result = await geminiAI.generate({
cards, intention, lang: "en"
});
await incrementQuota(uid);
return { interpretation: result };
}); MultiBlocProvider(
providers: [
BlocProvider(create: (_) => AuthCubit(
signIn: sl(), signOut: sl(), watchAuth: sl(),
)),
BlocProvider(create: (_) => DrawCubit(
getDraw: sl(), createDraw: sl(),
)),
BlocProvider(create: (_) => JournalCubit(
getEntries: sl(), deleteEntry: sl(),
)),
],
child: const App(),
) class GetDrawUseCase {
final DrawRepository repo;
Future<Either<Failure, Draw>> call(String id) =>
repo.getDrawById(id);
} abstract class DrawRepository {
Future<Either<Failure, Draw>> getDrawById(String id);
Future<Either<Failure, void>> saveDraw(Draw draw);
Stream<List<Draw>> watchAllDraws();
} class DrawRemoteDataSource {
final FirebaseFirestore _firestore;
Future<DrawModel> getDraw(String id) async {
final doc = await _firestore
.collection('users')
.doc(userId)
.collection('draws')
.doc(id)
.get();
return DrawModel.fromFirestore(doc);
}
} GoRouter router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: 'draw/:id',
builder: (context, state) =>
DrawPage(id: state.pathParameters['id']!),
),
],
),
],
); match /users/{userId}/draws/{drawId} {
allow read: if request.auth.uid == userId;
allow create: if request.auth.uid == userId
&& request.resource.data.keys().hasAll(
['cards', 'intention', 'createdAt']
);
allow delete: if request.auth.uid == userId;
} class Draw extends Equatable {
final String id;
final List<TarotCard> cards;
final DateTime createdAt;
final Interpretation? interpretation;
} sealed class DrawState extends Equatable {}
class DrawInitial extends DrawState {}
class DrawLoading extends DrawState {}
class DrawLoaded extends DrawState {
final Draw draw;
}
class DrawError extends DrawState {
final Failure failure;
} class DrawRepositoryImpl implements DrawRepository {
final DrawRemoteDataSource remote;
final NetworkInfo networkInfo;
@override
Future<Either<Failure, Draw>> getDrawById(String id) async {
try {
final model = await remote.getDraw(id);
return Right(model.toEntity());
} on FirebaseException catch (e) {
return Left(ServerFailure(e.message));
}
}
} factory DrawModel.fromFirestore(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
return DrawModel(
id: doc.id,
cards: (data['cards'] as List)
.map((c) => TarotCard.fromMap(c))
.toList(),
intention: data['intention'] ?? '',
createdAt: (data['createdAt'] as Timestamp).toDate(),
);
} class DrawCubit extends Cubit<DrawState> {
final GetDrawUseCase _getDraw;
final CreateDrawUseCase _createDraw;
Future<void> loadDraw(String id) async {
emit(DrawLoading());
final result = await _getDraw(id);
result.fold(
(failure) => emit(DrawError(failure)),
(draw) => emit(DrawLoaded(draw)),
);
}
} You have an app idea
We align on scope & goals
I turn it into a real app
Published on the stores
[ what I use ]
Built from scratch to store, live in production.
Each feature is independent. Easy to maintain, easy to test, easy to scale.
lib/features/draw/ ├── data/ │ ├── datasources/ │ └── repositories/ ├── domain/ │ ├── entities/ │ └── usecases/ └── presentation/ ├── cubit/ └── pages/
Smart AI readings. Built with Gemini, secured server-side.
Read-only client. All business logic runs server-side.
Quick sign-in with email, Google, or Apple.
Freemium model. Users pay, upgrade, cancel — everything handled automatically.
3 languages. UI, AI responses, card data — everything adapts to the user's language.
Fast, responsive UI. Every action gives instant feedback.
emit(DrawLoading()); final result = await drawUseCase(params); emit(DrawComplete(result));
| [ live ] | ||
| MindTarot | Tarot app with AI interpretations — live on stores | website play ios |
| [ practice ] | ||
| study_flutter | Hands-on Flutter practice — everything coded by hand, no AI | github |
| dart_algos | Algorithmic logic practice in Dart | github |
| chat_app | Tutorial project — real-time chat with Firebase Auth & Firestore | github |
| la_tarot_academie | Interactive tarot draw tool used in live webinars | demo github |
| leadmagnet_audio | Audio lead-magnet landing page for a tarot masterclass | live github |
| [ client work ] | ||
| la-tarot-academy-website | Full website for a tarot academy | in progress |
Flutter developer · Self-taught
I started building the website for my cleaning business with no-code tools. They didn't let me do what I really wanted. All the no-code and low-code tools, all the courses — they were bad investments that disappointed me. So I decided to learn to code myself, so I could build my own projects.
It was hard at the beginning. The determination I built from eight years of jiu-jitsu got me through it. I know that learning means producing and moving forward through the obstacles and breakthroughs, layer by layer.
Over a year later, I have solid experience behind me and an app running in production. Ready for the next project.
Based in Thailand · Working internationally
Available for freelance projects and full-time roles.
[ what I use ]
Built from scratch to store, live in production.
Each feature is independent. Easy to maintain, easy to test, easy to scale.
lib/features/draw/ ├── data/ │ ├── datasources/ │ └── repositories/ ├── domain/ │ ├── entities/ │ └── usecases/ └── presentation/ ├── cubit/ └── pages/
Smart AI readings. Built with Gemini, secured server-side.
Read-only client. All business logic runs server-side.
Quick sign-in with email, Google, or Apple.
Freemium model. Users pay, upgrade, cancel — everything handled automatically.
3 languages. UI, AI responses, card data — everything adapts to the user's language.
Fast, responsive UI. Every action gives instant feedback.
emit(DrawLoading()); final result = await drawUseCase(params); emit(DrawComplete(result));
Free to download. Free trial available.