Skip to content

Commit

Permalink
UX fixes (#81)
Browse files Browse the repository at this point in the history
* Replace QR code scanner icon in AppBar with a button

* Fix opening file on Android by downgrading file_picker

* Made MainScreen content scrollable

* Made StartRemoteDocumentSigningScreen content scrollable

* Resolve issue with toggle torch button covered by info panel

* Update version and changelog

* Fix build Android workflow

* Add Widget test for MainScreen
  • Loading branch information
Matej-Hlatky authored Jan 31, 2025
1 parent 225094e commit faa867a
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 122 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 2025-01-31 - v1.2.1(38)

- #68 | Some screens have scrollable content so it's usable also with larger text size
- #80 | Action for remote signing using QR code scanner is by text button instead of icon
- #79 | Fix unable to open file via browser in Android

## 2025-01-03 - v1.2.0(37)

- #75 | Update to Flutter SDK v3.22.3
Expand Down
1 change: 1 addition & 0 deletions lib/data/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ abstract interface class Settings {
/// [Settings] implementation that uses [SharedPreferences].
/// Note, [clear] is from [NotifiedPreferences].
// TODO Register Settings using Injectable as singleton - would need to pass instance into DI so no need to use "async"
// This is needed not only for convenience when constructing Blocks, however needed for tests
class _SettingsImpl with NotifiedPreferences implements Settings {
@override
late final ValueNotifier<String?> acceptedPrivacyPolicyVersion =
Expand Down
1 change: 1 addition & 0 deletions lib/ui/app_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ThemeData appTheme(
seedColor: primaryColor,
primary: primaryColor,
);
// TODO Consider using Typography.material2021().black.apply(fontSizeFactor: 1.2) so that BuildContext is not needed
final textTheme = Theme.of(context).textTheme.apply(
fontSizeFactor: 1.2,
);
Expand Down
129 changes: 75 additions & 54 deletions lib/ui/screens/main_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ import 'start_remote_document_signing_screen.dart';
/// Main app screen that presents app features.
///
/// Has ability to:
/// - open new file and navigate next to [OpenDocumentScreen]
/// - show [MainMenuScreen]
/// - open new file and navigate next to [OpenDocumentScreen]
/// - navigate to [StartRemoteDocumentSigningScreen]
/// - start Onboarding by navigating to [OnboardingScreen]
/// - starting [Onboarding] flow
class MainScreen extends StatefulWidget {
/// URI to file being opened.
final Uri? incomingUri;

const MainScreen({super.key, required this.incomingUri});
const MainScreen({super.key, this.incomingUri});

@override
State<MainScreen> createState() => _MainScreenState();
Expand Down Expand Up @@ -83,13 +84,12 @@ class _MainScreenState extends State<MainScreen> {
child: Scaffold(
appBar: _MainAppBar(
context: context,
showQrCodeScannerIcon: (onboardingRequired == false),
onMenuPressed: _showMenu,
onQrCodeScannerPressed: _showQrCodeScanner,
),
body: _Body(
onboardingRequired: onboardingRequired,
onStartOnboardingRequested: _onStartOnboardingRequested,
onStartQrCodeScannerRequested: _showQrCodeScanner,
onOpenFileRequested: _onOpenFileRequested,
),
),
Expand Down Expand Up @@ -223,9 +223,7 @@ class _MainScreenState extends State<MainScreen> {
// ignore: non_constant_identifier_names
AppBar _MainAppBar({
required BuildContext context,
bool showQrCodeScannerIcon = true,
VoidCallback? onMenuPressed,
VoidCallback? onQrCodeScannerPressed,
}) {
final iconColor = Theme.of(context).colorScheme.onSecondary;
final colorFilter = ColorFilter.mode(iconColor, BlendMode.srcIn);
Expand All @@ -245,21 +243,6 @@ AppBar _MainAppBar({
onPressed: onMenuPressed,
),
),
actions: [
if (showQrCodeScannerIcon)
Semantics(
label: context.strings.qrCodeScannerOpenSemantics,
button: true,
excludeSemantics: true,
child: IconButton(
icon: SvgPicture.asset(
'assets/icons/qr_code_scanner.svg',
colorFilter: colorFilter,
),
onPressed: onQrCodeScannerPressed,
),
),
],
title: Builder(builder: (context) {
return Text(
context.strings.appName,
Expand All @@ -275,43 +258,84 @@ AppBar _MainAppBar({
class _Body extends StatelessWidget {
final bool? onboardingRequired;
final VoidCallback? onStartOnboardingRequested;
final VoidCallback? onStartQrCodeScannerRequested;
final VoidCallback? onOpenFileRequested;

const _Body({
required this.onboardingRequired,
required this.onStartOnboardingRequested,
required this.onOpenFileRequested,
this.onStartOnboardingRequested,
this.onStartQrCodeScannerRequested,
this.onOpenFileRequested,
});

@override
Widget build(BuildContext context) {
final strings = context.strings;

return Padding(
padding: kScreenMargin,
child: Column(
children: [
const SizedBox(height: 96),
const Padding(
padding: EdgeInsets.all(48),
child: AutogramLogo(),
),
Text(
strings.introHeading,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 24),
Text(
strings.introBody,
style: const TextStyle(height: 1.75),
final primaryColor = Theme.of(context).primaryColor;

return Column(
children: [
// logo, headline and body text
Expanded(
child: Padding(
padding: kScreenMargin.copyWith(bottom: 0),
child: SingleChildScrollView(
primary: true,
child: Column(
children: [
// TODO Decrease top space relatively to overall free space
const SizedBox(height: 60),
const Padding(
padding: EdgeInsets.all(48),
child: AutogramLogo(),
),
Text(
strings.introHeading,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 24),
Text(
strings.introBody,
style: const TextStyle(height: 1.75),
),
],
),
),
),
const Spacer(),
),

// Primary button
_buildPrimaryButton(context),
],
),
// two buttons
Padding(
padding: kScreenMargin,
child: Column(
children: [
// Secondary button
if (onboardingRequired == false)
FilledButton(
// OutlinedButton is ugly
style: FilledButton.styleFrom(
minimumSize: kPrimaryButtonMinimumSize,
backgroundColor: Colors.transparent,
foregroundColor: primaryColor,
side: BorderSide(color: primaryColor, width: 2),
),
onPressed: onStartQrCodeScannerRequested,
child: Text(strings.buttonScanQrCodeLabel),
),

if (onboardingRequired == false)
const SizedBox(height: kButtonSpace),

// Primary button
_buildPrimaryButton(context),
],
),
),
],
);
}

Expand Down Expand Up @@ -343,16 +367,10 @@ class _Body extends StatelessWidget {
type: AppBar,
)
Widget previewMainAppBar(BuildContext context) {
final showQrCodeScannerIcon = context.knobs.boolean(
label: "Show QR code scanner",
initialValue: true,
);

return SizedBox(
height: kToolbarHeight,
child: _MainAppBar(
context: context,
showQrCodeScannerIcon: showQrCodeScannerIcon,
onMenuPressed: () {
developer.log("onMenuPressed");
},
Expand All @@ -376,6 +394,9 @@ Widget previewMainScreen(BuildContext context) {
onStartOnboardingRequested: () {
developer.log("onStartOnboardingRequested");
},
onStartQrCodeScannerRequested: () {
developer.log("onStartQrCodeScannerRequested");
},
onOpenFileRequested: () {
developer.log("onOpenFileRequested");
},
Expand Down
90 changes: 48 additions & 42 deletions lib/ui/screens/qr_code_scanner_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class _QRCodeScannerScreenState extends State<QRCodeScannerScreen> {
alignment: Alignment.topLeft,
child: Padding(
padding: kScreenMargin.copyWith(
top: MediaQuery.of(context).padding.top,
top: MediaQuery.paddingOf(context).top,
),
child: Semantics(
label: context.strings.qrCodeScannerBackSemantics,
Expand All @@ -81,49 +81,44 @@ class _QRCodeScannerScreenState extends State<QRCodeScannerScreen> {
child: _ViewFinder(),
),

// Toggle torch button
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: kScreenMargin.copyWith(bottom: 128),
child: SquareButton(
onPressed: () {
_controller.toggleTorch();
},
child: ValueListenableBuilder<TorchState>(
valueListenable: _controller.torchState,
builder: (context, torchState, _) {
final icon = switch (torchState) {
TorchState.off => Icons.flashlight_on,
TorchState.on => Icons.flashlight_off,
};

final strings = context.strings;
final semanticsLabel = switch (torchState) {
TorchState.off => strings.qrCodeScannerTorchOnSemantics,
TorchState.on => strings.qrCodeScannerTorchOffSemantics,
};

return Semantics(
button: true,
label: semanticsLabel,
child: Icon(
icon,
color: Theme.of(context).colorScheme.onSurface,
),
);
// Toggle torch button + bottom info panel
Padding(
padding: kScreenMargin,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SquareButton(
onPressed: () {
_controller.toggleTorch();
},
child: ValueListenableBuilder<TorchState>(
valueListenable: _controller.torchState,
builder: (context, torchState, _) {
final icon = switch (torchState) {
TorchState.off => Icons.flashlight_on,
TorchState.on => Icons.flashlight_off,
};

final strings = context.strings;
final semanticsLabel = switch (torchState) {
TorchState.off => strings.qrCodeScannerTorchOnSemantics,
TorchState.on => strings.qrCodeScannerTorchOffSemantics,
};

return Semantics(
button: true,
label: semanticsLabel,
child: Icon(
icon,
color: Theme.of(context).colorScheme.onSurface,
),
);
},
),
),
),
),
),

// Bottom info panel
const Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: kScreenMargin,
child: _InfoPanel(),
const _InfoPanel(),
],
),
),
],
Expand Down Expand Up @@ -307,6 +302,17 @@ class _InfoPanel extends StatelessWidget {
}
}

@widgetbook.UseCase(
path: '[Core]',
name: '',
type: _InfoPanel,
)
Widget previewInfoPanel(BuildContext context) {
return const Center(
child: _InfoPanel(),
);
}

@widgetbook.UseCase(
path: '[Core]',
name: '',
Expand Down
Loading

0 comments on commit faa867a

Please sign in to comment.