Skip to content

Commit 82ed4bd

Browse files
committed
profile: display user's local time
1 parent 2ca6e0a commit 82ed4bd

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,10 +1,14 @@
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/initial_snapshot.dart';
69
import '../api/model/model.dart';
710
import '../generated/l10n/zulip_localizations.dart';
11+
import '../model/binding.dart';
812
import '../model/content.dart';
913
import '../model/narrow.dart';
1014
import '../model/store.dart';
@@ -90,7 +94,11 @@ class ProfilePage extends StatelessWidget {
9094
style: _TextStyles.primaryFieldText),
9195
// TODO(#197) render user status
9296
// TODO(#196) render active status
93-
// TODO(#292) render user local time
97+
DefaultTextStyle.merge(
98+
textAlign: TextAlign.center,
99+
style: _TextStyles.primaryFieldText,
100+
child: UserLocalTimeText(user: user)
101+
),
94102

95103
_ProfileDataTable(profileData: user.profileData),
96104
const SizedBox(height: 16),
@@ -307,3 +315,66 @@ class _UserWidget extends StatelessWidget {
307315
])));
308316
}
309317
}
318+
319+
/// The text of current time in [user]'s timezone.
320+
class UserLocalTimeText extends StatefulWidget {
321+
const UserLocalTimeText({
322+
super.key,
323+
required this.user,
324+
});
325+
326+
final User user;
327+
328+
/// Initialize the timezone database used to know time difference from a timezone string.
329+
///
330+
/// Usually, database initialization is done using `initializeTimeZones`, but it takes >100ms and not asynchronous.
331+
/// So, we initialize database from the assets file copied from timezone library.
332+
/// This file is checked up-to-date in `test/widgets/profile_test.dart`.
333+
static Future<void> initializeTimezonesUsingAssets() async {
334+
final blob = Uint8List.sublistView(await rootBundle.load('assets/timezone/latest_all.tzf'));
335+
tz.initializeDatabase(blob);
336+
}
337+
338+
@override
339+
State<UserLocalTimeText> createState() => _UserLocalTimeTextState();
340+
}
341+
342+
class _UserLocalTimeTextState extends State<UserLocalTimeText> {
343+
late final Timer _timer;
344+
final StreamController<DateTime> _streamController = StreamController();
345+
Stream<DateTime> get _stream => _streamController.stream;
346+
347+
@override
348+
void initState() {
349+
_streamController.add(ZulipBinding.instance.now());
350+
_timer = Timer.periodic(const Duration(seconds: 1), (_) { _streamController.add(ZulipBinding.instance.now()); });
351+
super.initState();
352+
}
353+
354+
@override
355+
void dispose() {
356+
_timer.cancel();
357+
super.dispose();
358+
}
359+
360+
Stream<String> _getDisplayLocalTimeFor(User user, ZulipLocalizations zulipLocalizations) async* {
361+
if (!tz.timeZoneDatabase.isInitialized) await UserLocalTimeText.initializeTimezonesUsingAssets();
362+
363+
await for (final DateTime time in _stream) {
364+
final location = tz.getLocation(user.timezone);
365+
final localTime = tz.TZDateTime.from(time, location);
366+
yield zulipLocalizations.userLocalTime(localTime);
367+
}
368+
}
369+
370+
@override
371+
Widget build(BuildContext context) {
372+
return StreamBuilder(
373+
stream: _getDisplayLocalTimeFor(widget.user, ZulipLocalizations.of(context)),
374+
builder: (context, snapshot) {
375+
if (snapshot.hasError) Error.throwWithStackTrace(snapshot.error!, snapshot.stackTrace!);
376+
return Text(snapshot.data ?? '');
377+
}
378+
);
379+
}
380+
}

0 commit comments

Comments
 (0)