Skip to content

Instantly share code, notes, and snippets.

@romanejaquez
Last active November 17, 2025 00:02
Show Gist options
  • Select an option

  • Save romanejaquez/0317cb3f930181fe9018831b4a4e8ea7 to your computer and use it in GitHub Desktop.

Select an option

Save romanejaquez/0317cb3f930181fe9018831b4a4e8ea7 to your computer and use it in GitHub Desktop.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:finger_painter/finger_painter.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:flutter_riverpod/legacy.dart';
import 'package:myapp/firebase_options.dart';
import 'dart:ui' as ui;
// root widget
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// initialize Firebase
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const ProviderScope(child: GeminiPictionaryApp()));
}
class GeminiPictionaryApp extends StatelessWidget {
const GeminiPictionaryApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: GeminiPictionaryHome());
}
}
class GeminiPictionaryHome extends StatelessWidget {
const GeminiPictionaryHome({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
centerTitle: true,
title: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.collections, color: Colors.purple),
SizedBox(width: 16),
Text('Pictionary!', style: TextStyle(color: Colors.purple)),
],
),
),
body: const Center(
child: Column(
children: [
PaintingSurface(),
PaintingValidationControls(),
PaintingValidator(),
],
),
),
);
}
}
final painterControllerProvider = Provider<PainterController>((ref) {
return PainterController()
..setPenType(PenType.paintbrush)
..setStrokeColor(Colors.black)
..setMinStrokeWidth(3)
..setMaxStrokeWidth(10)
..setBlurSigma(0.0)
..setBlendMode(ui.BlendMode.srcOver);
});
class PaintingSurface extends ConsumerStatefulWidget {
const PaintingSurface({super.key});
@override
ConsumerState<PaintingSurface> createState() => _PaintingSurfaceState();
}
class _PaintingSurfaceState extends ConsumerState<PaintingSurface> {
late PainterController painterController;
@override
void initState() {
super.initState();
initializePainterController();
}
void initializePainterController() async {
painterController = ref.read(painterControllerProvider);
ByteData bgImg = await rootBundle.load('assets/bgimg.png');
painterController.setBackgroundImage(bgImg.buffer.asUint8List());
}
@override
Widget build(BuildContext context) {
return Painter(
controller: painterController,
backgroundColor: Colors.white,
size: Size(MediaQuery.sizeOf(context).width, 300),
child: const ColoredBox(color: Colors.white),
);
}
}
class PaintingValidationControls extends ConsumerWidget {
const PaintingValidationControls({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Padding(
padding: const EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () async {
await ref.read(paintingServiceProvider).validatePicture();
},
icon: const Icon(Icons.image),
label: const Text('Validate Picture'),
),
const SizedBox(width: 24),
ElevatedButton.icon(
icon: const Icon(Icons.clear),
onPressed: () async {
ref.read(paintingServiceProvider).clear();
},
label: const Text('Clear'),
),
],
),
);
}
}
class ImageResultPayload {
final String name;
final Uint8List imgBytes;
const ImageResultPayload({required this.name, required this.imgBytes});
}
final imageResultNotifier = StateProvider<ImageResultPayload?>((ref) => null);
class GeminiPaintingService {
final Ref ref;
const GeminiPaintingService(this.ref);
Future<void> validatePicture() async {
try {
// get the drawn image as as sequence of bytes
Uint8List imgBytes = ref.read(painterControllerProvider).getImageBytes()!;
// create an instance of type GenerativeModel through the Firebase AI Logic SDK
// specifying the type of model and type of response expected from Gemini
final googleAIInstance = FirebaseAI.googleAI();
GenerativeModel model = googleAIInstance.generativeModel(
model: 'gemini-2.5-flash',
generationConfig: GenerationConfig(
responseMimeType: 'application/json',
responseSchema: Schema.object(
properties: {"guessedImage": Schema.string()},
),
),
);
// assemble a message that contains
// that contains a TextPart y un InlineDataPart, with a mimeType of 'image/png'
final content = Content.multi([
// text part (prompt)
TextPart(
'''
You are a Pictionary player. Given the provided image, identify its content
and respond in JSON format with the following properties:
{ "guessedImage": the name of the drawn image }
'''),
// image part
InlineDataPart('image/png', imgBytes),
]);
var response = await model.generateContent([content]);
var jsonPayload = json.decode(response.text!);
ref.read(imageResultNotifier.notifier).state = ImageResultPayload(
name: jsonPayload['guessedImage'],
imgBytes: imgBytes,
);
} on Exception catch (e) {
debugPrint(e.toString());
}
}
void clear() {
ref.read(painterControllerProvider).clearContent(clearColor: Colors.white);
ref.read(imageResultNotifier.notifier).state = null;
}
}
final paintingServiceProvider = Provider<GeminiPaintingService>((ref) {
return GeminiPaintingService(ref);
});
class PaintingValidator extends ConsumerWidget {
const PaintingValidator({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final imgPayload = ref.watch(imageResultNotifier);
if (imgPayload == null) {
return const SizedBox.shrink();
}
return Container(
margin: const EdgeInsets.all(20),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Row(
children: [
Image.memory(imgPayload.imgBytes, width: 200, height: 200),
const SizedBox(width: 24),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Guessed image:',
style: TextStyle(fontSize: 20, color: Colors.purpleAccent),
),
Text(
imgPayload.name,
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment