Browse Source

feat(neon): add AdaptiveListTile

Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
pull/1012/head
Nikolas Rimikis 1 year ago
parent
commit
c193a4a0ca
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 1
      .cspell/dart_flutter.txt
  2. 8
      packages/neon/neon/lib/src/settings/widgets/account_settings_tile.dart
  3. 11
      packages/neon/neon/lib/src/settings/widgets/custom_settings_tile.dart
  4. 19
      packages/neon/neon/lib/src/utils/adaptive.dart
  5. 3
      packages/neon/neon/lib/src/widgets/account_switcher_button.dart
  6. 23
      packages/neon/neon/lib/src/widgets/account_tile.dart
  7. 135
      packages/neon/neon/lib/src/widgets/adaptive_widgets/list_tile.dart
  8. 8
      packages/neon/neon/lib/src/widgets/error.dart
  9. 5
      packages/neon/neon/lib/src/widgets/unified_search_results.dart
  10. 3
      packages/neon/neon/lib/src/widgets/validation_tile.dart

1
.cspell/dart_flutter.txt

@ -1,4 +1,5 @@
autofocus autofocus
cupertino
endtemplate endtemplate
expando expando
gapless gapless

8
packages/neon/neon/lib/src/settings/widgets/account_settings_tile.dart

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
@ -18,11 +20,11 @@ class AccountSettingsTile extends SettingsTile {
/// {@macro neon.AccountTile.account} /// {@macro neon.AccountTile.account}
final Account account; final Account account;
/// {@macro neon.AccountTile.trailing} /// {@macro neon.AdaptiveListTile.trailing}
final Widget? trailing; final Widget? trailing;
/// {@macro neon.AccountTile.onTap} /// {@macro neon.AdaptiveListTile.onTap}
final GestureTapCallback? onTap; final FutureOr<void> Function()? onTap;
@override @override
Widget build(final BuildContext context) => NeonAccountTile( Widget build(final BuildContext context) => NeonAccountTile(

11
packages/neon/neon/lib/src/settings/widgets/custom_settings_tile.dart

@ -1,11 +1,14 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:neon/src/settings/widgets/settings_tile.dart'; import 'package:neon/src/settings/widgets/settings_tile.dart';
import 'package:neon/src/widgets/adaptive_widgets/list_tile.dart';
@internal @internal
class CustomSettingsTile extends SettingsTile { class CustomSettingsTile extends SettingsTile {
const CustomSettingsTile({ const CustomSettingsTile({
this.title, required this.title,
this.subtitle, this.subtitle,
this.leading, this.leading,
this.trailing, this.trailing,
@ -13,14 +16,14 @@ class CustomSettingsTile extends SettingsTile {
super.key, super.key,
}); });
final Widget? title; final Widget title;
final Widget? subtitle; final Widget? subtitle;
final Widget? leading; final Widget? leading;
final Widget? trailing; final Widget? trailing;
final GestureTapCallback? onTap; final FutureOr<void> Function()? onTap;
@override @override
Widget build(final BuildContext context) => ListTile( Widget build(final BuildContext context) => AdaptiveListTile(
title: title, title: title,
subtitle: subtitle, subtitle: subtitle,
leading: leading, leading: leading,

19
packages/neon/neon/lib/src/utils/adaptive.dart

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
/// Returns whether the current platform is a Cupertino one.
///
/// This is true for both `TargetPlatform.iOS` and `TargetPlatform.macOS`.
bool isCupertino(final BuildContext context) {
final theme = Theme.of(context);
switch (theme.platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return false;
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return true;
}
}

3
packages/neon/neon/lib/src/widgets/account_switcher_button.dart

@ -7,6 +7,7 @@ import 'package:neon/src/pages/settings.dart';
import 'package:neon/src/router.dart'; import 'package:neon/src/router.dart';
import 'package:neon/src/utils/provider.dart'; import 'package:neon/src/utils/provider.dart';
import 'package:neon/src/widgets/account_selection_dialog.dart'; import 'package:neon/src/widgets/account_selection_dialog.dart';
import 'package:neon/src/widgets/adaptive_widgets/list_tile.dart';
import 'package:neon/src/widgets/user_avatar.dart'; import 'package:neon/src/widgets/user_avatar.dart';
@internal @internal
@ -23,7 +24,7 @@ class AccountSwitcherButton extends StatelessWidget {
builder: (final context) => NeonAccountSelectionDialog( builder: (final context) => NeonAccountSelectionDialog(
highlightActiveAccount: true, highlightActiveAccount: true,
children: [ children: [
ListTile( AdaptiveListTile(
leading: const Icon(Icons.settings), leading: const Icon(Icons.settings),
title: Text(NeonLocalizations.of(context).settingsAccountManage), title: Text(NeonLocalizations.of(context).settingsAccountManage),
onTap: () { onTap: () {

23
packages/neon/neon/lib/src/widgets/account_tile.dart

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intersperse/intersperse.dart'; import 'package:intersperse/intersperse.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
@ -5,6 +7,7 @@ import 'package:neon/src/bloc/result.dart';
import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/utils/provider.dart'; import 'package:neon/src/utils/provider.dart';
import 'package:neon/src/widgets/adaptive_widgets/list_tile.dart';
import 'package:neon/src/widgets/error.dart'; import 'package:neon/src/widgets/error.dart';
import 'package:neon/src/widgets/linear_progress_indicator.dart'; import 'package:neon/src/widgets/linear_progress_indicator.dart';
import 'package:neon/src/widgets/user_avatar.dart'; import 'package:neon/src/widgets/user_avatar.dart';
@ -27,23 +30,11 @@ class NeonAccountTile extends StatelessWidget {
/// {@endtemplate} /// {@endtemplate}
final Account account; final Account account;
/// {@template neon.AccountTile.trailing} /// {@macro neon.AdaptiveListTile.trailing}
/// A widget to display after the title.
///
/// Typically an [Icon] widget.
///
/// To show right-aligned metadata (assuming left-to-right reading order;
/// left-aligned for right-to-left reading order), consider using a [Row] with
/// [CrossAxisAlignment.baseline] alignment whose first item is [Expanded] and
/// whose second child is the metadata text, instead of using the [trailing]
/// property.
/// {@endtemplate}
final Widget? trailing; final Widget? trailing;
/// {@template neon.AccountTile.onTap} /// {@macro neon.AdaptiveListTile.onTap}
/// Called when the user taps this list tile. final FutureOr<void> Function()? onTap;
/// {@endtemplate}
final GestureTapCallback? onTap;
/// Whether to also show the status on the avatar. /// Whether to also show the status on the avatar.
/// ///
@ -55,7 +46,7 @@ class NeonAccountTile extends StatelessWidget {
Widget build(final BuildContext context) { Widget build(final BuildContext context) {
final userDetailsBloc = NeonProvider.of<AccountsBloc>(context).getUserDetailsBlocFor(account); final userDetailsBloc = NeonProvider.of<AccountsBloc>(context).getUserDetailsBlocFor(account);
return ListTile( return AdaptiveListTile(
onTap: onTap, onTap: onTap,
leading: NeonUserAvatar( leading: NeonUserAvatar(
account: account, account: account,

135
packages/neon/neon/lib/src/widgets/adaptive_widgets/list_tile.dart

@ -0,0 +1,135 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
/// A wrapper widget that adaptively displays a [ListTile] on Material platforms
/// and a [CupertinoListTile] on Cupertino ones.
class AdaptiveListTile extends StatelessWidget {
/// Creates a new adaptive list tile.
///
/// If supplied the [subtitle] will be displayed below the title.
const AdaptiveListTile({
required this.title,
this.enabled = true,
this.subtitle,
this.leading,
this.trailing,
this.onTap,
super.key,
}) : additionalInfo = null;
/// Creates a new adaptive list tile.
///
/// If supplied the [additionalInfo] will be displayed below the title on
/// Material platforms and as a trailing widget on Cupertino ones.
const AdaptiveListTile.additionalInfo({
required this.title,
this.enabled = true,
this.additionalInfo,
this.leading,
this.trailing,
this.onTap,
super.key,
}) : subtitle = additionalInfo;
/// {@template neon.AdaptiveListTile.title}
/// A [title] is used to convey the central information. Usually a [Text].
/// {@endtemplate}
final Widget title;
/// {@template neon.AdaptiveListTile.subtitle}
/// A [subtitle] is used to display additional information. It is located
/// below [title]. Usually a [Text] widget.
final Widget? subtitle;
/// {@template neon.AdaptiveListTile.additionalInfo}
/// Similar to [subtitle], an [additionalInfo] is used to display additional
/// information. However, instead of being displayed below [title], it is
/// displayed on the right, before [trailing]. Usually a [Text] widget.
///
/// This is only available on Cupertino platforms.
/// {@endtemplate}
final Widget? additionalInfo;
/// {@template neon.AdaptiveListTile.leading}
/// A widget displayed at the start of the [AdaptiveListTile]. This is
/// typically an `Icon` or an `Image`.
/// {@endtemplate}
final Widget? leading;
/// {@template neon.AdaptiveListTile.trailing}
/// A widget displayed at the end of the [AdaptiveListTile].
/// {@endtemplate}
final Widget? trailing;
/// {@template neon.AdaptiveListTile.onTap}
/// The [onTap] function is called when a user taps on the[AdaptiveListTile].
/// If left `null`, the [AdaptiveListTile] will not react to taps.
///
/// If the platform is a Cupertino one and this is a `Future<void> Function()`,
/// then the [AdaptiveListTile] remains activated until the returned future is
/// awaited. This is according to iOS behavior.
/// However, if this function is a `void Function()`, then the tile is active
/// only for the duration of invocation.
/// {@endtemplate}
final FutureOr<void> Function()? onTap;
/// {@template neon.AdaptiveListTile.enabled}
/// Whether this list tile is interactive.
///
/// If false, this list tile is styled with the disabled color from the
/// current [Theme] and the [onTap] callback is inoperative.
/// {@endtemplate}
final bool enabled;
@override
Widget build(final BuildContext context) {
final theme = Theme.of(context);
switch (theme.platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return ListTile(
title: title,
subtitle: subtitle,
leading: leading,
trailing: trailing,
onTap: onTap,
enabled: enabled,
);
case TargetPlatform.iOS:
case TargetPlatform.macOS:
final tile = CupertinoListTile(
title: title,
subtitle: additionalInfo == null ? subtitle : null,
additionalInfo: additionalInfo,
leading: leading,
trailing: trailing,
onTap: enabled ? onTap : null,
);
if (!enabled) {
var data = CupertinoTheme.of(context);
data = data.copyWith(
textTheme: data.resolveFrom(context).textTheme.copyWith(
textStyle: data.textTheme.textStyle.merge(
TextStyle(
color: theme.disabledColor,
),
),
),
);
return CupertinoTheme(
data: data,
child: tile,
);
}
return tile;
}
}
}

8
packages/neon/neon/lib/src/widgets/error.dart

@ -8,6 +8,7 @@ import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/router.dart'; import 'package:neon/src/router.dart';
import 'package:neon/src/utils/exceptions.dart'; import 'package:neon/src/utils/exceptions.dart';
import 'package:neon/src/utils/provider.dart'; import 'package:neon/src/utils/provider.dart';
import 'package:neon/src/widgets/adaptive_widgets/list_tile.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
import 'package:universal_io/io.dart'; import 'package:universal_io/io.dart';
@ -22,7 +23,7 @@ enum NeonErrorType {
/// Shows a column with the error message and a retry button. /// Shows a column with the error message and a retry button.
column, column,
/// Shows a [ListTile] with the error. /// Shows a [AdaptiveListTile] with the error.
listTile, listTile,
} }
@ -147,10 +148,9 @@ class NeonError extends StatelessWidget {
), ),
); );
case NeonErrorType.listTile: case NeonErrorType.listTile:
return ListTile( return AdaptiveListTile(
leading: errorIcon, leading: errorIcon,
title: Text(message), title: Text(message, style: textStyle),
titleTextStyle: textStyle,
onTap: onPressed, onTap: onPressed,
); );
} }

5
packages/neon/neon/lib/src/widgets/unified_search_results.dart

@ -8,6 +8,7 @@ import 'package:neon/src/blocs/unified_search.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/theme/sizes.dart'; import 'package:neon/src/theme/sizes.dart';
import 'package:neon/src/utils/provider.dart'; import 'package:neon/src/utils/provider.dart';
import 'package:neon/src/widgets/adaptive_widgets/list_tile.dart';
import 'package:neon/src/widgets/error.dart'; import 'package:neon/src/widgets/error.dart';
import 'package:neon/src/widgets/image.dart'; import 'package:neon/src/widgets/image.dart';
import 'package:neon/src/widgets/linear_progress_indicator.dart'; import 'package:neon/src/widgets/linear_progress_indicator.dart';
@ -81,7 +82,7 @@ class NeonUnifiedSearchResults extends StatelessWidget {
visible: result.isLoading, visible: result.isLoading,
), ),
if (entries.isEmpty) ...[ if (entries.isEmpty) ...[
ListTile( AdaptiveListTile(
leading: const Icon( leading: const Icon(
Icons.close, Icons.close,
size: largeIconSize, size: largeIconSize,
@ -90,7 +91,7 @@ class NeonUnifiedSearchResults extends StatelessWidget {
), ),
], ],
for (final entry in entries) ...[ for (final entry in entries) ...[
ListTile( AdaptiveListTile(
leading: NeonImageWrapper( leading: NeonImageWrapper(
size: const Size.square(largeIconSize), size: const Size.square(largeIconSize),
child: _buildThumbnail(context, accountsBloc.activeAccount.value!, entry), child: _buildThumbnail(context, accountsBloc.activeAccount.value!, entry),

3
packages/neon/neon/lib/src/widgets/validation_tile.dart

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:neon/src/widgets/adaptive_widgets/list_tile.dart';
/// Validation list tile. /// Validation list tile.
/// ///
@ -48,7 +49,7 @@ class NeonValidationTile extends StatelessWidget {
size: size, size: size,
), ),
}; };
return ListTile( return AdaptiveListTile(
leading: leading, leading: leading,
title: Text( title: Text(
title, title,

Loading…
Cancel
Save