Files
lunasea/lib/core/ui/appbar.dart
Jagandeep Brar 139c9a0cd3 (ui): Removed all deprecated UI elements, added scroll-back support to entire UI, clean-up codebase (#396)
* Added RadarrManualImportConfigure for manual import config options

* Default bottom modal sheet to not expand

* Shrinkwrap modal listviews around content

* use keyboardDismissBehavior for all ListView

* Ensure keyboard is unfocused when opening the drawer

* Fetch and store quality definitions in global Radarr state

* Ability to configure language and quality profile for manual import instances

* Reworking all search bar implementations, integrated scroll back for Lidarr across module

* (ui): [Dashboard] Clean up of dashboard code, integrated scroll to top support

* (ui): [SnackBar] Remove all deprecated uses of LSSnackBar, add scrollback for NZBGet, improve error handling in queue

* (ui): Removed usage of old StickyListView, ReorderableListView, integrated LunaScaffold into base routes

* (flutter): Update packages

* (flutter): Remove flutter_sticky_header package

* (ui): Remove unused deprecated classes

* (chore): Update changelog

* (fix): [Dashboard/Calendar] Radarr releases were not considering digital release date

* (ui): [Sonarr] Adapted for scroll back to top functionality, remove more deprecated UI elements

* (ui): Refactored Tautulli, Removed ALL deprecated UI elements from LunaSea

* (ui): Fixed inconsistent usage of material and luna colours across UI

* (fix): [Sonarr/Radarr] Do not show "Search For..." button when the query is empty, but there are no results

* (chore): Updated changelog

* (fix): [Sonarr/Add] Fix search queries not gettting executed

* (fix): [Tautulli] Remove unused global state elements

* (feat): [UI/AppBar] Ability to hide the leading widget

* (fix): [Radarr/Languages] Fix "Unknown" language not being shown in selection dialog

* (feat): [Radarr/Import] Ability to search movies to select, toggle real and proper toggles

* (chore): Updated packages
2021-04-01 22:06:07 -05:00

285 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:lunasea/core.dart';
enum _APPBAR_TYPE {
DEFAULT,
EMPTY,
DROPDOWN,
}
class LunaAppBar extends StatefulWidget implements PreferredSizeWidget {
final _APPBAR_TYPE type;
final String title;
final List<Widget> actions;
final PreferredSizeWidget bottom;
final bool hideLeading;
final bool useDrawer;
final Widget child;
final double height;
final List<String> profiles;
final PageController pageController;
final List<ScrollController> scrollControllers;
@override
Size get preferredSize {
double _size = (height ?? kToolbarHeight);
if(bottom != null) _size += bottom.preferredSize.height;
return new Size.fromHeight(_size);
}
LunaAppBar._internal({
@required this.type,
@required this.useDrawer,
this.title,
this.actions,
this.bottom,
this.child,
this.height,
this.profiles,
this.pageController,
this.scrollControllers,
this.hideLeading = false,
});
/// Create a new [AppBar] widget pre-styled for LunaSea.
///
/// Will register an onTap gesture for the AppBar if:
/// - [state] is supplied, and will call [scrollBackList] on the state controller.
/// - [pageController] and [scrollControllers] are supplied, will register a listener on the page controller and scroll back the respective scroll controller.
///
/// Passing in all 3 will result in state.scrollBackList taking precedence.
factory LunaAppBar({
@required String title,
List<Widget> actions,
PreferredSizeWidget bottom,
bool useDrawer = false,
bool hideLeading = false,
PageController pageController,
List<ScrollController> scrollControllers,
}) {
assert(title != null);
if(pageController != null) assert(scrollControllers != null, 'pageController is defined, scrollControllers should as well.');
return LunaAppBar._internal(
title: title,
actions: actions,
bottom: bottom,
useDrawer: useDrawer,
pageController: pageController,
scrollControllers: scrollControllers,
hideLeading: hideLeading,
type: _APPBAR_TYPE.DEFAULT,
);
}
/// Create a new, empty [LunaAppBar] which can be used to attach to a [Scaffold] in a [PageView] that is already wrapped in an [AppBar].
///
/// Example usages would be a [PageView] but a single page needs an [AppBar] bottom widget.
///
/// The default padding is for a [LunaTextInputBar].
factory LunaAppBar.empty({
@required Widget child,
@required double height,
EdgeInsets padding = LunaTextInputBar.appBarMargin,
Alignment alignment = Alignment.topCenter,
}) {
assert(child != null);
assert(height != null);
return LunaAppBar._internal(
child: Container(
child: child,
height: height,
padding: padding,
alignment: alignment,
),
height: height,
useDrawer: false,
type: _APPBAR_TYPE.EMPTY,
);
}
/// Create a [lunaAppBar] with the title widget having a dropdown to switch to the supplied list of profiles.
///
/// Will register an onTap gesture for the AppBar if:
/// - [state] is supplied, and will call [scrollBackList] on the state controller.
/// - [pageController] and [scrollControllers] are supplied, will register a listener on the page controller and scroll back the respective scroll controller.
///
/// Passing in all 3 will result in state.scrollBackList taking precedence.
factory LunaAppBar.dropdown({
@required String title,
@required List<String> profiles,
bool useDrawer = true,
bool hideLeading = false,
List<Widget> actions,
PageController pageController,
List<ScrollController> scrollControllers,
}) {
assert(title != null);
assert(profiles != null);
if(pageController != null) assert(scrollControllers != null, 'if pageController is defined, scrollControllers should as well.');
if(profiles == null || profiles.length < 2) return LunaAppBar._internal(
title: title,
actions: actions,
useDrawer: useDrawer,
hideLeading: hideLeading,
pageController: pageController,
scrollControllers: scrollControllers,
type: _APPBAR_TYPE.DEFAULT,
);
return LunaAppBar._internal(
title: title,
profiles: profiles,
actions: actions,
useDrawer: useDrawer,
hideLeading: hideLeading,
pageController: pageController,
scrollControllers: scrollControllers,
type: _APPBAR_TYPE.DROPDOWN,
);
}
@override
State<StatefulWidget> createState() => _State();
}
class _State extends State<LunaAppBar> {
int _index;
@override
void initState() {
_index = widget.pageController?.initialPage ?? 0;
widget.pageController?.addListener(_pageControllerListener);
super.initState();
}
@override
void dispose() {
widget.pageController?.removeListener(_pageControllerListener);
super.dispose();
}
void _pageControllerListener() {
if((widget.pageController?.page?.round() ?? _index) == _index) return;
_index = widget.pageController.page.round();
}
void _onTap() {
try {
if(
widget.scrollControllers != null && // Ensure that the array isn't null
widget.scrollControllers.length != 0 && // Ensure that there is at least one scroll controller
(widget.scrollControllers.length-1) >= _index && // Ensure that the index isn't outside of the array bounds
widget.scrollControllers[_index] != null // Ensure that the scroll controller at the position isn't null
) widget.scrollControllers[_index]?.lunaAnimateToStart();
} catch (error, stack) {
LunaLogger().error('Failed to scroll back: Index: $_index, ScrollControllers: ${widget.scrollControllers?.length ?? 0}', error, stack);
}
}
@override
Widget build(BuildContext context) {
Widget child;
switch(widget.type) {
case _APPBAR_TYPE.DEFAULT: child = _default(context); break;
case _APPBAR_TYPE.EMPTY: child = _empty(context); break;
case _APPBAR_TYPE.DROPDOWN: child = _dropdown(context); break;
default: throw Exception('Unknown AppBar type.');
}
return GestureDetector(
child: child,
onTap: _onTap,
);
}
Widget _sharedLeading(BuildContext context) {
if(widget.hideLeading ?? false) return null;
if(widget.useDrawer) return IconButton(
icon: Icon(Icons.menu_rounded),
onPressed: () async {
HapticFeedback.lightImpact();
if(Scaffold.of(context).hasDrawer) {
Scaffold.of(context).openDrawer();
FocusManager.instance.primaryFocus?.unfocus();
}
},
);
return InkWell(
child: Icon(Icons.arrow_back_ios_rounded),
onTap: () async {
HapticFeedback.lightImpact();
Navigator.of(context).lunaSafetyPop();
},
onLongPress: () async {
HapticFeedback.heavyImpact();
Navigator.of(context).lunaPopToFirst();
},
borderRadius: BorderRadius.circular(28.0),
);
}
Widget _default(BuildContext context) {
return AppBar(
title: Text(
widget.title ?? '',
overflow: TextOverflow.fade,
style: TextStyle(fontSize: LunaUI.FONT_SIZE_APP_BAR),
),
leading: _sharedLeading(context),
automaticallyImplyLeading: !(widget.hideLeading ?? false),
centerTitle: false,
elevation: 0,
actions: widget.actions,
bottom: widget.bottom,
);
}
Widget _empty(BuildContext context) {
return AppBar(
automaticallyImplyLeading: false,
toolbarHeight: widget.height,
leadingWidth: 0.0,
elevation: 0.0,
titleSpacing: 0.0,
title: widget.child,
);
}
Widget _dropdown(BuildContext context) {
return AppBar(
title: LunaPopupMenuButton<String>(
tooltip: 'Change Profiles',
child: Wrap(
direction: Axis.horizontal,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Text(
widget.title,
style: TextStyle(fontSize: LunaUI.FONT_SIZE_HEADER),
),
Icon(Icons.arrow_drop_down),
],
),
onSelected: (result) {
HapticFeedback.selectionClick();
LunaProfile().safelyChangeProfiles(result);
},
itemBuilder: (context) {
return <PopupMenuEntry<String>>[for(String profile in widget.profiles) PopupMenuItem<String>(
value: profile,
child: Text(
profile,
style: TextStyle(
fontSize: LunaUI.FONT_SIZE_SUBTITLE,
),
),
)];
},
),
leading: _sharedLeading(context),
centerTitle: false,
elevation: 0,
actions: widget.actions,
);
}
}