Skip to content

Commit

Permalink
feat: Add manufacturer banner (#1598)
Browse files Browse the repository at this point in the history
* [WIP] Implement manufacturer banner

* Hide manufacturer banner when user is logged in.

* [WIP] Update manufacturer_banner theme data

* [WIP] Implement ManufacturerAdvertisementContainer class, add tests

* [WIP] Fix tests

* Minor changes

* Add copyright licenses

* Add copyright licenses

Co-authored-by: solid-vovabeloded <vova.beloded@solid.software>
Co-authored-by: solid-maksymtielnyi <maksym.tielnyi@solid.software>
  • Loading branch information
3 people authored Sep 13, 2021
1 parent 7548703 commit cd19923
Show file tree
Hide file tree
Showing 11 changed files with 569 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:metrics/common/presentation/manufacturer_banner/widget/manufacturer_banner.dart';

/// A widget that displays the banner with the product manufacturer information
/// in the bottom right over the child widget.
class ManufacturerAdvertisementContainer extends StatelessWidget {
final Widget _child;
final bool _isEnabled;

/// Creates a new instance of [ManufacturerAdvertisementContainer].
///
/// Throws an [AssertionError] if the given [child] parameter is `null`.
/// Throws an [AssertionError] if the giver [isEnabled] parameter is `null`.
const ManufacturerAdvertisementContainer({
@required Widget child,
@required bool isEnabled,
}) : assert(child != null),
assert(isEnabled != null),
_child = child,
_isEnabled = isEnabled;

@override
Widget build(BuildContext context) {
return Stack(
children: [
_child,
if (_isEnabled)
const Positioned(
bottom: 0.0,
right: 0.0,
child: ManufacturerBanner(),
),
],
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ class ManufacturerBannerThemeData {
/// A [TextStyle] of the text in the manufacturer banner widget.
final TextStyle textStyle;

/// The z-coordinate at which to place the banner relative to its parent.
final double elevation;

/// Creates a new instance of the [ManufacturerBannerThemeData] with the given
/// parameters.
const ManufacturerBannerThemeData({
this.backgroundColor,
this.textStyle,
this.elevation = 10.0,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:metrics/base/presentation/widgets/material_container.dart';
import 'package:metrics/base/presentation/widgets/svg_image.dart';
import 'package:metrics/common/presentation/metrics_theme/widgets/metrics_theme.dart';
import 'package:metrics/common/presentation/strings/common_strings.dart';
import 'package:url_launcher/url_launcher.dart';

/// A widget that displays the banner with the product manufacturer information.
class ManufacturerBanner extends StatefulWidget {
/// Creates a new instance of the [ManufacturerBanner].
const ManufacturerBanner({Key key}) : super(key: key);

@override
_ManufacturerBannerState createState() => _ManufacturerBannerState();
}

class _ManufacturerBannerState extends State<ManufacturerBanner>
with SingleTickerProviderStateMixin {
/// A [Duration] of this banner folding animation.
static const _animationDuration = Duration(milliseconds: 250);

/// An [AnimationController] of this banner folding animation.
AnimationController _controller;

/// A [Timer] of this banner preview.
Timer _previewTimer;

@override
void initState() {
_controller = AnimationController(
vsync: this,
duration: _animationDuration,
);
_controller.value = 1.0;

_previewTimer = Timer(const Duration(seconds: 10), _closeBanner);

super.initState();
}

@override
Widget build(BuildContext context) {
final themeData = MetricsTheme.of(context).manufacturerBannerThemeData;

return GestureDetector(
onTap: _openManufacturerLink,
child: MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) => _openBanner(),
onExit: (_) => _closeBanner(),
child: AnimatedBuilder(
animation: _controller,
builder: (_, __) {
return MaterialContainer(
padding: const EdgeInsets.all(8.0),
elevation: themeData.elevation,
backgroundColor: themeData.backgroundColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10.0),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const SvgImage(
'icons/solid_logo.svg',
),
SizeTransition(
sizeFactor: _controller,
axis: Axis.horizontal,
axisAlignment: -1.0,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
CommonStrings.builtBySolidSoftware,
style: themeData.textStyle,
),
),
),
],
),
);
},
),
),
);
}

/// Starts this banner opening animation.
///
/// Cancels the [_previewTimer] is there any running.
void _openBanner() {
_cancelPreviewTimer();
_controller.stop();
_controller.forward();
}

/// Starts this banner closing animation.
///
/// Cancels the [_previewTimer] is there any running.
void _closeBanner() {
_cancelPreviewTimer();
_controller.stop();
_controller.reverse();
}

/// Cancels the [_previewTimer] if it is running.
///
/// If the [_previewTimer] is null does nothing.
void _cancelPreviewTimer() {
if (_previewTimer == null) return;

_previewTimer.cancel();
_previewTimer = null;
}

/// Opens the manufacturer link.
void _openManufacturerLink() {
launch('https://solid.software/');
}

@override
void dispose() {
_cancelPreviewTimer();
_controller.dispose();
super.dispose();
}
}
15 changes: 12 additions & 3 deletions metrics/web/lib/common/presentation/metrics_app/metrics_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import 'package:flutter/material.dart';
import 'package:metrics/analytics/presentation/state/analytics_notifier.dart';
import 'package:metrics/common/presentation/injector/widget/injection_container.dart';
import 'package:metrics/common/presentation/manufacturer_advertisement_container/widget/manufacturer_advertisement_container.dart';
import 'package:metrics/common/presentation/metrics_theme/config/dimensions_config.dart';
import 'package:metrics/common/presentation/metrics_theme/config/metrics_colors.dart';
import 'package:metrics/common/presentation/metrics_theme/config/text_field_config.dart';
Expand All @@ -20,6 +21,7 @@ import 'package:metrics/common/presentation/routes/observers/toast_route_observe
import 'package:metrics/common/presentation/strings/common_strings.dart';
import 'package:metrics/common/presentation/widgets/metrics_fps_monitor.dart';
import 'package:metrics/common/presentation/widgets/metrics_scroll_behavior.dart';
import 'package:metrics/feature_config/presentation/state/feature_config_notifier.dart';
import 'package:metrics_core/metrics_core.dart';
import 'package:provider/provider.dart';

Expand Down Expand Up @@ -91,9 +93,16 @@ class _MetricsAppState extends State<MetricsApp> {
title: CommonStrings.metrics,
debugShowCheckedModeBanner: false,
builder: (context, child) {
return ScrollConfiguration(
behavior: MetricsScrollBehavior(),
child: child,
final featureConfigNotifier =
Provider.of<FeatureConfigNotifier>(context);

return ManufacturerAdvertisementContainer(
isEnabled: featureConfigNotifier
.publicDashboardFeatureConfigModel.isEnabled,
child: ScrollConfiguration(
behavior: MetricsScrollBehavior(),
child: child,
),
);
},
themeMode: isDark ? ThemeMode.dark : ThemeMode.light,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Use of this source code is governed by the Apache License, Version 2.0
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.

import 'package:flutter/material.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Use of this source code is governed by the Apache License, Version 2.0
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.

import 'package:duration/duration.dart';
Expand Down Expand Up @@ -34,6 +34,7 @@ class CommonStrings {
static const String closeConnectionFailedErrorMessage =
'An error occurred while closing a connection with the persistent store, please try again.';
static const String debugMenu = 'Debug menu';
static const String builtBySolidSoftware = 'Built by Solid Software';

static String duration(Duration duration) => prettyDuration(
duration,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:metrics/common/presentation/manufacturer_advertisement_container/widget/manufacturer_advertisement_container.dart';
import 'package:metrics/common/presentation/manufacturer_banner/widget/manufacturer_banner.dart';
import 'package:network_image_mock/network_image_mock.dart';

void main() {
group("ManufacturerAdvertisementContainer", () {
final Key childKey = GlobalKey();
final Widget child = Text('Child widget', key: childKey);

testWidgets(
"throws an AssertionError if the isEnabled parameter is null",
(tester) async {
expect(
() => ManufacturerAdvertisementContainer(
isEnabled: null,
child: child,
),
throwsAssertionError,
);
},
);

testWidgets(
"throws an AssertionError if the child parameter is null",
(tester) async {
expect(
() => ManufacturerAdvertisementContainer(
isEnabled: true,
child: null,
),
throwsAssertionError,
);
},
);

testWidgets(
"shows the widget passed to the child parameter",
(tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ManufacturerAdvertisementContainer(
isEnabled: false,
child: child,
),
),
),
);

final childWidgetFinder = find.byKey(childKey);

expect(childWidgetFinder, findsOneWidget);
},
);

testWidgets(
"shows the manufacturer banner if isEnabled parameter is true",
(tester) async {
await mockNetworkImagesFor(() {
return tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ManufacturerAdvertisementContainer(
isEnabled: true,
child: child,
),
),
),
);
});

final manufacturerBannerFinder = find.byType(ManufacturerBanner);

expect(manufacturerBannerFinder, findsOneWidget);
},
);

testWidgets(
"does not show the manufacturer banner if isEnabled parameter is false",
(tester) async {
await mockNetworkImagesFor(() {
return tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ManufacturerAdvertisementContainer(
isEnabled: false,
child: child,
),
),
),
);
});

final manufacturerBannerFinder = find.byType(ManufacturerBanner);

expect(manufacturerBannerFinder, findsNothing);
},
);
});
}
Loading

0 comments on commit cd19923

Please sign in to comment.