Skip to content

Commit

Permalink
feat: Favorite remove option (closes #53)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gustl22 committed Apr 21, 2024
1 parent 053275b commit e5f5c92
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 64 deletions.
29 changes: 29 additions & 0 deletions wrestling_scoreboard_client/lib/view/screens/home/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import 'package:wrestling_scoreboard_client/view/screens/overview/team_match/tea
import 'package:wrestling_scoreboard_client/view/screens/overview/team_overview.dart';
import 'package:wrestling_scoreboard_client/view/screens/overview/weight_class_overview.dart';
import 'package:wrestling_scoreboard_client/view/widgets/consumer.dart';
import 'package:wrestling_scoreboard_client/view/widgets/dialogs.dart';
import 'package:wrestling_scoreboard_client/view/widgets/exception.dart';
import 'package:wrestling_scoreboard_client/view/widgets/loading_builder.dart';
import 'package:wrestling_scoreboard_client/view/widgets/responsive_container.dart';
import 'package:wrestling_scoreboard_client/view/widgets/scaffold.dart';
Expand Down Expand Up @@ -130,7 +132,34 @@ class HomeState extends ConsumerState<Home> {
}

Widget _createItem<T extends DataObject>(int id, String route, String Function(T dataObject) getTitle) {
final localizations = AppLocalizations.of(context)!;
return SingleConsumer<T>(
onException: (context, exception, {stackTrace}) => Card(
child: Center(
child: IconButton(
onPressed: () async {
final removeItem = await showOkCancelDialog(
okText: localizations.remove,
getResult: () => true,
context: context,
child: Column(
children: [
Text('There was a problem with the object of type $T and id $id.'),
ExceptionInfo(
exception ?? localizations.errorOccurred,
stackTrace: stackTrace,
),
],
),
);
if (removeItem == true) {
final notifier = ref.read(favoritesNotifierProvider.notifier);
notifier.removeFavorite(getTableNameFromType(T), id);
}
},
icon: const Icon(Icons.warning)),
),
),
id: id,
builder: (context, data) {
return InkWell(
Expand Down
38 changes: 16 additions & 22 deletions wrestling_scoreboard_client/lib/view/screens/more/about.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
import 'package:wrestling_scoreboard_client/main.dart';
import 'package:wrestling_scoreboard_client/view/widgets/card.dart';
import 'package:wrestling_scoreboard_client/view/widgets/dialogs.dart';
import 'package:wrestling_scoreboard_client/view/widgets/loading_builder.dart';
import 'package:wrestling_scoreboard_client/view/widgets/responsive_container.dart';
import 'package:wrestling_scoreboard_client/view/widgets/scaffold.dart';
Expand All @@ -14,6 +15,7 @@ const loadCompleteChangelog = false;

class AboutScreen extends StatelessWidget {
static const route = 'about';

const AboutScreen({super.key});

Future<String> _loadChangelog() async {
Expand Down Expand Up @@ -60,29 +62,21 @@ class AboutScreen extends StatelessWidget {
ListTile(
leading: const Icon(Icons.list),
title: Text(localizations.about_Changelog),
onTap: () => showDialog(
onTap: () => showOkDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
actions: [
TextButton(
child: Text(localizations.ok),
onPressed: () => Navigator.of(context).pop(),
),
],
content: LoadingBuilder(
future: _loadChangelog(),
builder: (context, data) {
return SingleChildScrollView(
child: MarkdownBody(
listItemCrossAxisAlignment: MarkdownListItemCrossAxisAlignment.start,
shrinkWrap: true,
selectable: true,
data: data,
onTapLink: (text, href, title) => launchUrl(Uri.parse(href!)),
),
);
},
),
child: LoadingBuilder(
future: _loadChangelog(),
builder: (context, data) {
return SingleChildScrollView(
child: MarkdownBody(
listItemCrossAxisAlignment: MarkdownListItemCrossAxisAlignment.start,
shrinkWrap: true,
selectable: true,
data: data,
onTapLink: (text, href, title) => launchUrl(Uri.parse(href!)),
),
);
},
),
),
),
Expand Down
30 changes: 20 additions & 10 deletions wrestling_scoreboard_client/lib/view/widgets/consumer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ class NullableSingleConsumer<T extends DataObject> extends ConsumerWidget {
final T? initialData;
final int? id;
final Widget Function(BuildContext context, T? data) builder;
final Widget Function(BuildContext context, Object? exception, {StackTrace? stackTrace})? onException;

const NullableSingleConsumer({
required this.builder,
this.onException,
required this.id,
this.initialData,
super.key,
Expand All @@ -30,11 +32,13 @@ class NullableSingleConsumer<T extends DataObject> extends ConsumerWidget {
return LoadingBuilder<T>(
builder: builder,
future: stream,
initialData: null, // Handle initial data via the stream
initialData: null,
// Handle initial data via the stream
onRetry: () async => (await ref.read(webSocketManagerNotifierProvider))
.onWebSocketConnection
.sink
.add(WebSocketConnectionState.connecting),
onException: onException,
);
}
}
Expand All @@ -43,28 +47,34 @@ class SingleConsumer<T extends DataObject> extends StatelessWidget {
final T? initialData;
final int? id;
final Widget Function(BuildContext context, T data) builder;
final Widget Function(BuildContext context, Object? exception, {StackTrace? stackTrace})? onException;

const SingleConsumer({
required this.builder,
required this.id,
this.initialData,
super.key,
this.onException,
});

@override
Widget build(BuildContext context) {
if (id == null && initialData == null) {
return ExceptionCard(AppLocalizations.of(context)!.notFoundException, stackTrace: null);
return onException?.call(context, null) ??
ExceptionCard(AppLocalizations.of(context)!.notFoundException, stackTrace: null);
}
return NullableSingleConsumer(
builder: (BuildContext context, T? data) {
if (data == null) {
return ExceptionCard(AppLocalizations.of(context)!.notFoundException, stackTrace: null);
}
return builder(context, data);
},
id: id,
initialData: initialData);
builder: (BuildContext context, T? data) {
if (data == null) {
return onException?.call(context, null) ??
ExceptionCard(AppLocalizations.of(context)!.notFoundException, stackTrace: null);
}
return builder(context, data);
},
id: id,
initialData: initialData,
onException: onException,
);
}
}

Expand Down
34 changes: 23 additions & 11 deletions wrestling_scoreboard_client/lib/view/widgets/dialogs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,41 @@ import 'package:wrestling_scoreboard_client/view/utils.dart';
import 'package:wrestling_scoreboard_client/view/widgets/duration_picker.dart';
import 'package:wrestling_scoreboard_client/view/widgets/exception.dart';

class OkDialog extends StatelessWidget {
final Widget child;
class SizedDialog extends StatelessWidget {
/// Do not wrap this into a column with shrinkwrap, so that ListViews act dynamically.
const SizedDialog({super.key, required this.actions, required this.child});

const OkDialog({required this.child, super.key});
final List<Widget> actions;
final Widget child;

@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
return AlertDialog(
content: SizedBox(
width: 300,
child: SingleChildScrollView(child: child),
),
actions: <Widget>[
actions: actions,
);
}
}

class OkDialog extends StatelessWidget {
final Widget child;

const OkDialog({required this.child, super.key});

@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
return SizedDialog(
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(localizations.ok),
),
],
child: child,
);
}
}
Expand All @@ -47,12 +63,7 @@ class OkCancelDialog<T extends Object?> extends StatelessWidget {
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
return AlertDialog(
// Do not wrap this into a column with shrinkwrap, so that ListViews act dynamically.
content: SizedBox(
width: 300,
child: SingleChildScrollView(child: child),
),
return SizedDialog(
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
Expand All @@ -63,6 +74,7 @@ class OkCancelDialog<T extends Object?> extends StatelessWidget {
child: Text(okText ?? localizations.ok),
),
],
child: child,
);
}
}
Expand Down
32 changes: 12 additions & 20 deletions wrestling_scoreboard_client/lib/view/widgets/info.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:wrestling_scoreboard_client/view/widgets/dialogs.dart';
import 'package:wrestling_scoreboard_client/view/widgets/grouped_list.dart';
import 'package:wrestling_scoreboard_common/common.dart';

Expand Down Expand Up @@ -40,26 +41,17 @@ class InfoWidget extends StatelessWidget {
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => showDialog<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
content: Text('${localizations.remove} $classLocale?'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(localizations.cancel),
),
TextButton(
onPressed: () {
onDelete();
Navigator.of(context).pop();
Navigator.of(context).pop();
},
child: Text(localizations.ok),
),
],
),
),
onPressed: () async {
final confirmDelete = await showOkCancelDialog<bool>(
context: context,
child: Text('${localizations.remove} $classLocale?'),
getResult: () => true,
);
if (confirmDelete == true && context.mounted) {
Navigator.of(context).pop();
onDelete();
}
},
),
],
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ class LoadingBuilder<T> extends ConsumerWidget {
final T? initialData;
final void Function()? onRetry;
final Widget Function(BuildContext context, T data) builder;
final Widget Function(BuildContext context, Object? exception, {StackTrace? stackTrace})? onException;

const LoadingBuilder({
super.key,
required this.future,
required this.builder,
this.onException,
this.initialData,
this.onRetry,
});
Expand All @@ -23,7 +25,8 @@ class LoadingBuilder<T> extends ConsumerWidget {
future: ref.read(networkTimeoutNotifierProvider).then((timeout) => future.timeout(timeout)),
builder: (BuildContext context, AsyncSnapshot<T> snapshot) {
if (snapshot.hasError) {
return ExceptionCard(snapshot.error!, stackTrace: snapshot.stackTrace, onRetry: onRetry);
return onException?.call(context, snapshot.error!, stackTrace: snapshot.stackTrace) ??
ExceptionCard(snapshot.error!, stackTrace: snapshot.stackTrace, onRetry: onRetry);
}
if (initialData != null) {
return builder(context, initialData as T);
Expand Down

0 comments on commit e5f5c92

Please sign in to comment.