Skip to content

Commit 02943a7

Browse files
committed
profile: display user's local time
1 parent 76a1447 commit 02943a7

File tree

1 file changed

+72
-1
lines changed

1 file changed

+72
-1
lines changed

lib/widgets/profile.dart

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import 'dart:async';
12
import 'dart:convert';
23

34
import 'package:flutter/material.dart';
5+
import 'package:flutter/services.dart';
6+
import 'package:timezone/timezone.dart' as tz;
47

58
import '../api/model/model.dart';
69
import '../generated/l10n/zulip_localizations.dart';
10+
import '../model/binding.dart';
711
import '../model/content.dart';
812
import '../model/narrow.dart';
913
import 'app_bar.dart';
@@ -62,7 +66,11 @@ class ProfilePage extends StatelessWidget {
6266
style: _TextStyles.primaryFieldText),
6367
// TODO(#197) render user status
6468
// TODO(#196) render active status
65-
// TODO(#292) render user local time
69+
DefaultTextStyle.merge(
70+
textAlign: TextAlign.center,
71+
style: _TextStyles.primaryFieldText,
72+
child: UserLocalTimeText(user: user)
73+
),
6674

6775
_ProfileDataTable(profileData: user.profileData),
6876
const SizedBox(height: 16),
@@ -279,3 +287,66 @@ class _UserWidget extends StatelessWidget {
279287
])));
280288
}
281289
}
290+
291+
/// The text of current time in [user]'s timezone.
292+
class UserLocalTimeText extends StatefulWidget {
293+
const UserLocalTimeText({
294+
super.key,
295+
required this.user,
296+
});
297+
298+
final User user;
299+
300+
/// Initialize the timezone database used to know time difference from a timezone string.
301+
///
302+
/// Usually, database initialization is done using `initializeTimeZones`, but it takes >100ms and not asynchronous.
303+
/// So, we initialize database from the assets file copied from timezone library.
304+
/// This file is checked up-to-date in `test/widgets/profile_test.dart`.
305+
static Future<void> initializeTimezonesUsingAssets() async {
306+
final blob = Uint8List.sublistView(await rootBundle.load('assets/timezone/latest_all.tzf'));
307+
tz.initializeDatabase(blob);
308+
}
309+
310+
@override
311+
State<UserLocalTimeText> createState() => _UserLocalTimeTextState();
312+
}
313+
314+
class _UserLocalTimeTextState extends State<UserLocalTimeText> {
315+
late final Timer _timer;
316+
final StreamController<DateTime> _streamController = StreamController();
317+
Stream<DateTime> get _stream => _streamController.stream;
318+
319+
@override
320+
void initState() {
321+
_streamController.add(ZulipBinding.instance.now());
322+
_timer = Timer.periodic(const Duration(seconds: 1), (_) { _streamController.add(ZulipBinding.instance.now()); });
323+
super.initState();
324+
}
325+
326+
@override
327+
void dispose() {
328+
_timer.cancel();
329+
super.dispose();
330+
}
331+
332+
Stream<String> _getDisplayLocalTimeFor(User user, ZulipLocalizations zulipLocalizations) async* {
333+
if (!tz.timeZoneDatabase.isInitialized) await UserLocalTimeText.initializeTimezonesUsingAssets();
334+
335+
await for (final DateTime time in _stream) {
336+
final location = tz.getLocation(user.timezone);
337+
final localTime = tz.TZDateTime.from(time, location);
338+
yield zulipLocalizations.userLocalTime(localTime);
339+
}
340+
}
341+
342+
@override
343+
Widget build(BuildContext context) {
344+
return StreamBuilder(
345+
stream: _getDisplayLocalTimeFor(widget.user, ZulipLocalizations.of(context)),
346+
builder: (context, snapshot) {
347+
if (snapshot.hasError) Error.throwWithStackTrace(snapshot.error!, snapshot.stackTrace!);
348+
return Text(snapshot.data ?? '');
349+
}
350+
);
351+
}
352+
}

0 commit comments

Comments
 (0)