mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +01:00
New nav bar design in Collections page
This commit is contained in:
parent
791a65713b
commit
1adbb453fd
6 changed files with 430 additions and 193 deletions
|
@ -8,6 +8,12 @@ import 'package:nc_photos/exception.dart';
|
|||
import 'package:nc_photos/navigation_manager.dart';
|
||||
import 'package:nc_photos/widget/trusted_cert_manager.dart';
|
||||
|
||||
class AppMessageException implements Exception {
|
||||
const AppMessageException(this.message);
|
||||
|
||||
final String message;
|
||||
}
|
||||
|
||||
/// Convert an exception to a user-facing string
|
||||
///
|
||||
/// Typically used with SnackBar to show a proper error message
|
||||
|
@ -65,6 +71,8 @@ String toUserString(Object? exception) {
|
|||
"Failed to update files: ${exception.files.map((f) => f.filename).join(", ")}",
|
||||
null
|
||||
);
|
||||
} else if (exception is AppMessageException) {
|
||||
return (exception.message, null);
|
||||
}
|
||||
return (exception?.toString() ?? "Unknown error", null);
|
||||
}
|
||||
|
|
|
@ -11,3 +11,34 @@ class ValueStreamBuilder<T> extends StreamBuilder<T> {
|
|||
initialData: stream?.value,
|
||||
);
|
||||
}
|
||||
|
||||
class ValueStreamBuilderEx<T> extends StreamBuilder<T> {
|
||||
ValueStreamBuilderEx({
|
||||
super.key,
|
||||
ValueStream<T>? stream,
|
||||
required StreamWidgetBuilder builder,
|
||||
}) : super(
|
||||
stream: stream,
|
||||
initialData: stream?.value,
|
||||
builder: builder.snapshotBuilder ??
|
||||
(context, snapshot) {
|
||||
return builder.valueBuilder!(context, snapshot.requireData);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class StreamWidgetBuilder<T> {
|
||||
const StreamWidgetBuilder._({
|
||||
this.snapshotBuilder,
|
||||
this.valueBuilder,
|
||||
});
|
||||
|
||||
const StreamWidgetBuilder.snapshot(AsyncWidgetBuilder<T> builder)
|
||||
: this._(snapshotBuilder: builder);
|
||||
const StreamWidgetBuilder.value(
|
||||
Widget Function(BuildContext context, T value) builder)
|
||||
: this._(valueBuilder: builder);
|
||||
|
||||
final AsyncWidgetBuilder<T>? snapshotBuilder;
|
||||
final Widget Function(BuildContext context, T value)? valueBuilder;
|
||||
}
|
||||
|
|
|
@ -21,10 +21,12 @@ import 'package:nc_photos/entity/collection/content_provider/album.dart';
|
|||
import 'package:nc_photos/entity/collection/content_provider/nc_album.dart';
|
||||
import 'package:nc_photos/entity/collection/util.dart' as collection_util;
|
||||
import 'package:nc_photos/exception_event.dart';
|
||||
import 'package:nc_photos/exception_util.dart';
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/np_api_util.dart';
|
||||
import 'package:nc_photos/platform/features.dart' as features;
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/stream_util.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/theme/dimension.dart';
|
||||
import 'package:nc_photos/widget/album_importer.dart';
|
||||
|
@ -49,6 +51,7 @@ import 'package:to_string/to_string.dart';
|
|||
part 'home_collections.g.dart';
|
||||
part 'home_collections/app_bar.dart';
|
||||
part 'home_collections/bloc.dart';
|
||||
part 'home_collections/navigation_bar.dart';
|
||||
part 'home_collections/state_event.dart';
|
||||
part 'home_collections/type.dart';
|
||||
part 'home_collections/view.dart';
|
||||
|
@ -138,41 +141,7 @@ class _WrappedHomeCollectionsState extends State<_WrappedHomeCollections>
|
|||
? const _AppBar()
|
||||
: const _SelectionAppBar(),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
sliver: _BlocBuilder(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.selectedItems.isEmpty !=
|
||||
current.selectedItems.isEmpty,
|
||||
builder: (context, state) => _ButtonGrid(
|
||||
account: _bloc.account,
|
||||
isEnabled: state.selectedItems.isEmpty,
|
||||
onSharingPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
SharingBrowser.routeName,
|
||||
arguments: SharingBrowserArguments(_bloc.account));
|
||||
},
|
||||
onEnhancedPhotosPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
EnhancedPhotoBrowser.routeName,
|
||||
arguments:
|
||||
const EnhancedPhotoBrowserArguments(null));
|
||||
},
|
||||
onArchivePressed: () {
|
||||
Navigator.of(context)
|
||||
.pushNamed(ArchiveBrowser.routeName);
|
||||
},
|
||||
onTrashbinPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
TrashbinBrowser.routeName,
|
||||
arguments: TrashbinBrowserArguments(_bloc.account));
|
||||
},
|
||||
onNewCollectionPressed: () {
|
||||
_onNewCollectionPressed(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const _NavigationBar(),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 8),
|
||||
),
|
||||
|
@ -241,36 +210,6 @@ class _WrappedHomeCollectionsState extends State<_WrappedHomeCollections>
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _onNewCollectionPressed(BuildContext context) async {
|
||||
try {
|
||||
final collection = await showDialog<Collection>(
|
||||
context: context,
|
||||
builder: (_) => NewCollectionDialog(
|
||||
account: _bloc.account,
|
||||
),
|
||||
);
|
||||
if (collection == null) {
|
||||
return;
|
||||
}
|
||||
// Right now we don't have a way to add photos inside the
|
||||
// CollectionBrowser, eventually we should add that and remove this
|
||||
// branching
|
||||
if (collection.isDynamicCollection) {
|
||||
// open the newly created collection
|
||||
unawaited(Navigator.of(context).pushNamed(
|
||||
CollectionBrowser.routeName,
|
||||
arguments: CollectionBrowserArguments(collection),
|
||||
));
|
||||
}
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout("[_onNewCollectionPressed] Failed", e, stacktrace);
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().createCollectionFailureNotification),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _onBackButtonPressed(BuildContext context) async {
|
||||
if (context.state.selectedItems.isEmpty) {
|
||||
return DoubleTapExitHandler()();
|
||||
|
|
|
@ -85,6 +85,13 @@ extension _$_BlocNpLog on _Bloc {
|
|||
static final log = Logger("widget.home_collections._Bloc");
|
||||
}
|
||||
|
||||
extension _$_NavBarNewButtonNpLog on _NavBarNewButton {
|
||||
// ignore: unused_element
|
||||
Logger get _log => log;
|
||||
|
||||
static final log = Logger("widget.home_collections._NavBarNewButton");
|
||||
}
|
||||
|
||||
extension _$_ItemNpLog on _Item {
|
||||
// ignore: unused_element
|
||||
Logger get _log => log;
|
||||
|
|
380
app/lib/widget/home_collections/navigation_bar.dart
Normal file
380
app/lib/widget/home_collections/navigation_bar.dart
Normal file
|
@ -0,0 +1,380 @@
|
|||
part of '../home_collections.dart';
|
||||
|
||||
enum HomeCollectionsNavBarButtonType {
|
||||
// the order must not be changed
|
||||
sharing,
|
||||
edited,
|
||||
archive,
|
||||
trash,
|
||||
;
|
||||
|
||||
static HomeCollectionsNavBarButtonType fromValue(int value) =>
|
||||
HomeCollectionsNavBarButtonType.values[value];
|
||||
}
|
||||
|
||||
class _NavigationBar extends StatefulWidget {
|
||||
const _NavigationBar();
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _NavigationBarState();
|
||||
}
|
||||
|
||||
class _NavigationBarState extends State<_NavigationBar> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = ScrollController();
|
||||
_scrollController
|
||||
.addListener(() => _updateButtonScroll(_scrollController.position));
|
||||
_ensureUpdateButtonScroll();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final buttons =
|
||||
_buttons.map((e) => _buildButton(context, e)).nonNulls.toList();
|
||||
return SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
ListView.separated(
|
||||
controller: _scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
itemCount: buttons.length,
|
||||
itemBuilder: (context, i) => buttons[i],
|
||||
separatorBuilder: (context, _) => const SizedBox(width: 16),
|
||||
),
|
||||
if (_hasLeftContent)
|
||||
Positioned(
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: IgnorePointer(
|
||||
ignoring: true,
|
||||
child: Container(
|
||||
width: 32,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.background,
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.background
|
||||
.withOpacity(0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_hasRightContent)
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: IgnorePointer(
|
||||
ignoring: true,
|
||||
child: Container(
|
||||
width: 32,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.background
|
||||
.withOpacity(0),
|
||||
Theme.of(context).colorScheme.background,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const _NavBarNewButton(),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildButton(
|
||||
BuildContext context, HomeCollectionsNavBarButtonType type) {
|
||||
switch (type) {
|
||||
case HomeCollectionsNavBarButtonType.sharing:
|
||||
return const _NavBarSharingButton();
|
||||
case HomeCollectionsNavBarButtonType.edited:
|
||||
return features.isSupportEnhancement
|
||||
? const _NavBarEditedButton()
|
||||
: null;
|
||||
case HomeCollectionsNavBarButtonType.archive:
|
||||
return const _NavBarArchiveButton();
|
||||
case HomeCollectionsNavBarButtonType.trash:
|
||||
return const _NavBarTrashButton();
|
||||
}
|
||||
}
|
||||
|
||||
bool _updateButtonScroll(ScrollPosition pos) {
|
||||
if (!pos.hasContentDimensions || !pos.hasPixels) {
|
||||
return false;
|
||||
}
|
||||
if (pos.pixels <= pos.minScrollExtent) {
|
||||
if (_hasLeftContent) {
|
||||
setState(() {
|
||||
_hasLeftContent = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!_hasLeftContent) {
|
||||
setState(() {
|
||||
_hasLeftContent = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (pos.pixels >= pos.maxScrollExtent) {
|
||||
if (_hasRightContent) {
|
||||
setState(() {
|
||||
_hasRightContent = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!_hasRightContent) {
|
||||
setState(() {
|
||||
_hasRightContent = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
_hasFirstScrollUpdate = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void _ensureUpdateButtonScroll() {
|
||||
if (_hasFirstScrollUpdate || !mounted) {
|
||||
return;
|
||||
}
|
||||
if (_scrollController.hasClients) {
|
||||
if (_updateButtonScroll(_scrollController.position)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Timer(const Duration(milliseconds: 100), _ensureUpdateButtonScroll);
|
||||
}
|
||||
|
||||
static const _buttons = [
|
||||
HomeCollectionsNavBarButtonType.sharing,
|
||||
HomeCollectionsNavBarButtonType.edited,
|
||||
HomeCollectionsNavBarButtonType.archive,
|
||||
HomeCollectionsNavBarButtonType.trash,
|
||||
];
|
||||
|
||||
late final ScrollController _scrollController;
|
||||
var _hasFirstScrollUpdate = false;
|
||||
var _hasLeftContent = false;
|
||||
var _hasRightContent = false;
|
||||
}
|
||||
|
||||
class _NavBarButtonIndicator extends StatelessWidget {
|
||||
const _NavBarButtonIndicator();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipOval(
|
||||
child: Container(
|
||||
width: 4,
|
||||
height: 4,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NavBarButton extends StatelessWidget {
|
||||
const _NavBarButton({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.isMinimized,
|
||||
this.isShowIndicator = false,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _BlocSelector(
|
||||
selector: (state) => state.selectedItems.isEmpty,
|
||||
builder: (context, isEnabled) => isMinimized
|
||||
? IconButton.outlined(
|
||||
icon: Stack(
|
||||
children: [
|
||||
icon,
|
||||
if (isShowIndicator)
|
||||
const Positioned(
|
||||
right: 2,
|
||||
top: 2,
|
||||
child: _NavBarButtonIndicator(),
|
||||
),
|
||||
],
|
||||
),
|
||||
tooltip: label,
|
||||
onPressed: isEnabled ? onPressed : null,
|
||||
)
|
||||
: ActionChip(
|
||||
avatar: icon,
|
||||
label: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(label),
|
||||
if (isShowIndicator) ...const [
|
||||
SizedBox(width: 4),
|
||||
_NavBarButtonIndicator(),
|
||||
],
|
||||
],
|
||||
),
|
||||
onPressed: isEnabled ? onPressed : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final Widget icon;
|
||||
final String label;
|
||||
final bool isMinimized;
|
||||
final bool isShowIndicator;
|
||||
final VoidCallback onPressed;
|
||||
}
|
||||
|
||||
@npLog
|
||||
class _NavBarNewButton extends StatelessWidget {
|
||||
const _NavBarNewButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _NavBarButton(
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
label: L10n.global().createCollectionTooltip,
|
||||
isMinimized: true,
|
||||
onPressed: () async {
|
||||
try {
|
||||
final collection = await showDialog<Collection>(
|
||||
context: context,
|
||||
builder: (_) => NewCollectionDialog(
|
||||
account: context.bloc.account,
|
||||
),
|
||||
);
|
||||
if (collection == null) {
|
||||
return;
|
||||
}
|
||||
// Right now we don't have a way to add photos inside the
|
||||
// CollectionBrowser, eventually we should add that and remove this
|
||||
// branching
|
||||
if (collection.isDynamicCollection) {
|
||||
// open the newly created collection
|
||||
unawaited(Navigator.of(context).pushNamed(
|
||||
CollectionBrowser.routeName,
|
||||
arguments: CollectionBrowserArguments(collection),
|
||||
));
|
||||
}
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout("[build] Uncaught exception", e, stacktrace);
|
||||
context.addEvent(_SetError(AppMessageException(
|
||||
L10n.global().createCollectionFailureNotification)));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NavBarSharingButton extends StatelessWidget {
|
||||
const _NavBarSharingButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueStreamBuilderEx(
|
||||
stream: context
|
||||
.read<AccountController>()
|
||||
.accountPrefController
|
||||
.hasNewSharedAlbum,
|
||||
builder: StreamWidgetBuilder.value(
|
||||
(context, hasNewSharedAlbum) => _NavBarButton(
|
||||
icon: const Icon(Icons.share_outlined),
|
||||
label: L10n.global().collectionSharingLabel,
|
||||
isMinimized: false,
|
||||
isShowIndicator: hasNewSharedAlbum,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
SharingBrowser.routeName,
|
||||
arguments: SharingBrowserArguments(context.bloc.account),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NavBarEditedButton extends StatelessWidget {
|
||||
const _NavBarEditedButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _NavBarButton(
|
||||
icon: const Icon(Icons.auto_fix_high_outlined),
|
||||
label: L10n.global().collectionEditedPhotosLabel,
|
||||
isMinimized: false,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
EnhancedPhotoBrowser.routeName,
|
||||
arguments: const EnhancedPhotoBrowserArguments(null),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NavBarArchiveButton extends StatelessWidget {
|
||||
const _NavBarArchiveButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _NavBarButton(
|
||||
icon: const Icon(Icons.archive_outlined),
|
||||
label: L10n.global().albumArchiveLabel,
|
||||
isMinimized: false,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(ArchiveBrowser.routeName);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NavBarTrashButton extends StatelessWidget {
|
||||
const _NavBarTrashButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _NavBarButton(
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
label: L10n.global().albumTrashLabel,
|
||||
isMinimized: false,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
TrashbinBrowser.routeName,
|
||||
arguments: TrashbinBrowserArguments(context.bloc.account),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,133 +1,5 @@
|
|||
part of '../home_collections.dart';
|
||||
|
||||
class _ButtonGrid extends StatelessWidget {
|
||||
const _ButtonGrid({
|
||||
required this.account,
|
||||
required this.isEnabled,
|
||||
this.onSharingPressed,
|
||||
this.onEnhancedPhotosPressed,
|
||||
this.onArchivePressed,
|
||||
this.onTrashbinPressed,
|
||||
this.onNewCollectionPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// needed to workaround a scrolling bug when there are more than one
|
||||
// SliverStaggeredGrids in a CustomScrollView
|
||||
// see: https://github.com/letsar/flutter_staggered_grid_view/issues/98 and
|
||||
// https://github.com/letsar/flutter_staggered_grid_view/issues/265
|
||||
return SliverToBoxAdapter(
|
||||
child: StaggeredGridView.extent(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(0),
|
||||
maxCrossAxisExtent: 256,
|
||||
staggeredTiles: List.filled(5, const StaggeredTile.fit(1)),
|
||||
children: [
|
||||
_ButtonGridItemView(
|
||||
icon: Icons.share_outlined,
|
||||
label: L10n.global().collectionSharingLabel,
|
||||
isShowIndicator: context
|
||||
.read<AccountController>()
|
||||
.accountPrefController
|
||||
.hasNewSharedAlbumValue,
|
||||
isEnabled: isEnabled,
|
||||
onTap: () {
|
||||
onSharingPressed?.call();
|
||||
},
|
||||
),
|
||||
if (features.isSupportEnhancement)
|
||||
_ButtonGridItemView(
|
||||
icon: Icons.auto_fix_high_outlined,
|
||||
label: L10n.global().collectionEditedPhotosLabel,
|
||||
isEnabled: isEnabled,
|
||||
onTap: () {
|
||||
onEnhancedPhotosPressed?.call();
|
||||
},
|
||||
),
|
||||
_ButtonGridItemView(
|
||||
icon: Icons.archive_outlined,
|
||||
label: L10n.global().albumArchiveLabel,
|
||||
isEnabled: isEnabled,
|
||||
onTap: () {
|
||||
onArchivePressed?.call();
|
||||
},
|
||||
),
|
||||
_ButtonGridItemView(
|
||||
icon: Icons.delete_outlined,
|
||||
label: L10n.global().albumTrashLabel,
|
||||
isEnabled: isEnabled,
|
||||
onTap: () {
|
||||
onTrashbinPressed?.call();
|
||||
},
|
||||
),
|
||||
_ButtonGridItemView(
|
||||
icon: Icons.add,
|
||||
label: L10n.global().createCollectionTooltip,
|
||||
isEnabled: isEnabled,
|
||||
onTap: () {
|
||||
onNewCollectionPressed?.call();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final Account account;
|
||||
final bool isEnabled;
|
||||
final VoidCallback? onSharingPressed;
|
||||
final VoidCallback? onEnhancedPhotosPressed;
|
||||
final VoidCallback? onArchivePressed;
|
||||
final VoidCallback? onTrashbinPressed;
|
||||
final VoidCallback? onNewCollectionPressed;
|
||||
}
|
||||
|
||||
class _ButtonGridItemView extends StatelessWidget {
|
||||
const _ButtonGridItemView({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
this.isShowIndicator = false,
|
||||
required this.isEnabled,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ActionChip(
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
labelPadding: const EdgeInsetsDirectional.fromSTEB(8, 0, 0, 0),
|
||||
// specify icon size explicitly to workaround size flickering during
|
||||
// theme transition
|
||||
avatar: Icon(icon, size: 18),
|
||||
label: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(label),
|
||||
),
|
||||
if (isShowIndicator)
|
||||
Icon(
|
||||
Icons.circle,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
size: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: isEnabled ? onTap : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final bool isShowIndicator;
|
||||
final bool isEnabled;
|
||||
final VoidCallback? onTap;
|
||||
}
|
||||
|
||||
class _ItemView extends StatelessWidget {
|
||||
const _ItemView({
|
||||
required this.account,
|
||||
|
|
Loading…
Reference in a new issue