arblant
firefox-vpn-client / app / lib / ui / welcome_page.dart
welcome_page.dart162 lines · 5.5 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
import 'dart:ui';

import 'package:flutter/material.dart';

import '../app_settings.dart';
import '../core/core_controller.dart';
import '../l10n/app_localizations.dart';
import 'widgets.dart';

/// The first run: the user picks a language and a country, and the
/// country's sites go directly (the core's routing region).
class WelcomePage extends StatefulWidget {
  const WelcomePage({super.key, required this.core, required this.settings});

  final CoreController core;
  final AppSettings settings;

  @override
  State<WelcomePage> createState() => _WelcomePageState();
}

class _WelcomePageState extends State<WelcomePage> {
  late String _language;
  late String _region;

  /// The region follows the language until the user picks one.
  bool _regionPicked = false;
  bool _saving = false;

  @override
  void initState() {
    super.initState();
    final system = PlatformDispatcher.instance.locale;
    _language = widget.settings.language.isNotEmpty
        ? widget.settings.language
        : AppSettings.languageFor(system);
    _region = AppSettings.suggestedRegion(_language, system.countryCode);
  }

  void _pickLanguage(String language) {
    setState(() {
      _language = language;
      if (!_regionPicked) {
        _region = AppSettings.regionForLanguage(language) ?? _region;
      }
    });
    // Show the page in the chosen language at once.
    widget.settings.update(language: language);
  }

  Future<void> _start() async {
    setState(() => _saving = true);
    final region = _region;
    await runShowingErrors(
      context,
      () => widget.core.editSettings((config) {
        final routing = config['routing'];
        if (routing is! Map<String, Object?>) return;
        routing['enabled'] = region.isNotEmpty;
        if (region.isNotEmpty) routing['region'] = region;
      }),
    );
    widget.settings.update(language: _language, firstRun: false);
  }

  @override
  Widget build(BuildContext context) {
    final l = AppLocalizations.of(context);
    final theme = Theme.of(context);
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(24),
          child: ConstrainedBox(
            constraints: const BoxConstraints(maxWidth: 460),
            child: Panel(
              padding: const EdgeInsets.all(32),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Icon(
                    Icons.shield_rounded,
                    size: 56,
                    color: theme.colorScheme.primary,
                  ),
                  const SizedBox(height: 16),
                  Text(
                    l.welcomeTitle,
                    textAlign: TextAlign.center,
                    style: theme.textTheme.headlineSmall?.copyWith(
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    l.welcomeBody,
                    textAlign: TextAlign.center,
                    style: theme.textTheme.bodyMedium?.copyWith(
                      color: theme.colorScheme.onSurfaceVariant,
                    ),
                  ),
                  const SizedBox(height: 28),
                  DropdownButtonFormField<String>(
                    initialValue: _language,
                    decoration: InputDecoration(
                      labelText: l.language,
                      prefixIcon: const Icon(Icons.translate_rounded),
                      border: const OutlineInputBorder(),
                    ),
                    onChanged: (v) => _pickLanguage(v!),
                    items: [
                      for (final code in AppSettings.supportedLanguages)
                        DropdownMenuItem(
                          value: code,
                          child: Text(
                            languageNames[code]!,
                            style: languageStyle(code),
                          ),
                        ),
                    ],
                  ),
                  const SizedBox(height: 16),
                  DropdownButtonFormField<String>(
                    key: ValueKey(_region),
                    initialValue: _region,
                    decoration: InputDecoration(
                      labelText: l.region,
                      helperText: l.routingDirectHint,
                      helperMaxLines: 3,
                      prefixIcon: const Icon(Icons.alt_route_rounded),
                      border: const OutlineInputBorder(),
                    ),
                    onChanged: (v) => setState(() {
                      _region = v!;
                      _regionPicked = true;
                    }),
                    items: [
                      for (final r in AppSettings.regions)
                        DropdownMenuItem(
                          value: r,
                          child: Text(regionName(l, r)),
                        ),
                    ],
                  ),
                  const SizedBox(height: 28),
                  FilledButton(
                    style: FilledButton.styleFrom(
                      minimumSize: const Size.fromHeight(48),
                    ),
                    onPressed: _saving ? null : _start,
                    child: Text(l.start),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}