invoice/lib/ui/app/entities/entity_list_tile.dart

247 lines
8.4 KiB
Dart

// Flutter imports:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
// Package imports:
import 'package:flutter_redux/flutter_redux.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
// Project imports:
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/actions_menu_button.dart';
import 'package:invoiceninja_flutter/ui/app/entity_state_label.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/app/lists/selected_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
import 'package:invoiceninja_flutter/utils/icons.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:invoiceninja_flutter/utils/strings.dart';
class EntityListTile extends StatefulWidget {
const EntityListTile({
@required this.entity,
@required this.isFilter,
this.onEntityActionSelected,
this.subtitle,
this.client,
});
final String subtitle;
final BaseEntity entity;
final bool isFilter;
final ClientEntity client;
final Function(BuildContext, BaseEntity, EntityAction) onEntityActionSelected;
@override
_EntityListTileState createState() => _EntityListTileState();
}
class _EntityListTileState extends State<EntityListTile> {
bool _isHovered = false;
@override
Widget build(BuildContext context) {
if (widget.entity == null || widget.entity.isNew) {
return SizedBox();
}
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final isFilteredBy = state.uiState.filterEntityId == widget.entity.id &&
state.uiState.filterEntityType == widget.entity.entityType;
final entityClient = widget.client ??
(widget.entity is BelongsToClient
? state.clientState.map[(widget.entity as BelongsToClient).clientId]
: null);
final isHovered =
(!RendererBinding.instance.mouseTracker.mouseIsConnected &&
isFilteredBy) ||
_isHovered;
final leading = ActionMenuButton(
iconData:
isHovered ? Icons.more_vert : getEntityIcon(widget.entity.entityType),
iconSize: isHovered ? null : 18,
entityActions: widget.entity.getActions(
userCompany: state.userCompany,
includeEdit: true,
client: entityClient),
isSaving: false,
entity: widget.entity,
onSelected: (context, action) => widget.onEntityActionSelected != null
? widget.onEntityActionSelected(context, widget.entity, action)
: handleEntityAction(widget.entity, action),
);
// we may have the id (so it's not considered new) but it hasn't yet been
// created in the backend. ie, the invoice after converting a quote
final trailing = widget.entity.createdAt == 0
? null
: IgnorePointer(
ignoring: !isHovered || widget.isFilter,
child: IconButton(
icon: Icon(isHovered ||
widget.isFilter ||
isMobile(context) ||
state.uiState.previewStack.isNotEmpty
? Icons.chevron_right
: Icons.filter_list),
onPressed: () => viewEntity(
entity: widget.entity,
addToStack: isDesktop(context) && !widget.isFilter,
),
),
);
return MouseRegion(
onEnter: (event) => setState(() => _isHovered = true),
onExit: (event) => setState(() => _isHovered = false),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SelectedIndicator(
isSelected: isFilteredBy && isDesktop(context),
isMenu: true,
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
onTap: () {
if (state.prefState
.isViewerFullScreen(widget.entity.entityType))
store.dispatch(ToggleViewerLayout(widget.entity.entityType));
inspectEntity(entity: widget.entity);
},
onLongPress: () =>
inspectEntity(entity: widget.entity, longPress: true),
title: Text(
EntityPresenter().initialize(widget.entity, context).title(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle:
(widget.subtitle ?? '').isEmpty && widget.entity.isActive
? null
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if ((widget.subtitle ?? '').isNotEmpty)
Text(widget.subtitle),
if (!widget.entity.isActive)
EntityStateLabel(widget.entity),
],
),
leading: leading,
trailing: trailing,
isThreeLine:
(widget.subtitle ?? '').isNotEmpty && !widget.entity.isActive,
),
),
ListDivider(),
],
),
);
}
}
class EntitiesListTile extends StatefulWidget {
const EntitiesListTile({
@required this.isFilter,
@required this.entity,
this.entityType,
this.title,
this.subtitle,
this.hideNew = false,
});
final BaseEntity entity;
final EntityType entityType;
final String title;
final String subtitle;
final bool isFilter;
final bool hideNew;
@override
_EntitiesListTileState createState() => _EntitiesListTileState();
}
class _EntitiesListTileState extends State<EntitiesListTile> {
bool _isHovered = false;
void _onTap(BuildContext context) => viewEntitiesByType(
entityType: widget.entityType, filterEntity: widget.entity);
void _onLongPress() {
if (widget.entity.isDeleted) {
return;
}
final store = StoreProvider.of<AppState>(context);
final uiState = store.state.uiState;
final entity = widget.entity;
if (uiState.filterEntityId != entity.id ||
uiState.filterEntityType != entity.entityType) {
store.dispatch(FilterByEntity(entity: entity));
}
handleEntityAction(entity, EntityAction.newEntityType(widget.entityType));
}
@override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final mainRoute = state.uiState.mainRoute;
final isFilterMatch =
widget.isFilter && '${widget.entityType}' == toCamelCase(mainRoute);
return MouseRegion(
onEnter: (event) => setState(() => _isHovered = true),
onExit: (event) => setState(() => _isHovered = false),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SelectedIndicator(
isSelected: isFilterMatch,
isMenu: true,
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
title: Text(widget.title),
subtitle: Text((widget.subtitle ?? '').isEmpty
? AppLocalization.of(context).none
: widget.subtitle),
leading: _isHovered &&
!widget.hideNew &&
!widget.entity.isDeleted &&
state.userCompany.canCreate(widget.entityType)
? IconButton(
icon: Icon(Icons.add_circle_outline),
onPressed: _onLongPress,
)
: IgnorePointer(
child: IconButton(
icon:
Icon(getEntityIcon(widget.entityType), size: 18.0),
onPressed: () => _onTap(context),
),
),
trailing: IgnorePointer(
child: IconButton(
icon: Icon(MdiIcons.chevronDoubleRight),
onPressed: () => null,
),
),
onTap: () => _onTap(context),
onLongPress: _onLongPress,
),
),
ListDivider(),
],
),
);
}
}