Skip to content

Commit

Permalink
feat: Input Source picker in a bottom sheet (#4281)
Browse files Browse the repository at this point in the history
  • Loading branch information
g123k committed Jul 12, 2023
1 parent 965ee71 commit a5fb0e1
Show file tree
Hide file tree
Showing 2 changed files with 260 additions and 39 deletions.
100 changes: 100 additions & 0 deletions packages/smooth_app/lib/generic_lib/widgets/smooth_bottom_sheet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';

Future<T?> showSmoothModalSheet<T>({
required BuildContext context,
required WidgetBuilder builder,
}) {
return showModalBottomSheet<T>(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: ROUNDED_RADIUS),
),
builder: builder,
);
}

class SmoothModalSheet extends StatelessWidget {
const SmoothModalSheet({
required this.title,
required this.body,
this.closeButton = true,
this.bodyPadding,
this.closeButtonSemanticsOrder = 2.0,
});

final String title;
final bool closeButton;
final double closeButtonSemanticsOrder;
final Widget body;
final EdgeInsetsGeometry? bodyPadding;

@override
Widget build(BuildContext context) {
final Color primaryColor = Theme.of(context).primaryColor;

return ClipRRect(
borderRadius: const BorderRadius.vertical(top: ROUNDED_RADIUS),
child: DecoratedBox(
decoration: const BoxDecoration(
borderRadius: BorderRadius.vertical(top: ROUNDED_RADIUS),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
color: primaryColor.withOpacity(0.2),
padding: EdgeInsetsDirectional.only(
start: VERY_LARGE_SPACE,
top: VERY_SMALL_SPACE,
bottom: VERY_SMALL_SPACE,
end: VERY_LARGE_SPACE - (closeButton ? LARGE_SPACE : 0),
),
child: Row(
children: <Widget>[
Expanded(
child: Semantics(
sortKey: const OrdinalSortKey(1.0),
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleLarge,
),
),
),
if (closeButton)
Semantics(
value: MaterialLocalizations.of(context)
.closeButtonTooltip,
button: true,
excludeSemantics: true,
onScrollDown: () {},
sortKey: OrdinalSortKey(closeButtonSemanticsOrder),
child: Tooltip(
message: MaterialLocalizations.of(context)
.closeButtonTooltip,
enableFeedback: true,
child: InkWell(
onTap: () => Navigator.of(context).pop(),
customBorder: const CircleBorder(),
child: const Padding(
padding: EdgeInsets.all(MEDIUM_SPACE),
child: Icon(Icons.clear),
),
),
),
)
],
),
),
Padding(
padding: bodyPadding ?? const EdgeInsets.all(MEDIUM_SPACE),
child: body,
),
],
)),
);
}
}
199 changes: 160 additions & 39 deletions packages/smooth_app/lib/pages/image_crop_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
Expand All @@ -10,8 +11,8 @@ import 'package:provider/provider.dart';
import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/database/dao_int.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
import 'package:smooth_app/generic_lib/loading_dialog.dart';
import 'package:smooth_app/generic_lib/widgets/smooth_bottom_sheet.dart';
import 'package:smooth_app/helpers/camera_helper.dart';
import 'package:smooth_app/helpers/database_helper.dart';
import 'package:smooth_app/pages/crop_page.dart';
Expand Down Expand Up @@ -41,49 +42,169 @@ Future<UserPictureSource?> _getUserPictureSource(
if (source != UserPictureSource.SELECT) {
return source;
}
final AppLocalizations appLocalizations = AppLocalizations.of(context);
bool? remember = false;
return showDialog<UserPictureSource>(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (
final BuildContext context,
final void Function(VoidCallback fn) setState,
) =>
SmoothAlertDialog(
title: appLocalizations.choose_image_source_title,
actionsAxis: Axis.vertical,
body: CheckboxListTile(
activeColor: FAIR_GREY_COLOR,
value: remember,
onChanged: (final bool? value) => setState(
() => remember = value,

return showSmoothModalSheet<UserPictureSource>(
context: context,
builder: (BuildContext context) {
final AppLocalizations appLocalizations = AppLocalizations.of(context);

return SmoothModalSheet(
title: appLocalizations.choose_image_source_title,
closeButton: true,
closeButtonSemanticsOrder: 5.0,
body: const _ImageSourcePicker(),
bodyPadding: const EdgeInsetsDirectional.only(
start: 10.0,
end: MEDIUM_SPACE,
top: LARGE_SPACE,
bottom: MEDIUM_SPACE,
),
);
});
}

class _ImageSourcePicker extends StatefulWidget {
const _ImageSourcePicker();

@override
State<_ImageSourcePicker> createState() => _ImageSourcePickerState();
}

class _ImageSourcePickerState extends State<_ImageSourcePicker> {
bool rememberChoice = false;

@override
Widget build(BuildContext context) {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
final Color primaryColor = Theme.of(context).primaryColor;

return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Row(
children: <Widget>[
Expanded(
flex: 5,
child: _ImageSourceButton(
semanticsOrder: 2.0,
onPressed: () => _selectSource(UserPictureSource.CAMERA),
label: Text(
appLocalizations.settings_app_camera,
textAlign: TextAlign.center,
),
icon: const Icon(Icons.camera_alt, size: 30.0),
),
),
const Spacer(),
Expanded(
flex: 5,
child: _ImageSourceButton(
onPressed: () => _selectSource(UserPictureSource.GALLERY),
semanticsOrder: 3.0,
label: Text(
appLocalizations.gallery_source_label,
textAlign: TextAlign.center,
),
icon: const Icon(Icons.image, size: 30.0),
),
),
],
),
),
title: Text(appLocalizations.user_picture_source_remember),
),
positiveAction: SmoothActionButton(
text: appLocalizations.settings_app_camera,
onPressed: () {
const UserPictureSource result = UserPictureSource.CAMERA;
if (remember == true) {
userPreferences.setUserPictureSource(result);
}
Navigator.pop(context, result);
},
const SizedBox(height: VERY_LARGE_SPACE),
Semantics(
sortKey: const OrdinalSortKey(4.0),
value: appLocalizations.user_picture_source_remember,
checked: rememberChoice,
excludeSemantics: true,
child: InkWell(
onTap: () => setState(() => rememberChoice = !rememberChoice),
borderRadius: ANGULAR_BORDER_RADIUS,
splashColor: primaryColor.withOpacity(0.2),
child: Row(
children: <Widget>[
IgnorePointer(
child: Checkbox.adaptive(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6.0)),
),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
value: rememberChoice,
onChanged: (final bool? value) => setState(
() => rememberChoice = value ?? false,
),
),
),
Expanded(
child: Text(appLocalizations.user_picture_source_remember),
)
],
),
),
),
negativeAction: SmoothActionButton(
text: appLocalizations.gallery_source_label,
onPressed: () {
const UserPictureSource result = UserPictureSource.GALLERY;
if (remember == true) {
userPreferences.setUserPictureSource(result);
}
Navigator.pop(context, result);
},
],
);
}

void _selectSource(UserPictureSource source) {
if (rememberChoice == true) {
context.read<UserPreferences>().setUserPictureSource(source);
}
Navigator.pop(context, source);
}
}

class _ImageSourceButton extends StatelessWidget {
const _ImageSourceButton({
required this.icon,
required this.label,
required this.onPressed,
this.semanticsOrder,
});

final Icon icon;
final Widget label;
final VoidCallback onPressed;
final double? semanticsOrder;

@override
Widget build(BuildContext context) {
final Color primaryColor = Theme.of(context).primaryColor;

return Semantics(
sortKey: semanticsOrder != null ? OrdinalSortKey(semanticsOrder!) : null,
child: OutlinedButton(
onPressed: onPressed,
style: ButtonStyle(
side: MaterialStatePropertyAll<BorderSide>(
BorderSide(color: primaryColor),
),
padding: const MaterialStatePropertyAll<EdgeInsetsGeometry>(
EdgeInsets.symmetric(vertical: LARGE_SPACE),
),
shape: MaterialStatePropertyAll<OutlinedBorder>(
RoundedRectangleBorder(
borderRadius: ROUNDED_BORDER_RADIUS,
side: BorderSide(color: primaryColor),
),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
icon,
const SizedBox(height: SMALL_SPACE),
label,
],
),
),
),
);
);
}
}

/// Lets the user pick a picture, crop it, and save it.
Expand Down

0 comments on commit a5fb0e1

Please sign in to comment.