Skip to content

Commit

Permalink
Use visual density to drive touchMode
Browse files Browse the repository at this point in the history
Add visual density support to more areas of the app
Add responsive icons 2x/3x
Cleanup device / form factor detection
Update BooksHomeView to feel better on mobile
Fix bug with tooltip getting stuck open
  • Loading branch information
esDotDev committed Mar 19, 2021
1 parent f7ef3ed commit 33f523f
Show file tree
Hide file tree
Showing 51 changed files with 456 additions and 661 deletions.
40 changes: 36 additions & 4 deletions lib/_utils/device_info.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,42 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_folio/core_packages.dart';
import 'package:universal_platform/universal_platform.dart';

class DeviceInfo {
static bool get isDesktop => UniversalPlatform.isWindows || UniversalPlatform.isMacOS || UniversalPlatform.isLinux;
enum FormFactorType { Monitor, SmallPhone, LargePhone, Tablet }

static bool get isDesktopOrWeb => isDesktop || kIsWeb == true;
class DeviceOS {
// Syntax sugar, proxy the UniversalPlatform methods so our views can reference a single class
static bool isIOS = UniversalPlatform.isIOS;
static bool isAndroid = UniversalPlatform.isAndroid;
static bool isMac = UniversalPlatform.isMacOS;
static bool isLinux = UniversalPlatform.isLinux;
static bool isWindows = UniversalPlatform.isWindows;

static bool get isMobile => !isDesktopOrWeb;
// Higher level device class abstractions (more syntax sugar for the views)
static bool isWeb = kIsWeb;
static bool get isDesktop => isWindows || isMac || isLinux;
static bool get isMobile => isAndroid || isIOS;
static bool get isDesktopOrWeb => isDesktop || isWeb;
static bool get isMobileOrWeb => isMobile || isWeb;
}

class DeviceScreen {
// Get the device form factor as best we can.
// Otherwise we will use the screen size to determine which class we fall into.
static FormFactorType get(BuildContext context) {
double size = context.widthPx;
print(size);
if (context.widthPx <= 400) return FormFactorType.SmallPhone;
if (context.widthPx <= 600) return FormFactorType.LargePhone;
if (context.widthPx <= 1200) return FormFactorType.Tablet;
return FormFactorType.Monitor;
}

// Shortcuts for various mobile device types
static bool isPhone(BuildContext context) => isSmallPhone(context) || isLargePhone(context);
static bool isTablet(BuildContext context) => get(context) == FormFactorType.Tablet;
static bool isMonitor(BuildContext context) => get(context) == FormFactorType.Monitor;
static bool isSmallPhone(BuildContext context) => get(context) == FormFactorType.SmallPhone;
static bool isLargePhone(BuildContext context) => get(context) == FormFactorType.LargePhone;
}
9 changes: 4 additions & 5 deletions lib/_utils/keyboard_utils.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flutter/services.dart';
import 'package:universal_platform/universal_platform.dart';
import 'package:flutter_folio/_utils/device_info.dart';

class KeyboardUtils {
static bool get isShiftDown => isKeyDown([LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight]);
Expand All @@ -18,11 +18,10 @@ class KeyboardUtils {
return false;
}

//TODO: Feature this snippet
static bool get isCommandOrControlDown {
bool isDown = false;
// If shift is not down, look for Command on Mac, and Control on Windows/Linux
if (UniversalPlatform.isMacOS) {
// Command on MacOS, and Control on Windows/Linux are generally analogous
if (DeviceOS.isMac) {
isDown = KeyboardUtils.isCommandDown;
} else {
isDown = KeyboardUtils.isControlDown;
Expand Down Expand Up @@ -67,7 +66,7 @@ class KeyboardUtils {
// Keyboard mode, without the modifier key, is a simple single-select tap
else {
// On Mac, tapping a selected thing in Finder does nothing
if ((UniversalPlatform.isMacOS) && wasSelected) {
if ((DeviceOS.isMac) && wasSelected) {
return selected;
}
// On Linux/Win clicking a thing will select it and de-select any others
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Debouncer {
Duration duration;
Timer? _timer;

void call(VoidCallback action) {
void run(VoidCallback action) {
_timer?.cancel();
_timer = Timer(duration, action);
}
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions lib/_widgets/context_menu_overlay.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ContextMenuRegion extends StatelessWidget {
Widget build(BuildContext context) {
void showMenu() => ShowContextMenuNotification(child: contextMenu).dispatch(context);
if (isEnabled == false) return child;
// Enable context menus on long press when we're in touch mode
bool touchMode = context.select((AppModel m) => m.enableTouchMode);
return GestureDetector(
behavior: HitTestBehavior.translucent,
Expand Down
17 changes: 10 additions & 7 deletions lib/_widgets/popover/popover_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_folio/core_packages.dart';

import 'popover_notifications.dart';

Expand Down Expand Up @@ -33,7 +34,7 @@ class PopOverControllerState extends State<PopOverController> {

bool get isBarrierOpen => barrierOverlay != null;

void _closeOverlay() {
void closeCurrent() {
_sizeNotifier.value = null;
barrierOverlay?.remove();
mainContentOverlay?.remove();
Expand All @@ -43,7 +44,8 @@ class PopOverControllerState extends State<PopOverController> {
bool _handleNotification(Notification n) {
// Close any open popovers
if (n is ClosePopoverNotification) {
_closeOverlay();
safePrint("PopoverController: _closeOverlay");
closeCurrent();
return true;
}
// Show a new popover
Expand All @@ -52,15 +54,15 @@ class PopOverControllerState extends State<PopOverController> {
n.onContextHandled?.call(this);

//Close existing popOver if one is open
_closeOverlay();
closeCurrent();

// Use Barrier? Hovers and Toasts don't user barriers, ClickOvers do
if (n.useBarrier) {
OverlayEntry b = OverlayEntry(
builder: (_) {
return GestureDetector(
onTap: n.dismissOnBarrierClick ? _closeOverlay : null,
onPanStart: n.dismissOnBarrierClick ? (_) => _closeOverlay() : null,
onTap: n.dismissOnBarrierClick ? closeCurrent : null,
onPanStart: n.dismissOnBarrierClick ? (_) => closeCurrent() : null,
child: Container(color: n.barrierColor),
);
},
Expand All @@ -80,12 +82,12 @@ class PopOverControllerState extends State<PopOverController> {
child: ValueListenableBuilder<Size?>(
valueListenable: _sizeNotifier,
builder: (_, size, __) {
// Guard against null size
size ??= Size(0, 0);
// Calculate the normalized offset, from a top-left starting point
// This means a top-left align is 0,0, and bottom-right is -1,-1 as we shift left and up
double ox = -(n.popAnchor.x + 1) / 2; // Normalize from 0-1
double oy = -(n.popAnchor.y + 1) / 2; // Normalize from 0-1
// Guard against null size
size ??= Size.zero;
print("BUILD OVERLAY: $size");
return CompositedTransformFollower(
offset: Offset(ox * size.width, oy * size.height),
Expand All @@ -106,6 +108,7 @@ class PopOverControllerState extends State<PopOverController> {
),
);
});
safePrint("PopoverController: insert overlay");
Overlay.of(n.context)?.insert(content);
mainContentOverlay = content;
return true;
Expand Down
38 changes: 27 additions & 11 deletions lib/_widgets/popover/popover_region.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_folio/_utils/safe_print.dart';
import 'package:flutter_folio/styled_widgets/buttons/styled_buttons.dart';

import 'popover_controller.dart';
Expand Down Expand Up @@ -116,19 +117,13 @@ class PopOverRegionState extends State<PopOverRegion> {
opaque: true,
onEnter: (_) {
_timer?.cancel();
_timer = Timer.periodic(Duration(milliseconds: 400), (timer) {
_timer = Timer.periodic(Duration(milliseconds: 400), (_) {
safePrint("PopoverRegion: Show!");
show();
timer.cancel();
_timer?.cancel();
});
},
onExit: (_) {
_timer?.cancel();
// Don't close if the overlay is open, it means we've been replaced by a Click action.
if (_popContext?.isBarrierOpen == false) {
ClosePopoverNotification().dispatch(context);
_popContext = null;
}
},
onExit: (_) => hide(),
child: widget.child,
);
} else {
Expand All @@ -137,8 +132,18 @@ class PopOverRegionState extends State<PopOverRegion> {
return CompositedTransformTarget(link: _link, child: content);
}

@override
void dispose() {
hide();
super.dispose();
}

void show() {
if (mounted == false) return;
if (mounted == false) {
safePrint("PopoverRegion: Exiting early not mounted anymore");
return;
}
safePrint("PopoverRegion: Sending notification...");
ShowPopOverNotification(
// Send context with the notification, so the Overlay can use it to send more messages in the future.
context,
Expand All @@ -158,4 +163,15 @@ class PopOverRegionState extends State<PopOverRegion> {
}

void _handleContextHandled(PopOverControllerState value) => _popContext = value;

void hide() {
_timer?.cancel();
// Don't close if the overlay is open, it means we've been replaced by a Click action.
if (_popContext != null && _popContext!.isBarrierOpen == false) {
_popContext?.closeCurrent();
} else {
// safePrint(
// "PopoverRegion: Hide on exit was skipped, context: $_popContext, isOpen: ${_popContext?.isBarrierOpen}");
}
}
}
44 changes: 0 additions & 44 deletions lib/_widgets/single_emoji_paint.dart

This file was deleted.

8 changes: 5 additions & 3 deletions lib/commands/app/bootstrap_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class BootstrapCommand extends Commands.BaseAppCommand {
_configureMemoryCache();

// Once model is loaded, we can configure the desktop.
if (DeviceInfo.isDesktop) {
if (DeviceOS.isDesktop) {
_configureDesktop();
}
// Login?
Expand All @@ -62,9 +62,10 @@ class BootstrapCommand extends Commands.BaseAppCommand {
}

void _configureMemoryCache() {
int cacheSize = (DeviceInfo.isDesktop ? 2048 : 512) << 20;
// Use more memory by default on desktop
int cacheSize = (DeviceOS.isDesktop ? 2048 : 512) << 20;
// If we're on a native platform, reserve some reasonable amt of RAM
if (DeviceInfo.isDesktop) {
if (DeviceOS.isDesktop) {
try {
// Use some percentage of system ram, but don't fall below the default, in case this returns 0 or some other invalid value.
cacheSize = max(cacheSize, (SysInfo.getTotalPhysicalMemory() / 4).round());
Expand All @@ -80,6 +81,7 @@ class BootstrapCommand extends Commands.BaseAppCommand {
IoUtils.instance.showWindowWhenReady();
IoUtils.instance.setTitle("Flutter Folio");
Size minSize = Size(600, 700);
if (kDebugMode) minSize = Size(400, 400);
DesktopWindow.setMinWindowSize(minSize);
if (appModel.windowSize.shortestSide > 0) {
DesktopWindow.setWindowSize(appModel.windowSize);
Expand Down
5 changes: 3 additions & 2 deletions lib/commands/app/copy_share_link_command.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'package:flutter/services.dart';
import 'package:flutter_folio/_utils/cooldown.dart';
import 'package:flutter_folio/_utils/device_info.dart';
import 'package:flutter_folio/commands/commands.dart';
import 'package:flutter_folio/routing/app_link.dart';
import 'package:share/share.dart';

import '../../_utils/timed/cooldown.dart';

class CopyShareLinkCommand extends BaseAppCommand {
String get baseUrl => "https://flutterfolio.com/#";
static CoolDown mobileShareCooldown = CoolDown(Duration(seconds: 1));
Expand All @@ -19,7 +20,7 @@ class CopyShareLinkCommand extends BaseAppCommand {
).toLocation();

// Device clipboard
if (DeviceInfo.isDesktopOrWeb) {
if (DeviceOS.isDesktopOrWeb) {
showToast("Share link copied!");
Clipboard.setData(ClipboardData(text: url));
}
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/app/save_image_to_disk_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter_folio/commands/commands.dart';

class SaveImageToDiskCommand extends BaseAppCommand {
//TODO: Add support for web https://github.com/flutter/flutter/issues/78142
static bool get canUse => DeviceInfo.isDesktop;
static bool get canUse => DeviceOS.isDesktop;

Future<void> run(String url) async {
if (canUse == false) return;
Expand Down
5 changes: 3 additions & 2 deletions lib/commands/app/save_window_size_command.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import 'package:desktop_window/desktop_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_folio/_utils/device_info.dart';
import 'package:flutter_folio/commands/commands.dart';
import 'package:universal_platform/universal_platform.dart';

class SaveWindowSizeCommand extends BaseAppCommand {
Future<void> run() async {
if (UniversalPlatform.isLinux || UniversalPlatform.isWindows || UniversalPlatform.isMacOS) {
// Only save window size to disk on desktop platforms.
if (DeviceOS.isDesktop) {
Size size = await DesktopWindow.getWindowSize();
if (size != appModel.windowSize) {
appModel.windowSize = size;
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/pick_images_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class PickedImage {
class PickImagesCommand extends BaseAppCommand {
Future<List<PickedImage>> run({bool allowMultiple = false, bool enableCamera = true}) async {
List<PickedImage> images = [];
if (DeviceInfo.isDesktopOrWeb) {
if (DeviceOS.isDesktopOrWeb) {
final typeGroup = XTypeGroup(label: 'images', extensions: ['jpg', 'jpeg', 'png']);
images =
(await openFiles(acceptedTypeGroups: [typeGroup])).map((file) => PickedImage()..path = file.path).toList();
Expand Down
Loading

0 comments on commit 33f523f

Please sign in to comment.