Skip to content

Commit fbbcc13

Browse files
login: Link to doc for what "server URL" is and how to find it
- Add informative helper text below the "server URL" field in the login screen. - When tapped, the helper text opens Zulip documentation explaining server URLs and how to find them. - Improves user experience during login by providing clear guidance. - Add i18n for "What's this?" helper text. - Fixes: #109
1 parent b715ce3 commit fbbcc13

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed

assets/l10n/app_en.arb

+4
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,10 @@
289289
"@loginServerUrlInputLabel": {
290290
"description": "Input label in login page for Zulip server URL entry."
291291
},
292+
"serverUrlDocLinkLabel": "What's this?",
293+
"@serverUrlDocLinkLabel": {
294+
"description": "Link to doc to help users understand what a server URL is and how to find theirs."
295+
},
292296
"errorUnableToOpenLinkTitle": "Unable to open link",
293297
"@errorUnableToOpenLinkTitle": {
294298
"description": "Error title when a link fails to open."

lib/widgets/login.dart

+11-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import '../model/store.dart';
99
import 'app.dart';
1010
import 'dialog.dart';
1111
import 'input.dart';
12+
import 'launch_url.dart';
1213
import 'page.dart';
1314
import 'store.dart';
1415

@@ -101,6 +102,8 @@ class ServerUrlTextEditingController extends TextEditingController {
101102
class AddAccountPage extends StatefulWidget {
102103
const AddAccountPage({super.key});
103104

105+
static const String serverUrlHelpUrl = 'https://zulip.com/help/logging-in#find-the-zulip-log-in-url';
106+
104107
static Route<void> buildRoute() {
105108
return _LoginSequenceRoute(page: const AddAccountPage());
106109
}
@@ -207,7 +210,6 @@ class _AddAccountPageState extends State<AddAccountPage> {
207210
child: ConstrainedBox(
208211
constraints: const BoxConstraints(maxWidth: 400),
209212
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
210-
// TODO(#109) Link to doc about what a "server URL" is and how to find it
211213
// TODO(#111) Perhaps give tappable realm URL suggestions based on text typed so far
212214
TextField(
213215
controller: _controller,
@@ -223,7 +225,14 @@ class _AddAccountPageState extends State<AddAccountPage> {
223225
decoration: InputDecoration(
224226
labelText: zulipLocalizations.loginServerUrlInputLabel,
225227
errorText: errorText,
226-
helperText: kLayoutPinningHelperText,
228+
helper: GestureDetector(
229+
onTap: () {
230+
launchUrlWithoutRealm(context, Uri.parse(AddAccountPage.serverUrlHelpUrl));
231+
},
232+
child: Text(
233+
zulipLocalizations.serverUrlDocLinkLabel,
234+
style: Theme.of(context).textTheme.bodySmall!
235+
.apply(color: const HSLColor.fromAHSL(1, 200, 1, 0.4).toColor()))),
227236
hintText: 'your-org.zulipchat.com')),
228237
const SizedBox(height: 8),
229238
ElevatedButton(

test/widgets/login_test.dart

+39
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
33
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
44
import 'package:flutter_test/flutter_test.dart';
55
import 'package:http/http.dart' as http;
6+
import 'package:url_launcher/url_launcher.dart';
67
import 'package:zulip/api/route/account.dart';
78
import 'package:zulip/api/route/realm.dart';
89
import 'package:zulip/model/localizations.dart';
@@ -13,6 +14,7 @@ import '../api/fake_api.dart';
1314
import '../example_data.dart' as eg;
1415
import '../model/binding.dart';
1516
import '../stdlib_checks.dart';
17+
import 'dialog_checks.dart';
1618

1719
void main() {
1820
TestZulipBinding.ensureInitialized();
@@ -142,4 +144,41 @@ void main() {
142144
// TODO test handling failure in fetchApiKey request
143145
// TODO test _inProgress logic
144146
});
147+
148+
group('Server URL Helper Text', () {
149+
Future<void> prepareAddAccountPage(WidgetTester tester) async {
150+
await tester.pumpWidget(const MaterialApp(
151+
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
152+
supportedLocales: ZulipLocalizations.supportedLocales,
153+
home: AddAccountPage(),
154+
));
155+
}
156+
157+
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
158+
159+
Future<Finder> findHelperText(WidgetTester tester) async {
160+
return find.text(zulipLocalizations.serverUrlDocLinkLabel);
161+
}
162+
163+
testWidgets('launches URL when helper text is tapped', (tester) async {
164+
await prepareAddAccountPage(tester);
165+
final helper = await findHelperText(tester);
166+
await tester.tap(helper);
167+
168+
check(testBinding.takeLaunchUrlCalls())
169+
.single.equals((url: Uri.parse(AddAccountPage.serverUrlHelpUrl), mode: LaunchMode.platformDefault));
170+
});
171+
172+
testWidgets('shows error dialog when URL fails to open', (tester) async {
173+
await prepareAddAccountPage(tester);
174+
testBinding.launchUrlResult = false;
175+
final helper = await findHelperText(tester);
176+
await tester.tap(helper);
177+
await tester.pump();
178+
179+
checkErrorDialog(tester,
180+
expectedTitle: zulipLocalizations.errorUnableToOpenLinkTitle,
181+
expectedMessage: zulipLocalizations.errorLinkCouldNotBeOpened(AddAccountPage.serverUrlHelpUrl));
182+
});
183+
});
145184
}

0 commit comments

Comments
 (0)