Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bottom bar for navigation, tabs for inbox #578

Closed
wants to merge 13 commits into from
4 changes: 3 additions & 1 deletion lib/widgets/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'about_zulip.dart';
import 'inbox.dart';
import 'login.dart';
import 'message_list.dart';
import 'navigation_bar.dart';
import 'page.dart';
import 'recent_dm_conversations.dart';
import 'store.dart';
Expand Down Expand Up @@ -177,7 +178,7 @@ class ChooseAccountPage extends StatelessWidget {
title: title,
subtitle: subtitle,
onTap: () => Navigator.push(context,
HomePage.buildRoute(accountId: accountId))));
InboxPage.buildRoute(accountId: accountId))));
}

@override
Expand Down Expand Up @@ -255,6 +256,7 @@ class HomePage extends StatelessWidget {

return Scaffold(
appBar: AppBar(title: const Text("Home")),
bottomNavigationBar: ZulipNavigationBar(selectedPage: HomePage,),
body: Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
DefaultTextStyle.merge(
Expand Down
84 changes: 57 additions & 27 deletions lib/widgets/inbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import '../model/recent_dm_conversations.dart';
import '../model/unreads.dart';
import 'icons.dart';
import 'message_list.dart';
import 'navigation_bar.dart';
import 'page.dart';
import 'sticky_header.dart';
import 'store.dart';
import 'text.dart';
import 'unread_count_badge.dart';

class InboxPage extends StatefulWidget {
class InboxPage extends StatelessWidget {
const InboxPage({super.key});

static Route<void> buildRoute({int? accountId, BuildContext? context}) {
Expand All @@ -21,10 +22,44 @@ class InboxPage extends StatefulWidget {
}

@override
State<InboxPage> createState() => _InboxPageState();
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: 0,
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text('Inbox'),
bottom: const TabBar(
tabs: <Widget>[
Tab(
text: 'Streams + Topics',
),
Tab(
text: 'All Messages',
),
],
),
),
bottomNavigationBar: ZulipNavigationBar(selectedPage: InboxPage),
body: const SafeArea(child: TabBarView(
children: <Widget>[
Inbox(),
MessageList(narrow: AllMessagesNarrow()),
],
)) ,
),
);
}
}

class _InboxPageState extends State<InboxPage> with PerAccountStoreAwareStateMixin<InboxPage> {
class Inbox extends StatefulWidget {
const Inbox({super.key});

@override
State<Inbox> createState() => _InboxState();
}

class _InboxState extends State<Inbox> with PerAccountStoreAwareStateMixin<Inbox> {
Unreads? unreadsModel;
RecentDmConversationsView? recentDmConversationsModel;

Expand Down Expand Up @@ -158,27 +193,22 @@ class _InboxPageState extends State<InboxPage> with PerAccountStoreAwareStateMix
sections.add(_StreamSectionData(streamId, countInStream, streamHasMention, topicItems));
}

return Scaffold(
appBar: AppBar(title: const Text('Inbox')),
body: SafeArea(
// Don't pad the bottom here; we want the list content to do that.
bottom: false,
child: StickyHeaderListView.builder(
itemCount: sections.length,
itemBuilder: (context, index) {
final section = sections[index];
switch (section) {
case _AllDmsSectionData():
return _AllDmsSection(
data: section,
collapsed: allDmsCollapsed,
pageState: this,
);
case _StreamSectionData(:var streamId):
final collapsed = collapsedStreamIds.contains(streamId);
return _StreamSection(data: section, collapsed: collapsed, pageState: this);
}
})));
return StickyHeaderListView.builder(
itemCount: sections.length,
itemBuilder: (context, index) {
final section = sections[index];
switch (section) {
case _AllDmsSectionData():
return _AllDmsSection(
data: section,
collapsed: allDmsCollapsed,
pageState: this,
);
case _StreamSectionData(:var streamId):
final collapsed = collapsedStreamIds.contains(streamId);
return _StreamSection(data: section, collapsed: collapsed, pageState: this);
}
});
}
}

Expand All @@ -205,7 +235,7 @@ class _StreamSectionData extends _InboxSectionData {

abstract class _HeaderItem extends StatelessWidget {
final bool collapsed;
final _InboxPageState pageState;
final _InboxState pageState;
final int count;
final bool hasMention;

Expand Down Expand Up @@ -295,7 +325,7 @@ class _AllDmsSection extends StatelessWidget {

final _AllDmsSectionData data;
final bool collapsed;
final _InboxPageState pageState;
final _InboxState pageState;

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -416,7 +446,7 @@ class _StreamSection extends StatelessWidget {

final _StreamSectionData data;
final bool collapsed;
final _InboxPageState pageState;
final _InboxState pageState;

@override
Widget build(BuildContext context) {
Expand Down
72 changes: 72 additions & 0 deletions lib/widgets/navigation_bar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';

import 'app.dart';
import 'inbox.dart';
import 'recent_dm_conversations.dart';
import 'store.dart';
import 'subscription_list.dart';

class ZulipNavigationBar extends StatelessWidget {
final int testStreamId = 7;
final Type selectedPage;
final Map<Type, int> pageToIndex = {
InboxPage: 0,
SubscriptionListPage: 1,
RecentDmConversationsPage: 2,
HomePage: 3,
};

ZulipNavigationBar({super.key, required this.selectedPage});

@override
Widget build(BuildContext context) {
final accountId = PerAccountStoreWidget.accountIdOf(context);
final selectedIndex = pageToIndex[selectedPage] ?? 0;
return NavigationBar(
selectedIndex: selectedIndex,
destinations: const [
NavigationDestination(
selectedIcon: Icon(Icons.inbox),
icon: Icon(Icons.inbox_outlined),
label: 'Inbox'),
NavigationDestination(
selectedIcon: Icon(Icons.tag),
icon: Icon(Icons.tag_outlined),
label: 'Streams'),
NavigationDestination(
selectedIcon: Icon(Icons.group),
icon: Icon(Icons.group_outlined),
label: 'Direct Messages'),
// TODO enable this when the profile page is available
// NavigationDestination(
// selectedIcon: Icon(Icons.account_circle),
// icon: Icon(Icons.account_circle_outlined),
// label: 'Profile'),
NavigationDestination(
selectedIcon: Icon(Icons.bug_report_outlined),
icon: Icon(Icons.bug_report_outlined),
label: 'Test Page'),
],
onDestinationSelected: (int index) {
if (selectedIndex == index) {
return;
}
switch (index) {
case 0:
Navigator.pushReplacement(context, InboxPage.buildRoute(context: context));
break;
case 1:
Navigator.pushReplacement(context, SubscriptionListPage.buildRoute(context: context));
break;
case 2:
Navigator.pushReplacement(context, RecentDmConversationsPage.buildRoute(context: context));
break;
case 3:
Navigator.pushReplacement(context,
HomePage.buildRoute(accountId: accountId));
break;
}
},
);
}
}
2 changes: 2 additions & 0 deletions lib/widgets/recent_dm_conversations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import '../model/unreads.dart';
import 'content.dart';
import 'icons.dart';
import 'message_list.dart';
import 'navigation_bar.dart';
import 'page.dart';
import 'store.dart';
import 'unread_count_badge.dart';
Expand Down Expand Up @@ -58,6 +59,7 @@ class _RecentDmConversationsPageState extends State<RecentDmConversationsPage> w
final sorted = model!.sorted;
return Scaffold(
appBar: AppBar(title: Text(zulipLocalizations.recentDmConversationsPageTitle)),
bottomNavigationBar: ZulipNavigationBar(selectedPage: RecentDmConversationsPage),
body: SafeArea(
// Don't pad the bottom here; we want the list content to do that.
bottom: false,
Expand Down
2 changes: 2 additions & 0 deletions lib/widgets/subscription_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '../model/narrow.dart';
import '../model/unreads.dart';
import 'icons.dart';
import 'message_list.dart';
import 'navigation_bar.dart';
import 'page.dart';
import 'store.dart';
import 'text.dart';
Expand Down Expand Up @@ -82,6 +83,7 @@ class _SubscriptionListPageState extends State<SubscriptionListPage> with PerAcc

return Scaffold(
appBar: AppBar(title: const Text("Streams")),
bottomNavigationBar: ZulipNavigationBar(selectedPage: SubscriptionListPage),
body: SafeArea(
// Don't pad the bottom here; we want the list content to do that.
bottom: false,
Expand Down
2 changes: 1 addition & 1 deletion test/widgets/inbox_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void main() {
navigatorObservers: [if (navigatorObserver != null) navigatorObserver],
home: PerAccountStoreWidget(
accountId: eg.selfAccount.id,
child: const InboxPage()))));
child: const Inbox()))));

// global store and per-account store get loaded
await tester.pumpAndSettle();
Expand Down
4 changes: 3 additions & 1 deletion test/widgets/subscription_list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ void main() {
subscription,
], unreadMsgs: unreadMsgs);
check(getItemCount()).equals(1);
check(tester.widget<Icon>(find.byType(Icon)).color)
final scaffoldBody = tester.widget<Scaffold>(find.byType(Scaffold)).body;
check(tester.widget<Icon>(find.descendant(of:
find.byWidget(scaffoldBody!), matching: find.byType(Icon))).color)
.equals(swatch.iconOnPlainBackground);
check(tester.widget<UnreadCountBadge>(find.byType(UnreadCountBadge)).backgroundColor)
.equals(swatch);
Expand Down
Loading