Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ImageViewer Control feature #960

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions package/lib/src/controls/create_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import 'haptic_feedback.dart';
import 'icon.dart';
import 'icon_button.dart';
import 'image.dart';
import 'image_viewer.dart';
import 'linechart.dart';
import 'list_tile.dart';
import 'list_view.dart';
Expand Down Expand Up @@ -340,6 +341,11 @@ Widget createControl(Control? parent, String id, bool parentDisabled) {
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled);
case "imageviewer":
return ImageViewerControl(
parent: parent,
control: controlView.control,
parentDisabled: parentDisabled);
case "tabs":
return TabsControl(
parent: parent,
Expand Down
89 changes: 89 additions & 0 deletions package/lib/src/controls/image_viewer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'package:easy_image_viewer/easy_image_viewer.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';

import '../models/app_state.dart';
import '../models/page_args_model.dart';
import '../models/control.dart';
import '../utils/colors.dart';
import '../utils/images.dart';

class ImageViewerControl extends StatefulWidget {
final Control? parent;
final Control control;
final bool parentDisabled;

const ImageViewerControl(
{Key? key,
this.parent,
required this.control,
required this.parentDisabled})
: super(key: key);

@override
State<ImageViewerControl> createState() => _ImageViewerControlState();
}

class _ImageViewerControlState extends State<ImageViewerControl> {
bool _open = false;

@override
Widget build(BuildContext context) {
debugPrint("ImageViewer build: ${widget.control.id}");

var src = widget.control.attrString("src", "")!;
bool swipeDismissible = widget.control.attrBool("swipeDismissible", true)!;
bool doubleTapZoomable = widget.control.attrBool("doubleTapZoomable", true)!;
String closeButtonTooltip = widget.control.attrString("closeButtonTooltip", "Close")!;
var backgroundColor = HexColor.fromString(
Theme.of(context), widget.control.attrString("backgroundColor", "black")!);
var closeButtonColor = HexColor.fromString(
Theme.of(context), widget.control.attrString("closeButtonColor", "white")!);
bool immersive = widget.control.attrBool("immersive", true)!;
var initialIndex = widget.control.attrInt("initialIndex", 0)!;

return StoreConnector<AppState, PageArgsModel>(
distinct: true,
converter: (store) => PageArgsModel.fromStore(store),
builder: (context, pageArgs) {
EasyImageProvider? imageProvider;
if (src.contains('|')) {
List<ImageProvider<Object>> imageList = [];
for (String img in src.split('|')) {
var imageSrc = getAssetSrc(img, pageArgs.pageUri!, pageArgs.assetsDir);
imageList.add(imageSrc.isFile ? getFileImageProvider(imageSrc.path) : NetworkImage(imageSrc.path));
}
imageProvider = MultiImageProvider(imageList, initialIndex: initialIndex);
} else {
var imageSrc = getAssetSrc(src, pageArgs.pageUri!, pageArgs.assetsDir);
imageProvider = SingleImageProvider(imageSrc.isFile ? getFileImageProvider(imageSrc.path) : NetworkImage(imageSrc.path));
}

debugPrint("ImageViewer StoreConnector build: ${widget.control.id}");

var open = widget.control.attrBool("open", false)!;

debugPrint("Current open state: $_open");
debugPrint("New open state: $open");

if (open && (open != _open)) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showImageViewerPager(
context,
imageProvider!,
swipeDismissible: swipeDismissible,
doubleTapZoomable: doubleTapZoomable,
backgroundColor: backgroundColor!,
closeButtonColor: closeButtonColor!,
closeButtonTooltip: closeButtonTooltip,
immersive: immersive,
);
});
}

_open = open;

return const SizedBox.shrink();
});
}
}
1 change: 1 addition & 0 deletions package/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies:
window_to_front: ^0.0.3
audioplayers: ^3.0.0
shake: ^2.1.0
easy_image_viewer: ^1.2.0
path: ^1.8.2
js: ^0.6.5
fl_chart: ^0.61.0
Expand Down
1 change: 1 addition & 0 deletions sdk/python/packages/flet-core/src/flet_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
from flet_core.icon import Icon
from flet_core.icon_button import IconButton
from flet_core.image import Image
from flet_core.image_viewer import ImageViewer
from flet_core.list_tile import ListTile
from flet_core.list_view import ListView
from flet_core.margin import Margin
Expand Down
179 changes: 179 additions & 0 deletions sdk/python/packages/flet-core/src/flet_core/image_viewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
from typing import Any, List, Dict, Optional, Union

from flet_core.control import Control
from flet_core.ref import Ref
from flet_core.types import (
MaterialState
)

class ImageViewer(Control):
"""
A ImageViewer displays a full screen Image that allows Zoom, Pan, and paging through multipe images.

The image src can be a single image path String or a List of image strings to page through.

Example:
```
import flet as ft

def main(page):
def show_image_viewer_click(e):
img = e.control.data
page.dialog = ft.ImageViewer(src=img, swipe_dismissable=False)
page.dialog.open = True
page.update()

images = ft.GridView(expand=1, runs_count=5)

for i in range(0, 60):
images.controls.append(
ft.GestureDetector(
content=ft.Image(
src=f"https://picsum.photos/150/150?{i}",
fit=ft.ImageFit.NONE,
),
data=f"https://picsum.photos/150/150?{i}",
on_tap=show_image_viewer_click,
)
)

page.add(images)

ft.app(target=main)
```

-----

Online docs: https://flet.dev/docs/controls/ImageViewer
"""

def __init__(
self,
ref: Optional[Ref] = None,
disabled: Optional[bool] = None,
visible: Optional[bool] = None,
data: Any = None,
#
# Specific
#
open: bool = False,
src: Union[str, List[str]] = None,
swipe_dismissible: Optional[bool] = True,
double_tap_zoomable: Optional[bool] = True,
background_color: Optional[str] = None,
close_button_color: Optional[str] = None,
close_button_tooltip: Optional[str] = "Close",
immersive: Optional[bool] = True,
initial_index: Optional[int] = 0,
):
Control.__init__(
self,
ref=ref,
disabled=disabled,
visible=visible,
data=data,
)

self.open = open
self.src = src
self.swipe_dismissible = swipe_dismissible
self.double_tap_zoomable = double_tap_zoomable
self.background_color = background_color
self.close_button_color = close_button_color
self.close_button_tooltip = close_button_tooltip
self.immersive = immersive
self.initial_index = initial_index

def _get_control_name(self):
return "imageviewer"

def _before_build_command(self):
super()._before_build_command()

# open
@property
def open(self) -> Optional[bool]:
return self._get_attr("open", data_type="bool", def_value=False)

@open.setter
def open(self, value: Optional[bool]):
self._set_attr("open", value)

# src
@property
def src(self) -> Union[str, List[str]]:
img = self._get_attr("src")
if "|" in img:
img = img.split("|")
return img

@src.setter
def src(self, value: Union[str, List[str]]):
self.src = value
img = value
if isinstance(value, List):
img = "|".join(value)
self._set_attr("src", img)

# swipe_dismissible
@property
def swipe_dismissible(self) -> Optional[bool]:
return self._get_attr("swipeDismissible", data_type="bool", def_value=False)

@swipe_dismissible.setter
def swipe_dismissible(self, value: Optional[bool]):
self._set_attr("swipeDismissible", value)

# double_tap_zoomable
@property
def double_tap_zoomable(self) -> Optional[bool]:
return self._get_attr("doubleTapZoomable", data_type="bool", def_value=True)

@double_tap_zoomable.setter
def double_tap_zoomable(self, value: Optional[bool]):
self._set_attr("doubleTapZoomable", value)

# close_button_color
@property
def background_color(self):
return self._get_attr("backgroundColor")

@background_color.setter
def background_color(self, value):
self._set_attr("backgroundColor", value)

# close_button_color
@property
def close_button_color(self):
return self._get_attr("closeButtonColor")

@close_button_color.setter
def close_button_color(self, value):
self._set_attr("closeButtonColor", value)

# close_button_tooltip
@property
def close_button_tooltip(self):
return self._get_attr("closeButtonTooltip", def_value="Close")

@close_button_tooltip.setter
def close_button_tooltip(self, value):
self._set_attr("closeButtonTooltip", value)

# immersive
@property
def immersive(self) -> Optional[bool]:
return self._get_attr("immersive", data_type="bool", def_value=True)

@immersive.setter
def immersive(self, value: Optional[bool]):
self._set_attr("immersive", value)

# initial_index
@property
def initial_index(self) -> Optional[int]:
return self._get_attr("initialIndex", def_value=0)

@initial_index.setter
def divisions(self, value: Optional[int]):
self._set_attr("initialIndex", value)