forked from zulip/zulip-flutter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
channel_list: Create "All channels" page
This creates the page that contains all channels list, still we need to implement subscribe/unsubscribe logic which will be introduced in the upcoming commits. Fixes parts of zulip#188
- Loading branch information
Showing
5 changed files
with
266 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart'; | ||
|
||
import '../api/model/model.dart'; | ||
import '../model/narrow.dart'; | ||
import 'app_bar.dart'; | ||
import 'icons.dart'; | ||
import 'message_list.dart'; | ||
import 'page.dart'; | ||
import 'store.dart'; | ||
import 'theme.dart'; | ||
|
||
class ChannelListPage extends StatelessWidget { | ||
const ChannelListPage({super.key}); | ||
|
||
static Route<void> buildRoute({int? accountId, BuildContext? context}) { | ||
return MaterialAccountWidgetRoute(accountId: accountId, context: context, | ||
page: const ChannelListPage()); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final store = PerAccountStoreWidget.of(context); | ||
final zulipLocalizations = ZulipLocalizations.of(context); | ||
final streams = store.streams.values.toList()..sort((a, b) { | ||
return a.name.toLowerCase().compareTo(b.name.toLowerCase()); | ||
}); | ||
return Scaffold( | ||
appBar: ZulipAppBar(title: Text(zulipLocalizations.channelListPageTitle)), | ||
body: SafeArea( | ||
// Don't pad the bottom here; we want the list content to do that. | ||
bottom: false, | ||
child: streams.isEmpty ? const _NoChannelsItem() : ListView.builder( | ||
itemCount: streams.length, | ||
itemBuilder: (context, index) => ChannelItem(stream: streams[index])))); | ||
} | ||
} | ||
|
||
class _NoChannelsItem extends StatelessWidget { | ||
const _NoChannelsItem(); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final zulipLocalizations = ZulipLocalizations.of(context); | ||
final designVariables = DesignVariables.of(context); | ||
|
||
return Center( | ||
child: Padding( | ||
padding: const EdgeInsets.all(10), | ||
child: Text(zulipLocalizations.noChannelsFound, | ||
textAlign: TextAlign.center, | ||
style: TextStyle( | ||
// TODO(design) check if this is the right variable | ||
color: designVariables.subscriptionListHeaderText, | ||
fontSize: 18, | ||
height: (20 / 18), | ||
)))); | ||
} | ||
} | ||
|
||
@visibleForTesting | ||
class ChannelItem extends StatelessWidget { | ||
const ChannelItem({super.key, required this.stream}); | ||
|
||
final ZulipStream stream; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final designVariables = DesignVariables.of(context); | ||
return Material( | ||
color: designVariables.background, | ||
child: InkWell( | ||
onTap: () => Navigator.push(context, MessageListPage.buildRoute(context: context, | ||
narrow: ChannelNarrow(stream.streamId))), | ||
child: Padding( | ||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), | ||
child: Row(children: [ | ||
Icon(size: 16, iconDataForStream(stream)), | ||
const SizedBox(width: 8), | ||
Expanded(child: Column( | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||
children: [ | ||
Text(stream.name, | ||
style: TextStyle( | ||
fontSize: 18, | ||
height: (20 / 18), | ||
// TODO(design) check if this is the right variable | ||
color: designVariables.labelMenuButton), | ||
maxLines: 1, | ||
overflow: TextOverflow.ellipsis), | ||
// TODO(#488) parse and show `stream.renderedDescription` with content widget | ||
if (stream.description.isNotEmpty) Text( | ||
stream.description, | ||
style: TextStyle( | ||
fontSize: 12, | ||
// TODO(design) check if this is the right variable | ||
color: designVariables.labelMenuButton.withOpacity(0.75)), | ||
maxLines: 1, | ||
overflow: TextOverflow.ellipsis), | ||
])), | ||
])))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import 'package:checks/checks.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:zulip/api/model/model.dart'; | ||
import 'package:zulip/widgets/channel_list.dart'; | ||
|
||
import '../model/binding.dart'; | ||
import '../example_data.dart' as eg; | ||
import 'test_app.dart'; | ||
|
||
void main() { | ||
TestZulipBinding.ensureInitialized(); | ||
|
||
Future<void> setupChannelListPage(WidgetTester tester, { | ||
required List<ZulipStream> streams, | ||
required List<Subscription> subscriptions | ||
}) async { | ||
addTearDown(testBinding.reset); | ||
final initialSnapshot = eg.initialSnapshot( | ||
subscriptions: subscriptions, | ||
streams: streams, | ||
); | ||
await testBinding.globalStore.add(eg.selfAccount, initialSnapshot); | ||
|
||
await tester.pumpWidget(TestZulipApp(accountId: eg.selfAccount.id, child: const ChannelListPage())); | ||
|
||
// global store, per-account store | ||
await tester.pumpAndSettle(); | ||
} | ||
|
||
void checkItemCount(int expectedCount) { | ||
check(find.byType(ChannelItem).evaluate().length).equals(expectedCount); | ||
} | ||
|
||
testWidgets('smoke', (tester) async { | ||
await setupChannelListPage(tester, streams: [], subscriptions: []); | ||
checkItemCount(0); | ||
check(find.text('There are no channels you can view in this organization.').evaluate()).single; | ||
}); | ||
|
||
testWidgets('basic list', (tester) async { | ||
final streams = List.generate(3, (index) => eg.stream()); | ||
await setupChannelListPage(tester, streams: streams, subscriptions: []); | ||
checkItemCount(3); | ||
}); | ||
|
||
group('list ordering', () { | ||
Iterable<String> listedStreamNames(WidgetTester tester) => tester | ||
.widgetList<ChannelItem>(find.byType(ChannelItem)) | ||
.map((e) => e.stream.name); | ||
|
||
List<ZulipStream> streamsFromNames(List<String> names) { | ||
return names.map((name) => eg.stream(name: name)).toList(); | ||
} | ||
|
||
testWidgets('is alphabetically case-insensitive', (tester) async { | ||
final streams = streamsFromNames(['b', 'C', 'A']); | ||
await setupChannelListPage(tester, streams: streams, subscriptions: []); | ||
|
||
check(listedStreamNames(tester)).deepEquals(['A', 'b', 'C']); | ||
}); | ||
|
||
testWidgets('is insensitive of user subscription', (tester) async { | ||
final streams = streamsFromNames(['b', 'c', 'a']); | ||
await setupChannelListPage(tester, streams: streams, | ||
subscriptions: [eg.subscription(streams[0])]); | ||
|
||
check(listedStreamNames(tester)).deepEquals(['a', 'b', 'c']); | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters