Refactor sign in screen to do more account management functions and remove menus/profile screens

codemagic-setup
Hank Grabowski 2023-03-10 23:23:44 -05:00
rodzic 6b15b2136d
commit 854c142c20
7 zmienionych plików z 194 dodań i 243 usunięć

Wyświetl plik

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:relatica/utils/snackbar_builder.dart';
import '../routes.dart';
import '../services/notifications_manager.dart';
@ -9,7 +10,7 @@ enum NavBarButtons {
timelines,
notifications,
contacts,
menu,
search,
}
class AppBottomNavBar extends StatelessWidget {
@ -44,8 +45,8 @@ class AppBottomNavBar extends StatelessWidget {
case NavBarButtons.contacts:
context.pushNamed(ScreenPaths.contacts);
break;
case NavBarButtons.menu:
context.pushNamed(ScreenPaths.menu);
case NavBarButtons.search:
buildSnackbar(context, 'Search screen coming soon...');
break;
}
},
@ -63,7 +64,7 @@ class AppBottomNavBar extends StatelessWidget {
return 1;
case NavBarButtons.contacts:
return 2;
case NavBarButtons.menu:
case NavBarButtons.search:
return 3;
}
}
@ -82,7 +83,7 @@ class AppBottomNavBar extends StatelessWidget {
}
if (index == 3) {
return NavBarButtons.menu;
return NavBarButtons.search;
}
throw ArgumentError('$index has no button type');
@ -111,9 +112,9 @@ class AppBottomNavBar extends StatelessWidget {
activeIcon: Icon(Icons.people_sharp),
),
const BottomNavigationBarItem(
label: 'Menu',
icon: Icon(Icons.menu),
activeIcon: Icon(Icons.menu_open),
label: 'Search',
icon: Icon(Icons.search),
activeIcon: Icon(Icons.search),
),
];
}

Wyświetl plik

@ -35,7 +35,12 @@ class StandardAppDrawer extends StatelessWidget {
),
),
),
Divider(),
buildMenuButton(
context,
'Manage Profiles',
() => context.pushNamed(ScreenPaths.manageProfiles),
),
const Divider(),
buildMenuButton(
context,
'Gallery',
@ -46,11 +51,6 @@ class StandardAppDrawer extends StatelessWidget {
'Direct Messages',
() => context.pushNamed(ScreenPaths.messages),
),
buildMenuButton(
context,
'Profile',
() => context.pushNamed(ScreenPaths.profile),
),
buildMenuButton(
context,
'Settings',

Wyświetl plik

@ -22,7 +22,7 @@ final useVideoPlayer = kIsWeb || Platform.isAndroid || Platform.isIOS;
Future<bool?> showConfirmDialog(BuildContext context, String caption) {
return showDialog<bool>(
context: context,
barrierDismissible: false,
barrierDismissible: true,
builder: (BuildContext context) {
return AlertDialog(
title: Text(caption),

Wyświetl plik

@ -9,13 +9,11 @@ import 'screens/gallery_browsers_screen.dart';
import 'screens/gallery_screen.dart';
import 'screens/home.dart';
import 'screens/interactions_viewer_screen.dart';
import 'screens/menus_screen.dart';
import 'screens/message_thread_screen.dart';
import 'screens/message_threads_browser_screen.dart';
import 'screens/messages_new_thread.dart';
import 'screens/notifications_screen.dart';
import 'screens/post_screen.dart';
import 'screens/profile_screen.dart';
import 'screens/settings_screen.dart';
import 'screens/sign_in.dart';
import 'screens/splash.dart';
@ -28,15 +26,13 @@ class ScreenPaths {
static String connectHandle = '/connect';
static String contacts = '/contacts';
static String splash = '/splash';
static String menu = '/menu';
static String settings = '/settings';
static String messages = '/messages';
static String timelines = '/';
static String gallery = '/gallery';
static String profile = '/profile';
static String notifications = '/notifications';
static String signin = '/signin';
static String switchProfiles = '/switchProfiles';
static String manageProfiles = '/switchProfiles';
static String signup = '/signup';
static String userProfile = '/user_profile';
static String userPosts = '/user_posts';
@ -75,8 +71,8 @@ final appRouter = GoRouter(
builder: (context, state) => SignInScreen(),
),
GoRoute(
path: ScreenPaths.switchProfiles,
name: ScreenPaths.switchProfiles,
path: ScreenPaths.manageProfiles,
name: ScreenPaths.manageProfiles,
builder: (context, state) => SignInScreen(),
),
GoRoute(
@ -101,20 +97,6 @@ final appRouter = GoRouter(
child: HomeScreen(),
),
),
GoRoute(
path: ScreenPaths.profile,
name: ScreenPaths.profile,
pageBuilder: (context, state) => NoTransitionPage(
child: ProfileScreen(),
),
),
GoRoute(
path: ScreenPaths.menu,
name: ScreenPaths.menu,
pageBuilder: (context, state) => NoTransitionPage(
child: MenusScreen(),
),
),
GoRoute(
path: ScreenPaths.messages,
name: ScreenPaths.messages,

Wyświetl plik

@ -1,57 +0,0 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../controls/app_bottom_nav_bar.dart';
import '../globals.dart';
import '../routes.dart';
class MenusScreen extends StatelessWidget {
static const menuButtonWidth = 350.0;
static const menuButtonHeight = 125.0;
@override
Widget build(BuildContext context) {
final menuItems = [
buildMenuButton('Gallery', () => context.pushNamed(ScreenPaths.gallery)),
buildMenuButton(
'Direct Messages', () => context.pushNamed(ScreenPaths.messages)),
buildMenuButton('Profile', () => context.pushNamed(ScreenPaths.profile)),
buildMenuButton(
'Settings', () => context.pushNamed(ScreenPaths.settings)),
buildMenuButton('Clear Caches', () async {
final confirm = await showYesNoDialog(
context, 'You want to clear all memory and disk cache data?');
if (confirm == true) {
clearCaches();
}
}),
];
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GridView(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
mainAxisExtent: menuButtonHeight,
maxCrossAxisExtent: menuButtonWidth,
),
children: menuItems,
),
),
),
bottomNavigationBar: const AppBottomNavBar(
currentButton: NavBarButtons.menu,
),
);
}
Widget buildMenuButton(String title, Function() onPressed) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: onPressed,
child: Text(title),
),
);
}
}

Wyświetl plik

@ -1,58 +0,0 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:relatica/controls/padding.dart';
import 'package:relatica/routes.dart';
import '../controls/standard_appbar.dart';
import '../globals.dart';
import '../services/auth_service.dart';
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
@override
Widget build(BuildContext context) {
final authService = context.watch<AccountsService>();
if (!authService.loggedIn) {
return Scaffold(
appBar: StandardAppBar.build(context, 'Not Logged In'),
body: Center(
child: Text('Not logged in'),
));
}
final profile = authService.currentProfile;
return Scaffold(
appBar: StandardAppBar.build(context, 'Profile'),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Profile: ${profile.handle}'),
const VerticalPadding(),
ElevatedButton(
onPressed: () async {
final confirm =
await showYesNoDialog(context, 'Log out account?');
if (confirm == true) {
await getIt<AccountsService>().signOut(profile);
}
},
child: const Text('Logout')),
const VerticalPadding(),
ElevatedButton(
onPressed: () async {
context.pushNamed(ScreenPaths.switchProfiles);
},
child: const Text('Switch Profile'))
],
),
),
);
}
}

Wyświetl plik

@ -9,6 +9,7 @@ import '../controls/padding.dart';
import '../globals.dart';
import '../models/auth/credentials_intf.dart';
import '../models/auth/oauth_credentials.dart';
import '../models/auth/profile.dart';
import '../services/auth_service.dart';
import '../utils/snackbar_builder.dart';
@ -29,16 +30,30 @@ class _SignInScreenState extends State<SignInScreen> {
var authType = oauthType;
var hidePassword = true;
var showUsernameAndPasswordFields = false;
var signInButtonEnabled = false;
var existingAccount = false;
@override
void initState() {
super.initState();
final service = getIt<AccountsService>();
if (service.loggedIn) {
setCredentials(null, service.currentProfile.credentials);
setCredentials(null, service.currentProfile);
} else {
newProfile();
}
}
void newProfile() {
usernameController.text = '';
passwordController.text = '';
serverNameController.text = '';
showUsernameAndPasswordFields = false;
authType = oauthType;
signInButtonEnabled = true;
existingAccount = false;
}
void setBasicCredentials(BasicCredentials credentials) {
usernameController.text = credentials.username;
passwordController.text = credentials.password;
@ -53,7 +68,10 @@ class _SignInScreenState extends State<SignInScreen> {
authType = oauthType;
}
void setCredentials(BuildContext? context, ICredentials credentials) {
void setCredentials(BuildContext? context, Profile profile) {
final ICredentials credentials = profile.credentials;
existingAccount = true;
signInButtonEnabled = !profile.loggedIn;
if (credentials is BasicCredentials) {
setBasicCredentials(credentials);
return;
@ -86,32 +104,39 @@ class _SignInScreenState extends State<SignInScreen> {
child: Form(
key: formKey,
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
child: ListView(
children: [
DropdownButton<String>(
value: authType,
items: authTypes
.map((a) => DropdownMenuItem(value: a, child: Text(a)))
.toList(),
onChanged: (value) {
setState(() {
authType = value ?? '';
switch (value) {
case usernamePasswordType:
showUsernameAndPasswordFields = true;
break;
case oauthType:
showUsernameAndPasswordFields = false;
break;
default:
print("Don't know this");
Center(
child: DropdownButton<String>(
value: authType,
items: authTypes
.map(
(a) => DropdownMenuItem(value: a, child: Text(a)))
.toList(),
onChanged: (value) {
if (existingAccount) {
buildSnackbar(context,
"Can't change the type on an existing account");
return;
}
});
}),
setState(() {
authType = value ?? '';
switch (value) {
case usernamePasswordType:
showUsernameAndPasswordFields = true;
break;
case oauthType:
showUsernameAndPasswordFields = false;
break;
default:
print("Don't know this");
}
});
}),
),
const VerticalPadding(),
TextFormField(
readOnly: existingAccount,
autovalidateMode: AutovalidateMode.onUserInteraction,
controller: serverNameController,
validator: (value) =>
@ -130,6 +155,7 @@ class _SignInScreenState extends State<SignInScreen> {
const VerticalPadding(),
if (showUsernameAndPasswordFields) ...[
TextFormField(
readOnly: existingAccount,
autovalidateMode: AutovalidateMode.onUserInteraction,
controller: usernameController,
keyboardType: TextInputType.emailAddress,
@ -162,6 +188,7 @@ class _SignInScreenState extends State<SignInScreen> {
),
const VerticalPadding(),
TextFormField(
readOnly: existingAccount,
obscureText: hidePassword,
controller: passwordController,
decoration: InputDecoration(
@ -188,77 +215,129 @@ class _SignInScreenState extends State<SignInScreen> {
),
const VerticalPadding(),
],
ElevatedButton(
onPressed: () => _signIn(context),
child: const Text('Signin'),
),
const VerticalPadding(),
const Text('Logged out:'),
Expanded(
flex: 1,
child: ListView.separated(
itemBuilder: (context, index) {
final p = loggedOutProfiles[index];
return ListTile(
onTap: () {
setCredentials(context, p.credentials);
setState(() {});
signInButtonEnabled
? ElevatedButton(
onPressed: () => _signIn(context),
child: const Text('Signin'),
)
: ElevatedButton(
onPressed: () {
setState(() {
newProfile();
});
},
title: Text(p.handle),
subtitle: Text(p.credentials is BasicCredentials
? 'Username/Password'
: 'OAuth Login'),
);
},
separatorBuilder: (_, __) => const Divider(),
itemCount: loggedOutProfiles.length,
),
),
child: const Text('New'),
),
const VerticalPadding(),
const Text('Logged in:'),
Expanded(
flex: 1,
child: ListView.separated(
itemBuilder: (context, index) {
final p = loggedInProfiles[index];
final active = service.loggedIn
? p.id == service.currentProfile.id
: false;
return ListTile(
onTap: () async {
setCredentials(context, p.credentials);
setState(() {});
await service.setActiveProfile(p);
Text(
'Logged out:',
style: Theme.of(context).textTheme.headlineSmall,
),
loggedOutProfiles.isEmpty
? const Text(
'No logged out profiles',
textAlign: TextAlign.center,
)
: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 0.5,
)),
child: Column(
children: loggedOutProfiles.map((p) {
return ListTile(
onTap: () {
setCredentials(context, p);
setState(() {});
},
title: Text(p.handle),
subtitle: Text(p.credentials is BasicCredentials
? 'Username/Password'
: 'OAuth Login'),
trailing: ElevatedButton(
onPressed: () async {
final confirm = await showYesNoDialog(context,
'Remove login information from app?');
if (confirm ?? false) {
await service.removeProfile(p);
}
},
child: const Text('Remove'),
),
);
}).toList(),
),
),
const VerticalPadding(),
Text(
'Logged in:',
style: Theme.of(context).textTheme.headlineSmall,
),
loggedInProfiles.isEmpty
? const Text(
'No logged in profiles',
textAlign: TextAlign.center,
)
: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 0.5,
)),
child: Column(
children: loggedInProfiles.map((p) {
final active = service.loggedIn
? p.id == service.currentProfile.id
: false;
return ListTile(
onTap: () async {
setCredentials(context, p);
setState(() {});
if (mounted) {
clearCaches();
context.goNamed(ScreenPaths.timelines);
}
},
title: Text(
p.handle,
style: active
? const TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)
: null,
final confirm = await showYesNoDialog(
context, 'Switch to account?');
if (confirm ?? false) {
service.setActiveProfile(p);
clearCaches();
if (mounted) {
context.goNamed(ScreenPaths.timelines);
}
}
},
title: Text(
p.handle,
style: active
? const TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)
: null,
),
subtitle: Text(
p.credentials is BasicCredentials
? 'Username/Password'
: 'OAuth Login',
style: active
? const TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)
: null,
),
trailing: ElevatedButton(
onPressed: () async {
final confirm = await showYesNoDialog(
context, 'Log out account?');
if (confirm == true) {
await getIt<AccountsService>().signOut(p);
}
},
child: const Text('Sign out'),
),
);
}).toList(),
),
subtitle: Text(
p.credentials is BasicCredentials
? 'Username/Password'
: 'OAuth Login',
style: active
? const TextStyle(
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)
: null,
),
);
},
separatorBuilder: (_, __) => const Divider(),
itemCount: loggedInProfiles.length,
),
),
),
],
),
),
@ -295,8 +374,12 @@ class _SignInScreenState extends State<SignInScreen> {
}
if (result.isFailure) {
buildSnackbar(context, 'Error signing in: ${result.error}');
return;
}
await getIt<AccountsService>().setActiveProfile(result.value);
if (mounted) {
context.goNamed(ScreenPaths.timelines);
}
context.goNamed(ScreenPaths.timelines);
}
}
}