Redesign & refactor home screen
- Removed hamburger/drawer menu - Moved search to bottom navigation bar - Moved settings and profile to app bar - Moved channel list from screen to widget folder - Created search screen for viewing results - Switched from Cupertino to Material routes due to rebuild issues
This commit is contained in:
parent
d0f17634ba
commit
e3f774d8e5
11 changed files with 186 additions and 147 deletions
|
|
@ -32,7 +32,10 @@ class MyApp extends StatelessWidget {
|
|||
),
|
||||
home: Scaffold(
|
||||
body: FutureBuilder(
|
||||
future: context.read<AuthStore>().init(),
|
||||
future: Future.wait([
|
||||
context.read<AuthStore>().init(),
|
||||
context.read<SettingsStore>().init(),
|
||||
]),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
return Home(
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/screens/video_chat.dart';
|
||||
import 'package:frosty/screens/search.dart';
|
||||
import 'package:frosty/screens/settings.dart';
|
||||
import 'package:frosty/stores/auth_store.dart';
|
||||
import 'package:frosty/stores/channel_list_store.dart';
|
||||
import 'package:frosty/stores/home_store.dart';
|
||||
import 'package:frosty/widgets/drawer_menu.dart';
|
||||
import 'package:frosty/stores/settings_store.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'channel_list.dart';
|
||||
import '../widgets/channel_list.dart';
|
||||
|
||||
class Home extends StatefulWidget {
|
||||
final HomeStore homeStore;
|
||||
|
|
@ -29,55 +29,49 @@ class _HomeState extends State<Home> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final titles = [if (context.read<AuthStore>().isLoggedIn) 'Followed Channels', 'Top Channels', 'Categories'];
|
||||
final titles = [
|
||||
if (context.read<AuthStore>().isLoggedIn) 'Followed Channels',
|
||||
'Top Channels',
|
||||
'Categories',
|
||||
'Search',
|
||||
];
|
||||
|
||||
debugPrint('build home');
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Observer(
|
||||
builder: (_) {
|
||||
return widget.homeStore.search
|
||||
? TextField(
|
||||
controller: _textController,
|
||||
autocorrect: false,
|
||||
autofocus: true,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Search for a channel',
|
||||
),
|
||||
onSubmitted: (string) async {
|
||||
if (await Twitch.getUser(userLogin: string, headers: context.read<AuthStore>().headersTwitch) != null) {
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
builder: (_) {
|
||||
return VideoChat(
|
||||
userLogin: string,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const snackBar = SnackBar(content: Text('User does not exist :('));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
widget.homeStore.search = false;
|
||||
_textController.clear();
|
||||
},
|
||||
)
|
||||
: Text(titles[widget.homeStore.selectedIndex]);
|
||||
return Text(
|
||||
titles[widget.homeStore.selectedIndex],
|
||||
style: GoogleFonts.daysOne(),
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Observer(builder: (_) => widget.homeStore.search ? const Icon(Icons.cancel) : const Icon(Icons.search)),
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () {
|
||||
widget.homeStore.search = !widget.homeStore.search;
|
||||
_textController.clear();
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
'Settings',
|
||||
style: GoogleFonts.daysOne(),
|
||||
),
|
||||
),
|
||||
body: Settings(settingsStore: context.read<SettingsStore>()),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
drawer: const DrawerMenu(),
|
||||
body: Observer(
|
||||
builder: (_) {
|
||||
return IndexedStack(
|
||||
|
|
@ -95,6 +89,7 @@ class _HomeState extends State<Home> {
|
|||
const Center(
|
||||
child: Text('Games'),
|
||||
),
|
||||
const Search(),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
@ -103,6 +98,7 @@ class _HomeState extends State<Home> {
|
|||
child: Observer(
|
||||
builder: (_) {
|
||||
return BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
items: [
|
||||
if (context.read<AuthStore>().isLoggedIn)
|
||||
const BottomNavigationBarItem(
|
||||
|
|
@ -117,6 +113,10 @@ class _HomeState extends State<Home> {
|
|||
icon: Icon(Icons.gamepad),
|
||||
label: 'Categories',
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
icon: Icon(Icons.search),
|
||||
label: 'Search',
|
||||
),
|
||||
],
|
||||
currentIndex: widget.homeStore.selectedIndex,
|
||||
onTap: widget.homeStore.handleTap,
|
||||
|
|
|
|||
50
lib/screens/search.dart
Normal file
50
lib/screens/search.dart
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/screens/video_chat.dart';
|
||||
import 'package:frosty/stores/auth_store.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class Search extends StatefulWidget {
|
||||
const Search({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SearchState createState() => _SearchState();
|
||||
}
|
||||
|
||||
class _SearchState extends State<Search> {
|
||||
final _textController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: _textController,
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.all(8.0),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
),
|
||||
hintText: 'Search',
|
||||
),
|
||||
onSubmitted: (string) async {
|
||||
if (await Twitch.getUser(userLogin: string, headers: context.read<AuthStore>().headersTwitch) != null) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) {
|
||||
return VideoChat(
|
||||
userLogin: string,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const snackBar = SnackBar(content: Text('User does not exist :('));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
_textController.clear();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,30 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/stores/auth_store.dart';
|
||||
import 'package:frosty/stores/settings_store.dart';
|
||||
import 'package:frosty/widgets/profile_card.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class Settings extends StatelessWidget {
|
||||
const Settings({Key? key}) : super(key: key);
|
||||
final SettingsStore settingsStore;
|
||||
|
||||
const Settings({Key? key, required this.settingsStore}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final settingsStore = context.read<SettingsStore>();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Settings'),
|
||||
),
|
||||
body: Observer(
|
||||
builder: (_) {
|
||||
return ListView(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
title: const Text('Enable Video'),
|
||||
value: settingsStore.videoEnabled,
|
||||
onChanged: (newValue) => settingsStore.videoEnabled = newValue,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
return ListView(
|
||||
children: [
|
||||
ProfileCard(authStore: context.read<AuthStore>()),
|
||||
SwitchListTile(
|
||||
title: const Text('Enable Video'),
|
||||
value: settingsStore.videoEnabled,
|
||||
onChanged: (newValue) => settingsStore.videoEnabled = newValue,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ abstract class _HomeStoreBase with Store {
|
|||
|
||||
@action
|
||||
void handleTap(int index) {
|
||||
if (index == 3) {
|
||||
search = !search;
|
||||
}
|
||||
if (index != selectedIndex) {
|
||||
selectedIndex = index;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,29 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'settings_store.g.dart';
|
||||
|
||||
class SettingsStore = _SettingsStoreBase with _$SettingsStore;
|
||||
|
||||
abstract class _SettingsStoreBase with Store {
|
||||
late final SharedPreferences prefs;
|
||||
late ReactionDisposer disposeSetter;
|
||||
|
||||
Future<void> init() async {
|
||||
// debugPrint('Created settings provider!');
|
||||
// prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// // Initialize settings from stored preferences if any.
|
||||
// videoEnabled = prefs.getBool('video_enabled') ?? videoEnabled;
|
||||
|
||||
// // Set up autorun to store setting anytime they're changed.
|
||||
// disposeSetter = autorun((_) {
|
||||
// debugPrint('video changed');
|
||||
// prefs.setBool('video_enabled', videoEnabled);
|
||||
// });
|
||||
}
|
||||
|
||||
@observable
|
||||
bool videoEnabled = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/models/channel.dart';
|
||||
import 'package:frosty/screens/video_chat.dart';
|
||||
|
|
@ -18,7 +17,7 @@ class ChannelCard extends StatelessWidget {
|
|||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return VideoChat(
|
||||
userLogin: channelInfo.userLogin,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class ChannelList extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint('build channel list');
|
||||
debugPrint('Build channel list!');
|
||||
|
||||
return RefreshIndicator(
|
||||
child: Observer(
|
||||
|
|
@ -3,6 +3,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
|||
import 'package:frosty/screens/settings.dart';
|
||||
import 'package:frosty/stores/auth_store.dart';
|
||||
import 'package:frosty/stores/chat_store.dart';
|
||||
import 'package:frosty/stores/settings_store.dart';
|
||||
import 'package:frosty/widgets/chat_stats.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
|
@ -50,7 +51,7 @@ class _ChatState extends State<Chat> {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => widget.chatStore.resumeScroll(),
|
||||
onPressed: widget.chatStore.resumeScroll,
|
||||
child: const Text('Resume Scroll'),
|
||||
),
|
||||
),
|
||||
|
|
@ -62,13 +63,17 @@ class _ChatState extends State<Chat> {
|
|||
),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return const Settings();
|
||||
return Settings(settingsStore: context.read<SettingsStore>());
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:frosty/screens/settings.dart';
|
||||
import 'package:frosty/widgets/profile_card.dart';
|
||||
|
||||
class DrawerMenu extends StatelessWidget {
|
||||
const DrawerMenu({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Drawer(
|
||||
child: ListView(
|
||||
children: [
|
||||
const ProfileCard(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: const Text('Settings'),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) {
|
||||
return const Settings();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,59 +2,55 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/stores/auth_store.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ProfileCard extends StatelessWidget {
|
||||
const ProfileCard({Key? key}) : super(key: key);
|
||||
final AuthStore authStore;
|
||||
const ProfileCard({Key? key, required this.authStore}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final authStore = context.read<AuthStore>();
|
||||
|
||||
Future<void> _showDialog() async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Logout'),
|
||||
content: const Text('Are you sure you want to log out?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel')),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
authStore.logout();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('Yes')),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
return Center(
|
||||
child: authStore.isLoggedIn
|
||||
? UserAccountsDrawerHeader(
|
||||
currentAccountPicture: InkWell(
|
||||
child: CircleAvatar(
|
||||
foregroundImage: NetworkImage(
|
||||
authStore.user!.profileImageUrl,
|
||||
),
|
||||
),
|
||||
onTap: () => _showDialog(),
|
||||
),
|
||||
accountName: Text(authStore.user!.displayName),
|
||||
accountEmail: Text('Joined on ${DateFormat.yMMMMd('en_US').format(DateTime.parse(authStore.user!.createdAt)).toString()}'),
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: () => authStore.login(),
|
||||
child: const Text('Login'),
|
||||
),
|
||||
Future<void> _showDialog(BuildContext context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Logout'),
|
||||
content: const Text('Are you sure you want to log out?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel')),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
authStore.logout();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('Yes'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
if (authStore.isLoggedIn) {
|
||||
return ListTile(
|
||||
leading: CircleAvatar(
|
||||
foregroundImage: NetworkImage(
|
||||
authStore.user!.profileImageUrl,
|
||||
),
|
||||
),
|
||||
title: Text(authStore.user!.displayName),
|
||||
onTap: () => _showDialog(context),
|
||||
);
|
||||
}
|
||||
return ElevatedButton(
|
||||
onPressed: authStore.login,
|
||||
child: const Text('Login'),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue