From 3270f5d888bf5f48209de8cc602e0e9d24e848cb Mon Sep 17 00:00:00 2001 From: Lea Date: Mon, 30 Jan 2023 08:28:21 +0100 Subject: [PATCH] display favicons (fixes #6) --- lib/api/api.dart | 16 ++++++++++++++++ lib/api/cache.dart | 3 +++ lib/api/favicon.dart | 23 +++++++++++++++++++++++ lib/main.dart | 12 +++++------- lib/widgets/homepage_post.dart | 26 +++++++++++++++++++++++--- 5 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 lib/api/favicon.dart diff --git a/lib/api/api.dart b/lib/api/api.dart index c47e441..39264aa 100644 --- a/lib/api/api.dart +++ b/lib/api/api.dart @@ -8,6 +8,7 @@ import 'package:crypto/crypto.dart'; part 'feed.dart'; part 'item.dart'; part 'group.dart'; +part 'favicon.dart'; part 'feeds_group.dart'; part 'cache.dart'; part 'exceptions.dart'; @@ -103,6 +104,11 @@ class APIRequestBuilder { return this; } + APIRequestBuilder withFavicons() { + _addArg("favicons"); + return this; + } + /// Mark single item as read, unread, saved, unsaved APIRequestBuilder markItem(Item item, ItemMarkType markAs) { _addArg('mark=item'); @@ -176,6 +182,16 @@ class APIRequestBuilder { } } + if (data.containsKey('favicons')) { + List favicons = (data['favicons'] as List) + .map((json) => Favicon.fromJSON(api, json)) + .toList(); + + for (var favicon in favicons) { + api.cache.favicons.set(favicon.id, favicon); + } + } + if (data.containsKey('feeds_groups')) { List feedsGroups = (data['feeds_groups'] as List) .map((json) => FeedsGroup.fromJSON(api, json)) diff --git a/lib/api/cache.dart b/lib/api/cache.dart index 66ef7a6..b9b459d 100644 --- a/lib/api/cache.dart +++ b/lib/api/cache.dart @@ -7,18 +7,21 @@ class APICache { late final ItemCache items; late final ObjectCache feeds; late final ObjectCache groups; + late final ObjectCache favicons; final List feedsGroups = []; APICache(this.api) { items = ItemCache(api); feeds = ObjectCache(api); groups = ObjectCache(api); + favicons = ObjectCache(api); } void clear() { items._items.clear(); feeds._items.clear(); groups._items.clear(); + favicons._items.clear(); feedsGroups.clear(); } } diff --git a/lib/api/favicon.dart b/lib/api/favicon.dart new file mode 100644 index 0000000..493a90b --- /dev/null +++ b/lib/api/favicon.dart @@ -0,0 +1,23 @@ +part of fever_api; + +class Favicon { + FeverAPI api; + late int id; + + /// base64 encoded image data; prefixed by image type. Example: + /// `image/gif;base64,R0lGODlhAQABAIAAAObm5gAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==` + late String data; + + Favicon( + this.api, + { + required this.id, + required this.data, + } + ); + + Favicon.fromJSON(this.api, Map json) { + id = toInt(json['id']); + data = json['data']; + } +} diff --git a/lib/main.dart b/lib/main.dart index a7cc2ec..ea60b0e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -100,6 +100,7 @@ class _MyHomePageState extends State { .request() .withFeeds() .withGroups() + .withFavicons() .withItems() .sinceId(0) .execute(); @@ -139,9 +140,8 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { var loggedIn = widget.api.loggedIn(); - var posts = widget.api.cache.items.getAll().values.where( - (element) => _feedFilter.isEmpty || _feedFilter.contains(element.feedId) - ); + var posts = widget.api.cache.items.getAll().values.where((element) => + _feedFilter.isEmpty || _feedFilter.contains(element.feedId)); var savedPosts = posts.where((element) => element.isSaved); var unreadPosts = posts.where((element) => !element.isRead); @@ -260,11 +260,9 @@ class _MyHomePageState extends State { ), PopupMenuItem( value: "filter", - child: Text( - _feedFilter.isEmpty + child: Text(_feedFilter.isEmpty ? "Filter..." - : "Filter (${_feedFilter.length}/${widget.api.cache.feeds.size})" - ), + : "Filter (${_feedFilter.length}/${widget.api.cache.feeds.size})"), onTap: () { Future.delayed( Duration.zero, diff --git a/lib/widgets/homepage_post.dart b/lib/widgets/homepage_post.dart index 28e557d..ebd6e98 100644 --- a/lib/widgets/homepage_post.dart +++ b/lib/widgets/homepage_post.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:feet/pages/article.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -20,6 +22,8 @@ class _HomepagePostState extends State { @override Widget build(BuildContext context) { var post = widget.post; + var favicon = post.api.cache.favicons.get(post.feedId); + return Container( margin: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 8.0), decoration: BoxDecoration( @@ -69,9 +73,25 @@ class _HomepagePostState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(post.title, - style: Theme.of(context).textTheme.titleMedium, - overflow: TextOverflow.ellipsis), + Row( + children: [ + favicon?.data != null + ? Container( + margin: const EdgeInsets.fromLTRB(0, 0, 6, 4), + child: Image.memory( + base64Decode(favicon!.data.split(',')[1]), + width: 20, + height: 20, + ), + ) + : Container(), + Expanded( + child: Text(post.title, + style: Theme.of(context).textTheme.titleMedium, + overflow: TextOverflow.ellipsis), + ), + ], + ), ClipRRect( clipBehavior: Clip.antiAlias, child: Row(