diff --git a/README.md b/README.md index 0c13036..be777bb 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ flutter install ## API -Drinkify uses [liquor-lovers](https://github.com/Kawaii-Addicts/liquor-lovers). Make sure to install it and run in order to use all the features provided by the app. +Drinkify uses [liquor-lovers](https://github.com/Kawaii-Addicts/liquor-lovers). Without it, app won't be able to work properly. ## Contributing diff --git a/l10n.yaml b/l10n.yaml index 9b5ae41..15338f2 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,4 +1,3 @@ arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart -untranslated-messages-file: untranslated_msgs.txt diff --git a/lib/controllers/auth_controller.dart b/lib/controllers/auth_controller.dart index 5c29fa6..7398a50 100644 --- a/lib/controllers/auth_controller.dart +++ b/lib/controllers/auth_controller.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; @@ -11,20 +10,18 @@ import '../models/create_user.dart'; ///Used for logging in and signing up a user class AuthController { - ///Used only for logging in - TextEditingController emailCtrl = TextEditingController(); - - ///Used only for logging in - TextEditingController passwordCtrl = TextEditingController(); - ///Handles logging in - Future loginUser() async { + static Future loginUser( + String email, + String password, + bool rememberMe, + ) async { final url = "$mainUrl/auth/token/"; final res = await http.post( Uri.parse(url), body: { - "email": emailCtrl.text, - "password": passwordCtrl.text, + "email": email, + "password": password, }, ); if (res.statusCode == 200) { @@ -38,6 +35,10 @@ class AuthController { key: "refresh", value: loginArr["refresh"], ); + await storage.write( + key: "remember", + value: "$rememberMe", + ); return true; } return false; diff --git a/lib/controllers/search_controller.dart b/lib/controllers/search_controller.dart index 24fe6bd..c057322 100644 --- a/lib/controllers/search_controller.dart +++ b/lib/controllers/search_controller.dart @@ -27,7 +27,9 @@ class SearchController { return users; } - static Future> seachPartiesByDistance(double meters) async { + static Future> seachPartiesByDistance({ + double meters = 1000, + }) async { const storage = FlutterSecureStorage(); final token = await storage.read(key: "access"); final url = "$mainUrl/parties/?range=$meters"; @@ -39,7 +41,7 @@ class SearchController { }, ); final parties = []; - for (final p in jsonDecode(res.body)) { + for (final p in jsonDecode(res.body)["results"]) { parties.add(Party.fromMap(p)); } return parties; diff --git a/lib/routes/login_page.dart b/lib/routes/login_page.dart index ca74a3c..61a6db5 100644 --- a/lib/routes/login_page.dart +++ b/lib/routes/login_page.dart @@ -16,7 +16,8 @@ class LoginPage extends StatefulWidget { } class _LoginPageState extends State { - late final AuthController authCtrl; + late String email; + late String password; late final TextEditingController passwordResetEmailCtrl; late bool rememberPassword; late int? selectedFieldIndex; @@ -24,26 +25,30 @@ class _LoginPageState extends State { @override void initState() { super.initState(); - //TODO do the points below - // 1. check if JWT access token is still active - // 2. if so redirect user to home page - // 3. if access token is not active show regular login page instead of routing - authCtrl = AuthController(); - passwordResetEmailCtrl = TextEditingController(); + email = ""; + password = ""; rememberPassword = false; selectedFieldIndex = null; - } - @override - void dispose() { - super.dispose(); - authCtrl.emailCtrl.dispose(); - authCtrl.passwordCtrl.dispose(); - passwordResetEmailCtrl.dispose(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + const storage = FlutterSecureStorage(); + final storageEmail = await storage.read(key: "user_email"); + final storagePassword = await storage.read(key: "user_password"); + final storageRemember = await storage.read(key: "remember"); + if (storageRemember != "true" || + storageEmail == null || + storagePassword == null) return; + final loggedIn = await AuthController.loginUser( + storageEmail, + storagePassword, + true, + ); + if (loggedIn && mounted) context.go("/"); + }); } bool get _userCanLogin { - return !(authCtrl.emailCtrl.text == "" || authCtrl.passwordCtrl.text == ""); + return !(email.isEmpty || password.isEmpty); } void _onFieldSelect(int index) { @@ -86,9 +91,10 @@ class _LoginPageState extends State { caption: AppLocalizations.of(context)!.email, icon: Icons.email_rounded, placeholder: AppLocalizations.of(context)!.emailField, - ctrl: authCtrl.emailCtrl, + additionalValue: email, keyboardType: TextInputType.emailAddress, onSelect: (idx) => _onFieldSelect(idx), + onType: (val) => email = val, ), EditField( index: 1, @@ -96,9 +102,10 @@ class _LoginPageState extends State { caption: AppLocalizations.of(context)!.password, icon: Icons.password, placeholder: AppLocalizations.of(context)!.passwordField, - ctrl: authCtrl.passwordCtrl, isPassword: true, + additionalValue: password, onSelect: (idx) => _onFieldSelect(idx), + onType: (val) => password = val, ), const SizedBox(height: 10), GestureDetector( @@ -165,18 +172,34 @@ class _LoginPageState extends State { child: GestureDetector( onTap: () async { if (_userCanLogin) { - final canLogin = await authCtrl.loginUser(); + final loggedIn = await AuthController.loginUser( + email, + password, + rememberPassword, + ); final userData = await UserController.me(); const storage = FlutterSecureStorage(); await storage.write( key: "user_publicId", value: userData.publicId, ); - if (mounted && canLogin) { - context.go("/"); + if (loggedIn) { + if (rememberPassword) { + await storage.write( + key: "user_email", + value: email, + ); + await storage.write( + key: "user_password", + value: password, + ); + } else { + await storage.delete(key: "user_email"); + await storage.delete(key: "user_password"); + } + if (mounted) context.go("/"); } } - //TODO implement remembering user's login and password }, child: AnimatedContainer( width: double.infinity, diff --git a/lib/routes/parties_page.dart b/lib/routes/parties_page.dart index bdd1414..f18e759 100644 --- a/lib/routes/parties_page.dart +++ b/lib/routes/parties_page.dart @@ -28,9 +28,14 @@ class _PartiesPageState extends State { @override void initState() { super.initState(); + parties = []; + users = []; searchType = SearchType.nearbyParties; - const storage = FlutterSecureStorage(); - storage.read(key: "user_publicId").then((id) => userId = id!); + WidgetsBinding.instance.addPostFrameCallback((_) async { + const storage = FlutterSecureStorage(); + final id = await storage.read(key: "user_publicId"); + userId = id!; + }); } @override @@ -65,59 +70,71 @@ class _PartiesPageState extends State { } Widget _resultList(List iter) { - return Column( - children: [ - const SizedBox(height: 130), - for (final i in iter) - T is Party - ? PartyHolder(i as Party) - : UserHolder( - i as Friend, - onButtonTap: () async { - final success = await UserController.sendFriendInvitation( - FriendInvitation( - receiverPublicId: i.publicId!, - senderPublicId: userId, - ), - ); - if (success && mounted) { - showModalBottomSheet( - context: context, - backgroundColor: Theming.bgColor, - isScrollControlled: true, - builder: (ctx) => SuccessSheet( - success: true, - successMsg: - AppLocalizations.of(ctx)!.successFriendInvite, - failureMsg: - AppLocalizations.of(ctx)!.failureFriendInvite, - ), - ); - } else { - showModalBottomSheet( - context: context, - backgroundColor: Theming.bgColor, - isScrollControlled: true, - builder: (ctx) => SuccessSheet( - success: false, - successMsg: - AppLocalizations.of(ctx)!.successFriendInvite, - failureMsg: - AppLocalizations.of(ctx)!.failureFriendInvite, + return iter.isEmpty + ? Center( + child: Text( + AppLocalizations.of(context)!.emptyHere, + style: TextStyle( + color: Theming.whiteTone.withOpacity(0.7), + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + ) + : Column( + children: [ + const SizedBox(height: 130), + for (final i in iter) + T is Party + ? PartyHolder(i as Party) + : UserHolder( + i as Friend, + onButtonTap: () async { + final success = + await UserController.sendFriendInvitation( + FriendInvitation( + receiverPublicId: i.publicId!, + senderPublicId: userId, + ), + ); + if (success && mounted) { + showModalBottomSheet( + context: context, + backgroundColor: Theming.bgColor, + isScrollControlled: true, + builder: (ctx) => SuccessSheet( + success: true, + successMsg: AppLocalizations.of(ctx)! + .successFriendInvite, + failureMsg: AppLocalizations.of(ctx)! + .failureFriendInvite, + ), + ); + } else { + showModalBottomSheet( + context: context, + backgroundColor: Theming.bgColor, + isScrollControlled: true, + builder: (ctx) => SuccessSheet( + success: false, + successMsg: AppLocalizations.of(ctx)! + .successFriendInvite, + failureMsg: AppLocalizations.of(ctx)! + .failureFriendInvite, + ), + ); + } + }, + buttonChild: const Icon( + Icons.person_add_alt_1_rounded, + color: Theming.whiteTone, ), - ); - } - }, - buttonChild: const Icon( - Icons.person_add_alt_1_rounded, - color: Theming.whiteTone, - ), - ), - SizedBox( - height: MediaQuery.of(context).viewPadding.bottom + 120, - ), - ], - ); + ), + SizedBox( + height: MediaQuery.of(context).viewPadding.bottom + 120, + ), + ], + ); } } diff --git a/lib/routes/register_page.dart b/lib/routes/register_page.dart index 37c8bcc..adeb1e6 100644 --- a/lib/routes/register_page.dart +++ b/lib/routes/register_page.dart @@ -279,7 +279,13 @@ class _RegisterPageState extends State { pfp: pfp != null ? File(pfp!.path) : null, ), ); - if (canRegister && mounted) { + if (!canRegister) return; + final loggedIn = await AuthController.loginUser( + emailCtrl.text, + passwordCtrl.text, + false, + ); + if (loggedIn && mounted) { context.go("/"); } } diff --git a/lib/routes/settings_routes/log_out_page.dart b/lib/routes/settings_routes/log_out_page.dart index ce2df47..7c6aedf 100644 --- a/lib/routes/settings_routes/log_out_page.dart +++ b/lib/routes/settings_routes/log_out_page.dart @@ -37,9 +37,8 @@ class LogoutPage extends StatelessWidget { const storage = FlutterSecureStorage(); await storage.delete(key: "access"); await storage.delete(key: "refresh"); - if (context.mounted) { - context.go("/login"); - } + await storage.write(key: "remember", value: "false"); + if (context.mounted) context.go("/login"); }, child: Container( padding: const EdgeInsets.symmetric( diff --git a/lib/utils/router.dart b/lib/utils/router.dart index b2920b2..25c9dc7 100644 --- a/lib/utils/router.dart +++ b/lib/utils/router.dart @@ -27,7 +27,7 @@ final GlobalKey _navBarKey = GlobalKey(); GoRouter router = GoRouter( navigatorKey: _rootKey, - initialLocation: "/", + initialLocation: "/login", routes: [ ShellRoute( navigatorKey: _navBarKey, diff --git a/lib/widgets/edit_field.dart b/lib/widgets/edit_field.dart index 72c0a32..7fa9672 100644 --- a/lib/widgets/edit_field.dart +++ b/lib/widgets/edit_field.dart @@ -13,11 +13,13 @@ class EditField extends StatelessWidget { final IconData icon; final String placeholder; final TextEditingController? ctrl; + final String? additionalValue; //Use if controller is null final bool isDateSelected; final bool isDate; final bool isPassword; final TextInputType? keyboardType; final void Function(Index) onSelect; + final Function(String)? onType; const EditField({ required this.index, @@ -26,11 +28,13 @@ class EditField extends StatelessWidget { required this.icon, required this.placeholder, this.ctrl, + this.additionalValue, this.isDateSelected = false, this.isDate = false, this.isPassword = false, this.keyboardType, required this.onSelect, + this.onType, this.errorFields = const [], super.key, }); @@ -69,6 +73,9 @@ class EditField extends StatelessWidget { ), child: TextField( onTap: () => onSelect(index), + onChanged: (val) { + if (onType != null) onType!(val); + }, obscureText: isPassword, keyboardType: keyboardType, style: TextStyle( @@ -84,7 +91,8 @@ class EditField extends StatelessWidget { hintStyle: TextStyle( fontSize: 14, letterSpacing: 0, - color: errorFields.contains(index) && selectedFieldIndex == index + color: errorFields.contains(index) && + selectedFieldIndex == index ? Theming.errorColor : index == selectedFieldIndex ? Theming.whiteTone.withOpacity(_borderOpacity + 0.2) @@ -107,13 +115,15 @@ class EditField extends StatelessWidget { padding: EdgeInsets.only( top: index == selectedFieldIndex || (ctrl != null && ctrl!.text.isNotEmpty) || - isDateSelected + isDateSelected || + (additionalValue != null && additionalValue!.isNotEmpty) ? 0 : 30, left: _iconSize * 2, bottom: index == selectedFieldIndex || (ctrl != null && ctrl!.text.isNotEmpty) || - isDateSelected + isDateSelected || + (additionalValue != null && additionalValue!.isNotEmpty) ? 0 : 10, ), diff --git a/lib/widgets/partiespage/search_and_map.dart b/lib/widgets/partiespage/search_and_map.dart index 87db091..f9d76ae 100644 --- a/lib/widgets/partiespage/search_and_map.dart +++ b/lib/widgets/partiespage/search_and_map.dart @@ -30,8 +30,8 @@ class _SearchAndMapState extends State { selectedIndex = 0; searchCtrl = TextEditingController(); WidgetsBinding.instance.addPostFrameCallback((_) async { - final tempParties = await SearchController.seachPartiesByDistance(1000); - widget.onPartySearch(tempParties); + final parties = await SearchController.seachPartiesByDistance(); + widget.onPartySearch(parties); }); } @@ -103,14 +103,29 @@ class _SearchAndMapState extends State { const Spacer(), //Search bar - Container( - decoration: const BoxDecoration( - color: Theming.primaryColor, + GestureDetector( + onTap: () { + //TODO implement searching + }, + child: Container( + height: 55, + width: 55, + decoration: const BoxDecoration( + color: Theming.primaryColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10), + ), + ), + child: const Icon( + Icons.search_rounded, + color: Theming.whiteTone, + ), ), ), Container( height: 55, - width: MediaQuery.of(context).size.width - 55 - 60 - 20, + width: MediaQuery.of(context).size.width - 180, padding: const EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.center, decoration: const BoxDecoration( diff --git a/untranslated_msgs.txt b/untranslated_msgs.txt deleted file mode 100644 index 9e26dfe..0000000 --- a/untranslated_msgs.txt +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file