diff --git a/packages/dart_flutter_common/lib/dart_flutter_common.dart b/packages/dart_flutter_common/lib/dart_flutter_common.dart index c6d57ef1..9f705370 100644 --- a/packages/dart_flutter_common/lib/dart_flutter_common.dart +++ b/packages/dart_flutter_common/lib/dart_flutter_common.dart @@ -1,10 +1,6 @@ -export 'src/components/_export.dart'; export 'src/date_time.dart'; -export 'src/image_detail_view.dart'; export 'src/image_picker.dart'; export 'src/int.dart'; -export 'src/pick_image.dart'; export 'src/scaffold_messenger_controller.dart'; -export 'src/section.dart'; -export 'src/selectable_chips.dart'; export 'src/string.dart'; +export 'src/widgets/_export.dart'; diff --git a/packages/dart_flutter_common/lib/src/components/_export.dart b/packages/dart_flutter_common/lib/src/components/_export.dart deleted file mode 100644 index d7370d04..00000000 --- a/packages/dart_flutter_common/lib/src/components/_export.dart +++ /dev/null @@ -1 +0,0 @@ -export 'image.dart'; diff --git a/packages/dart_flutter_common/lib/src/components/image.dart b/packages/dart_flutter_common/lib/src/components/image.dart deleted file mode 100644 index 4eea7b17..00000000 --- a/packages/dart_flutter_common/lib/src/components/image.dart +++ /dev/null @@ -1,187 +0,0 @@ -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; - -enum ImageShape { - circle, - square, - rectangle, -} - -class GenericImageWidget extends StatelessWidget { - const GenericImageWidget.circle({ - required this.imageUrl, - this.onTap, - this.size, - this.loadingWidget, - super.key, - }) : imageShape = ImageShape.circle, - borderRadius = null, - height = null, - width = null; - - const GenericImageWidget.square({ - required this.imageUrl, - this.onTap, - this.size, - this.borderRadius, - this.loadingWidget, - super.key, - }) : imageShape = ImageShape.square, - height = null, - width = null; - - const GenericImageWidget.reqtangle({ - required this.imageUrl, - this.onTap, - required this.height, - required this.width, - this.borderRadius, - this.loadingWidget, - super.key, - }) : imageShape = ImageShape.rectangle, - size = null; - - final String imageUrl; - final ImageShape imageShape; - final VoidCallback? onTap; - final double? size; - final double? height; - final double? width; - final double? borderRadius; - final Widget? loadingWidget; - @override - Widget build(BuildContext context) { - if (imageUrl.isEmpty) { - return _ImageDisplayContainer.placeholder( - imageShape: imageShape, - size: size, - height: height, - width: width, - borderRadius: borderRadius, - ); - } - return GestureDetector( - onTap: onTap, - child: CachedNetworkImage( - imageUrl: imageUrl, - imageBuilder: (context, imageProvider) { - return _ImageDisplayContainer( - imageShape: imageShape, - size: size, - height: height, - width: width, - borderRadius: borderRadius, - decorationImage: DecorationImage( - image: imageProvider, - fit: BoxFit.cover, - ), - ); - }, - placeholder: (context, url) => - loadingWidget ?? - _ImageDisplayContainer.placeholder( - imageShape: imageShape, - size: size, - height: height, - width: width, - borderRadius: borderRadius, - ), - errorWidget: (context, url, error) => - _ImageDisplayContainer.errorWidget( - imageShape: imageShape, - size: size, - height: height, - width: width, - borderRadius: borderRadius, - ), - ), - ); - } -} - -class _ImageDisplayContainer extends StatelessWidget { - const _ImageDisplayContainer({ - required this.imageShape, - this.size, - this.height, - this.width, - this.borderRadius, - this.decorationImage, - }) : color = null, - icon = null; - - const _ImageDisplayContainer.placeholder({ - required this.imageShape, - this.size, - this.height, - this.width, - this.borderRadius, - }) : color = Colors.grey, - decorationImage = null, - icon = null; - - const _ImageDisplayContainer.errorWidget({ - required this.imageShape, - this.size, - this.height, - this.width, - this.borderRadius, - }) : color = Colors.grey, - decorationImage = null, - icon = const Icon( - Icons.broken_image, - color: Colors.white, - ); - - final ImageShape imageShape; - final Color? color; - final double? size; - final double? height; - final double? width; - final double? borderRadius; - final DecorationImage? decorationImage; - final Icon? icon; - - static const double _defaultSize = 64; - - @override - Widget build(BuildContext context) { - switch (imageShape) { - case ImageShape.circle: - return Container( - height: size ?? _defaultSize, - width: size ?? _defaultSize, - decoration: BoxDecoration( - color: color, - shape: BoxShape.circle, - image: decorationImage, - ), - child: icon, - ); - - case ImageShape.square: - return Container( - height: size ?? _defaultSize, - width: size ?? _defaultSize, - decoration: BoxDecoration( - color: color, - image: decorationImage, - borderRadius: BorderRadius.circular(borderRadius ?? 0), - ), - child: icon, - ); - - case ImageShape.rectangle: - return Container( - height: height, - width: width, - decoration: BoxDecoration( - color: color, - image: decorationImage, - borderRadius: BorderRadius.circular(borderRadius ?? 0), - ), - child: icon, - ); - } - } -} diff --git a/packages/dart_flutter_common/lib/src/date_time.dart b/packages/dart_flutter_common/lib/src/date_time.dart index 0b3d3de6..45f171ba 100644 --- a/packages/dart_flutter_common/lib/src/date_time.dart +++ b/packages/dart_flutter_common/lib/src/date_time.dart @@ -1,7 +1,7 @@ import 'package:intl/intl.dart'; +/// [DateTime] 型の拡張クラス。 extension DateTimeExtension on DateTime { - /// _daysBeforeメソッドで使用される定数。 /// 「N日前」と表示される最小日数を示す。 static const _daysBeforeLowerLimit = 2; diff --git a/packages/dart_flutter_common/lib/src/image_detail_view.dart b/packages/dart_flutter_common/lib/src/image_detail_view.dart deleted file mode 100644 index abc302f3..00000000 --- a/packages/dart_flutter_common/lib/src/image_detail_view.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:flutter/material.dart'; - -class DetailDisplayableImage extends StatelessWidget { - const DetailDisplayableImage({ - required this.image, - super.key, - }); - - final Image image; - - /// インスタンス生成された回数。 - /// Heroのタグとして使用。環境にもよるが2^64回でオーバーフロー - /// そこまでの数生成されることはないため、この方法としている。 - static int _instanceCount = 0; - - @override - Widget build(BuildContext context) { - final tag = _instanceCount.toString(); - _instanceCount++; - return GestureDetector( - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => _ImageDetailView( - tag: tag, - image: image, - ), - ), - ), - child: Hero( - tag: tag, - child: image, - ), - ); - } -} - -// 詳細画像表示 -class _ImageDetailView extends StatelessWidget { - const _ImageDetailView({ - required this.tag, - required this.image, - }); - - final String tag; - final Image image; - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - // 背景、タッチイベント捕捉Widget - GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Scaffold( - floatingActionButtonLocation: FloatingActionButtonLocation.startTop, - floatingActionButton: IconButton( - icon: const Icon(Icons.close), - onPressed: () { - Navigator.pop(context); - }, - ), - body: Container( - height: MediaQuery.of(context).size.height, - width: MediaQuery.of(context).size.width, - decoration: const BoxDecoration( - color: Colors.black, - ), - ), - ), - ), - // 画像表示Widget - Center( - child: InteractiveViewer( - clipBehavior: Clip.none, - child: Hero( - tag: tag, - child: image, - ), - ), - ), - ], - ); - } -} diff --git a/packages/dart_flutter_common/lib/src/int.dart b/packages/dart_flutter_common/lib/src/int.dart index 1a5e39b7..7bfdc5af 100644 --- a/packages/dart_flutter_common/lib/src/int.dart +++ b/packages/dart_flutter_common/lib/src/int.dart @@ -2,6 +2,7 @@ import 'package:intl/intl.dart'; final _threeDigitsFormatter = NumberFormat('#,###'); +/// [int] 型の拡張クラス。 extension IntExtension on int { /// 3 桁区切りのコンマを付加する。 String get withComma => _threeDigitsFormatter.format(this); diff --git a/packages/dart_flutter_common/lib/src/pick_image.dart b/packages/dart_flutter_common/lib/src/pick_image.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/dart_flutter_common/lib/src/scaffold_messenger_controller.dart b/packages/dart_flutter_common/lib/src/scaffold_messenger_controller.dart index b5513f47..335cf218 100644 --- a/packages/dart_flutter_common/lib/src/scaffold_messenger_controller.dart +++ b/packages/dart_flutter_common/lib/src/scaffold_messenger_controller.dart @@ -4,6 +4,7 @@ import 'string.dart'; /// ツリー上部の [ScaffoldMessenger] 上でスナックバーやダイアログの表示を操作する。 class ScaffoldMessengerController { + /// [ScaffoldMessengerController] を作成する。 ScaffoldMessengerController({ required GlobalKey scaffoldMessengerKey, }) : _scaffoldMessengerKey = scaffoldMessengerKey; diff --git a/packages/dart_flutter_common/lib/src/string.dart b/packages/dart_flutter_common/lib/src/string.dart index decd3294..41abebef 100644 --- a/packages/dart_flutter_common/lib/src/string.dart +++ b/packages/dart_flutter_common/lib/src/string.dart @@ -1,3 +1,4 @@ +/// [String] 型の拡張クラス。 extension StringExtension on String { /// 空文字の場合に代わりの文字を返す。 String ifIsEmpty(String placeholder) => isEmpty ? placeholder : this; diff --git a/packages/dart_flutter_common/lib/src/widgets/_export.dart b/packages/dart_flutter_common/lib/src/widgets/_export.dart new file mode 100644 index 00000000..9fecfbc7 --- /dev/null +++ b/packages/dart_flutter_common/lib/src/widgets/_export.dart @@ -0,0 +1,3 @@ +export 'generic_image.dart'; +export 'section.dart'; +export 'selectable_chips.dart'; diff --git a/packages/dart_flutter_common/lib/src/widgets/generic_image.dart b/packages/dart_flutter_common/lib/src/widgets/generic_image.dart new file mode 100644 index 00000000..30793f20 --- /dev/null +++ b/packages/dart_flutter_common/lib/src/widgets/generic_image.dart @@ -0,0 +1,294 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +/// [GenericImage] で表示する画像の形状。 +enum ImageShape { + /// 円形。 + circle, + + /// 正方形。 + square, + + /// 長方形。 + rectangle, +} + +/// [CachedNetworkImage] を使用した各種の形状の汎用的な画像ウィジェット。 +class GenericImage extends StatelessWidget { + /// 円形の画像を表示する。 + const GenericImage.circle({ + required this.imageUrl, + this.onTap, + this.showDetailOnTap = true, + this.size, + this.loadingWidget, + super.key, + }) : imageShape = ImageShape.circle, + borderRadius = null, + height = null, + width = null; + + /// 正方形の画像を表示する。 + const GenericImage.square({ + required this.imageUrl, + this.onTap, + this.showDetailOnTap = true, + this.size, + this.borderRadius, + this.loadingWidget, + super.key, + }) : imageShape = ImageShape.square, + height = null, + width = null; + + /// 長方形。 + const GenericImage.rectangle({ + required this.imageUrl, + this.onTap, + this.showDetailOnTap = true, + required this.height, + required this.width, + this.borderRadius, + this.loadingWidget, + super.key, + }) : imageShape = ImageShape.rectangle, + size = null; + + /// 表示する画像の URL 文字列。 + final String imageUrl; + + /// 表示する画像の形状。 + final ImageShape imageShape; + + /// 画像ウィジェットをタップした際のコールバック関数。 + final VoidCallback? onTap; + + /// 画像ウィジェットをタップした際に画像の詳細画面を表示するかどうか。 + /// [onTap] が指定されているときは、[onTap] が優先される。 + final bool showDetailOnTap; + + /// 円形・正方形で指定する画像のサイズ(直径、一辺の長さ)。 + final double? size; + + /// 長方形で指定する画像の横幅。 + final double? width; + + /// 長方形で指定する画像の高さ。 + final double? height; + + /// 角丸の半径。 + final double? borderRadius; + + /// 読み込み中に表示するウィジェット。 + final Widget? loadingWidget; + + /// 当該画像ウィジェットがインスタンス化された回数。 + /// [Hero] のタグとして使用する。 + /// (環境にもよるが)2^64 回でオーバーフローするが、その上限まで生成される + /// ことはまずないため許容している。 + static int _instanceCount = 0; + + @override + Widget build(BuildContext context) { + if (imageUrl.isEmpty) { + return _Image.placeholder( + imageShape: imageShape, + size: size, + height: height, + width: width, + borderRadius: borderRadius, + ); + } + final tag = _instanceCount.toString(); + _instanceCount++; + return GestureDetector( + onTap: () async { + if (onTap != null) { + return onTap!(); + } + if (showDetailOnTap) { + return Navigator.push( + context, + MaterialPageRoute( + builder: (context) => _ImageDetailView( + tag: tag, + image: _image(), + ), + ), + ); + } + }, + child: Hero( + tag: tag, + child: _image(), + ), + ); + } + + CachedNetworkImage _image() { + return CachedNetworkImage( + imageUrl: imageUrl, + imageBuilder: (context, imageProvider) { + return _Image( + imageShape: imageShape, + size: size, + height: height, + width: width, + borderRadius: borderRadius, + decorationImage: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ); + }, + placeholder: (context, url) => + loadingWidget ?? + _Image.placeholder( + imageShape: imageShape, + size: size, + height: height, + width: width, + borderRadius: borderRadius, + ), + errorWidget: (context, url, error) => _Image.errorWidget( + imageShape: imageShape, + size: size, + height: height, + width: width, + borderRadius: borderRadius, + ), + ); + } +} + +class _Image extends StatelessWidget { + const _Image({ + required this.imageShape, + this.size, + this.height, + this.width, + this.borderRadius, + this.decorationImage, + }) : color = null, + icon = null; + + const _Image.placeholder({ + required this.imageShape, + this.size, + this.height, + this.width, + this.borderRadius, + }) : color = Colors.grey, + decorationImage = null, + icon = null; + + const _Image.errorWidget({ + required this.imageShape, + this.size, + this.height, + this.width, + this.borderRadius, + }) : color = Colors.grey, + decorationImage = null, + icon = const Icon( + Icons.broken_image, + color: Colors.white, + ); + + final ImageShape imageShape; + final Color? color; + final double? size; + final double? height; + final double? width; + final double? borderRadius; + final DecorationImage? decorationImage; + final Icon? icon; + + static const double _defaultSize = 64; + + @override + Widget build(BuildContext context) { + switch (imageShape) { + case ImageShape.circle: + return Container( + height: size ?? _defaultSize, + width: size ?? _defaultSize, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + image: decorationImage, + ), + child: icon, + ); + + case ImageShape.square: + return Container( + height: size ?? _defaultSize, + width: size ?? _defaultSize, + decoration: BoxDecoration( + color: color, + image: decorationImage, + borderRadius: BorderRadius.circular(borderRadius ?? 0), + ), + child: icon, + ); + + case ImageShape.rectangle: + return Container( + height: height, + width: width, + decoration: BoxDecoration( + color: color, + image: decorationImage, + borderRadius: BorderRadius.circular(borderRadius ?? 0), + ), + child: icon, + ); + } + } +} + +/// 画像詳細を全画面で表示する UI. +class _ImageDetailView extends StatelessWidget { + const _ImageDetailView({ + required this.tag, + required this.image, + }); + + final String tag; + final CachedNetworkImage image; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Scaffold( + floatingActionButtonLocation: FloatingActionButtonLocation.startTop, + floatingActionButton: IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + body: Container( + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + decoration: const BoxDecoration( + color: Colors.black, + ), + ), + ), + ), + Center( + child: InteractiveViewer( + clipBehavior: Clip.none, + child: Hero( + tag: tag, + child: image, + ), + ), + ), + ], + ); + } +} diff --git a/packages/dart_flutter_common/lib/src/section.dart b/packages/dart_flutter_common/lib/src/widgets/section.dart similarity index 98% rename from packages/dart_flutter_common/lib/src/section.dart rename to packages/dart_flutter_common/lib/src/widgets/section.dart index 3b7ccd81..ee4807e9 100644 --- a/packages/dart_flutter_common/lib/src/section.dart +++ b/packages/dart_flutter_common/lib/src/widgets/section.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; /// /// を上から順に並べて構成されるシンプルなセクションウィジェット。 class Section extends StatelessWidget { + /// [Section] を作成する。 const Section({ required this.title, this.titleStyle, diff --git a/packages/dart_flutter_common/lib/src/selectable_chips.dart b/packages/dart_flutter_common/lib/src/widgets/selectable_chips.dart similarity index 98% rename from packages/dart_flutter_common/lib/src/selectable_chips.dart rename to packages/dart_flutter_common/lib/src/widgets/selectable_chips.dart index 147e7bef..b7c1df7a 100644 --- a/packages/dart_flutter_common/lib/src/selectable_chips.dart +++ b/packages/dart_flutter_common/lib/src/widgets/selectable_chips.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; /// 実際に選択されている選択肢 [enabledItems] を受け取る。 /// オプションで選択されていないデータの表示非表示を選択可能である。 class SelectableChips extends StatelessWidget { + /// [SelectableChips] を作成する。 const SelectableChips({ required this.allItems, required this.labels, diff --git a/packages/mottai_flutter_app/lib/development/development_items/ui/development_items.dart b/packages/mottai_flutter_app/lib/development/development_items/ui/development_items.dart index 1c7afc58..19a5ddd7 100644 --- a/packages/mottai_flutter_app/lib/development/development_items/ui/development_items.dart +++ b/packages/mottai_flutter_app/lib/development/development_items/ui/development_items.dart @@ -12,7 +12,7 @@ import '../../../scaffold_messenger_controller.dart'; import '../../../user/user.dart'; import '../../../user/user_mode.dart'; import '../../color/ui/color.dart'; -import '../../deveopment_components/development_components.dart'; +import '../../generic_image/generic_image.dart'; import '../../image_detail_view/image_detail_view_stub.dart'; import '../../image_picker/ui/image_picker_sample.dart'; import '../../sample_todo/ui/sample_todos.dart'; @@ -243,7 +243,7 @@ class DevelopmentItemsPage extends ConsumerWidget { onTap: () => Navigator.push( context, MaterialPageRoute( - builder: (context) => const DevelopmentComponents(), + builder: (context) => const GenericImages(), ), ), ), diff --git a/packages/mottai_flutter_app/lib/development/deveopment_components/development_components.dart b/packages/mottai_flutter_app/lib/development/generic_image/generic_image.dart similarity index 50% rename from packages/mottai_flutter_app/lib/development/deveopment_components/development_components.dart rename to packages/mottai_flutter_app/lib/development/generic_image/generic_image.dart index dbbd4fa2..0b8b8b4c 100644 --- a/packages/mottai_flutter_app/lib/development/deveopment_components/development_components.dart +++ b/packages/mottai_flutter_app/lib/development/generic_image/generic_image.dart @@ -1,60 +1,55 @@ import 'package:dart_flutter_common/dart_flutter_common.dart'; import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; -class DevelopmentComponents extends StatelessWidget { - const DevelopmentComponents({super.key}); +/// [GenericImage] ウィジェットを使用した汎用的な画像ウィジェットのサンプル。 +class GenericImages extends StatelessWidget { + const GenericImages({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('開発中のComponents'), + title: const Text('汎用的な画像ウィジェット'), ), body: Column( children: [ Column( children: [ - const Text('汎用的な画像Widget'), - const GenericImageWidget.circle( + const Text('円形'), + const GenericImage.circle( imageUrl: '', ), - const SizedBox( - height: 10, - ), - const GenericImageWidget.circle( + const Gap(4), + const GenericImage.circle( imageUrl: 'https://picsum.photos/128', ), - const SizedBox( - height: 10, - ), - const GenericImageWidget.square( + const Gap(4), + const Text('正方形'), + const GenericImage.square( imageUrl: 'https://picsum.photos/200', size: 100, ), - const SizedBox( - height: 10, - ), - GenericImageWidget.reqtangle( + const Gap(4), + const Text('長方形'), + GenericImage.rectangle( imageUrl: 'https://picsum.photos/100', height: 100, - width: 250, - borderRadius: 10, + width: 240, + borderRadius: 12, onTap: () => debugPrint('onTap'), ), - const SizedBox( - height: 10, - ), + const Gap(4), // errorWidgetのサンプル - GenericImageWidget.reqtangle( + GenericImage.rectangle( imageUrl: 'https://testinvalidurl.com', height: 100, - width: 250, - borderRadius: 10, + width: 240, + borderRadius: 12, onTap: () => debugPrint('onTap'), ), ], ), - const Divider(), ], ), ); diff --git a/packages/mottai_flutter_app/lib/development/image_detail_view/image_detail_view_stub.dart b/packages/mottai_flutter_app/lib/development/image_detail_view/image_detail_view_stub.dart index c437cccf..290a7819 100644 --- a/packages/mottai_flutter_app/lib/development/image_detail_view/image_detail_view_stub.dart +++ b/packages/mottai_flutter_app/lib/development/image_detail_view/image_detail_view_stub.dart @@ -10,10 +10,10 @@ class ImageDetailViewStubPage extends StatelessWidget { appBar: AppBar( title: const Text('画像詳細拡大画面サンプル'), ), - body: DetailDisplayableImage( - image: Image.network( - 'https://free-materials.com/adm/wp-content/uploads/2017/02/adtDSC_0905-300x199.jpg', - ), + body: const GenericImage.square( + size: 300, + imageUrl: + 'https://free-materials.com/adm/wp-content/uploads/2017/02/adtDSC_0905-300x199.jpg', ), ); }