Skip to content

Commit

Permalink
bump/v0.1.6 (12/10/23) (#28)
Browse files Browse the repository at this point in the history
Merge pull request #28 from insolite-dev/develop
  • Loading branch information
theiskaa authored Oct 11, 2023
2 parents b6a4402 + 2ba992f commit 9fa99bb
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 50 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# v1.0.6 - 12/10/2023

- Resolved [#26](https://github.com/insolite-dev/hidable/issues/26)
- Added `deltaFactor` field which controls the speed of hiding/appearing speed of hidable.
- Added `visibility` field which makes it able for user to provide custom scrolling algorithm
instead of using default.

Huge thanks for feature request to [@radibobovich](https://github.com/radibobovich)

# v1.0.5 - 25/08/2023

- Resolved [#21](https://github.com/insolite-dev/hidable/issues/21)
Expand Down
2 changes: 1 addition & 1 deletion lib/hidable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

library hidable;

export 'src/hidable_widget.dart';
export 'src/hidable_widget.dart';
73 changes: 46 additions & 27 deletions lib/src/hidable_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// that can be found in the LICENSE file.
//

import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';

Expand All @@ -15,62 +16,80 @@ extension HidableControllerExt on ScrollController {
/// from [hidableControllers].
///
/// Identifys each controller via passed [hashCode] property.
HidableController hidable(double size, int hashCode) {
HidableController hidable(int hashCode, HidableVisibility? visibility, double deltaFactor) {
// If the same instance was created before, we should keep using it.
if (hidableControllers.containsKey(hashCode)) {
return hidableControllers[hashCode]!;
}

return hidableControllers[hashCode] = HidableController(
scrollController: this,
size: size,
hideableVisibility: visibility,
deltaFactor: deltaFactor,
);
}
}

/// Defines a function signature for determining the visibility of a scrollable element
/// that can be hidden or revealed based on scrolling behavior.
///
/// The `HidableVisibility` function takes four parameters:
/// - `position`: A [ScrollPosition] object representing the current scroll position.
/// - `currentVisibility`: A [double] representing the current visibility status, typically
/// a value between 0.0 (completely hidden) and 1.0 (completely visible).
///
/// The function should return a [double] value representing the updated visibility status
/// of the scrollable element, typically also in the range of 0.0 to 1.0.
///
/// Example usage:
/// ```dart
/// HidableVisibility myVisibilityFunction(ScrollPosition position, double currentVisibility) {
/// // Your visibility logic here.
/// // Return the updated visibility value.
/// }
/// ```
///
/// This typedef is often used in conjunction with a [HidableController] to define custom
/// visibility behavior for scrollable elements.
typedef HidableVisibility = double Function(
ScrollPosition position,
double currentVisibility,
);

/// A custom wrapper for scroll controller.
///
/// Implements the main listener mehtod for [ScrollController].
/// And the [sizeNotifier] for providing/updating the hideable status.
class HidableController {
ScrollController scrollController;
double size;
double previousOffset = 0.0;
double visiblePercentage = 1.0;

final visibilityNotifier = ValueNotifier<double>(1.0);
HidableVisibility? hideableVisibility;
double deltaFactor;

HidableController({
required this.scrollController,
required this.size,
this.hideableVisibility,
required this.deltaFactor,
}) {
scrollController.addListener(updateVisibility);
scrollController.addListener(() => updateVisibility(hideableVisibility, deltaFactor));
}

double calculateVisiblePercentage() => 1.0 - (previousOffset / size);
final visibilityNotifier = ValueNotifier<double>(1.0);

void updateVisibility() {
void updateVisibility(HidableVisibility? visibility, double deltaFactor) {
final position = scrollController.position;
final currentOffset = position.pixels;

previousOffset = (previousOffset + currentOffset - previousOffset).clamp(0.0, size);

if (position.axisDirection == AxisDirection.down && position.extentAfter == 0.0) {
if (visibilityNotifier.value == 0.0) return;
visibilityNotifier.value = 0.0;
if (visibility != null) {
visibilityNotifier.value = visibility(
position,
visibilityNotifier.value,
);
return;
}

if (position.axisDirection == AxisDirection.up && position.extentBefore == 0.0) {
if (visibilityNotifier.value == 1.0) return;
visibilityNotifier.value = 1.0;
return;
if (position.userScrollDirection == ScrollDirection.reverse) {
visibilityNotifier.value = (visibilityNotifier.value - deltaFactor).clamp(0, 1);
} else if (position.userScrollDirection == ScrollDirection.forward) {
visibilityNotifier.value = (visibilityNotifier.value + deltaFactor).clamp(0, 1);
}

final isFullyVisible = previousOffset == 0.0 && visibilityNotifier.value == 0.0;
if (isFullyVisible || (previousOffset == size && visibilityNotifier.value == 1.0)) return;

visibilityNotifier.value = calculateVisiblePercentage();
}

void close() => visibilityNotifier.dispose();
Expand Down
39 changes: 37 additions & 2 deletions lib/src/hidable_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,63 @@ class Hidable extends StatelessWidget implements PreferredSizeWidget {
/// (56 heights with page-size width).
final Size preferredWidgetSize;

/// This parameter allows you to define a custom visibility behavior for the [child] widget
/// based on scrolling actions. You can provide a function of type [HidableVisibility]
/// to determine when and how the widget should be hidden or revealed during scrolling.
///
/// Example usage:
/// ```dart
/// Hidable(
/// child: MyWidget(),
/// controller: scrollController,
/// visibility: (position, currentVisibility) {
/// // Custom visibility logic here.
/// // Return the updated visibility value.
/// },
/// )
/// ```
///
/// If not provided, the default visibility behavior will be used.
final HidableVisibility? visibility;

/// A factor that determines the speed at which the [child] widget's visibility changes
/// when scrolling occurs.
///
/// The `deltaFactor` value should be a double between 0.0 and 1.0, where:
/// - 0.0 indicates that the [child] widget's visibility won't change when scrolling.
/// - 1.0 indicates that the [child] widget's visibility will change rapidly when scrolling.
///
/// A lower `deltaFactor` value results in a slower change in visibility, making the
/// [child] widget's hiding/revealing behavior more gradual. Conversely, a higher value
/// makes the change in visibility more immediate.
///
/// The default value is 0.04, which provides a moderate speed of visibility change.
final double deltaFactor;

const Hidable({
Key? key,
required this.child,
required this.controller,
@deprecated this.wOpacity = true,
this.enableOpacityAnimation = true,
this.preferredWidgetSize = const Size.fromHeight(56),
this.visibility,
this.deltaFactor = 0.06,
}) : super(key: key);

@override
Size get preferredSize => preferredWidgetSize;

@override
Widget build(BuildContext context) {
final hidable = controller.hidable(preferredWidgetSize.height, hashCode);
final hidable = controller.hidable(hashCode, visibility, deltaFactor);
return ValueListenableBuilder<double>(
valueListenable: hidable.visibilityNotifier,
builder: (_, factor, __) => Align(
heightFactor: factor,
alignment: const Alignment(0, -1),
child: SizedBox(
height: hidable.size,
height: preferredWidgetSize.height,
child: enableOpacityAnimation ? Opacity(opacity: factor, child: child) : child,
),
),
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: hidable
description: Widget, that can make any static located widget hidable (scroll to hide).
version: 1.0.5
version: 1.0.6
homepage: https://github.com/insolite-dev/hidable
issue_tracker: https://github.com/insolite-dev/hidable/issues
documentation: https://github.com/insolite-dev/hidable#readme
Expand Down
19 changes: 6 additions & 13 deletions test/hidable_controller_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,27 @@ import 'package:hidable/src/hidable_controller.dart';
import 'test_widget.dart';

void main() {
const kSize = kBottomNavigationBarHeight;

late ScrollController controller;
late ScrollController controllerFromHidable;

setUpAll(() {
controller = ScrollController();
controllerFromHidable = controller.hidable(kSize, 0).scrollController;
controllerFromHidable = controller.hidable(0, null, 0.08).scrollController;
});

group("HidableControllerExt", () {
test('should generate hidable controller from scroll controller', () {
final hidable = controller.hidable(kBottomNavigationBarHeight, 1);
final hidable = controller.hidable(1, (p, cv) {
return 1;
}, 0.08);
expect(hidable.runtimeType, HidableController);

final reCreatedHidable = controller.hidable(kBottomNavigationBarHeight, 1);
final reCreatedHidable = controller.hidable(1, null, 0.08);
expect(hidable, reCreatedHidable);
});
});

group("HidableController", () {
test('size factor should return right value', () {
final hidable = controller.hidable(kSize, 2);

final factor = 1 - (hidable.previousOffset / hidable.size);
expect(hidable.calculateVisiblePercentage(), factor);
});

testWidgets(
'listener should work correctly',
(WidgetTester tester) async {
Expand All @@ -60,7 +53,7 @@ void main() {

test(
'close should disable value notifiers correctly',
() => controller.hidable(kSize, 3).close(),
() => controller.hidable(1, null, 0.08).close(),
);
});
}
3 changes: 3 additions & 0 deletions test/hidable_widet_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ void main() {
await tester.pumpWidget(TestWidget(
wAppBar: true,
scrollController: scrollController,
visibility: (position, current) {
return 1;
},
));

expect(find.byType(Align), findsNWidgets(2));
Expand Down
16 changes: 10 additions & 6 deletions test/test_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@

import 'package:flutter/material.dart';
import 'package:hidable/hidable.dart';
import 'package:hidable/src/hidable_controller.dart';

// Just a simple testing utility widget.
// Used to test, hidable widget and hidable controller.
class TestWidget extends StatelessWidget {
final bool enableOpacityAnimation;
final ScrollController scrollController;
final bool wAppBar;
final HidableVisibility? visibility;

const TestWidget({
Key? key,
required this.scrollController,
this.enableOpacityAnimation = true,
this.wAppBar = false,
}) : super(key: key);
const TestWidget(
{Key? key,
required this.scrollController,
this.enableOpacityAnimation = true,
this.wAppBar = false,
this.visibility})
: super(key: key);

@override
Widget build(BuildContext context) {
Expand All @@ -33,6 +36,7 @@ class TestWidget extends StatelessWidget {
bottomNavigationBar: wAppBar
? null
: Hidable(
visibility: visibility,
controller: scrollController,
enableOpacityAnimation: enableOpacityAnimation,
child: Container(
Expand Down

0 comments on commit 9fa99bb

Please sign in to comment.