Skip to content

startup: Add beta-complete dialog #1797

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

Open
wants to merge 3 commits into
base: beta-prelaunch
Choose a base branch
from
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
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ jobs:
# so that Flutter knows its version and sees the constraint in our
# pubspec is satisfied. It's uncommon for flutter/flutter to go
# more than 100 commits between tags. Fetch 1000 for good measure.
# TODO(upstream): Around 2025-05, Flutter upstream stopped making
# tags within the main/master branch. Get that fixed:
# https://github.com/zulip/zulip-flutter/issues/1710
# Pending that, fetch more than 1000 commits.
run: |
git clone --depth=1000 -b main https://github.com/flutter/flutter ~/flutter
git clone --depth=3000 -b main https://github.com/flutter/flutter ~/flutter
cd ~/flutter
git --git-dir ~/flutter/.git checkout ee089d09b21ec3ccc20d179c5be100d2a9d9f866
TZ=UTC git --git-dir ~/flutter/.git log -1 --format='%h | %ci | %s' --date=iso8601-local
echo ~/flutter/bin >> "$GITHUB_PATH"

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/update-translations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ jobs:
# so that Flutter knows its version and sees the constraint in our
# pubspec is satisfied. It's uncommon for flutter/flutter to go
# more than 100 commits between tags. Fetch 1000 for good measure.
# TODO(upstream): See ci.yml for why we fetch more than 1000.
run: |
git clone --depth=1000 -b main https://github.com/flutter/flutter ~/flutter
git clone --depth=3000 -b main https://github.com/flutter/flutter ~/flutter
TZ=UTC git --git-dir ~/flutter/.git log -1 --format='%h | %ci | %s' --date=iso8601-local
echo ~/flutter/bin >> "$GITHUB_PATH"

Expand Down
4 changes: 4 additions & 0 deletions lib/widgets/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);

// On every startup is fine; the goal is to be assertive but stop short of a
// rug-pull where we just disable all the app's features.
BetaCompleteDialog.show();
}

@override
Expand Down
88 changes: 88 additions & 0 deletions lib/widgets/dialog.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../generated/l10n/zulip_localizations.dart';
import 'actions.dart';
import 'app.dart';

Widget _dialogActionText(String text) {
return Text(
Expand Down Expand Up @@ -112,3 +116,87 @@ DialogStatus<bool> showSuggestedActionDialog({
]));
return DialogStatus(future);
}

bool debugDisableBetaCompleteDialog = false;

/// A brief dialog box saying that this beta channel has ended,
/// offering a way to get the app from prod.
///
/// Shown on every startup.
class BetaCompleteDialog extends StatelessWidget {
const BetaCompleteDialog._();

static void show() async {
if (debugDisableBetaCompleteDialog) return;

final navigator = await ZulipApp.navigator;
final context = navigator.context;
assert(context.mounted);
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

switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.iOS:
break;
case TargetPlatform.macOS:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
// Do nothing on these unsupported platforms.
return;
}

unawaited(showDialog(
context: context,
builder: (BuildContext context) => BetaCompleteDialog._()));
}

Widget _linkButton(BuildContext context, {
required String url,
required String label,
}) {
return TextButton(
onPressed: () {
Navigator.pop(context);
PlatformActions.launchUrl(context,
Uri.parse(url));
},
child: _dialogActionText(label));
}

@override
Widget build(BuildContext context) {
final message = 'Thanks for being a beta tester of the new Zulip app!'
' This app became the main Zulip mobile app in June 2025,'
' and this beta version is no longer maintained.'
' We recommend uninstalling this beta after switching'
' to the main Zulip app, in order to get the latest features'
' and bug fixes.';

return AlertDialog(
title: Text('Time to switch to the new app'),
content: SingleChildScrollView(child: Text(message)),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: _dialogActionText('Got it')),
...(switch (defaultTargetPlatform) {
TargetPlatform.android => [
_linkButton(context,
url: 'https://github.com/zulip/zulip-flutter/releases/latest',
label: 'Download official APKs (less common)'),
_linkButton(context,
url: 'https://play.google.com/store/apps/details?id=com.zulipmobile',
label: 'Open Google Play Store'),
],
TargetPlatform.iOS => [
_linkButton(context,
url: 'https://apps.apple.com/app/zulip/id1203036395',
label: 'Open App Store'),
],
TargetPlatform.macOS || TargetPlatform.fuchsia
|| TargetPlatform.linux || TargetPlatform.windows => throw UnimplementedError(),
}),
]);
}
}
3 changes: 3 additions & 0 deletions test/notifications/open_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:zulip/model/narrow.dart';
import 'package:zulip/notifications/open.dart';
import 'package:zulip/notifications/receive.dart';
import 'package:zulip/widgets/app.dart';
import 'package:zulip/widgets/dialog.dart';
import 'package:zulip/widgets/home.dart';
import 'package:zulip/widgets/message_list.dart';
import 'package:zulip/widgets/page.dart';
Expand Down Expand Up @@ -76,6 +77,8 @@ void main() {
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;

Future<void> init({bool addSelfAccount = true}) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
if (addSelfAccount) {
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
}
Expand Down
13 changes: 13 additions & 0 deletions test/widgets/app_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:zulip/log.dart';
import 'package:zulip/model/actions.dart';
import 'package:zulip/model/database.dart';
import 'package:zulip/widgets/app.dart';
import 'package:zulip/widgets/dialog.dart';
import 'package:zulip/widgets/home.dart';
import 'package:zulip/widgets/page.dart';

Expand All @@ -27,6 +28,8 @@ void main() {
late List<Route<dynamic>> pushedRoutes = [];

Future<void> prepare(WidgetTester tester) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
addTearDown(testBinding.reset);

pushedRoutes = [];
Expand Down Expand Up @@ -64,6 +67,8 @@ void main() {
late List<Route<void>> poppedRoutes;

Future<void> prepare(WidgetTester tester) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
addTearDown(testBinding.reset);

pushedRoutes = [];
Expand Down Expand Up @@ -279,6 +284,8 @@ void main() {
});

testWidgets('choosing an account clears the navigator stack', (tester) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
addTearDown(testBinding.reset);
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
await testBinding.globalStore.add(eg.otherAccount, eg.initialSnapshot());
Expand Down Expand Up @@ -391,6 +398,8 @@ void main() {
});

testWidgets('reportErrorToUserBriefly with details', (tester) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
addTearDown(testBinding.reset);
await tester.pumpWidget(const ZulipApp());
const message = 'test error message';
Expand Down Expand Up @@ -418,6 +427,8 @@ void main() {
});

Future<void> prepareSnackBarWithDetails(WidgetTester tester, String message, String details) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
addTearDown(testBinding.reset);
await tester.pumpWidget(const ZulipApp());
await tester.pump();
Expand Down Expand Up @@ -484,6 +495,8 @@ void main() {
});

testWidgets('reportErrorToUserModally', (tester) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
addTearDown(testBinding.reset);
await tester.pumpWidget(const ZulipApp());
const title = 'test title';
Expand Down
11 changes: 11 additions & 0 deletions test/widgets/home_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:zulip/model/store.dart';
import 'package:zulip/widgets/about_zulip.dart';
import 'package:zulip/widgets/app.dart';
import 'package:zulip/widgets/app_bar.dart';
import 'package:zulip/widgets/dialog.dart';
import 'package:zulip/widgets/home.dart';
import 'package:zulip/widgets/icons.dart';
import 'package:zulip/widgets/inbox.dart';
Expand Down Expand Up @@ -48,6 +49,8 @@ void main () {
..onPopped = ((route, prevRoute) => lastPoppedRoute = route);

Future<void> prepare(WidgetTester tester) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
addTearDown(testBinding.reset);
topRoute = null;
previousTopRoute = null;
Expand Down Expand Up @@ -272,6 +275,8 @@ void main () {
});

testWidgets('menu buttons dismiss the menu', (tester) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
addTearDown(testBinding.reset);
topRoute = null;
previousTopRoute = null;
Expand Down Expand Up @@ -328,6 +333,8 @@ void main () {
}

Future<void> prepare(WidgetTester tester) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
addTearDown(testBinding.reset);
topRoute = null;
previousTopRoute = null;
Expand Down Expand Up @@ -521,6 +528,8 @@ void main () {
});

testWidgets('logging out while still loading', (tester) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
// Regression test for: https://github.com/zulip/zulip-flutter/issues/1219
addTearDown(testBinding.reset);
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
Expand All @@ -537,6 +546,8 @@ void main () {
});

testWidgets('logging out after fully loaded', (tester) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
// Regression test for: https://github.com/zulip/zulip-flutter/issues/1219
addTearDown(testBinding.reset);
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
Expand Down
3 changes: 3 additions & 0 deletions test/widgets/login_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:zulip/model/binding.dart';
import 'package:zulip/model/database.dart';
import 'package:zulip/model/localizations.dart';
import 'package:zulip/widgets/app.dart';
import 'package:zulip/widgets/dialog.dart';
import 'package:zulip/widgets/home.dart';
import 'package:zulip/widgets/login.dart';
import 'package:zulip/widgets/page.dart';
Expand Down Expand Up @@ -83,6 +84,8 @@ void main() {

Future<void> prepare(WidgetTester tester,
GetServerSettingsResult serverSettings) async {
debugDisableBetaCompleteDialog = true;
addTearDown(() => debugDisableBetaCompleteDialog = false);
addTearDown(testBinding.reset);

connection = testBinding.globalStore.apiConnection(
Expand Down