Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions assets/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
"@switchAccountButton": {
"description": "Label for main-menu button leading to the choose-account page."
},
"organizationsButton": "Organizations",
"@organizationsButton": {
"description": "Label for main-menu header button leading to the choose-account page."
},
"tryAnotherAccountMessage": "Your account at {url} is taking a while to load.",
"@tryAnotherAccountMessage": {
"description": "Message that appears on the loading screen after waiting for some time.",
Expand Down
6 changes: 6 additions & 0 deletions lib/generated/l10n/zulip_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ abstract class ZulipLocalizations {
/// **'Switch account'**
String get switchAccountButton;

/// Label for main-menu header button leading to the choose-account page.
///
/// In en, this message translates to:
/// **'Organizations'**
String get organizationsButton;

/// Message that appears on the loading screen after waiting for some time.
///
/// In en, this message translates to:
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_ar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
@override
String get switchAccountButton => 'تبديل الحساب';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Your account at $url is taking a while to load.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_de.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ZulipLocalizationsDe extends ZulipLocalizations {
@override
String get switchAccountButton => 'Konto wechseln';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Dein Account bei $url benötigt einige Zeit zum Laden.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
@override
String get switchAccountButton => 'Switch account';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Your account at $url is taking a while to load.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_fr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class ZulipLocalizationsFr extends ZulipLocalizations {
@override
String get switchAccountButton => 'Changer de compte';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Votre compte à $url prend du temps à se charger.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_it.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ZulipLocalizationsIt extends ZulipLocalizations {
@override
String get switchAccountButton => 'Cambia account';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Il caricamento dell\'account su $url sta richiedendo un po\' di tempo.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_ja.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
@override
String get switchAccountButton => 'アカウントを切り替える';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return '$url のアカウントの読み込みに時間がかかっています。';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_nb.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
@override
String get switchAccountButton => 'Switch account';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Your account at $url is taking a while to load.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_pl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
@override
String get switchAccountButton => 'Przełącz konto';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Twoje konto na $url wymaga jeszcze chwili na załadowanie.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_ru.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
@override
String get switchAccountButton => 'Сменить учетную запись';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Ваша учетная запись на $url загружается медленно.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_sk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
@override
String get switchAccountButton => 'Zmeniť účet';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Načítavanie vášho konta na adrese $url chvílu trvá.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_sl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class ZulipLocalizationsSl extends ZulipLocalizations {
@override
String get switchAccountButton => 'Preklopi račun';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Nalaganje vašega računa iz $url traja dlje kot običajno.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_uk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ZulipLocalizationsUk extends ZulipLocalizations {
@override
String get switchAccountButton => 'Змінити обліковий запис';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Ваш обліковий запис на $url завантажується деякий час.';
Expand Down
3 changes: 3 additions & 0 deletions lib/generated/l10n/zulip_localizations_zh.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ZulipLocalizationsZh extends ZulipLocalizations {
@override
String get switchAccountButton => 'Switch account';

@override
String get organizationsButton => 'Organizations';

@override
String tryAnotherAccountMessage(Object url) {
return 'Your account at $url is taking a while to load.';
Expand Down
12 changes: 9 additions & 3 deletions lib/model/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,15 @@ abstract class PerAccountStoreBase {
/// Always equal to `account.realmUrl` and `connection.realmUrl`.
Uri get realmUrl => connection.realmUrl;

String? get realmName => account.realmName;

Uri? get realmIcon => account.realmIcon;
// The `account` is populated with the `realmName` before
// PerAccountStore is created, so this should never be null.
// See `UpdateMachine.load`.
String get realmName => account.realmName!;

// The `account` is populated with the `realmIcon` before
// PerAccountStore is created, so this should never be null.
// See `UpdateMachine.load`.
Uri get realmIcon => account.realmIcon!;
Comment on lines -426 to +434
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

store: Make realmName and realmIcon getters non-null

Commit-message nit: "non-nullable", right? Also the commit message repeats reasoning that's already clear from added code comments, so we can remove it from the commit message.


/// Resolve [reference] as a URL relative to [realmUrl].
///
Expand Down
116 changes: 96 additions & 20 deletions lib/widgets/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'app.dart';
import 'app_bar.dart';
import 'button.dart';
import 'color.dart';
import 'content.dart';
import 'icons.dart';
import 'inbox.dart';
import 'inset_shadow.dart';
Expand Down Expand Up @@ -307,29 +308,104 @@ void _showMainMenu(BuildContext context, {
builder: (BuildContext _) {
return PerAccountStoreWidget(
accountId: accountId,
child: SafeArea(
minimum: const EdgeInsets.only(bottom: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Flexible(child: InsetShadowBox(
top: 8, bottom: 8,
color: designVariables.bgBotBar,
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
child: Column(children: menuItems)))),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: AnimatedScaleOnTap(
scaleEnd: 0.95,
duration: Duration(milliseconds: 100),
child: BottomSheetDismissButton(
style: BottomSheetDismissButtonStyle.close))),
])));
child: _MainMenu(menuItems: menuItems));
});
}

/// The main-menu sheet.
///
/// Figma link:
/// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=11367-20647&t=lSnHudU6l7NWx0Fa-0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This URL is taking me to the "Read receipts" sheet; do you reproduce? (Could be a Figma issue)

class _MainMenu extends StatelessWidget {
const _MainMenu({
required this.menuItems,
});

final List<Widget> menuItems;

@override
Widget build(BuildContext context) {
final designVariables = DesignVariables.of(context);

return SafeArea(
minimum: const EdgeInsets.only(bottom: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
_MainMenuHeader(),
Flexible(child: InsetShadowBox(
top: 8, bottom: 8,
color: designVariables.bgBotBar,
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
child: Column(children: menuItems)))),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: AnimatedScaleOnTap(
scaleEnd: 0.95,
duration: Duration(milliseconds: 100),
child: BottomSheetDismissButton(
style: BottomSheetDismissButtonStyle.close))),
]));
}
}

class _MainMenuHeader extends StatelessWidget {
const _MainMenuHeader();

@override
Widget build(BuildContext context) {
final zulipLocalizations = ZulipLocalizations.of(context);
final designVariables = DesignVariables.of(context);
final store = PerAccountStoreWidget.of(context);

final realmIconUrl = store.tryResolveUrl(store.realmIcon.toString());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of this line seems like code we'd rather not need to recite whenever we need the realm icon—should be possible to encapsulate it in PerAccountStore (maybe even Account?) where we have access to the realm URL.


return Padding(
padding: const EdgeInsets.only(top: 6),
child: Row(children: [
Expanded(child: Padding(
padding: const EdgeInsets.fromLTRB(12, 6, 4, 6),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use EdgeInsetsDirectional.fromSTEB here, for RTL support.

child: Row(spacing: 8, children: [
AvatarShape(
size: 28,
borderRadius: 4,
child: realmIconUrl != null
? RealmContentNetworkImage(realmIconUrl)
: const SizedBox.shrink()),
Comment on lines +375 to +376
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the icon URL doesn't parse for whatever reason, and also when it does but the network request fails, how about we show an empty rounded square colored by DesignVariables.avatarPlaceholderBg? Maybe we could have a reusable RealmIconImage widget that takes care of that, much like AvatarImage takes care of choosing a placeholder (which it'll do in more cases with #1558).

Expanded(child: Text(store.realmName,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: designVariables.title,
fontSize: 20,
height: 24 / 20,
).merge(weightVariableTextStyle(context, wght: 600)))),
]))),
AnimatedScaleOnTap(
duration: const Duration(milliseconds: 100),
scaleEnd: 0.95,
child: TextButton(
onPressed: () {
Navigator.pop(context); // Close the main menu.
Navigator.of(context).push(
MaterialWidgetRoute(page: const ChooseAccountPage()));
},
style: TextButton.styleFrom(
padding: const EdgeInsets.fromLTRB(8, 7, 14, 7),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Same comment about EdgeInsetsDirectional.fromSTEB.)

foregroundColor: designVariables.icon,
backgroundColor: Colors.transparent,
overlayColor: Colors.transparent,
splashFactory: NoSplash.splashFactory,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This border radius shouldn't make a difference, with a transparent background/overlay/etc., right? I see it in the Figma, but it looks accidental there, too; I think we can leave it out without comment.

tapTargetSize: MaterialTapTargetSize.shrinkWrap),
child: Text(zulipLocalizations.organizationsButton,
style: const TextStyle(fontSize: 19, height: 26 / 19)
.merge(weightVariableTextStyle(context, wght: 600))))),
]));
}
}

abstract class _MenuButton extends StatelessWidget {
const _MenuButton();

Expand Down
16 changes: 16 additions & 0 deletions test/widgets/home_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import '../flutter_checks.dart';
import '../model/binding.dart';
import '../model/store_checks.dart';
import '../model/test_store.dart';
import '../test_images.dart';
import '../test_navigation.dart';
import 'checks.dart';
import 'test_app.dart';
Expand Down Expand Up @@ -225,6 +226,7 @@ void main () {
}

testWidgets('navigation states reflect on navigation bar menu buttons', (tester) async {
prepareBoringImageHttpClient();
await prepare(tester);

await tapOpenMenuAndAwait(tester);
Expand All @@ -238,9 +240,11 @@ void main () {
await tapOpenMenuAndAwait(tester);
checkIconNotSelected(tester, inboxMenuIconFinder);
checkIconSelected(tester, channelsMenuIconFinder);
debugNetworkImageHttpClientProvider = null;
});

testWidgets('navigation bar menu buttons control navigation states', (tester) async {
prepareBoringImageHttpClient();
await prepare(tester);

await tapOpenMenuAndAwait(tester);
Expand All @@ -256,21 +260,27 @@ void main () {
await tapOpenMenuAndAwait(tester);
checkIconNotSelected(tester, inboxMenuIconFinder);
checkIconSelected(tester, channelsMenuIconFinder);
debugNetworkImageHttpClientProvider = null;
});

testWidgets('navigation bar menu buttons dismiss the menu', (tester) async {
prepareBoringImageHttpClient();
await prepare(tester);
await tapOpenMenuAndAwait(tester);
await tapButtonAndAwaitTransition(tester, channelsMenuIconFinder);
debugNetworkImageHttpClientProvider = null;
});

testWidgets('close button dismisses the menu', (tester) async {
prepareBoringImageHttpClient();
await prepare(tester);
await tapOpenMenuAndAwait(tester);
await tapButtonAndAwaitTransition(tester, find.text('Close'));
debugNetworkImageHttpClientProvider = null;
});

testWidgets('menu buttons dismiss the menu', (tester) async {
prepareBoringImageHttpClient();
addTearDown(testBinding.reset);
topRoute = null;
previousTopRoute = null;
Expand All @@ -297,21 +307,27 @@ void main () {
await tester.pump((topBeforePop as TransitionRoute).reverseTransitionDuration);

check(find.byType(BottomSheet)).findsNothing();
debugNetworkImageHttpClientProvider = null;
});

testWidgets('_MyProfileButton', (tester) async {
prepareBoringImageHttpClient();
await prepare(tester);
await tapOpenMenuAndAwait(tester);
await tapButtonAndAwaitTransition(tester, find.text('My profile'));
check(find.byType(ProfilePage)).findsOne();
check(find.text(eg.selfUser.fullName)).findsAny();
debugNetworkImageHttpClientProvider = null;
});

testWidgets('_AboutZulipButton', (tester) async {
tester.view.physicalSize = Size(1080, 1920);
prepareBoringImageHttpClient();
await prepare(tester);
await tapOpenMenuAndAwait(tester);
await tapButtonAndAwaitTransition(tester, find.byIcon(ZulipIcons.info));
check(find.byType(AboutZulipPage)).findsOne();
debugNetworkImageHttpClientProvider = null;
});
Comment on lines 323 to 331
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than thinking through what's accomplished with the numbers 1080 and 1920 and whether they're correct, I'd rather just scroll until the button is in view:

    testWidgets('_AboutZulipButton', (tester) async {
      prepareBoringImageHttpClient();
      await prepare(tester);
      await tapOpenMenuAndAwait(tester);
      await tester.ensureVisible(find.byIcon(ZulipIcons.info));
      await tapButtonAndAwaitTransition(tester, find.byIcon(ZulipIcons.info));
      check(find.byType(AboutZulipPage)).findsOne();
      debugNetworkImageHttpClientProvider = null;
    });

That's simpler and more transparent, and also adds some coverage of the scrolling logic; it seems like a win-win :)

});

Expand Down