diff --git a/Containerfile b/Containerfile index b7e28ec..c9973ef 100644 --- a/Containerfile +++ b/Containerfile @@ -19,7 +19,7 @@ ENV PATH="$FLUTTER_SDK/bin:$FLUTTER_SDK/bin/cache/dart-sdk/bin:${PATH}" RUN mkdir $APP COPY . $APP WORKDIR $APP -RUN flutter build web +RUN flutter build web --release # once heare the app will be compiled and ready to deploy diff --git a/Makefile b/Makefile index 4ec35f6..9c84173 100644 --- a/Makefile +++ b/Makefile @@ -2,3 +2,6 @@ format: dart format $$(find . -type f -name '*.dart') fix: dart fix ./lib --apply + +serve: + flutter run -d web-server --web-port 8080 diff --git a/assets/login_background.jpg b/assets/login_background.jpg new file mode 100644 index 0000000..d74468e Binary files /dev/null and b/assets/login_background.jpg differ diff --git a/lib/api/grpc/accounts.dart b/lib/api/grpc/accounts.dart index 4659d94..d8ae062 100644 --- a/lib/api/grpc/accounts.dart +++ b/lib/api/grpc/accounts.dart @@ -1,4 +1,3 @@ - import 'package:grpc/grpc_web.dart'; import 'package:softplayer_dart_proto/accounts/accounts_v1.pbgrpc.dart'; import 'package:softplayer_dart_proto/main.dart'; @@ -11,6 +10,7 @@ class AccountLocalData { String uuid; String token; } + class AccountsGrpc { final GrpcWebClientChannel channel; late AccountsClient accountsStub; @@ -22,7 +22,8 @@ class AccountsGrpc { accountsStub = AccountsClient(channel); } - Future signIn(String username, String email, String password) async { + Future signIn( + String username, String email, String password) async { final request = AccountWithPassword( data: AccountData( name: username, @@ -39,7 +40,8 @@ class AccountsGrpc { } } - Future signUp(String username, String email, String password) async { + Future signUp( + String username, String email, String password) async { final request = AccountWithPassword( data: AccountData( name: username, diff --git a/lib/api/grpc/environments.dart b/lib/api/grpc/environments.dart new file mode 100644 index 0000000..bb8317b --- /dev/null +++ b/lib/api/grpc/environments.dart @@ -0,0 +1,41 @@ +import 'dart:html'; + +import 'package:grpc/grpc_web.dart'; +import 'package:softplayer_dart_proto/main.dart'; + +class EnvironmentLocalData { + EnvironmentLocalData({ + required this.uuid, + required this.token, + }); + String uuid; + String token; +} + +class EnvironmentsGrpc { + final GrpcWebClientChannel channel; + late EnvironmentsClient envStub; + EnvironmentsGrpc({ + required this.channel, + }); + + void init() { + envStub = EnvironmentsClient(channel); + } + + Stream> list() async* { + List envs = []; + try { + await for (var feature in envStub.list(Empty(), + options: CallOptions(metadata: { + "uuid": window.localStorage["uuid"]!, + "token": window.localStorage["token"]!, + }))) { + envs.add(feature.data.name); + } + } catch (e) { + rethrow; + } + yield envs; + } +} diff --git a/lib/components/environments.dart b/lib/components/environments.dart new file mode 100644 index 0000000..f73c8a0 --- /dev/null +++ b/lib/components/environments.dart @@ -0,0 +1,100 @@ +import 'dart:js_interop'; + +import 'package:flutter/material.dart'; +import 'package:grpc/grpc_web.dart'; +import 'package:softplayer_web/api/grpc/environments.dart'; + +class EnvirnomentList extends StatefulWidget { + EnvirnomentList({ + super.key, + required this.channel, + }); + + final GrpcWebClientChannel channel; + @override + State createState() => _EnvirnomentListState(); +} + +class _EnvirnomentListState extends State { + late EnvironmentsGrpc envGrpc; + List envs = []; + @override + void initState() { + super.initState(); + envGrpc = EnvironmentsGrpc(channel: widget.channel); + envGrpc.init(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: StreamBuilder( + stream: envGrpc.list(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return CircularProgressIndicator(); + } else if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + return Text('Error!'); + } else { + return Column( + children: snapshot.data!.map((e) => Text(e)).toList(), + ); + } + } + return Text("err"); + }))); + } +} + +//class EnvirnomentList extends StatelessWidget { +// EnvirnomentList({ +// super.key, +// required this.channel, +// }); +// final GrpcWebClientChannel channel; +// late List envs; +// late EnvironmentsGrpc envGrpc; +// List getEnvs() { +// List envs = []; +// envGrpc.list().then((value) { +// return value; +// }).catchError((e) { +// return envs; +// }); +// return envs; +// } +// +// List bootstrapCards(List envs) { +// List cards = []; +// envs.forEach((element) { +// cards.add(Center(child: Text(element))); +// }); +// return cards; +// } +// +// +// @override +// Widget build(BuildContext context) { +// envGrpc = EnvironmentsGrpc(channel: channel); +// envGrpc.init(); +// envs = getEnvs(); +// return GridView.count( +// crossAxisCount: 2, +// children: bootstrapCards(envs), +// ); +// // children: List.generate(100, (index) { +// // return Center( +// // child: Text( +// // 'Item $index', +// // style: Theme.of(context).textTheme.headlineSmall, +// // ), +// // ); +// // }), +// // ); +// // return GridView.count( +// // children: bootstrapCards(getEnvs()), +// // ); +// } +//} diff --git a/lib/components/login_form.dart b/lib/components/login_form.dart new file mode 100644 index 0000000..10ec0b1 --- /dev/null +++ b/lib/components/login_form.dart @@ -0,0 +1,217 @@ +import 'dart:html'; + +import 'package:flutter/material.dart'; +import 'package:grpc/grpc_web.dart'; +import 'package:softplayer_web/api/grpc/accounts.dart'; + +class LoginForm extends StatefulWidget { + const LoginForm({ + super.key, + required this.grpcChannel, + }); + + final GrpcWebClientChannel grpcChannel; + @override + State createState() => _LoginFormState(); +} + +enum Action { singIn, signUp } + +class _LoginFormState extends State { + final _formKey = GlobalKey(); + + final usernameCtrl = TextEditingController(); + final passwordCtrl = TextEditingController(); + final passwordVerifyCtrl = TextEditingController(); + final emailCtrl = TextEditingController(); + + late AccountsGrpc accountsGrpc; + Action action = Action.singIn; + static const dialogName = "Login"; + + void submitSignUp() { + // 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; + accountsGrpc.signUp(username, email, password).then((rs) { + window.localStorage["token"] = rs.token; + window.localStorage["uuid"] = rs.uuid; + Navigator.of(context, rootNavigator: true).pop(); + }).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(); + }); + } + } + + void submitSignIn() { + // Validate returns true if the form is valid, or false otherwise. + if (_formKey.currentState!.validate()) { + final username = usernameCtrl.text; + final password = passwordCtrl.text; + accountsGrpc.signIn(username, "", password).then((rs) { + window.localStorage["token"] = rs.token; + window.localStorage["uuid"] = rs.uuid; + setState(() { + + }); + }).catchError((e) { + print(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(); + }); + } + } + + Widget signInForm() => SizedBox( + 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: 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), + ), + ])))); + + List signInActions() => [ + TextButton( + onPressed: () => setState(() { + action = Action.signUp; + }), + child: const Text('Sing Up'), + ), + TextButton( + onPressed: submitSignIn, + child: const Text('OK'), + ), + ]; + + List signUpActions() => [ + TextButton( + onPressed: () => setState(() { + action = Action.singIn; + }), + child: const Text('Sing In'), + ), + TextButton( + onPressed: submitSignUp, + child: const Text('OK'), + ), + ]; + Widget signUpForm() => SizedBox( + 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), + ), + ])))); + + @override + void initState() { + super.initState(); + accountsGrpc = AccountsGrpc(channel: widget.grpcChannel); + accountsGrpc.init(); + } + + @override + Widget build(BuildContext context) => AlertDialog( + title: const Text(dialogName), + content: action == Action.singIn ? signInForm() : signUpForm(), + actions: action == Action.singIn ? signInActions() : signUpActions(), + ); +} diff --git a/lib/components/menubar.dart b/lib/components/menubar.dart index 6b6477d..cfc3bcd 100644 --- a/lib/components/menubar.dart +++ b/lib/components/menubar.dart @@ -52,20 +52,16 @@ class _MenuPanel extends State { showDialog( context: context, builder: (BuildContext context) => SignUpForm( - accountsGrpc: widget.accountsGrpc, - )); + accountsGrpc: widget.accountsGrpc, + )); }, child: const Text("sign up")), ]; } } - final String linkCatalog = "catalog"; - final String linkAbout = "about"; - final String linkHome = "home"; @override PreferredSizeWidget build(BuildContext context) { - final TabName tab = widget.tab; return AppBar( title: Row(children: [ TextButton( @@ -73,36 +69,6 @@ class _MenuPanel extends State { onPressed: () { Navigator.pushNamed(context, "/"); }), - TextButton( - child: Text( - linkHome, - style: (tab == TabName.home) - ? const TextStyle(decoration: TextDecoration.underline) - : const TextStyle(), - ), - onPressed: () { - Navigator.pushNamed(context, "/"); - }), - TextButton( - child: Text( - linkCatalog, - style: (tab == TabName.catalog) - ? const TextStyle(decoration: TextDecoration.underline) - : const TextStyle(), - ), - onPressed: () { - Navigator.pushNamed(context, "/catalog"); - }), - TextButton( - child: Text( - linkAbout, - style: (tab == TabName.about) - ? const TextStyle(decoration: TextDecoration.underline) - : const TextStyle(), - ), - onPressed: () { - Navigator.pushNamed(context, "/about"); - }), ]), backgroundColor: const Color.fromRGBO(46, 51, 78, 1.0), automaticallyImplyLeading: false, diff --git a/lib/components/sign_in_form.dart b/lib/components/sign_in_form.dart index ddc7dd3..6df8f9b 100644 --- a/lib/components/sign_in_form.dart +++ b/lib/components/sign_in_form.dart @@ -25,29 +25,26 @@ class _SignInFormState extends State { if (_formKey.currentState!.validate()) { final username = usernameCtrl.text; final password = passwordCtrl.text; - widget.accountsGrpc - .signIn(username, "", password) - .then((rs) { - window.localStorage["token"] = rs.token; - window.localStorage["uuid"] = rs.uuid; - Navigator.of(context, rootNavigator: true).pop(); - }) - .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(); - }); + widget.accountsGrpc.signIn(username, "", password).then((rs) { + window.localStorage["token"] = rs.token; + window.localStorage["uuid"] = rs.uuid; + Navigator.of(context, rootNavigator: true).pop(); + }).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(); + }); } } diff --git a/lib/components/sign_up_form.dart b/lib/components/sign_up_form.dart index e3023ec..956f048 100644 --- a/lib/components/sign_up_form.dart +++ b/lib/components/sign_up_form.dart @@ -8,7 +8,6 @@ class SignUpForm extends StatefulWidget { const SignUpForm({ super.key, required this.accountsGrpc, - }); final AccountsGrpc accountsGrpc; @@ -18,46 +17,43 @@ class SignUpForm extends StatefulWidget { class _SignUpFormState extends State { final _formKey = GlobalKey(); - + 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((rs) { - window.localStorage["token"] = rs.token; - window.localStorage["uuid"] = rs.uuid; - Navigator.of(context, rootNavigator: true).pop(); - }) - - .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(); - }); + widget.accountsGrpc.signUp(username, email, password).then((rs) { + window.localStorage["token"] = rs.token; + window.localStorage["uuid"] = rs.uuid; + Navigator.of(context, rootNavigator: true).pop(); + }).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), diff --git a/lib/helpers/page_wrapper.dart b/lib/helpers/page_wrapper.dart index f101848..03e0a02 100644 --- a/lib/helpers/page_wrapper.dart +++ b/lib/helpers/page_wrapper.dart @@ -1,10 +1,22 @@ +import 'dart:html'; + import 'package:flutter/material.dart'; +import 'package:softplayer_web/api/grpc/accounts.dart'; import 'package:softplayer_web/components/menubar.dart'; class PageWrapper extends StatelessWidget { final Widget child; final MenuPanel appBar; - const PageWrapper({super.key, required this.child, required this.appBar}); + const PageWrapper( + {super.key, + required this.child, + required this.appBar, + required this.accountsGrpc}); + + final AccountsGrpc accountsGrpc; + bool isSignedIn() { + return window.localStorage.containsKey("token"); + } @override Widget build(BuildContext context) { diff --git a/lib/main.dart b/lib/main.dart index f778def..c519ab9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,10 @@ +import 'dart:html'; + 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'; -import 'package:softplayer_web/pages/catalog.dart'; -import 'package:softplayer_web/pages/home.dart'; +import 'package:softplayer_web/components/environments.dart'; +import 'package:softplayer_web/components/login_form.dart'; void main() async { const String backendURL = String.fromEnvironment( @@ -28,29 +27,13 @@ class MyApp extends StatelessWidget { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Softplayer', - routes: { - '/': (context) => PageWrapper( - appBar: MenuPanel( - tab: TabName.home, - accountsGrpc: accountsGrpc, - ), - child: const HomePage(), - ), - '/catalog': (context) => PageWrapper( - appBar: MenuPanel( - tab: TabName.catalog, - accountsGrpc: accountsGrpc, - ), - child: const CatalogPage(), - ), - '/about': (context) => PageWrapper( - appBar: MenuPanel( - tab: TabName.about, - accountsGrpc: accountsGrpc, - ), - child: const AboutPage(), - ) - }, + home: Scaffold( + body: TestAlert(channel: channel), + appBar: AppBar(), + floatingActionButton: FloatingActionButton( + onPressed: () => print("1"), + ), + ), theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, @@ -58,3 +41,29 @@ class MyApp extends StatelessWidget { ); } } + +class TestAlert extends StatelessWidget { + final GrpcWebClientChannel channel; + TestAlert({super.key, required this.channel}); + late final AccountsGrpc accountsGrpc = AccountsGrpc(channel: channel); + bool isSignedIn() { + return window.localStorage.containsKey("token"); + } + + @override + Widget build(BuildContext context) { + if (!isSignedIn()) { + return Container( + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage("assets/login_background.jpg"), + fit: BoxFit.fill, + ), + ), + child: LoginForm(grpcChannel: channel), + ); + } else { + return EnvirnomentList(channel: channel); + } + } +} diff --git a/lib/models/environments.dart b/lib/models/environments.dart new file mode 100644 index 0000000..c90f35c --- /dev/null +++ b/lib/models/environments.dart @@ -0,0 +1,10 @@ +class Environment { + final String name; + final List apps; + final String provider; + Environment({ + required this.name, + required this.apps, + required this.provider, + }); +} diff --git a/pubspec.yaml b/pubspec.yaml index 3d57b0c..23607ed 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,3 +26,5 @@ dev_dependencies: flutter: uses-material-design: true + assets: + - assets/