Skip to content

Commit

Permalink
✨ Stop fetching emotes if no more are available
Browse files Browse the repository at this point in the history
Previously once the user scrolls to the end of the loaded emotes the application would always try to load more even if no more where available
  • Loading branch information
gthvmt committed Jun 8, 2023
1 parent b54a6df commit 61e9abd
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 56 deletions.
53 changes: 37 additions & 16 deletions src/lib/models/seventv.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class SevenTv {
final _url = Uri.parse('https://7tv.io/v3/gql');
final _client = HttpClient();

// TODO: refactor the request creation into a seperate method to reduce code redundancy

Stream<Stream<Emote>> getTrending(int chunkSize) async* {
var currentPage = 1;
final variables = {
Expand All @@ -62,17 +64,18 @@ class SevenTv {
'aspect_ratio': ''
}
};
//TODO: figure out a way to break the loop
//we do get the emote count in the response, maybe figure out a way to read the stream until the count
//and return the transformed stream after. Then break once all emotes should have been loaded
//(currentPage*chunkSize >= emoteCount)
while (true) {
int countCollected = 0;
int countTotal = -1;
while (countTotal < 0 || countTotal > countCollected) {
variables['page'] = currentPage;
final req = await _client.postUrl(_url);
req.headers.add(HttpHeaders.contentTypeHeader, ContentType.json.mimeType);
req.write(jsonEncode({'query': _searchEmotesQuery, 'variables': variables}));
final resp = await req.close();
yield resp.transform(utf8.decoder).transform(EmoteTransformer());
final transformer = EmoteTransformer();
yield resp.transform(utf8.decoder).transform(transformer);
countTotal = transformer.countTotal;
countCollected += transformer.countCollected;
currentPage++;
}
}
Expand All @@ -94,25 +97,31 @@ class SevenTv {
'aspect_ratio': ''
}
};
//TODO: figure out a way to break the loop
//we do get the emote count in the response, maybe figure out a way to read the stream until the count
//and return the transformed stream after. Then break once all emotes should have been loaded
//(currentPage*chunkSize >= emoteCount)
while (true) {
int countCollected = 0;
int countTotal = -1;
while (countTotal < 0 || countTotal > countCollected) {
variables['page'] = currentPage;
final req = await _client.postUrl(_url);
req.headers.add(HttpHeaders.contentTypeHeader, ContentType.json.mimeType);
req.write(jsonEncode({'query': _searchEmotesQuery, 'variables': variables}));
final resp = await req.close();
yield resp.transform(utf8.decoder).transform(EmoteTransformer());
final transformer = EmoteTransformer();
yield resp.transform(utf8.decoder).transform(transformer);
countTotal = transformer.countTotal;
countCollected += transformer.countCollected;
currentPage++;
}
}
}

class EmoteTransformer implements StreamTransformer<String, Emote> {
final StreamController<Emote> _controller = StreamController();
int countCollected = 0;
int countTotal = -1;
String _buffer = '';
String _countBuffer = '';
bool _collectCount = false;

int _currentDepth = 0;
bool _inStrVal = false;
int _itemsArrayDepth = -1;
Expand All @@ -126,10 +135,20 @@ class EmoteTransformer implements StreamTransformer<String, Emote> {
Future onListen(String chunk) async {
for (final c in chunk.split('')) {
_buffer += c;
if (_collectCount) {
if (c == ',') {
countTotal = int.parse(_countBuffer);
_collectCount = false;
} else if (c != ' ') {
_countBuffer += c;
}
}
if (c == '"') {
_inStrVal = !_inStrVal;
} else if (!_inStrVal) {
if (c == '{' || c == '[') {
if (countTotal < 0 && c == ':' && _buffer.replaceAll(' ', '').toLowerCase().endsWith('"count":')) {
_collectCount = true;
} else if (c == '{' || c == '[') {
_currentDepth++;
if (c == '[') {
if (_buffer.replaceAll(' ', '').toLowerCase().endsWith('"items":[')) {
Expand All @@ -141,6 +160,7 @@ class EmoteTransformer implements StreamTransformer<String, Emote> {
} else if (c == '}' || c == ']') {
if (_currentDepth == _itemsArrayDepth + 1 && c == '}') {
//emote object closed
countCollected++;
_controller.add(Emote.fromJson(jsonDecode(_buffer.substring(_buffer.indexOf('{')))));
_buffer = '';
}
Expand Down Expand Up @@ -194,10 +214,11 @@ class Emote {
return data;
}

Uri getMaxSizeUrl({Format format = Format.webp}) =>
host!.getUrl(host!.files!.where((f) => f.format == format).reduce((a, b) => a.height > b.height ? a : b));
Uri getMaxSizeUrl({Format format = Format.webp}) => host!.getUrl(
host!.files!.where((f) => f.format == format).reduce((a, b) => a.height > b.height ? a : b));

File getMaxSizeFile({Format format = Format.webp}) => host!.files!.reduce((a, b) => a.height > b.height ? a : b);
File getMaxSizeFile({Format format = Format.webp}) =>
host!.files!.reduce((a, b) => a.height > b.height ? a : b);
}

class Owner {
Expand Down
76 changes: 40 additions & 36 deletions src/lib/screens/browser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class _BrowserState extends State<Browser> {
bool _isSearchMode = false;
bool _isLoading = false;
StreamIterator<Stream<Emote>>? _emoteStream;
bool _moreAvailable = true;

static const _gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150.0,
Expand All @@ -49,54 +50,56 @@ class _BrowserState extends State<Browser> {
_notificationService.initialize();
_scrollController.addListener(() async {
if (_scrollController.offset == _scrollController.position.maxScrollExtent) {
await loadAdditional();
await getEmotes();
}
});
loadTrending();
}

Future loadAdditional() async {
var stream = _emoteStream;
if (stream != null) {
debugPrint('loading more emotes');
if (await stream.moveNext()) {
await for (final emote in _emoteStream!.current) {
setState(() => _loadedEmotes.add(emote));
}
}
}
}

Future loadTrending() async {
setState(() {
_isLoading = true;
_moreAvailable = true;
_loadedEmotes.clear();
});
_emoteStream = StreamIterator(_api.getTrending(_chunkSize));
if (await _emoteStream!.moveNext()) {
if (_isLoading) {
setState(() => _isLoading = false);
}
await for (final emote in _emoteStream!.current) {
setState(() => _loadedEmotes.add(emote));
}
}
await getEmotes();
}

Future search(searchText) async {
setState(() {
_isLoading = true;
_moreAvailable = true;
_loadedEmotes.clear();
});
_emoteStream = StreamIterator(_api.search(searchText, _chunkSize));
if (await _emoteStream!.moveNext()) {
if (_isLoading) {
setState(() => _isLoading = false);
}
await for (final emote in _emoteStream!.current) {
// log('got emote ${emote.name} - url is ${emote.host!.getUrl(emote.host!.files!.where((f) => f.format == Format.avif).reduce((a, b) => a.height > b.height ? a : b))}');
setState(() => _loadedEmotes.add(emote));
}
await getEmotes();
}

Future getEmotes() async {
if (!_moreAvailable) {
return;
}
var stream = _emoteStream;
if (stream != null) {
debugPrint('loading more emotes');
try {
int fetchedEmoteCount = 0;
final streamIsExhausted = !(await stream.moveNext());
if (_isLoading) {
setState(() => _isLoading = false);
}
if (!streamIsExhausted) {
await for (final emote in stream.current) {
fetchedEmoteCount++;
setState(() => _loadedEmotes.add(emote));
}
}
if (fetchedEmoteCount < _chunkSize || streamIsExhausted) {
debugPrint('stream is exhausted');
setState(() => _moreAvailable = false);
}
} catch (_) {}
}
}

Expand Down Expand Up @@ -292,13 +295,14 @@ class _BrowserState extends State<Browser> {
)
]),
),
SliverList(
delegate: SliverChildListDelegate([
const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(child: CircularProgressIndicator()),
)
]))
if (_moreAvailable)
SliverList(
delegate: SliverChildListDelegate([
const Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: Center(child: CircularProgressIndicator()),
)
]))
],
),
),
Expand Down
12 changes: 8 additions & 4 deletions src/lib/screens/stickerpack.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ class _StickerPackState extends State<StickerPack> {
@override
void initState() {
super.initState();
debugPrint('Stickers in stickerpack "${widget.stickerPack.name}" (${widget.stickerPack.identifier}):');
debugPrint(
'Stickers in stickerpack "${widget.stickerPack.name}" (${widget.stickerPack.identifier}):');
for (var sticker in widget.stickerPack.stickers) {
debugPrint(jsonEncode(sticker.toJson()));
}
Expand All @@ -40,10 +41,12 @@ class _StickerPackState extends State<StickerPack> {
content: const Text(
'Deleting this sticker pack will remove all the stickers associated with it. Do you want to proceed?'),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
TextButton(
onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')),
TextButton(
onPressed: () => Navigator.pop(ctx, true),
child: Text('Delete', style: TextStyle(color: Theme.of(ctx).colorScheme.error)))
child:
Text('Delete', style: TextStyle(color: Theme.of(ctx).colorScheme.error)))
]));
if (!(delete ?? false)) {
return false;
Expand All @@ -68,7 +71,8 @@ class _StickerPackState extends State<StickerPack> {
SliverGrid(
gridDelegate: StickerPack.gridDelegate,
delegate: SliverChildListDelegate([
for (final sticker in widget.stickerPack.stickers) Image.file(File(sticker.imagePath)),
for (final sticker in widget.stickerPack.stickers)
Image.file(File(sticker.imagePath)),
// Stack(
// children: [
// ],
Expand Down

0 comments on commit 61e9abd

Please sign in to comment.