Skip to content

Commit c53135f

Browse files
committed
improve explore page performance
1 parent a56f8e6 commit c53135f

2 files changed

Lines changed: 170 additions & 42 deletions

File tree

lib/features/explore/presentation/controller/explore_controller.dart

Lines changed: 166 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,78 +7,195 @@ import 'package:waves/features/explore/repository/explore_repository.dart';
77
import 'package:waves/features/settings/repository/settings_repository.dart';
88

99
class ExploreController extends ChangeNotifier {
10+
ExploreController()
11+
: threadType = getIt<SettingsRepository>().readDefaultThread() {
12+
_applyCachedState();
13+
if (!_tagCache.containsKey(threadType)) {
14+
_loadTags();
15+
}
16+
}
17+
1018
final ExploreRepository _repository = getIt<ExploreRepository>();
1119

20+
static final Map<ThreadFeedType, _CachedExploreResult<TrendingTagModel>>
21+
_tagCache = {};
22+
static final Map<ThreadFeedType, _CachedExploreResult<TrendingAuthorModel>>
23+
_authorCache = {};
24+
1225
ThreadFeedType threadType;
1326
ViewState tagsState = ViewState.loading;
1427
ViewState authorsState = ViewState.loading;
1528

16-
List<TrendingTagModel> tags = [];
17-
List<TrendingAuthorModel> authors = [];
29+
List<TrendingTagModel> tags = const <TrendingTagModel>[];
30+
List<TrendingAuthorModel> authors = const <TrendingAuthorModel>[];
1831

19-
bool _authorsLoaded = false;
32+
bool _isLoadingTags = false;
33+
bool _isLoadingAuthors = false;
2034

21-
ExploreController()
22-
: threadType = getIt<SettingsRepository>().readDefaultThread() {
23-
_loadTags();
35+
void _applyCachedState() {
36+
final cachedTags = _tagCache[threadType];
37+
if (cachedTags != null) {
38+
tags = cachedTags.data;
39+
tagsState = cachedTags.state;
40+
} else {
41+
tags = const <TrendingTagModel>[];
42+
tagsState = ViewState.loading;
43+
}
44+
45+
final cachedAuthors = _authorCache[threadType];
46+
if (cachedAuthors != null) {
47+
authors = cachedAuthors.data;
48+
authorsState = cachedAuthors.state;
49+
} else {
50+
authors = const <TrendingAuthorModel>[];
51+
authorsState = ViewState.loading;
52+
}
2453
}
2554

26-
Future<void> _loadTags() async {
27-
tagsState = ViewState.loading;
28-
notifyListeners();
55+
Future<void> _loadTags({bool forceRefresh = false}) async {
56+
if (_isLoadingTags) return;
2957

30-
final container = _getContainer();
31-
final tagRes = await _repository.getTrendingTags(container);
32-
if (tagRes.isSuccess && tagRes.data != null && tagRes.data!.isNotEmpty) {
33-
tags = tagRes.data!;
34-
tagsState = ViewState.data;
58+
final currentType = threadType;
59+
if (!forceRefresh) {
60+
final cached = _tagCache[currentType];
61+
if (cached != null) {
62+
final previousState = tagsState;
63+
final previousTags = tags;
64+
tags = cached.data;
65+
tagsState = cached.state;
66+
if (!identical(previousTags, tags) || previousState != tagsState) {
67+
notifyListeners();
68+
}
69+
return;
70+
}
3571
} else {
36-
tags = [];
37-
tagsState = ViewState.error;
72+
_tagCache.remove(currentType);
3873
}
74+
75+
_isLoadingTags = true;
76+
tagsState = ViewState.loading;
3977
notifyListeners();
78+
79+
try {
80+
final container = _getContainer(currentType);
81+
final tagRes = await _repository.getTrendingTags(container);
82+
83+
if (threadType != currentType) {
84+
return;
85+
}
86+
87+
if (tagRes.isSuccess && tagRes.data != null) {
88+
final list = tagRes.data!;
89+
if (list.isEmpty) {
90+
tags = const <TrendingTagModel>[];
91+
tagsState = ViewState.empty;
92+
_tagCache[currentType] = _CachedExploreResult<TrendingTagModel>(
93+
data: const <TrendingTagModel>[],
94+
state: ViewState.empty,
95+
);
96+
} else {
97+
final immutableList =
98+
List<TrendingTagModel>.unmodifiable(List.of(list));
99+
tags = immutableList;
100+
tagsState = ViewState.data;
101+
_tagCache[currentType] = _CachedExploreResult<TrendingTagModel>(
102+
data: immutableList,
103+
state: ViewState.data,
104+
);
105+
}
106+
} else {
107+
tags = const <TrendingTagModel>[];
108+
tagsState = ViewState.error;
109+
}
110+
} finally {
111+
_isLoadingTags = false;
112+
if (threadType == currentType) {
113+
notifyListeners();
114+
}
115+
}
40116
}
41117

42-
Future<void> loadAuthorsIfNeeded() async {
43-
if (_authorsLoaded) {
44-
return;
118+
Future<void> loadAuthorsIfNeeded({bool forceRefresh = false}) async {
119+
if (_isLoadingAuthors) return;
120+
121+
final currentType = threadType;
122+
if (!forceRefresh) {
123+
final cached = _authorCache[currentType];
124+
if (cached != null) {
125+
final previousState = authorsState;
126+
final previousAuthors = authors;
127+
authors = cached.data;
128+
authorsState = cached.state;
129+
if (!identical(previousAuthors, authors) ||
130+
previousState != authorsState) {
131+
notifyListeners();
132+
}
133+
return;
134+
}
135+
} else {
136+
_authorCache.remove(currentType);
45137
}
46138

139+
_isLoadingAuthors = true;
47140
authorsState = ViewState.loading;
48141
notifyListeners();
49142

50-
final container = _getContainer();
51-
final authorRes = await _repository.getTrendingAuthors(container);
52-
if (authorRes.isSuccess && authorRes.data != null &&
53-
authorRes.data!.isNotEmpty) {
54-
authors = authorRes.data!;
55-
authorsState = ViewState.data;
56-
_authorsLoaded = true;
57-
} else {
58-
authors = [];
59-
authorsState = ViewState.error;
60-
_authorsLoaded = false;
143+
try {
144+
final container = _getContainer(currentType);
145+
final authorRes = await _repository.getTrendingAuthors(container);
146+
147+
if (threadType != currentType) {
148+
return;
149+
}
150+
151+
if (authorRes.isSuccess && authorRes.data != null) {
152+
final list = authorRes.data!;
153+
if (list.isEmpty) {
154+
authors = const <TrendingAuthorModel>[];
155+
authorsState = ViewState.empty;
156+
_authorCache[currentType] =
157+
const _CachedExploreResult<TrendingAuthorModel>(
158+
data: <TrendingAuthorModel>[],
159+
state: ViewState.empty,
160+
);
161+
} else {
162+
final immutableList =
163+
List<TrendingAuthorModel>.unmodifiable(List.of(list));
164+
authors = immutableList;
165+
authorsState = ViewState.data;
166+
_authorCache[currentType] = _CachedExploreResult<TrendingAuthorModel>(
167+
data: immutableList,
168+
state: ViewState.data,
169+
);
170+
}
171+
} else {
172+
authors = const <TrendingAuthorModel>[];
173+
authorsState = ViewState.error;
174+
}
175+
} finally {
176+
_isLoadingAuthors = false;
177+
if (threadType == currentType) {
178+
notifyListeners();
179+
}
61180
}
62-
notifyListeners();
63181
}
64182

65183
void onChangeThreadType(ThreadFeedType type) {
66-
if (threadType != type) {
67-
threadType = type;
68-
_resetAuthors();
69-
_loadTags();
184+
if (threadType == type) {
185+
return;
70186
}
71-
}
72187

73-
void _resetAuthors() {
74-
authors = [];
75-
authorsState = ViewState.loading;
76-
_authorsLoaded = false;
188+
threadType = type;
189+
_applyCachedState();
77190
notifyListeners();
191+
192+
if (!_tagCache.containsKey(threadType)) {
193+
_loadTags();
194+
}
78195
}
79196

80-
String _getContainer() {
81-
switch (threadType) {
197+
String _getContainer(ThreadFeedType type) {
198+
switch (type) {
82199
case ThreadFeedType.ecency:
83200
return 'ecency.waves';
84201
case ThreadFeedType.peakd:
@@ -92,3 +209,10 @@ class ExploreController extends ChangeNotifier {
92209
}
93210
}
94211
}
212+
213+
class _CachedExploreResult<T> {
214+
const _CachedExploreResult({required this.data, required this.state});
215+
216+
final List<T> data;
217+
final ViewState state;
218+
}

lib/features/explore/presentation/view/explore_view.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ class _TagsTab extends StatelessWidget {
122122
);
123123
},
124124
);
125+
} else if (state == ViewState.empty) {
126+
return Center(child: Text(LocaleText.noHashtagsFound));
125127
} else {
126128
return Center(child: Text(LocaleText.error));
127129
}
@@ -167,6 +169,8 @@ class _UsersTab extends StatelessWidget {
167169
);
168170
},
169171
);
172+
} else if (state == ViewState.empty) {
173+
return Center(child: Text(LocaleText.noUsersFound));
170174
} else {
171175
return Center(child: Text(LocaleText.error));
172176
}

0 commit comments

Comments
 (0)