From 6af1c4f214f3885d8e00b7e599433c387961c7c7 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Fri, 18 Nov 2022 22:49:11 -0500 Subject: [PATCH] Add button bar w/nav to home, notifications, and profile --- lib/controls/app_bottom_nav_bar.dart | 126 ++++++++++++++++++++++ lib/controls/timeline/status_control.dart | 3 + lib/friendica_client.dart | 4 +- lib/main.dart | 10 -- lib/routes.dart | 23 +++- lib/screens/home.dart | 6 ++ lib/screens/notifications_screen.dart | 27 +++++ lib/screens/profile_screen.dart | 38 +++++++ lib/services/secrets_service.dart | 1 + 9 files changed, 225 insertions(+), 13 deletions(-) create mode 100644 lib/controls/app_bottom_nav_bar.dart create mode 100644 lib/screens/notifications_screen.dart create mode 100644 lib/screens/profile_screen.dart diff --git a/lib/controls/app_bottom_nav_bar.dart b/lib/controls/app_bottom_nav_bar.dart new file mode 100644 index 0000000..25e10cf --- /dev/null +++ b/lib/controls/app_bottom_nav_bar.dart @@ -0,0 +1,126 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../routes.dart'; + +enum NavBarButtons { + home, + notifications, + messages, + contacts, + profile, +} + +class AppBottomNavBar extends StatelessWidget { + final NavBarButtons currentButton; + + const AppBottomNavBar({super.key, required this.currentButton}); + + @override + Widget build(BuildContext context) { + return BottomNavigationBar( + onTap: (index) { + final newButton = _indexToButton(index); + if (newButton == currentButton) { + print('same button do nothing'); + return; + } + + switch (newButton) { + case NavBarButtons.home: + Navigator.of(context).popUntil((route) { + return route.settings.name == ScreenPaths.home; + }); + break; + case NavBarButtons.notifications: + context.pushNamed(ScreenPaths.notifications); + break; + case NavBarButtons.messages: + // TODO: Handle this case. + break; + case NavBarButtons.contacts: + // TODO: Handle this case. + break; + case NavBarButtons.profile: + context.pushNamed(ScreenPaths.profile); + break; + } + }, + type: BottomNavigationBarType.fixed, + selectedItemColor: Colors.black, + unselectedItemColor: Colors.black54, + currentIndex: _buttonToIndex(currentButton), + items: _menuItems, + ); + } + + int _buttonToIndex(NavBarButtons button) { + switch (button) { + case NavBarButtons.home: + return 0; + case NavBarButtons.notifications: + return 1; + case NavBarButtons.messages: + return 2; + case NavBarButtons.contacts: + return 3; + case NavBarButtons.profile: + return 4; + } + } + + NavBarButtons _indexToButton(int index) { + if (index == 0) { + return NavBarButtons.home; + } + + if (index == 1) { + return NavBarButtons.notifications; + } + + if (index == 2) { + return NavBarButtons.messages; + } + + if (index == 3) { + return NavBarButtons.contacts; + } + + if (index == 4) { + return NavBarButtons.profile; + } + + throw ArgumentError('$index has no button type'); + } + + List get _menuItems { + return const [ + BottomNavigationBarItem( + label: 'Home', + icon: Icon(Icons.home_outlined), + activeIcon: Icon(Icons.home_filled), + ), + BottomNavigationBarItem( + label: 'Notifications', + icon: Icon(Icons.notifications_none_outlined), + activeIcon: Icon(Icons.notifications), + ), + BottomNavigationBarItem( + label: 'Messages', + icon: Icon(Icons.messenger_outline), + activeIcon: Icon(Icons.messenger), + ), + BottomNavigationBarItem( + label: 'Contacts', + icon: Icon(Icons.people_outline), + activeIcon: Icon(Icons.people_sharp), + ), + BottomNavigationBarItem( + label: 'Profile', + icon: Icon(Icons.person_outline), + activeIcon: Icon(Icons.person), + ), + ]; + } +} diff --git a/lib/controls/timeline/status_control.dart b/lib/controls/timeline/status_control.dart index 7504ef9..f228e1d 100644 --- a/lib/controls/timeline/status_control.dart +++ b/lib/controls/timeline/status_control.dart @@ -91,6 +91,9 @@ class _StatusControlState extends State { ElapsedDateUtils.epochSecondsToString(entry.backdatedTimestamp), style: Theme.of(context).textTheme.caption, ), + Text( + item.id, + ), ], ), ], diff --git a/lib/friendica_client.dart b/lib/friendica_client.dart index fd1f117..79f5a9c 100644 --- a/lib/friendica_client.dart +++ b/lib/friendica_client.dart @@ -46,7 +46,7 @@ class FriendicaClient { FutureResult, ExecError> getTimeline( {required TimelineIdentifiers type, int sinceId = 0, - int limit = 100}) async { + int limit = 20}) async { final String timelinePath = _typeToTimelinePath(type); final String timelineQPs = _typeToTimelineQueryParameters(type); final baseUrl = 'https://$serverName/api/v1/timelines/$timelinePath'; @@ -154,7 +154,7 @@ class FriendicaClient { type: ErrorType.authentication, message: '${response.statusCode}: ${response.reasonPhrase}')); } - return Result.ok(response.body); + return Result.ok(utf8.decode(response.bodyBytes)); } catch (e) { return Result.error( ExecError(type: ErrorType.localError, message: e.toString())); diff --git a/lib/main.dart b/lib/main.dart index 7a4ecd6..b513b45 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,6 @@ import 'package:result_monad/result_monad.dart'; import 'globals.dart'; import 'models/TimelineIdentifiers.dart'; import 'routes.dart'; -import 'screens/sign_in.dart'; import 'services/auth_service.dart'; import 'services/connections_manager.dart'; import 'services/entry_manager_service.dart'; @@ -86,12 +85,3 @@ class App extends StatelessWidget { ); } } - -class Home extends StatelessWidget { - const Home({super.key}); - - @override - Widget build(BuildContext context) { - return SignInScreen(); - } -} diff --git a/lib/routes.dart b/lib/routes.dart index d026860..6a29c93 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -3,6 +3,8 @@ import 'package:go_router/go_router.dart'; import 'globals.dart'; import 'screens/editor.dart'; import 'screens/home.dart'; +import 'screens/notifications_screen.dart'; +import 'screens/profile_screen.dart'; import 'screens/sign_in.dart'; import 'screens/splash.dart'; import 'services/auth_service.dart'; @@ -10,6 +12,8 @@ import 'services/auth_service.dart'; class ScreenPaths { static String splash = '/splash'; static String home = '/'; + static String profile = '/profile'; + static String notifications = '/notifications'; static String signin = '/signin'; static String signup = '/signup'; static String settings = '/settings'; @@ -52,7 +56,24 @@ final appRouter = GoRouter( GoRoute( path: ScreenPaths.home, name: ScreenPaths.home, - builder: (context, state) => HomeScreen(), + pageBuilder: (context, state) => NoTransitionPage( + name: ScreenPaths.home, + child: HomeScreen(), + ), + ), + GoRoute( + path: ScreenPaths.profile, + name: ScreenPaths.profile, + pageBuilder: (context, state) => NoTransitionPage( + child: ProfileScreen(), + ), + ), + GoRoute( + path: ScreenPaths.notifications, + name: ScreenPaths.notifications, + pageBuilder: (context, state) => NoTransitionPage( + child: NotificationsScreen(), + ), ), GoRoute( path: ScreenPaths.splash, diff --git a/lib/screens/home.dart b/lib/screens/home.dart index 233ca26..ac33b56 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -3,11 +3,14 @@ import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; +import '../controls/app_bottom_nav_bar.dart'; import '../controls/timeline/status_control.dart'; import '../models/TimelineIdentifiers.dart'; import '../services/timeline_manager.dart'; class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + @override State createState() => _HomeScreenState(); } @@ -57,6 +60,9 @@ class _HomeScreenState extends State { Expanded(child: buildTimelineComponent(context, tm)) ], ), + bottomNavigationBar: AppBottomNavBar( + currentButton: NavBarButtons.home, + ), ); } diff --git a/lib/screens/notifications_screen.dart b/lib/screens/notifications_screen.dart new file mode 100644 index 0000000..0026953 --- /dev/null +++ b/lib/screens/notifications_screen.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +import '../controls/app_bottom_nav_bar.dart'; + +class NotificationsScreen extends StatelessWidget { + const NotificationsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Notifications'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Notifications'), + ], + ), + ), + bottomNavigationBar: AppBottomNavBar( + currentButton: NavBarButtons.notifications, + ), + ); + } +} diff --git a/lib/screens/profile_screen.dart b/lib/screens/profile_screen.dart new file mode 100644 index 0000000..3d383b6 --- /dev/null +++ b/lib/screens/profile_screen.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../controls/app_bottom_nav_bar.dart'; +import '../controls/padding.dart'; +import '../services/auth_service.dart'; + +class ProfileScreen extends StatelessWidget { + const ProfileScreen({super.key}); + + @override + Widget build(BuildContext context) { + final authService = context.watch(); + return Scaffold( + appBar: AppBar( + title: Text('Profile'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Profile: ${authService.currentClient.fold(onSuccess: (client) => client.credentials.handle, onError: (error) => 'Error Getting Profile')}'), + VerticalPadding(), + ElevatedButton( + onPressed: () async { + await authService.signOut(); + }, + child: Text('Sign Out')), + ], + ), + ), + bottomNavigationBar: AppBottomNavBar( + currentButton: NavBarButtons.profile, + ), + ); + } +} diff --git a/lib/services/secrets_service.dart b/lib/services/secrets_service.dart index 182909a..9c6704b 100644 --- a/lib/services/secrets_service.dart +++ b/lib/services/secrets_service.dart @@ -50,6 +50,7 @@ class SecretsService { _secureStorage.write(key: _usernameKey, value: credentials.username); _secureStorage.write(key: _passwordKey, value: credentials.password); _secureStorage.write(key: _serverNameKey, value: credentials.serverName); + _cachedCredentials = credentials; return Result.ok(credentials); } on PlatformException catch (e) { return Result.error(ExecError(