added placeholder for empty custom films list

This commit is contained in:
Vadim 2024-10-26 12:55:55 +02:00
parent 68cecf5391
commit 45d7728c85
5 changed files with 145 additions and 116 deletions

View file

@ -95,7 +95,6 @@ class FilmsProviderState extends State<FilmsProvider> {
setState(() {}); setState(() {});
} }
// TODO: add delete button to UI
void deleteCustomFilm(FilmExponential film) { void deleteCustomFilm(FilmExponential film) {
customFilms.remove(film.id); customFilms.remove(film.id);
_discardSelectedIfNotIncluded(); _discardSelectedIfNotIncluded();
@ -170,12 +169,7 @@ class Films extends InheritedModel<_FilmsModelAspect> {
@override @override
bool updateShouldNotifyDependent(Films oldWidget, Set<_FilmsModelAspect> dependencies) { bool updateShouldNotifyDependent(Films oldWidget, Set<_FilmsModelAspect> dependencies) {
if (dependencies.contains(_FilmsModelAspect.customFilmsList)) {} // TODO: reduce unnecessary notifications
if (dependencies.contains(_FilmsModelAspect.selected)) { return true;
return selected != oldWidget.selected;
} else {
// TODO: reduce unnecessary notifications
return true;
}
} }
} }

View file

@ -4,6 +4,7 @@ import 'package:lightmeter/navigation/routes.dart';
import 'package:lightmeter/providers/films_provider.dart'; import 'package:lightmeter/providers/films_provider.dart';
import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/film_edit/flow_film_edit.dart'; import 'package:lightmeter/screens/film_edit/flow_film_edit.dart';
import 'package:lightmeter/screens/shared/sliver_placeholder/widget_sliver_placeholder.dart';
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart'; import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@ -17,6 +18,14 @@ class FilmsScreen extends StatefulWidget {
class _FilmsScreenState extends State<FilmsScreen> with SingleTickerProviderStateMixin { class _FilmsScreenState extends State<FilmsScreen> with SingleTickerProviderStateMixin {
late final tabController = TabController(length: 2, vsync: this); late final tabController = TabController(length: 2, vsync: this);
@override
void initState() {
super.initState();
tabController.addListener(() {
setState(() {});
});
}
@override @override
void dispose() { void dispose() {
tabController.dispose(); tabController.dispose();
@ -50,22 +59,19 @@ class _FilmsScreenState extends State<FilmsScreen> with SingleTickerProviderStat
), ),
], ],
slivers: [ slivers: [
SliverFillRemaining( if (tabController.index == 0)
child: TabBarView( _FilmsListBuilder(
controller: tabController, films: Films.predefinedFilmsOf(context).toList(),
children: [ onFilmSelected: FilmsProvider.of(context).toggleFilm,
_FilmsListBuilder( )
films: Films.predefinedFilmsOf(context).toList(), else if (tabController.index == 1 && Films.customFilmsOf(context).isNotEmpty)
onFilmSelected: FilmsProvider.of(context).toggleFilm, _FilmsListBuilder<FilmExponential>(
), films: Films.customFilmsOf(context).toList(),
_FilmsListBuilder<FilmExponential>( onFilmSelected: FilmsProvider.of(context).toggleFilm,
films: Films.customFilmsOf(context).toList(), onFilmEdit: _editFilm,
onFilmSelected: FilmsProvider.of(context).toggleFilm, )
onFilmEdit: _editFilm, else
), SliverPlaceholder(onTap: _addFilm),
],
),
),
], ],
); );
} }
@ -98,36 +104,41 @@ class _FilmsListBuilder<T extends Film> extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView.builder( return SliverList.builder(
padding: const EdgeInsets.all(Dimens.paddingM).add(EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom)),
itemCount: films.length, itemCount: films.length,
itemBuilder: (_, index) => Card( itemBuilder: (_, index) => Padding(
shape: RoundedRectangleBorder( padding: EdgeInsets.fromLTRB(
borderRadius: BorderRadius.only( Dimens.paddingM,
topLeft: index == 0 ? const Radius.circular(Dimens.borderRadiusL) : Radius.zero, index == 0 ? Dimens.paddingM : 0,
topRight: index == 0 ? const Radius.circular(Dimens.borderRadiusL) : Radius.zero, Dimens.paddingM,
bottomLeft: index == films.length - 1 ? const Radius.circular(Dimens.borderRadiusL) : Radius.zero, index == films.length - 1 ? Dimens.paddingM + MediaQuery.paddingOf(context).bottom : 0.0,
bottomRight: index == films.length - 1 ? const Radius.circular(Dimens.borderRadiusL) : Radius.zero,
),
), ),
child: Padding( child: Card(
padding: EdgeInsets.only( shape: RoundedRectangleBorder(
top: index == 0 ? Dimens.paddingM : 0.0, borderRadius: BorderRadius.vertical(
bottom: index == films.length - 1 ? Dimens.paddingM : 0.0, top: index == 0 ? const Radius.circular(Dimens.borderRadiusL) : Radius.zero,
bottom: index == films.length - 1 ? const Radius.circular(Dimens.borderRadiusL) : Radius.zero,
),
), ),
child: CheckboxListTile( child: Padding(
controlAffinity: ListTileControlAffinity.leading, padding: EdgeInsets.only(
value: Films.inUseOf(context).contains(films[index]), top: index == 0 ? Dimens.paddingM : 0.0,
title: Text(films[index].name), bottom: index == films.length - 1 ? Dimens.paddingM : 0.0,
onChanged: (value) { ),
onFilmSelected(films[index], value ?? false); child: CheckboxListTile(
}, controlAffinity: ListTileControlAffinity.leading,
secondary: onFilmEdit != null value: Films.inUseOf(context).contains(films[index]),
? IconButton( title: Text(films[index].name),
onPressed: () => onFilmEdit!(films[index]), onChanged: (value) {
icon: const Icon(Icons.edit), onFilmSelected(films[index], value ?? false);
) },
: null, secondary: onFilmEdit != null
? IconButton(
onPressed: () => onFilmEdit!(films[index]),
icon: const Icon(Icons.edit),
)
: null,
),
), ),
), ),
), ),

View file

@ -4,7 +4,7 @@ import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart'; import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart';
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart'; import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart';
import 'package:lightmeter/screens/shared/icon_placeholder/widget_icon_placeholder.dart'; import 'package:lightmeter/screens/shared/sliver_placeholder/widget_sliver_placeholder.dart';
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart'; import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@ -37,12 +37,7 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> {
), ),
], ],
slivers: profilesCount == 1 slivers: profilesCount == 1
? [ ? [SliverPlaceholder(onTap: _addProfile)]
SliverFillRemaining(
hasScrollBody: false,
child: _EquipmentProfilesListPlaceholder(onTap: _addProfile),
),
]
: [ : [
SliverList( SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
@ -131,32 +126,3 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> {
} }
} }
} }
class _EquipmentProfilesListPlaceholder extends StatelessWidget {
final VoidCallback onTap;
const _EquipmentProfilesListPlaceholder({required this.onTap});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: Dimens.sliverAppBarExpandedHeight),
child: FractionallySizedBox(
widthFactor: 1 / 1.618,
child: Center(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(Dimens.paddingL),
child: IconPlaceholder(
icon: Icons.add_outlined,
text: S.of(context).tapToAdd,
),
),
),
),
),
);
}
}

View file

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/shared/icon_placeholder/widget_icon_placeholder.dart';
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
class SliverPlaceholder extends StatelessWidget {
final VoidCallback onTap;
const SliverPlaceholder({required this.onTap});
@override
Widget build(BuildContext context) {
final sliverScreenBottomHeight =
context.findAncestorWidgetOfExactType<SliverScreen>()?.bottom?.preferredSize.height ?? 0.0;
return SliverFillRemaining(
hasScrollBody: false,
child: Padding(
padding: EdgeInsets.only(bottom: Dimens.sliverAppBarExpandedHeight - sliverScreenBottomHeight),
child: FractionallySizedBox(
widthFactor: 1 / 1.618,
child: Center(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(Dimens.paddingL),
child: IconPlaceholder(
icon: Icons.add_outlined,
text: S.of(context).tapToAdd,
),
),
),
),
),
),
);
}
}

View file

@ -25,36 +25,10 @@ class SliverScreen extends StatelessWidget {
bottom: false, bottom: false,
child: CustomScrollView( child: CustomScrollView(
slivers: <Widget>[ slivers: <Widget>[
SliverAppBar.large( _AppBar(
automaticallyImplyLeading: false, title: title,
expandedHeight: Dimens.sliverAppBarExpandedHeight + (bottom?.preferredSize.height ?? 0.0), appBarActions: appBarActions,
flexibleSpace: FlexibleSpaceBar(
centerTitle: false,
titlePadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
title: DefaultTextStyle(
style: Theme.of(context)
.textTheme
.headlineSmall!
.copyWith(color: Theme.of(context).colorScheme.onSurface),
maxLines: 2,
overflow: TextOverflow.ellipsis,
child: _Title(
actionsCount: appBarActions.length + (Navigator.of(context).canPop() ? 1 : 0),
bottomSize: bottom?.preferredSize.height ?? 0.0,
child: title,
),
),
),
bottom: bottom, bottom: bottom,
actions: [
...appBarActions,
if (Navigator.of(context).canPop())
IconButton(
onPressed: Navigator.of(context).pop,
icon: const Icon(Icons.close_outlined),
tooltip: S.of(context).tooltipClose,
),
],
), ),
...slivers, ...slivers,
], ],
@ -64,6 +38,51 @@ class SliverScreen extends StatelessWidget {
} }
} }
class _AppBar extends StatelessWidget {
final Widget title;
final List<Widget> appBarActions;
final PreferredSizeWidget? bottom;
const _AppBar({
required this.title,
this.appBarActions = const [],
this.bottom,
super.key,
});
@override
Widget build(BuildContext context) {
return SliverAppBar.large(
automaticallyImplyLeading: false,
expandedHeight: Dimens.sliverAppBarExpandedHeight + (bottom?.preferredSize.height ?? 0.0),
flexibleSpace: FlexibleSpaceBar(
centerTitle: false,
titlePadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
title: DefaultTextStyle(
style: Theme.of(context).textTheme.headlineSmall!.copyWith(color: Theme.of(context).colorScheme.onSurface),
maxLines: 2,
overflow: TextOverflow.ellipsis,
child: _Title(
actionsCount: appBarActions.length + (Navigator.of(context).canPop() ? 1 : 0),
bottomSize: bottom?.preferredSize.height ?? 0.0,
child: title,
),
),
),
bottom: bottom,
actions: [
...appBarActions,
if (Navigator.of(context).canPop())
IconButton(
onPressed: Navigator.of(context).pop,
icon: const Icon(Icons.close_outlined),
tooltip: S.of(context).tooltipClose,
),
],
);
}
}
class _Title extends StatelessWidget { class _Title extends StatelessWidget {
final Widget child; final Widget child;
final int actionsCount; final int actionsCount;