display favicons (fixes #6)
This commit is contained in:
parent
27915caf5f
commit
3270f5d888
|
@ -8,6 +8,7 @@ import 'package:crypto/crypto.dart';
|
||||||
part 'feed.dart';
|
part 'feed.dart';
|
||||||
part 'item.dart';
|
part 'item.dart';
|
||||||
part 'group.dart';
|
part 'group.dart';
|
||||||
|
part 'favicon.dart';
|
||||||
part 'feeds_group.dart';
|
part 'feeds_group.dart';
|
||||||
part 'cache.dart';
|
part 'cache.dart';
|
||||||
part 'exceptions.dart';
|
part 'exceptions.dart';
|
||||||
|
@ -103,6 +104,11 @@ class APIRequestBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
APIRequestBuilder withFavicons() {
|
||||||
|
_addArg("favicons");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// Mark single item as read, unread, saved, unsaved
|
/// Mark single item as read, unread, saved, unsaved
|
||||||
APIRequestBuilder markItem(Item item, ItemMarkType markAs) {
|
APIRequestBuilder markItem(Item item, ItemMarkType markAs) {
|
||||||
_addArg('mark=item');
|
_addArg('mark=item');
|
||||||
|
@ -176,6 +182,16 @@ class APIRequestBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.containsKey('favicons')) {
|
||||||
|
List<Favicon> favicons = (data['favicons'] as List<dynamic>)
|
||||||
|
.map((json) => Favicon.fromJSON(api, json))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
for (var favicon in favicons) {
|
||||||
|
api.cache.favicons.set(favicon.id, favicon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data.containsKey('feeds_groups')) {
|
if (data.containsKey('feeds_groups')) {
|
||||||
List<FeedsGroup> feedsGroups = (data['feeds_groups'] as List<dynamic>)
|
List<FeedsGroup> feedsGroups = (data['feeds_groups'] as List<dynamic>)
|
||||||
.map((json) => FeedsGroup.fromJSON(api, json))
|
.map((json) => FeedsGroup.fromJSON(api, json))
|
||||||
|
|
|
@ -7,18 +7,21 @@ class APICache {
|
||||||
late final ItemCache items;
|
late final ItemCache items;
|
||||||
late final ObjectCache<int, Feed> feeds;
|
late final ObjectCache<int, Feed> feeds;
|
||||||
late final ObjectCache<int, Group> groups;
|
late final ObjectCache<int, Group> groups;
|
||||||
|
late final ObjectCache<int, Favicon> favicons;
|
||||||
final List<FeedsGroup> feedsGroups = [];
|
final List<FeedsGroup> feedsGroups = [];
|
||||||
|
|
||||||
APICache(this.api) {
|
APICache(this.api) {
|
||||||
items = ItemCache(api);
|
items = ItemCache(api);
|
||||||
feeds = ObjectCache(api);
|
feeds = ObjectCache(api);
|
||||||
groups = ObjectCache(api);
|
groups = ObjectCache(api);
|
||||||
|
favicons = ObjectCache(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
items._items.clear();
|
items._items.clear();
|
||||||
feeds._items.clear();
|
feeds._items.clear();
|
||||||
groups._items.clear();
|
groups._items.clear();
|
||||||
|
favicons._items.clear();
|
||||||
feedsGroups.clear();
|
feedsGroups.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
23
lib/api/favicon.dart
Normal file
23
lib/api/favicon.dart
Normal file
|
@ -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<String, dynamic> json) {
|
||||||
|
id = toInt(json['id']);
|
||||||
|
data = json['data'];
|
||||||
|
}
|
||||||
|
}
|
|
@ -100,6 +100,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
.request()
|
.request()
|
||||||
.withFeeds()
|
.withFeeds()
|
||||||
.withGroups()
|
.withGroups()
|
||||||
|
.withFavicons()
|
||||||
.withItems()
|
.withItems()
|
||||||
.sinceId(0)
|
.sinceId(0)
|
||||||
.execute();
|
.execute();
|
||||||
|
@ -139,9 +140,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var loggedIn = widget.api.loggedIn();
|
var loggedIn = widget.api.loggedIn();
|
||||||
var posts = widget.api.cache.items.getAll().values.where(
|
var posts = widget.api.cache.items.getAll().values.where((element) =>
|
||||||
(element) => _feedFilter.isEmpty || _feedFilter.contains(element.feedId)
|
_feedFilter.isEmpty || _feedFilter.contains(element.feedId));
|
||||||
);
|
|
||||||
|
|
||||||
var savedPosts = posts.where((element) => element.isSaved);
|
var savedPosts = posts.where((element) => element.isSaved);
|
||||||
var unreadPosts = posts.where((element) => !element.isRead);
|
var unreadPosts = posts.where((element) => !element.isRead);
|
||||||
|
@ -260,11 +260,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: "filter",
|
value: "filter",
|
||||||
child: Text(
|
child: Text(_feedFilter.isEmpty
|
||||||
_feedFilter.isEmpty
|
|
||||||
? "Filter..."
|
? "Filter..."
|
||||||
: "Filter (${_feedFilter.length}/${widget.api.cache.feeds.size})"
|
: "Filter (${_feedFilter.length}/${widget.api.cache.feeds.size})"),
|
||||||
),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Future.delayed(
|
Future.delayed(
|
||||||
Duration.zero,
|
Duration.zero,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:feet/pages/article.dart';
|
import 'package:feet/pages/article.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -20,6 +22,8 @@ class _HomepagePostState extends State<HomepagePost> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var post = widget.post;
|
var post = widget.post;
|
||||||
|
var favicon = post.api.cache.favicons.get(post.feedId);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 8.0),
|
margin: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 8.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
@ -69,9 +73,25 @@ class _HomepagePostState extends State<HomepagePost> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(post.title,
|
Row(
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
children: [
|
||||||
overflow: TextOverflow.ellipsis),
|
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(
|
ClipRRect(
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
Loading…
Reference in a new issue