yubioath-flutter/lib/app/views/app_list_item.dart

158 lines
4.7 KiB
Dart
Raw Normal View History

2023-06-15 18:39:17 +03:00
/*
* Copyright (C) 2023 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
2023-06-15 18:39:17 +03:00
import '../../core/state.dart';
import '../models.dart';
import '../shortcuts.dart';
import 'action_popup_menu.dart';
class AppListItem<T> extends ConsumerStatefulWidget {
final T item;
2023-06-15 18:39:17 +03:00
final Widget? leading;
final String title;
final String? subtitle;
2024-01-09 16:26:52 +03:00
final String? semanticTitle;
2023-06-15 18:39:17 +03:00
final Widget? trailing;
final List<ActionItem> Function(BuildContext context)? buildPopupActions;
final Intent? tapIntent;
final Intent? doubleTapIntent;
2024-01-09 14:50:26 +03:00
final bool selected;
2023-06-15 18:39:17 +03:00
const AppListItem(
this.item, {
2023-06-15 18:39:17 +03:00
super.key,
this.leading,
required this.title,
2024-01-09 16:26:52 +03:00
this.semanticTitle,
2023-06-15 18:39:17 +03:00
this.subtitle,
this.trailing,
this.buildPopupActions,
this.tapIntent,
this.doubleTapIntent,
2024-01-09 14:50:26 +03:00
this.selected = false,
2023-06-15 18:39:17 +03:00
});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _AppListItemState<T>();
2023-06-15 18:39:17 +03:00
}
class _AppListItemState<T> extends ConsumerState<AppListItem> {
2023-06-15 18:39:17 +03:00
final FocusNode _focusNode = FocusNode();
int _lastTap = 0;
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final subtitle = widget.subtitle;
final buildPopupActions = widget.buildPopupActions;
final tapIntent = widget.tapIntent;
final doubleTapIntent = widget.doubleTapIntent;
2023-06-15 18:39:17 +03:00
final trailing = widget.trailing;
final hasFeature = ref.watch(featureProvider);
2023-06-15 18:39:17 +03:00
2024-01-09 16:26:52 +03:00
return Semantics(
label: widget.semanticTitle ?? widget.title,
child: ItemShortcuts<T>(
item: widget.item,
2024-01-09 16:26:52 +03:00
child: InkWell(
focusNode: _focusNode,
borderRadius: BorderRadius.circular(48),
2024-01-09 16:26:52 +03:00
onSecondaryTapDown: buildPopupActions == null
? null
: (details) {
final menuItems = buildPopupActions(context)
.where((action) =>
action.feature == null || hasFeature(action.feature!))
.toList();
if (menuItems.isNotEmpty) {
showPopupMenu(
context,
details.globalPosition,
menuItems,
);
}
},
onTap: () {
2024-01-19 14:59:57 +03:00
_focusNode.requestFocus();
if (tapIntent != null) {
Actions.invoke(context, tapIntent);
}
if (isDesktop && doubleTapIntent != null) {
2024-01-09 16:26:52 +03:00
final now = DateTime.now().millisecondsSinceEpoch;
if (now - _lastTap < 500) {
setState(() {
_lastTap = 0;
});
Actions.invoke(context, doubleTapIntent);
2024-01-09 16:26:52 +03:00
} else {
setState(() {
_lastTap = now;
});
}
2023-06-15 18:39:17 +03:00
}
2024-01-09 16:26:52 +03:00
},
onLongPress: doubleTapIntent == null
2024-01-09 16:26:52 +03:00
? null
: () {
Actions.invoke(context, doubleTapIntent);
2024-01-09 16:26:52 +03:00
},
child: Stack(
alignment: AlignmentDirectional.center,
children: [
const SizedBox(height: 64),
ListTile(
2024-01-09 17:48:26 +03:00
mouseCursor:
widget.tapIntent != null ? SystemMouseCursors.click : null,
2024-01-09 16:26:52 +03:00
selected: widget.selected,
leading: widget.leading,
title: Text(
widget.title,
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
),
subtitle: subtitle != null
? Text(
subtitle,
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
)
: null,
trailing: trailing == null
? null
: Focus(
skipTraversal: true,
descendantsAreTraversable: false,
child: trailing,
),
2023-06-15 18:39:17 +03:00
),
2024-01-09 16:26:52 +03:00
],
),
2023-06-15 18:39:17 +03:00
),
),
);
}
}