@ -21,13 +21,17 @@ import 'widgets/youtube_video_app.dart';
class ImageEmbedBuilder extends EmbedBuilder {
ImageEmbedBuilder ( {
this . onImageRemovedCallback ,
this . shouldRemoveImageCallback ,
required this . imageProviderBuilder ,
required this . imageErrorWidgetBuilder ,
required this . onImageRemovedCallback ,
required this . shouldRemoveImageCallback ,
this . forceUseMobileOptionMenu = false ,
} ) ;
final ImageEmbedBuilderOnRemovedCallback ? onImageRemovedCallback ;
final ImageEmbedBuilderWillRemoveCallback ? shouldRemoveImageCallback ;
final bool forceUseMobileOptionMenu ;
final ImageEmbedBuilderProviderBuilder ? imageProviderBuilder ;
final ImageEmbedBuilderErrorWidgetBuilder ? imageErrorWidgetBuilder ;
@ override
String get key = > BlockEmbed . imageType ;
@ -50,33 +54,73 @@ class ImageEmbedBuilder extends EmbedBuilder {
final imageUrl = standardizeImageUrl ( node . value . data ) ;
OptionalSize ? imageSize ;
final style = node . style . attributes [ ' style ' ] ;
if ( base . isMobile ( ) & & style ! = null ) {
final attrs = base . parseKeyValuePairs ( style . value . toString ( ) , {
Attribute . mobileWidth ,
Attribute . mobileHeight ,
Attribute . mobileMargin ,
Attribute . mobileAlignment
} ) ;
/ / TODO: Please use the one from [ Attribute . margin ]
const marginKey = ' margin ' ;
/ / TODO: Please use the one from [ Attribute . alignment ]
const alignmentKey = ' alignment ' ;
if ( style ! = null ) {
final attrs = base . isMobile ( )
? base . parseKeyValuePairs ( style . value . toString ( ) , {
Attribute . mobileWidth ,
Attribute . mobileHeight ,
Attribute . mobileMargin ,
Attribute . mobileAlignment ,
} )
: base . parseKeyValuePairs ( style . value . toString ( ) , {
Attribute . width . key ,
Attribute . height . key ,
marginKey ,
alignmentKey ,
} ) ;
if ( attrs . isNotEmpty ) {
final width = double . tryParse (
( base . isMobile ( )
? attrs [ Attribute . mobileWidth ]
: attrs [ Attribute . width . key ] ) ? ?
' ' ,
) ;
final height = double . tryParse (
( base . isMobile ( )
? attrs [ Attribute . mobileHeight ]
: attrs [ Attribute . height . key ] ) ? ?
' ' ,
) ;
final alignment = base . getAlignment ( base . isMobile ( )
? attrs [ Attribute . mobileAlignment ]
: attrs [ alignmentKey ] ) ;
final margin = ( base . isMobile ( )
? double . tryParse ( Attribute . mobileMargin )
: double . tryParse ( marginKey ) ) ? ?
0.0 ;
assert (
attrs [ Attribute . mobileWidth ] ! = null & &
attrs [ Attribute . mobileHeight ] ! = null ,
' mobileWidth and mobileHeight must be specified ' ) ;
final w = double . parse ( attrs [ Attribute . mobileWidth ] ! ) ;
final h = double . parse ( attrs [ Attribute . mobileHeight ] ! ) ;
imageSize = OptionalSize ( w , h ) ;
final m = attrs [ Attribute . mobileMargin ] = = null
? 0.0
: double . parse ( attrs [ Attribute . mobileMargin ] ! ) ;
final a = base . getAlignment ( attrs [ Attribute . mobileAlignment ] ) ;
width ! = null & & height ! = null ,
base . isMobile ( )
? ' mobileWidth and mobileHeight must be specified '
: ' width and height must be specified ' ,
) ;
imageSize = OptionalSize ( width , height ) ;
image = Padding (
padding: EdgeInsets . all ( m ) ,
child: imageByUrl ( imageUrl , width: w , height: h , alignment: a ) ) ;
padding: EdgeInsets . all ( margin ) ,
child: getQuillImageByUrl (
imageUrl ,
width: width ,
height: height ,
alignment: alignment ,
imageProviderBuilder: imageProviderBuilder ,
imageErrorWidgetBuilder: imageErrorWidgetBuilder ,
) ,
) ;
}
}
if ( imageSize = = null ) {
image = imageByUrl ( imageUrl ) ;
image = getQuillImageByUrl (
imageUrl ,
imageProviderBuilder: imageProviderBuilder ,
imageErrorWidgetBuilder: imageErrorWidgetBuilder ,
) ;
imageSize = OptionalSize ( ( image as Image ) . width , image . height ) ;
}
@ -86,34 +130,6 @@ class ImageEmbedBuilder extends EmbedBuilder {
showDialog (
context: context ,
builder: ( context ) {
final resizeOption = _SimpleDialogItem (
icon: Icons . settings_outlined ,
color: Colors . lightBlueAccent ,
text: ' Resize ' . i18n ,
onPressed: ( ) {
Navigator . pop ( context ) ;
showCupertinoModalPopup < void > (
context: context ,
builder: ( context ) {
final screenSize = MediaQuery . of ( context ) . size ;
return ImageResizer (
onImageResize: ( w , h ) {
final res = getEmbedNode (
controller , controller . selection . start ) ;
final attr = base . replaceStyleString (
getImageStyleString ( controller ) , w , h ) ;
controller
. . skipRequestKeyboard = true
. . formatText (
res . offset , 1 , StyleAttribute ( attr ) ) ;
} ,
imageWidth: imageSize ? . width ,
imageHeight: imageSize ? . height ,
maxWidth: screenSize . width ,
maxHeight: screenSize . height ) ;
} ) ;
} ,
) ;
final copyOption = _SimpleDialogItem (
icon: Icons . copy_all_outlined ,
color: Colors . cyanAccent ,
@ -161,9 +177,95 @@ class ImageEmbedBuilder extends EmbedBuilder {
padding: const EdgeInsets . fromLTRB ( 50 , 0 , 50 , 0 ) ,
child: SimpleDialog (
shape: const RoundedRectangleBorder (
borderRadius: BorderRadius . all ( Radius . circular ( 10 ) ) ) ,
borderRadius: BorderRadius . all (
Radius . circular ( 10 ) ,
) ,
) ,
children: [
if ( base . isMobile ( ) ) resizeOption ,
_SimpleDialogItem (
icon: Icons . settings_outlined ,
color: Colors . lightBlueAccent ,
text: ' Resize ' . i18n ,
onPressed: ( ) {
Navigator . pop ( context ) ;
showCupertinoModalPopup < void > (
context: context ,
builder: ( context ) {
final screenSize = MediaQuery . sizeOf ( context ) ;
return ImageResizer (
onImageResize: ( w , h ) {
final res = getEmbedNode (
controller ,
controller . selection . start ,
) ;
/ / For desktop
String _replaceStyleStringWithSize (
String s ,
double width ,
double height ,
) {
final result = < String , String > { } ;
final pairs = s . split ( ' ; ' ) ;
for ( final pair in pairs ) {
final _index = pair . indexOf ( ' : ' ) ;
if ( _index < 0 ) {
continue ;
}
final _key =
pair . substring ( 0 , _index ) . trim ( ) ;
result [ _key ] =
pair . substring ( _index + 1 ) . trim ( ) ;
}
result [ Attribute . width . key ] =
width . toString ( ) ;
result [ Attribute . height . key ] =
height . toString ( ) ;
final sb = StringBuffer ( ) ;
for ( final pair in result . entries ) {
sb
. . write ( pair . key )
. . write ( ' : ' )
. . write ( pair . value )
. . write ( ' ; ' ) ;
}
return sb . toString ( ) ;
}
/ / TODO: When update flutter_quill
/ / we should update flutter_quill_extensions
/ / to use the latest version and use
/ / base . replaceStyleStringWithSize ( )
/ / instead of replaceStyleString
final attr = base . isMobile ( )
? base . replaceStyleString (
getImageStyleString ( controller ) ,
w ,
h ,
)
: _replaceStyleStringWithSize (
getImageStyleString ( controller ) ,
w ,
h ,
) ;
controller
. . skipRequestKeyboard = true
. . formatText (
res . offset ,
1 ,
StyleAttribute ( attr ) ,
) ;
} ,
imageWidth: imageSize ? . width ,
imageHeight: imageSize ? . height ,
maxWidth: screenSize . width ,
maxHeight: screenSize . height ,
) ;
} ,
) ;
} ,
) ,
copyOption ,
removeOption ,
] ) ,
@ -179,9 +281,11 @@ class ImageEmbedBuilder extends EmbedBuilder {
/ / and that is up to the developer
if ( ! base . isMobile ( ) & & forceUseMobileOptionMenu ) {
return _menuOptionsForReadonlyImage (
context ,
imageUrl ,
image ,
context: context ,
imageUrl: imageUrl ,
image: image ,
imageProviderBuilder: imageProviderBuilder ,
imageErrorWidgetBuilder: imageErrorWidgetBuilder ,
) ;
}
return image ;
@ -189,9 +293,11 @@ class ImageEmbedBuilder extends EmbedBuilder {
/ / We provide option menu for mobile platform excluding base64 image
return _menuOptionsForReadonlyImage (
context ,
imageUrl ,
image ,
context: context ,
imageUrl: imageUrl ,
image: image ,
imageProviderBuilder: imageProviderBuilder ,
imageErrorWidgetBuilder: imageErrorWidgetBuilder ,
) ;
}
}
@ -299,72 +405,91 @@ class FormulaEmbedBuilder extends EmbedBuilder {
}
}
Widget _menuOptionsForReadonlyImage (
BuildContext context , String imageUrl , Widget image ) {
Widget _menuOptionsForReadonlyImage ( {
required BuildContext context ,
required String imageUrl ,
required Widget image ,
required ImageEmbedBuilderProviderBuilder ? imageProviderBuilder ,
required ImageEmbedBuilderErrorWidgetBuilder ? imageErrorWidgetBuilder ,
} ) {
return GestureDetector (
onTap: ( ) {
showDialog (
context: context ,
builder: ( context ) {
final saveOption = _SimpleDialogItem (
icon: Icons . save ,
color: Colors . greenAccent ,
text: ' Save ' . i18n ,
onPressed: ( ) async {
imageUrl = appendFileExtensionToImageUrl ( imageUrl ) ;
final messenger = ScaffoldMessenger . of ( context ) ;
Navigator . of ( context ) . pop ( ) ;
final saveImageResult = await saveImage ( imageUrl ) ;
final imageSavedSuccessfully = saveImageResult . isSuccess ;
messenger . clearSnackBars ( ) ;
if ( ! imageSavedSuccessfully ) {
messenger . showSnackBar ( SnackBar (
content: Text (
' Error while saving image ' . i18n ,
) ) ) ;
return ;
}
var message ;
switch ( saveImageResult . method ) {
case SaveImageResultMethod . network:
message = ' Saved using the network ' . i18n ;
break ;
case SaveImageResultMethod . localStorage:
message = ' Saved using the local storage ' . i18n ;
break ;
}
messenger . showSnackBar (
SnackBar (
content: Text ( message ) ,
context: context ,
builder: ( context ) {
final saveOption = _SimpleDialogItem (
icon: Icons . save ,
color: Colors . greenAccent ,
text: ' Save ' . i18n ,
onPressed: ( ) async {
imageUrl = appendFileExtensionToImageUrl ( imageUrl ) ;
final messenger = ScaffoldMessenger . of ( context ) ;
Navigator . of ( context ) . pop ( ) ;
final saveImageResult = await saveImage ( imageUrl ) ;
final imageSavedSuccessfully = saveImageResult . isSuccess ;
messenger . clearSnackBars ( ) ;
if ( ! imageSavedSuccessfully ) {
messenger . showSnackBar ( SnackBar (
content: Text (
' Error while saving image ' . i18n ,
) ) ) ;
return ;
}
var message ;
switch ( saveImageResult . method ) {
case SaveImageResultMethod . network:
message = ' Saved using the network ' . i18n ;
break ;
case SaveImageResultMethod . localStorage:
message = ' Saved using the local storage ' . i18n ;
break ;
}
messenger . showSnackBar (
SnackBar (
content: Text ( message ) ,
) ,
) ;
} ,
) ;
final zoomOption = _SimpleDialogItem (
icon: Icons . zoom_in ,
color: Colors . cyanAccent ,
text: ' Zoom ' . i18n ,
onPressed: ( ) {
Navigator . pushReplacement (
context ,
/ / TODO: Consider add support for other theme system
/ / like Cupertino or at least add the option to by
/ / by using PageRoute as option so dev can ovveride this
/ / this change should be done in all places if you want to
MaterialPageRoute (
builder: ( context ) = > ImageTapWrapper (
imageUrl: imageUrl ,
imageProviderBuilder: imageProviderBuilder ,
imageErrorWidgetBuilder: imageErrorWidgetBuilder ,
) ,
) ;
} ,
) ;
final zoomOption = _SimpleDialogItem (
icon: Icons . zoom_in ,
color: Colors . cyanAccent ,
text: ' Zoom ' . i18n ,
onPressed: ( ) {
Navigator . pushReplacement (
context ,
MaterialPageRoute (
builder: ( context ) = >
ImageTapWrapper ( imageUrl: imageUrl ) ) ) ;
} ,
) ;
return Padding (
padding: const EdgeInsets . fromLTRB ( 50 , 0 , 50 , 0 ) ,
child: SimpleDialog (
shape: const RoundedRectangleBorder (
borderRadius: BorderRadius . all ( Radius . circular ( 10 ) ) ) ,
children: [ saveOption , zoomOption ] ) ,
) ;
} ) ;
) ,
) ;
} ,
) ;
return Padding (
padding: const EdgeInsets . fromLTRB ( 50 , 0 , 50 , 0 ) ,
child: SimpleDialog (
shape: const RoundedRectangleBorder (
borderRadius: BorderRadius . all (
Radius . circular ( 10 ) ,
) ,
) ,
children: [ saveOption , zoomOption ] ,
) ,
) ;
} ,
) ;
} ,
child: image ) ;
}