diff --git a/lib/main.dart b/lib/main.dart index 856ea10..43249ab 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -80,12 +80,7 @@ class _HomePageState extends State { @override void initState() { drawController = context.read().drawController; - timer = Timer.periodic(Duration(milliseconds: 34), (Timer t) { - context - .read() - .add(TakeSnapshotEvent(globalKey: Doddler.globalKey)); - print("TakeSnapshotEvent"); - }); + context.read().add(StartRecordingEvent()); super.initState(); } diff --git a/lib/models/recorder_controller.dart b/lib/models/recorder_controller.dart index 695fd89..9a8b0a2 100644 --- a/lib/models/recorder_controller.dart +++ b/lib/models/recorder_controller.dart @@ -1,7 +1,13 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui; +import 'dart:ui'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'frame.dart'; +import 'package:image/image.dart' as image; class RecorderController { final GlobalKey? globalKey; @@ -11,6 +17,44 @@ class RecorderController { this.frames, }); + Future convertImageToUint8List(ui.Image image) async { + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + Uint8List pngBytes = byteData!.buffer.asUint8List(); + return pngBytes; + } + + Future?> export() async { + List bytes = []; + for (final frame in frames!) { + final i = await frame.frame!.toByteData(format: ui.ImageByteFormat.png); + if (i != null) { + bytes.add(RawFrame(32, i)); + } else { + print('Skipped frame while enconding'); + } + } + final result = compute(_export, bytes); + frames!.clear(); + return result; + } + + static Future?> _export(List frames) async { + final animation = image.Animation(); + animation.backgroundColor = Colors.transparent.value; + for (final frame in frames) { + final iAsBytes = frame.image.buffer.asUint8List(); + final decodedImage = image.decodePng(iAsBytes); + + if (decodedImage == null) { + print('Skipped frame while enconding'); + continue; + } + decodedImage.duration = frame.durationInMillis; + animation.addFrame(decodedImage); + } + return image.encodeGifAnimation(animation); + } + @override bool operator ==(Object other) { if (identical(this, other)) return true; @@ -37,3 +81,10 @@ class RecorderController { ); } } + +class RawFrame { + RawFrame(this.durationInMillis, this.image); + + final int durationInMillis; + final ByteData image; +} diff --git a/lib/pages/movie_time_page.dart b/lib/pages/movie_time_page.dart index 2b0896f..b9e4080 100644 --- a/lib/pages/movie_time_page.dart +++ b/lib/pages/movie_time_page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:typed_data'; import 'dart:ui' as ui; @@ -22,53 +23,75 @@ class MovieTimePageState extends State { @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => RecorderBloc(), - child: Scaffold( - body: Center( - child: BlocBuilder( - builder: (context, state) { - if (state is NextFrameState) { - return FutureBuilder( - future: convertImageToUint8List(state.frame!.frame!), - builder: (BuildContext context, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center( - child: Text('Please wait its loading...')); - } else { - if (snapshot.hasError) { - return Center(child: Text('Error: ${snapshot.error}')); - } else { - context.read().add( - CallNextFrameEvent(index: state.frame!.index! + 1)); + return Scaffold( + appBar: AppBar( + actions: [ + IconButton( + onPressed: () { + context.read().add(SaveGifEvent()); + }, + icon: Icon(Icons.not_started)) + ], + ), + body: Center( + child: BlocBuilder( + builder: (context, state) { + if (state is NextFrameState) { + Future.delayed(Duration(seconds: 1)); - return Center( - child: Image.memory( - snapshot.data!, - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height * 0.6, - ), - ); - } // snapshot.data :- get your object which is pass from your downloadData() function - } - }, - ); - } else if (state is InitialRecorderState) { - context.read().add(CallNextFrameEvent(index: 0)); - return const CircularProgressIndicator(); - } - return const CircularProgressIndicator( - color: Colors.red, + return FutureBuilder( + future: convertImageToUint8List(state.frame!.frame!), + builder: + (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: Text('Please wait its loading...')); + } else { + if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else { + return Center( + child: Image.memory( + snapshot.data!, + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height * 0.6, + ), + ); + } // snapshot.data :- get your object which is pass from your downloadData() function + } + }, ); - }, - ), + } else if (state is InitialRecorderState) { + // context.read().add(CallNextFrameEvent(index: 0)); + return const CircularProgressIndicator(); + } else if (state is MessageState) { + return LinearProgressIndicator( + semanticsLabel: state.message, + ); + } else if (state is ShowGifState) { + return Center( + child: Container( + color: Colors.black, + child: Image.memory( + Uint8List.fromList(state.gif!), + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height * 0.6, + ), + ), + ); + } + return const CircularProgressIndicator( + color: Colors.red, + ); + }, ), ), ); } - Future convertImageToUint8List(ui.Image image) { - return Future.value(); + Future convertImageToUint8List(ui.Image image) async { + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + Uint8List pngBytes = byteData!.buffer.asUint8List(); + return pngBytes; } } diff --git a/lib/recorder_bloc/recorder_bloc.dart b/lib/recorder_bloc/recorder_bloc.dart index 266c455..1bcd35b 100644 --- a/lib/recorder_bloc/recorder_bloc.dart +++ b/lib/recorder_bloc/recorder_bloc.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:ui' as ui; import 'package:bloc/bloc.dart'; @@ -7,6 +8,7 @@ import 'package:flutter/rendering.dart'; import 'package:doddle/models/recorder_controller.dart'; +import '../doddler.dart'; import 'recorder_event.dart'; import 'recorder_state.dart'; @@ -14,18 +16,34 @@ class RecorderBloc extends Bloc { RecorderController? recorderController = RecorderController(frames: [], globalKey: GlobalKey()); int index = 0; + bool isRecording = false; + int recordingIndex = 0; + Timer? timer; RecorderBloc({this.recorderController}) : super(InitialRecorderState()); @override Stream mapEventToState(RecorderEvent event) async* { if (event is TakeSnapshotEvent) { - final image = await canvasToImage(event.globalKey!); - final frames = recorderController!.frames; - frames!.add(Frame(frame: image, index: index++)); - recorderController = recorderController!.copyWith(frames: frames); - } else if (event is CallNextFrameEvent) { - yield NextFrameState(frame: recorderController!.frames![event.index]); + if (!isRecording) { + timer = + Timer.periodic(const Duration(milliseconds: 34), (Timer t) async { + final frames = recorderController!.frames; + + final image = await canvasToImage(event.globalKey!); + frames!.add(Frame(frame: image, index: index++)); + recorderController = recorderController!.copyWith(frames: frames); + }); + } + } else if (event is SaveGifEvent) { + add(StopRecordingEvent()); + yield MessageState("Waiting ... "); + final gif = await recorderController!.export(); + yield ShowGifState(gif: gif); + } else if (event is StartRecordingEvent) { + add(TakeSnapshotEvent(globalKey: Doddler.globalKey)); + } else if (event is StopRecordingEvent) { + timer!.cancel(); } } diff --git a/lib/recorder_bloc/recorder_event.dart b/lib/recorder_bloc/recorder_event.dart index dd25ec9..030fed5 100644 --- a/lib/recorder_bloc/recorder_event.dart +++ b/lib/recorder_bloc/recorder_event.dart @@ -16,6 +16,8 @@ class PlayVideoEvent extends RecorderEvent {} class StopVideoEvent extends RecorderEvent {} +class SaveGifEvent extends RecorderEvent {} + class CallNextFrameEvent extends RecorderEvent { final int index; CallNextFrameEvent({ diff --git a/lib/recorder_bloc/recorder_state.dart b/lib/recorder_bloc/recorder_state.dart index a2eec74..74676a1 100644 --- a/lib/recorder_bloc/recorder_state.dart +++ b/lib/recorder_bloc/recorder_state.dart @@ -10,3 +10,16 @@ class NextFrameState extends RecorderState { this.frame, }); } + +class MessageState extends RecorderState { + final String message; + + MessageState(this.message); +} + +class ShowGifState extends RecorderState { + final gif; + ShowGifState({ + this.gif, + }); +} diff --git a/lib/widgets/tools_widget.dart b/lib/widgets/tools_widget.dart index d502729..ab5f966 100644 --- a/lib/widgets/tools_widget.dart +++ b/lib/widgets/tools_widget.dart @@ -102,7 +102,7 @@ class _ToolsWidgetState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => const MovieTimePage()), + builder: (context) => MovieTimePage()), ); }, ),