Signup and signing are ready
This commit is contained in:
		
							
								
								
									
										52
									
								
								lib/api/grpc/accounts.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								lib/api/grpc/accounts.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
 | 
			
		||||
import 'package:grpc/grpc_web.dart';
 | 
			
		||||
import 'package:softplayer_dart_proto/accounts/accounts_v1.pbgrpc.dart';
 | 
			
		||||
import 'package:softplayer_dart_proto/main.dart';
 | 
			
		||||
 | 
			
		||||
class AccountsGrpc {
 | 
			
		||||
  final GrpcWebClientChannel channel;
 | 
			
		||||
  late AccountsClient accountsStub;
 | 
			
		||||
  AccountsGrpc({
 | 
			
		||||
    required this.channel,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  void init() {
 | 
			
		||||
    accountsStub = AccountsClient(channel);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<String> signIn(String username, String email, String password) async {
 | 
			
		||||
    final request = AccountWithPassword(
 | 
			
		||||
        data: AccountData(
 | 
			
		||||
          name: username,
 | 
			
		||||
          email: email,
 | 
			
		||||
        ),
 | 
			
		||||
        password: AccountPassword(
 | 
			
		||||
          password: password,
 | 
			
		||||
        ));
 | 
			
		||||
    try {
 | 
			
		||||
      final response = await accountsStub.signIn(request);
 | 
			
		||||
      print("$response");
 | 
			
		||||
      return "1";
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      rethrow;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<String> signUp(String username, String email, String password) async {
 | 
			
		||||
    final request = AccountWithPassword(
 | 
			
		||||
        data: AccountData(
 | 
			
		||||
          name: username,
 | 
			
		||||
          email: email,
 | 
			
		||||
        ),
 | 
			
		||||
        password: AccountPassword(
 | 
			
		||||
          password: password,
 | 
			
		||||
        ));
 | 
			
		||||
    try {
 | 
			
		||||
      final response = await accountsStub.signUp(request);
 | 
			
		||||
      print("$response");
 | 
			
		||||
      return "1";
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      rethrow;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +1,21 @@
 | 
			
		||||
// This project is not supposed to be cross-platform, 
 | 
			
		||||
// This project is not supposed to be cross-platform,
 | 
			
		||||
// so we don't care about this warning
 | 
			
		||||
// ignore: avoid_web_libraries_in_flutter
 | 
			
		||||
import 'dart:html';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:softplayer_web/api/grpc/accounts.dart';
 | 
			
		||||
import 'package:softplayer_web/components/sign_in_form.dart';
 | 
			
		||||
import 'package:softplayer_web/components/sign_up_form.dart';
 | 
			
		||||
 | 
			
		||||
class MenuPanel extends StatefulWidget implements PreferredSizeWidget {
 | 
			
		||||
  final TabName tab;
 | 
			
		||||
  const MenuPanel({super.key, required this.tab})
 | 
			
		||||
      : preferredSize = const Size.fromHeight(kToolbarHeight);
 | 
			
		||||
  final AccountsGrpc accountsGrpc;
 | 
			
		||||
  const MenuPanel({
 | 
			
		||||
    super.key,
 | 
			
		||||
    required this.tab,
 | 
			
		||||
    required this.accountsGrpc,
 | 
			
		||||
  }) : preferredSize = const Size.fromHeight(kToolbarHeight);
 | 
			
		||||
  @override
 | 
			
		||||
  final Size preferredSize;
 | 
			
		||||
  @override
 | 
			
		||||
@@ -37,14 +42,18 @@ class _MenuPanel extends State<MenuPanel> {
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              showDialog(
 | 
			
		||||
                  context: context,
 | 
			
		||||
                  builder: (BuildContext context) => const SignInForm());
 | 
			
		||||
                  builder: (BuildContext context) => SignInForm(
 | 
			
		||||
                        accountsGrpc: widget.accountsGrpc,
 | 
			
		||||
                      ));
 | 
			
		||||
            },
 | 
			
		||||
            child: const Text("sign in")),
 | 
			
		||||
        TextButton(
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              showDialog(
 | 
			
		||||
                  context: context,
 | 
			
		||||
                  builder: (BuildContext context) => const SignUpForm());
 | 
			
		||||
                  builder: (BuildContext context) => SignUpForm(
 | 
			
		||||
                    accountsGrpc: widget.accountsGrpc,
 | 
			
		||||
                    ));
 | 
			
		||||
            },
 | 
			
		||||
            child: const Text("sign up")),
 | 
			
		||||
      ];
 | 
			
		||||
 
 | 
			
		||||
@@ -1,64 +1,98 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:grpc/grpc_web.dart';
 | 
			
		||||
import 'package:softplayer_web/api/grpc/accounts.dart';
 | 
			
		||||
 | 
			
		||||
class SignInForm extends StatefulWidget {
 | 
			
		||||
  const SignInForm({super.key});
 | 
			
		||||
  const SignInForm({
 | 
			
		||||
    super.key,
 | 
			
		||||
    required this.accountsGrpc,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final AccountsGrpc accountsGrpc;
 | 
			
		||||
  @override
 | 
			
		||||
  State<StatefulWidget> createState() => _SignInFormState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SignInFormState extends State<SignInForm> {
 | 
			
		||||
  final _formKey = GlobalKey<FormState>();
 | 
			
		||||
  final usernameCtrl = TextEditingController();
 | 
			
		||||
  final passwordCtrl = TextEditingController();
 | 
			
		||||
  static const dialogName = "Sign In";
 | 
			
		||||
  void submitForm() {
 | 
			
		||||
    // Validate returns true if the form is valid, or false otherwise.
 | 
			
		||||
    if (_formKey.currentState!.validate()) {
 | 
			
		||||
      final username = usernameCtrl.text;
 | 
			
		||||
      final password = passwordCtrl.text;
 | 
			
		||||
      widget.accountsGrpc
 | 
			
		||||
          .signIn(username, "", password)
 | 
			
		||||
          .then((value) => null)
 | 
			
		||||
          .catchError((e) {
 | 
			
		||||
                GrpcError error = e;
 | 
			
		||||
                String msg;
 | 
			
		||||
                if (error.message != null) {
 | 
			
		||||
                  msg = error.message!;
 | 
			
		||||
                } else {
 | 
			
		||||
                  msg = error.toString();
 | 
			
		||||
                }
 | 
			
		||||
                ScaffoldMessenger.of(context).showSnackBar(SnackBar(
 | 
			
		||||
                  content: Text(msg),
 | 
			
		||||
                  backgroundColor: Colors.red,
 | 
			
		||||
                  showCloseIcon: true,
 | 
			
		||||
                  behavior: SnackBarBehavior.floating,
 | 
			
		||||
                ));
 | 
			
		||||
                passwordCtrl.clear();
 | 
			
		||||
              });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => AlertDialog(
 | 
			
		||||
        title: const Text(dialogName),
 | 
			
		||||
        content: SizedBox(
 | 
			
		||||
          width: 420,
 | 
			
		||||
          height: 140,
 | 
			
		||||
          child: Form(
 | 
			
		||||
            key: _formKey,
 | 
			
		||||
            child: Center(
 | 
			
		||||
                child: Column(children: [
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                autofocus: true,
 | 
			
		||||
                decoration: const InputDecoration(
 | 
			
		||||
                  hintText: "Enter your username or email",
 | 
			
		||||
                  icon: Icon(Icons.account_circle),
 | 
			
		||||
                  label: Text("Username"),
 | 
			
		||||
                ),
 | 
			
		||||
                cursorWidth: 1,
 | 
			
		||||
                cursorHeight: 18,
 | 
			
		||||
                cursorRadius: const Radius.circular(10),
 | 
			
		||||
              ),
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                obscureText: true,
 | 
			
		||||
                decoration: const InputDecoration(
 | 
			
		||||
                  hintText: "Enter your password",
 | 
			
		||||
                  icon: Icon(Icons.password),
 | 
			
		||||
                  label: Text("Password")
 | 
			
		||||
                ),
 | 
			
		||||
                cursorWidth: 1,
 | 
			
		||||
                cursorHeight: 18,
 | 
			
		||||
                cursorRadius: const Radius.circular(10),
 | 
			
		||||
              ),
 | 
			
		||||
            ])))),
 | 
			
		||||
            width: 420,
 | 
			
		||||
            height: 140,
 | 
			
		||||
            child: Form(
 | 
			
		||||
                key: _formKey,
 | 
			
		||||
                child: Center(
 | 
			
		||||
                    child: Column(children: [
 | 
			
		||||
                  TextFormField(
 | 
			
		||||
                    controller: usernameCtrl,
 | 
			
		||||
                    autofocus: true,
 | 
			
		||||
                    decoration: const InputDecoration(
 | 
			
		||||
                      hintText: "Enter your username or email",
 | 
			
		||||
                      icon: Icon(Icons.account_circle),
 | 
			
		||||
                      label: Text("Username"),
 | 
			
		||||
                    ),
 | 
			
		||||
                    cursorWidth: 1,
 | 
			
		||||
                    cursorHeight: 18,
 | 
			
		||||
                    cursorRadius: const Radius.circular(10),
 | 
			
		||||
                  ),
 | 
			
		||||
                  TextFormField(
 | 
			
		||||
                    controller: passwordCtrl,
 | 
			
		||||
                    obscureText: true,
 | 
			
		||||
                    decoration: const InputDecoration(
 | 
			
		||||
                        hintText: "Enter your password",
 | 
			
		||||
                        icon: Icon(Icons.password),
 | 
			
		||||
                        label: Text("Password")),
 | 
			
		||||
                    cursorWidth: 1,
 | 
			
		||||
                    cursorHeight: 18,
 | 
			
		||||
                    cursorRadius: const Radius.circular(10),
 | 
			
		||||
                    onFieldSubmitted: (v) {
 | 
			
		||||
                      if (usernameCtrl.text.isEmpty) {
 | 
			
		||||
                        FocusScope.of(context).nextFocus();
 | 
			
		||||
                      } else {
 | 
			
		||||
                        submitForm();
 | 
			
		||||
                      }
 | 
			
		||||
                    },
 | 
			
		||||
                  ),
 | 
			
		||||
                ])))),
 | 
			
		||||
        actions: <Widget>[
 | 
			
		||||
          TextButton(
 | 
			
		||||
            onPressed: () => Navigator.pop(context, 'Cancel'),
 | 
			
		||||
            child: const Text('Cancel'),
 | 
			
		||||
          ),
 | 
			
		||||
          TextButton(
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              // Validate returns true if the form is valid, or false otherwise.
 | 
			
		||||
              if (_formKey.currentState!.validate()) {
 | 
			
		||||
                // If the form is valid, display a snackbar. In the real world,
 | 
			
		||||
                // you'd often call a server or save the information in a database.
 | 
			
		||||
                ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
                  SnackBar(content: Text(_formKey.toString())),
 | 
			
		||||
                );
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            onPressed: submitForm,
 | 
			
		||||
            child: const Text('OK'),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -1,86 +1,120 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:grpc/grpc_web.dart';
 | 
			
		||||
import 'package:softplayer_web/api/grpc/accounts.dart';
 | 
			
		||||
 | 
			
		||||
class SignUpForm extends StatefulWidget {
 | 
			
		||||
  const SignUpForm({super.key});
 | 
			
		||||
  const SignUpForm({
 | 
			
		||||
    super.key,
 | 
			
		||||
    required this.accountsGrpc,
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final AccountsGrpc accountsGrpc;
 | 
			
		||||
  @override
 | 
			
		||||
  State<StatefulWidget> createState() => _SignUpFormState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SignUpFormState extends State<SignUpForm> {
 | 
			
		||||
  final _formKey = GlobalKey<FormState>();
 | 
			
		||||
  
 | 
			
		||||
  final usernameCtrl = TextEditingController();
 | 
			
		||||
  final passwordCtrl = TextEditingController();
 | 
			
		||||
  final passwordVerifyCtrl = TextEditingController();
 | 
			
		||||
  final emailCtrl = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  static const dialogName = "Sign Up";
 | 
			
		||||
  
 | 
			
		||||
  void submitForm() {
 | 
			
		||||
    // Validate returns true if the form is valid, or false otherwise.
 | 
			
		||||
    if (_formKey.currentState!.validate()) {
 | 
			
		||||
      final username = usernameCtrl.text;
 | 
			
		||||
      final password = passwordCtrl.text;
 | 
			
		||||
      final email = emailCtrl.text;
 | 
			
		||||
      widget.accountsGrpc
 | 
			
		||||
          .signUp(username, email, password)
 | 
			
		||||
          .then((value) => null)
 | 
			
		||||
          .catchError((e) {
 | 
			
		||||
                GrpcError error = e;
 | 
			
		||||
                String msg;
 | 
			
		||||
                if (error.message != null) {
 | 
			
		||||
                  msg = error.message!;
 | 
			
		||||
                } else {
 | 
			
		||||
                  msg = error.toString();
 | 
			
		||||
                }
 | 
			
		||||
                ScaffoldMessenger.of(context).showSnackBar(SnackBar(
 | 
			
		||||
                  content: Text(msg),
 | 
			
		||||
                  backgroundColor: Colors.red,
 | 
			
		||||
                  showCloseIcon: true,
 | 
			
		||||
                  behavior: SnackBarBehavior.floating,
 | 
			
		||||
                ));
 | 
			
		||||
                passwordCtrl.clear();
 | 
			
		||||
              });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => AlertDialog(
 | 
			
		||||
        title: const Text(dialogName),
 | 
			
		||||
        content: SizedBox(
 | 
			
		||||
          width: 420,
 | 
			
		||||
          height: 280,
 | 
			
		||||
          child: Form(
 | 
			
		||||
            key: _formKey,
 | 
			
		||||
            child: Center(
 | 
			
		||||
                child: Column(children: [
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                autofocus: true,
 | 
			
		||||
                decoration: const InputDecoration(
 | 
			
		||||
                  hintText: "Enter your username",
 | 
			
		||||
                  icon: Icon(Icons.account_circle),
 | 
			
		||||
                  label: Text("Username"),
 | 
			
		||||
                ),
 | 
			
		||||
                cursorWidth: 1,
 | 
			
		||||
                cursorHeight: 18,
 | 
			
		||||
                cursorRadius: const Radius.circular(10),
 | 
			
		||||
              ),
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                autofocus: true,
 | 
			
		||||
                decoration: const InputDecoration(
 | 
			
		||||
                  hintText: "Enter your email",
 | 
			
		||||
                  icon: Icon(Icons.email),
 | 
			
		||||
                  label: Text("Email"),
 | 
			
		||||
                ),
 | 
			
		||||
                cursorWidth: 1,
 | 
			
		||||
                cursorHeight: 18,
 | 
			
		||||
                cursorRadius: const Radius.circular(10),
 | 
			
		||||
              ),
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                obscureText: true,
 | 
			
		||||
                decoration: const InputDecoration(
 | 
			
		||||
                  hintText: "Enter your password",
 | 
			
		||||
                  icon: Icon(Icons.password),
 | 
			
		||||
                  label: Text("Password")
 | 
			
		||||
                ),
 | 
			
		||||
                cursorWidth: 1,
 | 
			
		||||
                cursorHeight: 18,
 | 
			
		||||
                cursorRadius: const Radius.circular(10),
 | 
			
		||||
              ),
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                obscureText: true,
 | 
			
		||||
                decoration: const InputDecoration(
 | 
			
		||||
                  hintText: "Verify your password",
 | 
			
		||||
                  icon: Icon(Icons.password),
 | 
			
		||||
                  label: Text("Confirm Password")
 | 
			
		||||
                ),
 | 
			
		||||
                cursorWidth: 1,
 | 
			
		||||
                cursorHeight: 18,
 | 
			
		||||
                cursorRadius: const Radius.circular(10),
 | 
			
		||||
              ),
 | 
			
		||||
            ])))),
 | 
			
		||||
            width: 420,
 | 
			
		||||
            height: 280,
 | 
			
		||||
            child: Form(
 | 
			
		||||
                key: _formKey,
 | 
			
		||||
                child: Center(
 | 
			
		||||
                    child: Column(children: [
 | 
			
		||||
                  TextFormField(
 | 
			
		||||
                    autofocus: true,
 | 
			
		||||
                    controller: usernameCtrl,
 | 
			
		||||
                    decoration: const InputDecoration(
 | 
			
		||||
                      hintText: "Enter your username",
 | 
			
		||||
                      icon: Icon(Icons.account_circle),
 | 
			
		||||
                      label: Text("Username"),
 | 
			
		||||
                    ),
 | 
			
		||||
                    cursorWidth: 1,
 | 
			
		||||
                    cursorHeight: 18,
 | 
			
		||||
                    cursorRadius: const Radius.circular(10),
 | 
			
		||||
                  ),
 | 
			
		||||
                  TextFormField(
 | 
			
		||||
                    controller: emailCtrl,
 | 
			
		||||
                    autofocus: true,
 | 
			
		||||
                    decoration: const InputDecoration(
 | 
			
		||||
                      hintText: "Enter your email",
 | 
			
		||||
                      icon: Icon(Icons.email),
 | 
			
		||||
                      label: Text("Email"),
 | 
			
		||||
                    ),
 | 
			
		||||
                    cursorWidth: 1,
 | 
			
		||||
                    cursorHeight: 18,
 | 
			
		||||
                    cursorRadius: const Radius.circular(10),
 | 
			
		||||
                  ),
 | 
			
		||||
                  TextFormField(
 | 
			
		||||
                    controller: passwordCtrl,
 | 
			
		||||
                    obscureText: true,
 | 
			
		||||
                    decoration: const InputDecoration(
 | 
			
		||||
                        hintText: "Enter your password",
 | 
			
		||||
                        icon: Icon(Icons.password),
 | 
			
		||||
                        label: Text("Password")),
 | 
			
		||||
                    cursorWidth: 1,
 | 
			
		||||
                    cursorHeight: 18,
 | 
			
		||||
                    cursorRadius: const Radius.circular(10),
 | 
			
		||||
                  ),
 | 
			
		||||
                  TextFormField(
 | 
			
		||||
                    controller: passwordVerifyCtrl,
 | 
			
		||||
                    obscureText: true,
 | 
			
		||||
                    decoration: const InputDecoration(
 | 
			
		||||
                        hintText: "Verify your password",
 | 
			
		||||
                        icon: Icon(Icons.password),
 | 
			
		||||
                        label: Text("Confirm Password")),
 | 
			
		||||
                    cursorWidth: 1,
 | 
			
		||||
                    cursorHeight: 18,
 | 
			
		||||
                    cursorRadius: const Radius.circular(10),
 | 
			
		||||
                  ),
 | 
			
		||||
                ])))),
 | 
			
		||||
        actions: <Widget>[
 | 
			
		||||
          TextButton(
 | 
			
		||||
            onPressed: () => Navigator.pop(context, 'Cancel'),
 | 
			
		||||
            child: const Text('Cancel'),
 | 
			
		||||
          ),
 | 
			
		||||
          TextButton(
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              // Validate returns true if the form is valid, or false otherwise.
 | 
			
		||||
              if (_formKey.currentState!.validate()) {
 | 
			
		||||
                // If the form is valid, display a snackbar. Up the real world,
 | 
			
		||||
                // you'd often call a server or save the information in a database.
 | 
			
		||||
                ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
                  SnackBar(content: Text(_formKey.toString())),
 | 
			
		||||
                );
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            onPressed: submitForm,
 | 
			
		||||
            child: const Text('OK'),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:grpc/grpc_web.dart';
 | 
			
		||||
import 'package:softplayer_web/api/grpc/accounts.dart';
 | 
			
		||||
import 'package:softplayer_web/components/menubar.dart';
 | 
			
		||||
import 'package:softplayer_web/helpers/page_wrapper.dart';
 | 
			
		||||
import 'package:softplayer_web/pages/about.dart';
 | 
			
		||||
@@ -9,7 +10,7 @@ import 'package:softplayer_web/pages/home.dart';
 | 
			
		||||
void main() async {
 | 
			
		||||
  const String backendURL = String.fromEnvironment(
 | 
			
		||||
    'SOFTPLAYER_BACKEND_URL',
 | 
			
		||||
    defaultValue: 'http://softplayer.badhouseplants.net:8080',
 | 
			
		||||
    defaultValue: 'https://softplayer-backend.badhouseplants.net:8080',
 | 
			
		||||
  );
 | 
			
		||||
  GrpcWebClientChannel grpcChannel =
 | 
			
		||||
      GrpcWebClientChannel.xhr(Uri.parse(backendURL));
 | 
			
		||||
@@ -18,28 +19,36 @@ void main() async {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MyApp extends StatelessWidget {
 | 
			
		||||
  const MyApp({super.key, required this.channel});
 | 
			
		||||
 | 
			
		||||
  // A channel that should be used to fire grpc calls
 | 
			
		||||
  MyApp({super.key, required this.channel});
 | 
			
		||||
  final GrpcWebClientChannel channel;
 | 
			
		||||
 | 
			
		||||
  late final AccountsGrpc accountsGrpc = AccountsGrpc(channel: channel);
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    accountsGrpc.init();
 | 
			
		||||
    return MaterialApp(
 | 
			
		||||
      debugShowCheckedModeBanner: false,
 | 
			
		||||
      title: 'Softplayer',
 | 
			
		||||
      routes: {
 | 
			
		||||
        '/': (context) => const PageWrapper(
 | 
			
		||||
              appBar: MenuPanel(tab: TabName.home),
 | 
			
		||||
              child: HomePage(),
 | 
			
		||||
        '/': (context) => PageWrapper(
 | 
			
		||||
              appBar: MenuPanel(
 | 
			
		||||
                tab: TabName.home,
 | 
			
		||||
                accountsGrpc: accountsGrpc,
 | 
			
		||||
              ),
 | 
			
		||||
              child: const HomePage(),
 | 
			
		||||
            ),
 | 
			
		||||
        '/catalog': (context) => const PageWrapper(
 | 
			
		||||
              appBar: MenuPanel(tab: TabName.catalog),
 | 
			
		||||
              child: CatalogPage(),
 | 
			
		||||
        '/catalog': (context) => PageWrapper(
 | 
			
		||||
              appBar: MenuPanel(
 | 
			
		||||
                tab: TabName.catalog,
 | 
			
		||||
                accountsGrpc: accountsGrpc,
 | 
			
		||||
              ),
 | 
			
		||||
              child: const CatalogPage(),
 | 
			
		||||
            ),
 | 
			
		||||
        '/about': (context) => const PageWrapper(
 | 
			
		||||
              appBar: MenuPanel(tab: TabName.about),
 | 
			
		||||
              child: AboutPage(),
 | 
			
		||||
        '/about': (context) => PageWrapper(
 | 
			
		||||
              appBar: MenuPanel(
 | 
			
		||||
                tab: TabName.about,
 | 
			
		||||
                accountsGrpc: accountsGrpc,
 | 
			
		||||
              ),
 | 
			
		||||
              child: const AboutPage(),
 | 
			
		||||
            )
 | 
			
		||||
      },
 | 
			
		||||
      theme: ThemeData(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user