blob: eedd358380b8be4eaaf652dcedde0ed7fbd6123b [file] [log] [blame]
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/gallery_localizations.dart';
import 'package:gallery/data/gallery_options.dart';
import 'package:gallery/layout/adaptive.dart';
import 'package:gallery/layout/image_placeholder.dart';
import 'package:gallery/layout/letter_spacing.dart';
import 'package:gallery/layout/text_scale.dart';
import 'package:gallery/studies/shrine/app.dart';
import 'package:gallery/studies/shrine/theme.dart';
const _horizontalPadding = 24.0;
double desktopLoginScreenMainAreaWidth({required BuildContext context}) {
return min(
360 * reducedTextScale(context),
MediaQuery.of(context).size.width - 2 * _horizontalPadding,
);
}
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
final isDesktop = isDisplayDesktop(context);
return ApplyTextOptions(
child: isDesktop
? LayoutBuilder(
builder: (context, constraints) => Scaffold(
body: SafeArea(
child: Center(
child: SizedBox(
width: desktopLoginScreenMainAreaWidth(context: context),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
_ShrineLogo(),
SizedBox(height: 40),
_UsernameTextField(),
SizedBox(height: 16),
_PasswordTextField(),
SizedBox(height: 24),
_CancelAndNextButtons(),
SizedBox(height: 62),
],
),
),
),
),
),
)
: Scaffold(
body: SafeArea(
child: ListView(
restorationId: 'login_list_view',
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.symmetric(
horizontal: _horizontalPadding,
),
children: const [
SizedBox(height: 80),
_ShrineLogo(),
SizedBox(height: 120),
_UsernameTextField(),
SizedBox(height: 12),
_PasswordTextField(),
_CancelAndNextButtons(),
],
),
),
),
);
}
}
class _ShrineLogo extends StatelessWidget {
const _ShrineLogo();
@override
Widget build(BuildContext context) {
return ExcludeSemantics(
child: Column(
children: [
const FadeInImagePlaceholder(
image: AssetImage('packages/shrine_images/diamond.png'),
placeholder: SizedBox(
width: 34,
height: 34,
),
),
const SizedBox(height: 16),
Text(
'SHRINE',
style: Theme.of(context).textTheme.headlineSmall,
),
],
),
);
}
}
class _UsernameTextField extends StatelessWidget {
const _UsernameTextField();
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return TextField(
textInputAction: TextInputAction.next,
restorationId: 'username_text_field',
cursorColor: colorScheme.onSurface,
decoration: InputDecoration(
labelText: GalleryLocalizations.of(context)!.shrineLoginUsernameLabel,
labelStyle: TextStyle(
letterSpacing: letterSpacingOrNone(mediumLetterSpacing),
),
),
);
}
}
class _PasswordTextField extends StatelessWidget {
const _PasswordTextField();
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return TextField(
restorationId: 'password_text_field',
cursorColor: colorScheme.onSurface,
obscureText: true,
decoration: InputDecoration(
labelText: GalleryLocalizations.of(context)!.shrineLoginPasswordLabel,
labelStyle: TextStyle(
letterSpacing: letterSpacingOrNone(mediumLetterSpacing),
),
),
);
}
}
class _CancelAndNextButtons extends StatelessWidget {
const _CancelAndNextButtons();
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDesktop = isDisplayDesktop(context);
final buttonTextPadding = isDesktop
? const EdgeInsets.symmetric(horizontal: 24, vertical: 16)
: EdgeInsets.zero;
return Padding(
padding: isDesktop ? EdgeInsets.zero : const EdgeInsets.all(8),
child: OverflowBar(
spacing: isDesktop ? 0 : 8,
alignment: MainAxisAlignment.end,
children: [
TextButton(
style: TextButton.styleFrom(
shape: const BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7)),
),
),
onPressed: () {
// The login screen is immediately displayed on top of
// the Shrine home screen using onGenerateRoute and so
// rootNavigator must be set to true in order to get out
// of Shrine completely.
Navigator.of(context, rootNavigator: true).pop();
},
child: Padding(
padding: buttonTextPadding,
child: Text(
GalleryLocalizations.of(context)!.shrineCancelButtonCaption,
style: TextStyle(color: colorScheme.onSurface),
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 8,
shape: const BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7)),
),
),
onPressed: () {
Navigator.of(context).restorablePushNamed(ShrineApp.homeRoute);
},
child: Padding(
padding: buttonTextPadding,
child: Text(
GalleryLocalizations.of(context)!.shrineNextButtonCaption,
style: TextStyle(
letterSpacing: letterSpacingOrNone(largeLetterSpacing)),
),
),
),
],
),
);
}
}