Skip to content

Commit f39ce58

Browse files
authored
chore: reduce repaints (#111)
1 parent 20c835c commit f39ce58

File tree

7 files changed

+139
-141
lines changed

7 files changed

+139
-141
lines changed

lib/chat_master/view/chat_master_detail_page.dart

Lines changed: 18 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import '../../chat_room/common/view/chat_room_page.dart';
1010
import '../../common/chat_manager.dart';
1111
import '../../common/platforms.dart';
1212
import '../../common/view/build_context_x.dart';
13-
import '../../common/view/common_widgets.dart';
1413
import '../../common/view/ui_constants.dart';
1514
import '../../encryption/encryption_manager.dart';
1615
import '../../encryption/view/key_verification_dialog.dart';
@@ -20,22 +19,9 @@ import 'chat_master_panel.dart';
2019

2120
final GlobalKey<ScaffoldState> masterScaffoldKey = GlobalKey();
2221

23-
class ChatMasterDetailPage extends StatefulWidget
24-
with WatchItStatefulWidgetMixin {
22+
class ChatMasterDetailPage extends StatelessWidget with WatchItMixin {
2523
const ChatMasterDetailPage({super.key});
2624

27-
@override
28-
State<ChatMasterDetailPage> createState() => _ChatMasterDetailPageState();
29-
}
30-
31-
class _ChatMasterDetailPageState extends State<ChatMasterDetailPage> {
32-
late final Future<void> _initAfterEncryptionSetup;
33-
@override
34-
void initState() {
35-
super.initState();
36-
_initAfterEncryptionSetup = di<ChatManager>().initAfterEncryptionSetup();
37-
}
38-
3925
@override
4026
Widget build(BuildContext context) {
4127
registerStreamHandler(
@@ -99,32 +85,23 @@ class _ChatMasterDetailPageState extends State<ChatMasterDetailPage> {
9985
endDrawer: Platforms.isMacOS
10086
? const Drawer(child: ChatMasterSidePanel())
10187
: null,
102-
body: FutureBuilder(
103-
future: _initAfterEncryptionSetup,
104-
builder: (context, snapshot) =>
105-
snapshot.connectionState == ConnectionState.done
106-
? Row(
107-
children: [
108-
if (context.showSideBar) ...[
109-
const SizedBox(
110-
width: kSideBarWith,
111-
child: ChatMasterSidePanel(),
112-
),
113-
],
114-
if (context.showSideBar)
115-
const VerticalDivider(width: 0, thickness: 0),
116-
if (selectedRoom == null)
117-
const Expanded(child: ChatNoSelectedRoomPage())
118-
else
119-
Expanded(
120-
child: ChatRoomPage(
121-
key: ValueKey('${selectedRoom.id} $isArchivedRoom'),
122-
room: selectedRoom,
123-
),
124-
),
125-
],
126-
)
127-
: const Center(child: Progress()),
88+
body: Row(
89+
children: [
90+
if (context.showSideBar) ...[
91+
const SizedBox(width: kSideBarWith, child: ChatMasterSidePanel()),
92+
],
93+
if (context.showSideBar)
94+
const VerticalDivider(width: 0, thickness: 0),
95+
if (selectedRoom == null)
96+
const Expanded(child: ChatNoSelectedRoomPage())
97+
else
98+
Expanded(
99+
child: ChatRoomPage(
100+
key: ValueKey('${selectedRoom.id} $isArchivedRoom'),
101+
room: selectedRoom,
102+
),
103+
),
104+
],
128105
),
129106
bottomNavigationBar: const PlayerView(),
130107
);
Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import 'package:flutter/material.dart';
22
import 'package:matrix/matrix.dart';
33
import 'package:watch_it/watch_it.dart';
4+
import 'package:yaru/yaru.dart';
45

6+
import '../../common/chat_manager.dart';
7+
import '../../common/view/build_context_x.dart';
8+
import '../../common/view/common_widgets.dart';
59
import '../../settings/account_manager.dart';
610
import '../../settings/view/chat_settings_avatar.dart';
711

8-
class ChatMasterSettingsTileAvatar extends StatefulWidget {
12+
class ChatMasterSettingsTileAvatar extends StatefulWidget
13+
with WatchItStatefulWidgetMixin {
914
const ChatMasterSettingsTileAvatar({super.key});
1015

1116
@override
@@ -24,15 +29,40 @@ class _ChatMasterSettingsTileAvatarState
2429
}
2530

2631
@override
27-
Widget build(BuildContext context) => FutureBuilder(
28-
future: _profileFuture,
29-
builder: (context, asyncSnapshot) {
30-
return ChatSettingsAvatar(
31-
key: ValueKey(asyncSnapshot.data?.avatarUrl ?? 'master_avatar'),
32-
profile: asyncSnapshot.hasError ? null : asyncSnapshot.data,
33-
dimension: 25,
34-
showEditButton: false,
35-
);
36-
},
37-
);
32+
Widget build(BuildContext context) {
33+
final syncStatus = watchStream(
34+
(ChatManager m) => m.syncStatusUpdateStream,
35+
initialValue: di<ChatManager>().syncStatusUpdate,
36+
).data;
37+
38+
final widget = switch (syncStatus?.status) {
39+
SyncStatus.error => Tooltip(
40+
key: const ValueKey('0'),
41+
message: syncStatus?.error?.exception?.toString() ?? '',
42+
child: Icon(YaruIcons.error, color: context.theme.colorScheme.error),
43+
),
44+
SyncStatus.cleaningUp => const Center(
45+
child: Padding(
46+
padding: EdgeInsets.all(4.0),
47+
child: Progress(strokeWidth: 2),
48+
),
49+
),
50+
_ => FutureBuilder(
51+
key: const ValueKey('2'),
52+
future: _profileFuture,
53+
builder: (context, asyncSnapshot) {
54+
return ChatSettingsAvatar(
55+
key: ValueKey(asyncSnapshot.data?.avatarUrl ?? 'master_avatar'),
56+
profile: asyncSnapshot.hasError ? null : asyncSnapshot.data,
57+
dimension: 25,
58+
showEditButton: false,
59+
);
60+
},
61+
),
62+
};
63+
return AnimatedSwitcher(
64+
duration: const Duration(milliseconds: 200),
65+
child: SizedBox.square(dimension: 25, child: widget),
66+
);
67+
}
3868
}

lib/common/chat_manager.dart

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ class ChatManager extends SafeChangeNotifier {
1313
// The matrix dart SDK client
1414
final Client _client;
1515

16+
SyncStatusUpdate? get syncStatusUpdate => _client.onSyncStatus.value;
17+
Stream<SyncStatusUpdate> get syncStatusUpdateStream =>
18+
_client.onSyncStatus.stream;
19+
1620
// Room management
1721
/// The list of all rooms the user is participating or invited.
1822
List<Room> get _rooms => _client.rooms;
@@ -156,13 +160,4 @@ class ChatManager extends SafeChangeNotifier {
156160
}
157161
setSelectedRoom(null);
158162
}
159-
160-
Future initAfterEncryptionSetup() async => Future.wait<Future<dynamic>?>(
161-
Iterable.castFrom(<Future<dynamic>?>[
162-
_client.roomsLoading,
163-
_client.accountDataLoading,
164-
_client.userDeviceKeysLoading,
165-
_client.firstSyncReceived,
166-
]),
167-
);
168163
}

lib/player/data/station_media.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,13 @@ class StationMedia extends UniqueMedia {
5050
int? get bitrate => station.bitrate;
5151

5252
@override
53-
String? get collectionName => throw UnimplementedError();
53+
String? get collectionName => null;
5454

5555
@override
5656
DateTime? get creationDateTime => station.lastCheckTime;
5757

5858
@override
59-
Duration? get duration => throw UnimplementedError();
59+
Duration? get duration => null;
6060

6161
@override
6262
List<String> get genres {
@@ -71,5 +71,5 @@ class StationMedia extends UniqueMedia {
7171
String? get language => station.language;
7272

7373
@override
74-
List<String>? get performers => throw UnimplementedError();
74+
List<String>? get performers => null;
7575
}

lib/player/player_manager.dart

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:audio_service/audio_service.dart';
24
import 'package:flutter/material.dart';
35
import 'package:media_kit/media_kit.dart';
@@ -31,6 +33,13 @@ class PlayerManager extends BaseAudioHandler with SeekHandler {
3133
],
3234
),
3335
);
36+
_positionSubscription = controller.player.stream.position.listen(
37+
_setPosition,
38+
);
39+
_durationSubscription = controller.player.stream.duration.listen(
40+
_setDuration,
41+
);
42+
_bufferedSubscription = controller.player.stream.buffer.listen(_setBuffer);
3443
}
3544

3645
final VideoController _controller;
@@ -48,27 +57,30 @@ class PlayerManager extends BaseAudioHandler with SeekHandler {
4857
String? remoteSourceArtUrl,
4958
String? remoteSourceTitle,
5059
}) {
51-
final art = remoteSourceArtUrl?.endsWith('.ico') == true
52-
? null
53-
: remoteSourceArtUrl;
60+
final previousArtUri = mediaItem.value?.artUri;
61+
final artUri =
62+
(remoteSourceArtUrl == null
63+
? previousArtUri
64+
: Uri.tryParse(remoteSourceArtUrl)) ??
65+
previousArtUri;
66+
5467
playerViewState.value = playerViewState.value.copyWith(
5568
fullMode: fullMode,
5669
showPlayerExplorer: showPlayerExplorer,
5770
explorerIndex: explorerIndex,
5871
color: color,
59-
remoteSourceArtUrl: art,
72+
remoteSourceArtUrl: artUri.toString(),
6073
remoteSourceTitle: remoteSourceTitle,
6174
);
6275

6376
if (remoteSourceTitle != null) {
64-
final uri = Uri.tryParse(art ?? '');
6577
final split = remoteSourceTitle.splitByDash;
6678
mediaItem.add(
6779
MediaItem(
6880
id: remoteSourceTitle,
6981
title: remoteSourceTitle,
7082
artist: currentMedia?.artist ?? split.artist,
71-
artUri: uri,
83+
artUri: artUri,
7284
),
7385
);
7486
}
@@ -77,24 +89,33 @@ class PlayerManager extends BaseAudioHandler with SeekHandler {
7789
Player get _player => _controller.player;
7890
Player get player => _player;
7991

80-
Stream<Duration> get positionStream => _player.stream.position.map((e) {
81-
playbackState.add(playbackState.value.copyWith(updatePosition: position));
82-
return e;
83-
}).distinct();
84-
85-
Duration get position => _player.state.position;
92+
StreamSubscription<Duration>? _positionSubscription;
93+
final _position = SafeValueNotifier(Duration.zero);
94+
SafeValueNotifier<Duration> get position => _position;
95+
void _setPosition(Duration value) {
96+
playbackState.add(playbackState.value.copyWith(updatePosition: value));
97+
if (value.inSeconds == _position.value.inSeconds) return;
98+
_position.value = value;
99+
}
86100

87-
Stream<Duration> get bufferedPositionStream =>
88-
_player.stream.buffer.distinct();
101+
StreamSubscription<Duration>? _bufferedSubscription;
102+
final _buffer = SafeValueNotifier(Duration.zero);
103+
SafeValueNotifier<Duration> get buffer => _buffer;
104+
void _setBuffer(Duration value) {
105+
if (value.inSeconds == _buffer.value.inSeconds) return;
106+
_buffer.value = value;
107+
}
89108

90-
Stream<Duration> get durationStream => _player.stream.duration.map((e) {
109+
StreamSubscription<Duration>? _durationSubscription;
110+
final _duration = SafeValueNotifier<Duration>(Duration.zero);
111+
SafeValueNotifier<Duration> get duration => _duration;
112+
void _setDuration(Duration d) {
91113
if (mediaItem.value != null) {
92-
mediaItem.add(mediaItem.value!.copyWith(duration: duration));
114+
mediaItem.add(mediaItem.value!.copyWith(duration: d));
93115
}
94-
return e;
95-
}).distinct();
96-
97-
Duration get duration => _player.state.duration;
116+
if (d.inSeconds == _duration.value.inSeconds) return;
117+
_duration.value = d;
118+
}
98119

99120
Stream<bool> get isPlayingStream => _player.stream.playing.map((e) {
100121
playbackState.add(
@@ -139,7 +160,7 @@ class PlayerManager extends BaseAudioHandler with SeekHandler {
139160
title: media.title ?? media.uri.toString(),
140161
artist: media.artist,
141162
album: media.collectionName,
142-
duration: media.duration ?? duration,
163+
duration: media.duration ?? duration.value,
143164
artUri: artUri,
144165
),
145166
);
@@ -224,7 +245,13 @@ class PlayerManager extends BaseAudioHandler with SeekHandler {
224245
Future<void> skipToNext() async => _player.next();
225246

226247
@override
227-
Future<void> skipToPrevious() async => _player.previous();
248+
Future<void> skipToPrevious() async {
249+
if (position.value.inSeconds < 10) {
250+
await seek(Duration.zero);
251+
return;
252+
}
253+
return _player.previous();
254+
}
228255

229256
Future<void> setShuffle(bool shuffle) async => _player.setShuffle(shuffle);
230257

@@ -248,7 +275,12 @@ class PlayerManager extends BaseAudioHandler with SeekHandler {
248275
Future<void> setPlaylistMode(PlaylistMode mode) async =>
249276
_player.setPlaylistMode(mode);
250277

251-
Future<void> dispose() async => _player.dispose();
278+
Future<void> dispose() async {
279+
await _positionSubscription?.cancel();
280+
await _durationSubscription?.cancel();
281+
await _bufferedSubscription?.cancel();
282+
await _player.dispose();
283+
}
252284

253285
Future<void> _setLocalColor(LocalMedia media) async {
254286
try {

0 commit comments

Comments
 (0)