Skip to content

Commit

Permalink
feat: ✨ device addon
Browse files Browse the repository at this point in the history
  • Loading branch information
jenshor committed Aug 25, 2022
1 parent db2a2b5 commit a289487
Show file tree
Hide file tree
Showing 23 changed files with 492 additions and 164 deletions.
15 changes: 15 additions & 0 deletions examples/widgetbook_example/widgetbook/widgetbook.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ class HotreloadWidgetbook extends StatelessWidget {
const HotreloadWidgetbook({Key? key}) : super(key: key);

Widget buildStorybook(BuildContext context) {
final activeFrameBuilder = WidgetbookFrameBuilder(
devices: [
Apple.iPhone11,
Apple.iPhone12,
],
);

return Widgetbook.material(
localizationsDelegates: [
AppLocalizations.delegate,
Expand All @@ -44,6 +51,14 @@ class HotreloadWidgetbook extends StatelessWidget {
textScales: [1, 2, 3],
),
),
DeviceAddon(
data: DeviceSelection(
activeFrameBuilder: activeFrameBuilder,
frameBuilders: [
activeFrameBuilder,
],
),
),
LocalizationAddon(
data: LocalizationSelection(
activeLocales: {
Expand Down
2 changes: 1 addition & 1 deletion packages/widgetbook/lib/src/addons/addon_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class _AddonPanelState extends State<AddonPanel> {
OverlayEntry _createEntry(WidgetBuilder childBuilder) => OverlayEntry(
builder: (context) => Positioned(
height: 350,
width: 200,
width: 600,
child: CompositedTransformFollower(
targetAnchor: Alignment.bottomCenter,
followerAnchor: Alignment.topCenter,
Expand Down
1 change: 1 addition & 0 deletions packages/widgetbook/lib/src/addons/addons.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export './device_addon/device_addon.dart';
export './localization_addon/localization_addon.dart';
export './text_scale_addon/text_scale_addon.dart';
export './theme_addon/theme_addon.dart';
166 changes: 166 additions & 0 deletions packages/widgetbook/lib/src/addons/device_addon/device_addon.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import 'package:flutter/material.dart';
import 'package:nested/nested.dart';
import 'package:provider/provider.dart';
import 'package:widgetbook/src/addons/addon.dart';
import 'package:widgetbook/src/addons/addon_provider.dart';
import 'package:widgetbook/src/addons/device_addon/device_provider.dart';
import 'package:widgetbook/src/addons/device_addon/device_selection_provider.dart';
import 'package:widgetbook/src/addons/device_addon/frame_builders/frame_builder.dart';
import 'package:widgetbook/src/addons/device_addon/frame_provider.dart';
import 'package:widgetbook/src/navigation/router.dart';
import 'package:widgetbook/widgetbook.dart';

export './device_selection.dart';
export './frame_builders/frame_builders.dart';

class DeviceAddon extends WidgetbookAddOn {
DeviceAddon({required DeviceSelection data})
: super(
icon: const Icon(Icons.phone),
name: 'frames',
wrapperBuilder: (context, routerData, child) => _wrapperBuilder(
context,
child,
routerData,
data,
),
builder: _builder,
providerBuilder: _providerBuilder,
selectionCount: _selectionCount,
getQueryParameter: _getQueryParameter,
);
}

String _getQueryParameter(BuildContext context) {
final selectedItems =
context.read<DeviceSelectionProvider>().value.activeFrameBuilder;

return selectedItems.name;
}

int _selectionCount(BuildContext context) {
final devices = context.read<DeviceSelectionProvider>().value.activeDevices;
return devices.isEmpty ? 1 : devices.length;
}

Widget _builder(BuildContext context) {
final data = context.watch<DeviceSelectionProvider>().value;
final frameBuilders = data.frameBuilders;
final activeFrameBuilder = data.activeFrameBuilder;

return Row(
children: [
Expanded(
child: ListView.separated(
itemBuilder: (context, index) {
final item = frameBuilders[index];
return ListTile(
title: Text(item.name),
onTap: () {
context
.read<DeviceSelectionProvider>()
.tappedFrameBuilder(item);
context.read<AddOnProvider>().update();
navigate(context);
},
);
},
separatorBuilder: (_, __) {
// TODO improve this
return const SizedBox(
height: 8,
);
},
itemCount: frameBuilders.length,
),
),
Expanded(
child: ListView.separated(
itemBuilder: (context, index) {
final item = activeFrameBuilder.devices[index];
return ListTile(
title: Text(item.name),
onTap: () {
context.read<DeviceSelectionProvider>().tappedDevice(item);
context.read<AddOnProvider>().update();
navigate(context);
},
);
},
separatorBuilder: (_, __) {
// TODO improve this
return const SizedBox(
height: 8,
);
},
itemCount: activeFrameBuilder.devices.length,
),
),
],
);
}

Widget _wrapperBuilder(
BuildContext context,
Widget child,
Map<String, dynamic> routerData,
DeviceSelection selection,
) {
final activeFrameString = routerData['frames'] as String?;
var activeFrame = selection.activeFrameBuilder;
if (activeFrameString != null) {
final mapLocales = {for (var e in selection.frameBuilders) e.name: e};

if (mapLocales.containsKey(activeFrameString)) {
activeFrame = mapLocales[activeFrameString]!;
}
}

return ChangeNotifierProvider(
create: (_) => DeviceSelectionProvider(
selection.copyWith(
activeFrameBuilder: activeFrame,
),
),
child: child,
);
}

SingleChildWidget _providerBuilder(
BuildContext context,
int index,
) {
final selection = context.watch<DeviceSelectionProvider>().value;
final frameBuilder = selection.activeFrameBuilder;
// TODO there is no check if a framebuilder actually has a device
// so calling first might cause a problem
final activeDevice = selection.activeDevices.isEmpty
? frameBuilder.devices.first
: selection.activeDevices.elementAt(index);

return MultiProvider(
providers: [
ChangeNotifierProvider(
key: ValueKey(frameBuilder),
create: (context) => FrameProvider(
frameBuilder,
),
),
ChangeNotifierProvider(
key: ValueKey(activeDevice),
create: (context) => DeviceProvider(
activeDevice,
),
),
],
);
}

extension FrameBuilderExtension on BuildContext {
/// Creates adjustable parameters for the WidgetbookUseCase
FrameBuilder get frameBuilder => watch<FrameProvider>().value;
}

extension DeviceExteionsion on BuildContext {
Device get device => watch<DeviceProvider>().value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';

class DeviceProvider extends ValueNotifier<Device> {
DeviceProvider(Device data) : super(data);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:widgetbook/src/addons/device_addon/frame_builders/frame_builder.dart';
import 'package:widgetbook/widgetbook.dart';

part 'device_selection.freezed.dart';

@freezed
class DeviceSelection with _$DeviceSelection {
factory DeviceSelection({
required FrameBuilder activeFrameBuilder,
@Default(<Device>{}) Set<Device> activeDevices,
required List<FrameBuilder> frameBuilders,
}) = _DeviceSelection;
}
Loading

0 comments on commit a289487

Please sign in to comment.