Skip to content

Add intro modals for Inbox and Combined Feed#2045

Open
khanak0509 wants to merge 4 commits intozulip:mainfrom
khanak0509:intro-modals-1856
Open

Add intro modals for Inbox and Combined Feed#2045
khanak0509 wants to merge 4 commits intozulip:mainfrom
khanak0509:intro-modals-1856

Conversation

@khanak0509
Copy link
Contributor

@khanak0509 khanak0509 commented Dec 20, 2025

Fixes: #1856
Adds one-time intro modals for inbox and combined feed screens to help new users understand what each view shows.

screenshots -
Screenshot 2025-12-19 at 12 51 27 PM
Screenshot 2025-12-19 at 12 51 44 PM

as mentioned in issue description and CZO discussion: #mobile > Intro video? intro is showing once per install of the app even if they login again . and if they reinstall the add it will visible again .

Tested on iOS emulator:
(1) Fresh install shows both modals on first visit
(2)Modals don't reappear after tapping "Got it"
(3) Modals stay dismissed across app restarts
(4) Reinstalling app shows modals again

video of testing
https://drive.google.com/file/d/1vg3yrCxwh44TSUv6730K7Lr-CPrxI1cm/view?usp=sharing

all tests passed
image

@gnprice
Copy link
Member

gnprice commented Dec 21, 2025

See the project's README:
https://github.com/zulip/zulip-flutter#submitting-a-pull-request

Before we can review this PR, it will need tests.

@khanak0509
Copy link
Contributor Author

now I have added test and also added test detection code in dialog.dart
because the in test mode inbox_test.dart create InboxPage widgets, the modal pops up , When message_list_test.dart creates message list widgets, the modal pops up , and due to this I was getting 45 test failures because it is blocking ui

but when I added test detection it solved
now all tests are passing

@chrisbobbe
Copy link
Collaborator

See the project's README:
https://github.com/zulip/zulip-flutter#submitting-a-pull-request

Before we can review this PR, it will need to be organized into clear and coherent commits, following Zulip's commit style guide.

@khanak0509 khanak0509 force-pushed the intro-modals-1856 branch 2 times, most recently from 9a9c7e3 to b319244 Compare December 24, 2025 18:11
@khanak0509
Copy link
Contributor Author

khanak0509 commented Dec 26, 2025

@gnprice @chrisbobbe now could you please review it ?

@gnprice
Copy link
Member

gnprice commented Jan 6, 2026

This still makes a number of irrelevant changes to other parts of the code.

@khanak0509 Before you ask others to spend time reviewing your work, you need to take the time to review it carefully yourself. See the discussion in our README (linked twice above) and in the Zulip contributing guide linked from there.

@khanak0509
Copy link
Contributor Author

Done with all the changes. Also, in lib/widgets/dialog.dart at line 188, I intentionally made this change and added curly brackets because I was getting ->
image

@gnprice
Copy link
Member

gnprice commented Jan 6, 2026

OK, that's clean enough to be reviewed.

This still needs tests, though. The tests need to check that the app satisfies the main point of the issue: when the user first visits the inbox or combined feed, they get the intro modal, and when they visit again, they don't.

The current version adds some test cases in test/widgets/dialog_test.dart, but these are "unit tests" of the kind that don't really get to the main point.

I gave a talk last year at Fluttercon USA about how to write good tests, including more details and examples about this point. I recommend watching that talk.

@khanak0509
Copy link
Contributor Author

Thanks for sharing this. I watched it and understood the difference between unit tests and integration tests.
I have tried to implement an integration test to check whether the app satisfies the main requirement of the issue when a user visits the inbox or combined feed for the first time, they see the intro modal, and when they visit again, they don’t, as you mentioned.
Could you please let me know whether I’m on the right track or not?

@gnprice
Copy link
Member

gnprice commented Jan 15, 2026

Great, glad that was helpful. This revision is closer: it effectively tests most of the behavior of showInboxIntroModal and showCombinedFeedIntroModal.

From the user's perspective, though, they don't call a function showInboxIntroModal; they visit the inbox page. So what we'd really like is a test that simulates that.

In particular, this version would still pass if the call to showInboxIntroModal were to get accidentally deleted from the InboxPage code. The tests we'd like to have here would correctly fail if that happened.

The other remaining piece for making these into end-to-end, user-oriented tests is that the tests should check the titles on the dialogs. As is, the tests would still pass if the dialogs accidentally had their titles swapped.

@khanak0509
Copy link
Contributor Author

OK, now I understood! . In the inbox test, I should navigate to HomePage() instead of calling showInboxIntroModal() directly, and same for combined feed navigate to MsgListPage() instead of calling the function. Based on this, I've made the changes. now is it fine ?

@gnprice gnprice added the maintainer review PR ready for review by Zulip maintainers label Jan 22, 2026
@gnprice
Copy link
Member

gnprice commented Feb 12, 2026

Yep! That's the right way for the tests to work.

There are other comments to make about more specific aspects of these tests, but I'll leave that for maintainer review.

@sm-sayedi
Copy link
Collaborator

@khanak0509 Can you please resolve the conflicts?

Copy link
Collaborator

@sm-sayedi sm-sayedi left a comment

Choose a reason for hiding this comment

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

Thanks @khanak0509 for working on this. I've gone through all the non-test changes. A few comments below.

For how to arrange changes in coherent commits, please read through the commit-discipline carefully.

For the next revision, please make sure to keep the branch up-to-date with the main.

inboxIntroModalShown(GlobalSettingType.internal, false),
combinedFeedIntroModalShown(GlobalSettingType.internal, false),


Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove the additional empty lines.

/// A pseudo-setting recording whether the user has been shown the
/// welcome dialog for upgrading from the legacy app.
upgradeWelcomeDialogShown(GlobalSettingType.internal, false),
inboxIntroModalShown(GlobalSettingType.internal, false),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's add dartdocs for the two new values. Also, separate the new values by adding blank lines before each one.

Comment on lines +188 to +190
if (!context.mounted) {
return; // TODO(linter): this is impossible as there's no actual async gap, but the use_build_context_synchronously lint doesn't see that
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This change shouldn't be necessary once you're updated with main.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Bump on this one! There is no need for the curly braces.

Comment on lines +166 to +167
matching: find.byType(SingleChildScrollView),)).findsOne();
}, variant: TargetPlatformVariant.all(),);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Unnecessary change — the commas are redundant.

}
}

class IntroModal extends StatelessWidget {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's rename this class to IntroDialog to match other classes/methods of the same file.

}
}

Future<void> showInboxIntroModal(BuildContext context) async {
Copy link
Collaborator

@sm-sayedi sm-sayedi Feb 18, 2026

Choose a reason for hiding this comment

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

Let's move this function and the other one for the combined feed into the IntroModal class. Even better to combine it into one function, similar to UpgradeWelcomeDialog.maybeShow. You can borrow some logic from that function, especially using the navigator BuildContext instead of the local one. This way, there is no need for the code to be wrapped inside addPostFrameCallback.

You can define an enum representing the different kinds of intro modals (dialogs) and pass it to this new function, and show different dialogs based on the value of that enum.

The enum can have the following definition, for now:

enum IntroDialogDestination { inbox, combinedFeed }

recentDmConversationsModel?.removeListener(_modelChanged);
recentDmConversationsModel = newStore.recentDmConversationsView
..addListener(_modelChanged);
showInboxIntroModal(context);
Copy link
Collaborator

@sm-sayedi sm-sayedi Feb 18, 2026

Choose a reason for hiding this comment

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

With the suggested changes in #2045 (comment), you can call this function inside initState, so that it's called only once per inbox page visit. onNewStore which is called by didChangeDependencies, can be called multiple times in the widget lifecycle, which is overkill for our case now.

@khanak0509
Copy link
Contributor Author

done with the requested changes .

Copy link
Collaborator

@sm-sayedi sm-sayedi left a comment

Choose a reason for hiding this comment

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

Thanks. A few comments below. I still haven't read the tests.

When submitting a (new) revision, make sure the CI is not failing. Also, I think you still haven't read commit-discipline because the commits are not coherent. When you submit a new revision, those changes typically go in the same commits as before. You just need to amend the previous commits. You can check: https://zulip.readthedocs.io/en/latest/git/fixing-commits.html.

Comment on lines +188 to +190
if (!context.mounted) {
return; // TODO(linter): this is impossible as there's no actual async gap, but the use_build_context_synchronously lint doesn't see that
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Bump on this one! There is no need for the curly braces.

Comment on lines +262 to +264
if (!context.mounted) {
return; // TODO(linter): this is impossible as there's no actual async gap, but the use_build_context_synchronously lint doesn't see that
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Make this one-liner, similar to how it is in UpgradeWelcomeDialog.maybeShow.

return; // TODO(linter): this is impossible as there's no actual async gap, but the use_build_context_synchronously lint doesn't see that
}

final globalSettings = GlobalStoreWidget.settingsOf(context);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Move this to where it is first used — just before if (globalSettings.getBool(setting)) return;.

builder: (context) => IntroDialog._(title: title, message: message),
);

await future;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
await future;
await future; // Wait for the dialog to be dismissed.

nit: Just like in UpgradeWelcomeDialog.maybeShow.

final String title;
final String message;

static void maybeShow(IntroDialogDestination destination) async {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's rename this to maybeShowIn. It will read more descriptively in its call sites.

Comment on lines +274 to +275
case IntroDialogDestination.inbox:
setting = BoolGlobalSetting.inboxIntroModalShown;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
case IntroDialogDestination.inbox:
setting = BoolGlobalSetting.inboxIntroModalShown;
case .inbox:
setting = .inboxIntroModalShown;

And the one case below. This is a new Dart feature called "dot shorthands": https://dart.dev/language/dot-shorthands

@override
void initState() {
super.initState();
IntroDialog.maybeShow(IntroDialogDestination.inbox);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
IntroDialog.maybeShow(IntroDialogDestination.inbox);
IntroDialog.maybeShow(.inbox);

Use dot shorthands here and in the other call site for the combined feed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer review PR ready for review by Zulip maintainers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add intro modals for main screens

4 participants