diff --git a/examples/stac_gallery/assets/json/home_screen.json b/examples/stac_gallery/assets/json/home_screen.json index 1a7ef129..d1c0050a 100644 --- a/examples/stac_gallery/assets/json/home_screen.json +++ b/examples/stac_gallery/assets/json/home_screen.json @@ -1440,5 +1440,27 @@ "assetPath": "assets/json/backdrop_filter_example.json" } } + }, + { + "type": "listTile", + "leading": { + "type": "icon", + "icon": "cloud_download" + }, + "title": { + "type": "text", + "data": "Stac Network Widget" + }, + "subtitle": { + "type": "text", + "data": "Fetch data from network with loading and error states" + }, + "onTap": { + "actionType": "navigate", + "widgetJson": { + "type": "exampleScreen", + "assetPath": "assets/json/network_widget_example.json" + } + } } ] \ No newline at end of file diff --git a/examples/stac_gallery/assets/json/network_widget_example.json b/examples/stac_gallery/assets/json/network_widget_example.json new file mode 100644 index 00000000..ecd32f80 --- /dev/null +++ b/examples/stac_gallery/assets/json/network_widget_example.json @@ -0,0 +1,74 @@ +{ + "type": "scaffold", + "appBar": { + "type": "appBar", + "title": { + "type": "text", + "data": "Network Widget Example" + } + }, + "body": { + "type": "networkWidget", + "request": { + "actionType": "networkRequest", + "url": "https://httpbin.org/delay/2", + "method": "get" + }, + "loadingWidget": { + "type": "center", + "child": { + "type": "column", + "mainAxisAlignment": "center", + "children": [ + { + "type": "circularProgressIndicator" + }, + { + "type": "sizedBox", + "height": 16.0 + }, + { + "type": "text", + "data": "Loading..." + } + ] + } + }, + "errorWidget": { + "type": "center", + "child": { + "type": "column", + "mainAxisAlignment": "center", + "children": [ + { + "type": "icon", + "icon": "error", + "size": 48.0, + "color": "#E53935" + }, + { + "type": "sizedBox", + "height": 16.0 + }, + { + "type": "text", + "data": "Failed to load", + "style": { + "fontSize": 18.0, + "fontWeight": "bold" + } + }, + { + "type": "sizedBox", + "height": 8.0 + }, + { + "type": "text", + "data": "Please check your connection and try again" + } + ] + } + } + } +} + diff --git a/packages/stac/lib/src/parsers/widgets/stac_network_widget/stac_network_widget_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_network_widget/stac_network_widget_parser.dart index 23a66e62..7f8e0738 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_network_widget/stac_network_widget_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_network_widget/stac_network_widget_parser.dart @@ -15,6 +15,23 @@ class StacNetworkWidgetParser extends StacParser { @override Widget parse(BuildContext context, StacNetworkWidget model) { - return Stac.fromNetwork(context: context, request: model.request); + return Stac.fromNetwork( + context: context, + request: model.request, + loadingWidget: model.loadingWidget == null + ? null + : (ctx) => StacService.fromStacWidget( + widget: model.loadingWidget!, + context: ctx, + ) ?? + const SizedBox(), + errorWidget: model.errorWidget == null + ? null + : (ctx, error) => StacService.fromStacWidget( + widget: model.errorWidget!, + context: ctx, + ) ?? + const SizedBox(), + ); } } diff --git a/packages/stac_core/lib/widgets/network_widget/stac_network_widget.dart b/packages/stac_core/lib/widgets/network_widget/stac_network_widget.dart index e910a64e..bfdf6031 100644 --- a/packages/stac_core/lib/widgets/network_widget/stac_network_widget.dart +++ b/packages/stac_core/lib/widgets/network_widget/stac_network_widget.dart @@ -38,11 +38,21 @@ part 'stac_network_widget.g.dart'; @JsonSerializable() class StacNetworkWidget extends StacWidget { /// Creates a [StacNetworkWidget]. - const StacNetworkWidget({required this.request}); + const StacNetworkWidget({ + required this.request, + this.loadingWidget, + this.errorWidget, + }); /// The network request to execute. final StacNetworkRequest request; + /// Optional widget to render while the network request is in progress. + final StacWidget? loadingWidget; + + /// Optional widget to render if the network request fails. + final StacWidget? errorWidget; + /// Widget type identifier. @override String get type => WidgetType.networkWidget.name; diff --git a/packages/stac_core/lib/widgets/network_widget/stac_network_widget.g.dart b/packages/stac_core/lib/widgets/network_widget/stac_network_widget.g.dart index 107ff2d0..d2c7ebb5 100644 --- a/packages/stac_core/lib/widgets/network_widget/stac_network_widget.g.dart +++ b/packages/stac_core/lib/widgets/network_widget/stac_network_widget.g.dart @@ -11,10 +11,18 @@ StacNetworkWidget _$StacNetworkWidgetFromJson(Map json) => request: StacNetworkRequest.fromJson( json['request'] as Map, ), + loadingWidget: json['loadingWidget'] == null + ? null + : StacWidget.fromJson(json['loadingWidget'] as Map), + errorWidget: json['errorWidget'] == null + ? null + : StacWidget.fromJson(json['errorWidget'] as Map), ); Map _$StacNetworkWidgetToJson(StacNetworkWidget instance) => { 'request': instance.request.toJson(), + 'loadingWidget': instance.loadingWidget?.toJson(), + 'errorWidget': instance.errorWidget?.toJson(), 'type': instance.type, }; diff --git a/packages/stac_core/test/widgets/network_widget/stac_network_widget_test.dart b/packages/stac_core/test/widgets/network_widget/stac_network_widget_test.dart new file mode 100644 index 00000000..8337e503 --- /dev/null +++ b/packages/stac_core/test/widgets/network_widget/stac_network_widget_test.dart @@ -0,0 +1,95 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:stac_core/stac_core.dart'; + +void main() { + group('StacNetworkWidget', () { + test('should create from JSON with loadingWidget and errorWidget', () { + // Arrange + const json = { + 'type': 'networkWidget', + 'request': { + 'actionType': 'networkRequest', + 'url': 'https://example.com/data', + 'method': 'get' + }, + 'loadingWidget': { + 'type': 'text', + 'data': 'Loading...' + }, + 'errorWidget': { + 'type': 'text', + 'data': 'Error occurred' + } + }; + + // Act + final widget = StacNetworkWidget.fromJson(json); + + // Assert + expect(widget.request.url, equals('https://example.com/data')); + expect(widget.loadingWidget, isNotNull); + expect(widget.errorWidget, isNotNull); + }); + + test('should create from JSON without optional widgets', () { + // Arrange + const json = { + 'type': 'networkWidget', + 'request': { + 'actionType': 'networkRequest', + 'url': 'https://example.com/data', + 'method': 'get' + } + }; + + // Act + final widget = StacNetworkWidget.fromJson(json); + + // Assert + expect(widget.request.url, equals('https://example.com/data')); + expect(widget.loadingWidget, isNull); + expect(widget.errorWidget, isNull); + }); + + test('should serialize to JSON with loadingWidget and errorWidget', () { + // Arrange + final widget = StacNetworkWidget( + request: StacNetworkRequest( + url: 'https://example.com/data', + method: 'get', + ), + loadingWidget: StacWidget.fromJson({'type': 'text', 'data': 'Loading...'}), + errorWidget: StacWidget.fromJson({'type': 'text', 'data': 'Error'}), + ); + + // Act + final json = widget.toJson(); + + // Assert + expect(json['request'], isNotNull); + expect(json['loadingWidget'], isNotNull); + expect(json['errorWidget'], isNotNull); + expect(json['loadingWidget']['data'], equals('Loading...')); + expect(json['errorWidget']['data'], equals('Error')); + }); + + test('should serialize to JSON without optional widgets', () { + // Arrange + final widget = StacNetworkWidget( + request: StacNetworkRequest( + url: 'https://example.com/data', + method: 'get', + ), + ); + + // Act + final json = widget.toJson(); + + // Assert + expect(json['request'], isNotNull); + expect(json['loadingWidget'], isNull); + expect(json['errorWidget'], isNull); + }); + }); +} +