mirror of
https://gitlab.com/mysocialportal/fediverse-archiving-tools.git
synced 2024-10-18 08:53:31 +00:00
Implement stats panel with top interactors
This commit is contained in:
parent
a2b4c1e3da
commit
764a5b9946
8 changed files with 353 additions and 55 deletions
|
@ -0,0 +1,151 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/models/time_element.dart';
|
||||
import 'package:friendica_archive_browser/src/services/friendica_connections.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/snackbar_status_builder.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/top_interactors_generator.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class TopInteractorsWidget extends StatefulWidget {
|
||||
final List<TimeElement> entries;
|
||||
final FriendicaConnections connections;
|
||||
|
||||
const TopInteractorsWidget(this.entries, this.connections, {Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<TopInteractorsWidget> createState() => _TopInteractionsWidget();
|
||||
}
|
||||
|
||||
class _TopInteractionsWidget extends State<TopInteractorsWidget> {
|
||||
static final _logger = Logger('$TopInteractorsWidget');
|
||||
int _currentThreshold = 10;
|
||||
int _sortIndex = 1;
|
||||
final _thresholds = [10, 20, 50, 100];
|
||||
final generator = TopInteractorsGenerator();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _generateStats() {
|
||||
_logger.finer('Filling list');
|
||||
generator.clear();
|
||||
for (final entry in widget.entries) {
|
||||
generator.processEntry(entry.entry, widget.connections);
|
||||
}
|
||||
_logger.finer('List filled');
|
||||
_calcTopList(false);
|
||||
}
|
||||
|
||||
Future<void> _calcTopList(bool updateState) async {
|
||||
if (updateState) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_logger.fine('Rebuilding Top Interactors');
|
||||
_generateStats();
|
||||
|
||||
final interactors = <InteractorItem>[];
|
||||
|
||||
if (_sortIndex == 1) {
|
||||
interactors.addAll(generator.getTopLikes(_currentThreshold));
|
||||
} else if (_sortIndex == 2) {
|
||||
interactors.addAll(generator.getTopDislikes(_currentThreshold));
|
||||
} else {
|
||||
interactors.addAll(generator.getTopCommentReshare(_currentThreshold));
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Text(
|
||||
'Top',
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 5.0, right: 5.0),
|
||||
child: DropdownButton<int>(
|
||||
value: _currentThreshold,
|
||||
items: _thresholds
|
||||
.map((t) => DropdownMenuItem(value: t, child: Text('$t')))
|
||||
.toList(),
|
||||
onChanged: (newValue) async {
|
||||
_currentThreshold = newValue ?? _thresholds.first;
|
||||
_calcTopList(true);
|
||||
}),
|
||||
),
|
||||
Text(
|
||||
'Interactors',
|
||||
textAlign: TextAlign.right,
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
_buildDataTable(context, interactors),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDataTable(
|
||||
BuildContext context, List<InteractorItem> interactors) {
|
||||
return DataTable(
|
||||
sortColumnIndex: _sortIndex,
|
||||
sortAscending: false,
|
||||
columns: [
|
||||
const DataColumn(label: Text('Name')),
|
||||
DataColumn(
|
||||
label: const Text('Likes'),
|
||||
numeric: true,
|
||||
onSort: (column, ascending) => setState(() {
|
||||
_sortIndex = column;
|
||||
})),
|
||||
DataColumn(
|
||||
label: const Text('Dislikes'),
|
||||
numeric: true,
|
||||
onSort: (column, ascending) => setState(() {
|
||||
_sortIndex = column;
|
||||
})),
|
||||
DataColumn(
|
||||
label: const Text('Reshares'),
|
||||
numeric: true,
|
||||
onSort: (column, ascending) => setState(() {
|
||||
_sortIndex = column;
|
||||
})),
|
||||
],
|
||||
rows: List.generate(
|
||||
interactors.length,
|
||||
(index) => DataRow(
|
||||
color: index.isEven
|
||||
? MaterialStateProperty.resolveWith(
|
||||
(states) => Theme.of(context).dividerColor)
|
||||
: null,
|
||||
cells: [
|
||||
DataCell(TextButton(
|
||||
onPressed: () async {
|
||||
final url =
|
||||
interactors[index].contact.profileUrl.toString();
|
||||
await canLaunch(url)
|
||||
? await launch(url)
|
||||
: SnackBarStatusBuilder.buildSnackbar(
|
||||
context, 'Failed to open $url');
|
||||
},
|
||||
child: Text(interactors[index].contact.name))),
|
||||
DataCell(Text('${interactors[index].likeCount}')),
|
||||
DataCell(Text('${interactors[index].dislikeCount}')),
|
||||
DataCell(
|
||||
Text('${interactors[index].resharedOrCommentedOn}')),
|
||||
])),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -52,19 +52,6 @@ class _WordFrequencyWidgetState extends State<WordFrequencyWidget> {
|
|||
_generateWordMap();
|
||||
|
||||
_logger.finer('Top elements count: ${topElements.length}');
|
||||
final rowElements = <Widget>[];
|
||||
|
||||
for (var i = 0; i < topElements.length; i++) {
|
||||
final element = topElements[i];
|
||||
final background = i % 2 == 0 ? null : Theme.of(context).dividerColor;
|
||||
final row = Container(
|
||||
color: background,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [Text(element.word), Text('${element.count}')],
|
||||
));
|
||||
rowElements.add(row);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
@ -101,14 +88,30 @@ class _WordFrequencyWidgetState extends State<WordFrequencyWidget> {
|
|||
],
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: Column(
|
||||
children: rowElements,
|
||||
),
|
||||
),
|
||||
_buildDataTable(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDataTable(BuildContext context) {
|
||||
return DataTable(
|
||||
sortAscending: false,
|
||||
columns: const [
|
||||
DataColumn(label: Text('Word')),
|
||||
DataColumn(label: Text('Count'), numeric: true),
|
||||
],
|
||||
rows: List.generate(
|
||||
topElements.length,
|
||||
(index) => DataRow(
|
||||
color: index.isEven
|
||||
? MaterialStateProperty.resolveWith(
|
||||
(states) => Theme.of(context).dividerColor)
|
||||
: null,
|
||||
cells: [
|
||||
DataCell(Text(topElements[index].word)),
|
||||
DataCell(Text('${topElements[index].count}')),
|
||||
])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ class FriendicaTimelineEntry {
|
|||
|
||||
final String parentAuthor;
|
||||
|
||||
final String parentAuthorId;
|
||||
|
||||
final int creationTimestamp;
|
||||
|
||||
final int backdatedTimestamp;
|
||||
|
@ -58,6 +60,7 @@ class FriendicaTimelineEntry {
|
|||
this.author = '',
|
||||
this.authorId = '',
|
||||
this.parentAuthor = '',
|
||||
this.parentAuthorId = '',
|
||||
this.externalLink = '',
|
||||
this.locationData = const LocationData(),
|
||||
this.likes = const <FriendicaContact>[],
|
||||
|
@ -80,6 +83,7 @@ class FriendicaTimelineEntry {
|
|||
author = 'Random author ${randomId()}',
|
||||
authorId = 'Random authorId ${randomId()}',
|
||||
parentAuthor = 'Random parent author ${randomId()}',
|
||||
parentAuthorId = 'Random parent author id ${randomId()}',
|
||||
locationData = LocationData.randomBuilt(),
|
||||
likes = const <FriendicaContact>[],
|
||||
dislikes = const <FriendicaContact>[],
|
||||
|
@ -105,6 +109,7 @@ class FriendicaTimelineEntry {
|
|||
String? author,
|
||||
String? authorId,
|
||||
String? parentAuthor,
|
||||
String? parentAuthorId,
|
||||
LocationData? locationData,
|
||||
List<FriendicaMediaAttachment>? mediaAttachments,
|
||||
List<FriendicaContact>? likes,
|
||||
|
@ -124,6 +129,7 @@ class FriendicaTimelineEntry {
|
|||
author: author ?? this.author,
|
||||
authorId: authorId ?? this.authorId,
|
||||
parentAuthor: parentAuthor ?? this.parentAuthor,
|
||||
parentAuthorId: parentAuthorId ?? this.parentAuthorId,
|
||||
locationData: locationData ?? this.locationData,
|
||||
mediaAttachments: mediaAttachments ?? this.mediaAttachments,
|
||||
likes: likes ?? this.likes,
|
||||
|
@ -186,6 +192,7 @@ class FriendicaTimelineEntry {
|
|||
final isReshare = json.containsKey('retweeted_status');
|
||||
final parentId = json['in_reply_to_status_id_str'] ?? '';
|
||||
final parentAuthor = json['in_reply_to_screen_name'] ?? '';
|
||||
final parentAuthorId = json['in_reply_to_user_id_str'] ?? '';
|
||||
final body = json['friendica_html'] ?? '';
|
||||
final author = json['user']['name'];
|
||||
final authorId = json['user']['id_str'];
|
||||
|
@ -225,6 +232,7 @@ class FriendicaTimelineEntry {
|
|||
isReshare: isReshare,
|
||||
id: id,
|
||||
parentId: parentId,
|
||||
parentAuthorId: parentAuthorId,
|
||||
author: author,
|
||||
authorId: authorId,
|
||||
parentAuthor: parentAuthor,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/components/heatmap_widget.dart';
|
||||
import 'package:friendica_archive_browser/src/components/timechart_widget.dart';
|
||||
import 'package:friendica_archive_browser/src/components/top_interactactors_widget.dart';
|
||||
import 'package:friendica_archive_browser/src/components/word_frequency_widget.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/components/filter_control_component.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/model_utils.dart';
|
||||
|
@ -47,26 +48,18 @@ class _StatsScreenState extends State<StatsScreen> {
|
|||
case StatType.post:
|
||||
newItems = (await archiveDataService!.getPosts()).fold(
|
||||
onSuccess: (posts) => posts.map((e) => TimeElement(
|
||||
timeInMS: e.entry.creationTimestamp * 1000,
|
||||
hasImages: e.entry.hasImages(),
|
||||
hasVideos: e.entry.hasVideos(),
|
||||
title: e.entry.title,
|
||||
text: e.entry.body)),
|
||||
timeInMS: e.entry.creationTimestamp * 1000, entry: e.entry)),
|
||||
onError: (error) {
|
||||
_logger.severe('Error getting posts: $error');
|
||||
return [];
|
||||
});
|
||||
break;
|
||||
case StatType.comment:
|
||||
newItems = (await archiveDataService!.getOrphanedComments()).fold(
|
||||
newItems = (await archiveDataService!.getAllComments()).fold(
|
||||
onSuccess: (comments) => comments.map((e) => TimeElement(
|
||||
timeInMS: e.entry.creationTimestamp * 1000,
|
||||
hasImages: e.entry.hasImages(),
|
||||
hasVideos: e.entry.hasVideos(),
|
||||
title: e.entry.title,
|
||||
text: e.entry.body)),
|
||||
timeInMS: e.entry.creationTimestamp * 1000, entry: e.entry)),
|
||||
onError: (error) {
|
||||
_logger.severe('Error getting oprhaned comments: $error');
|
||||
_logger.severe('Error getting comments: $error');
|
||||
return [];
|
||||
});
|
||||
break;
|
||||
|
@ -154,6 +147,8 @@ class _StatsScreenState extends State<StatsScreen> {
|
|||
child: Column(children: [
|
||||
..._buildGraphScreens(context, items),
|
||||
const Divider(),
|
||||
TopInteractorsWidget(items, archiveDataService!.connections),
|
||||
const Divider(),
|
||||
WordFrequencyWidget(items),
|
||||
]),
|
||||
));
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import 'package:friendica_archive_browser/src/friendica/models/friendica_timeline_entry.dart';
|
||||
|
||||
class TimeElement {
|
||||
final DateTime timestamp;
|
||||
final bool hasImages;
|
||||
final bool hasVideos;
|
||||
final String text;
|
||||
final String title;
|
||||
final FriendicaTimelineEntry entry;
|
||||
|
||||
TimeElement(
|
||||
{int timeInMS = 0,
|
||||
this.hasImages = false,
|
||||
this.hasVideos = false,
|
||||
this.text = '',
|
||||
this.title = ''})
|
||||
TimeElement({int timeInMS = 0, required this.entry})
|
||||
: timestamp = DateTime.fromMillisecondsSinceEpoch(timeInMS);
|
||||
|
||||
bool get hasImages => entry.hasImages();
|
||||
|
||||
bool get hasVideos => entry.hasVideos();
|
||||
|
||||
String get text => entry.body;
|
||||
|
||||
String get title => entry.title;
|
||||
|
||||
bool hasText(String phrase) =>
|
||||
text.contains(phrase) || title.contains(phrase);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ class FriendicaArchiveService extends ChangeNotifier {
|
|||
final Map<String, ImageEntry> _imagesByRequestUrl = {};
|
||||
final List<FriendicaEntryTreeItem> _postEntries = [];
|
||||
final List<FriendicaEntryTreeItem> _orphanedCommentEntries = [];
|
||||
final FriendicaConnections _connections = FriendicaConnections();
|
||||
final List<FriendicaEntryTreeItem> _allComments = [];
|
||||
final FriendicaConnections connections = FriendicaConnections();
|
||||
String _ownersName = '';
|
||||
|
||||
FriendicaArchiveService({required this.pathMappingService});
|
||||
|
@ -33,23 +34,32 @@ class FriendicaArchiveService extends ChangeNotifier {
|
|||
}
|
||||
|
||||
void clearCaches() {
|
||||
_connections.clearCaches();
|
||||
connections.clearCaches();
|
||||
_imagesByRequestUrl.clear();
|
||||
_orphanedCommentEntries.clear();
|
||||
_allComments.clear();
|
||||
_postEntries.clear();
|
||||
}
|
||||
|
||||
FutureResult<List<FriendicaEntryTreeItem>, ExecError> getPosts() async {
|
||||
if (_postEntries.isEmpty && _orphanedCommentEntries.isEmpty) {
|
||||
if (_postEntries.isEmpty && _allComments.isEmpty) {
|
||||
_loadEntries();
|
||||
}
|
||||
|
||||
return Result.ok(_postEntries);
|
||||
}
|
||||
|
||||
FutureResult<List<FriendicaEntryTreeItem>, ExecError> getAllComments() async {
|
||||
if (_postEntries.isEmpty && _allComments.isEmpty) {
|
||||
_loadEntries();
|
||||
}
|
||||
|
||||
return Result.ok(_allComments);
|
||||
}
|
||||
|
||||
FutureResult<List<FriendicaEntryTreeItem>, ExecError>
|
||||
getOrphanedComments() async {
|
||||
if (_postEntries.isEmpty && _orphanedCommentEntries.isEmpty) {
|
||||
if (_postEntries.isEmpty && _allComments.isEmpty) {
|
||||
_loadEntries();
|
||||
}
|
||||
|
||||
|
@ -75,21 +85,21 @@ class FriendicaArchiveService extends ChangeNotifier {
|
|||
if (jsonFile.existsSync()) {
|
||||
final json = jsonDecode(jsonFile.readAsStringSync()) as List<dynamic>;
|
||||
final entries =
|
||||
json.map((j) => FriendicaTimelineEntry.fromJson(j, _connections));
|
||||
json.map((j) => FriendicaTimelineEntry.fromJson(j, connections));
|
||||
final topLevelEntries =
|
||||
entries.where((element) => element.parentId.isEmpty);
|
||||
final ids = entries.map((e) => e.id).toSet();
|
||||
final commentEntries =
|
||||
entries.where((element) => element.parentId.isNotEmpty).toList();
|
||||
final entryTrees = <String, FriendicaEntryTreeItem>{};
|
||||
|
||||
final postTreeEntries = <FriendicaEntryTreeItem>[];
|
||||
for (final entry in topLevelEntries) {
|
||||
entryTrees[entry.id] = FriendicaEntryTreeItem(entry, false);
|
||||
final treeEntry = FriendicaEntryTreeItem(entry, false);
|
||||
entryTrees[entry.id] = treeEntry;
|
||||
postTreeEntries.add(treeEntry);
|
||||
}
|
||||
|
||||
final commentsWithParents = commentEntries
|
||||
.where((element) => ids.contains(element.parentId))
|
||||
.toList();
|
||||
print(commentsWithParents.length);
|
||||
final commentTreeEntries = <FriendicaEntryTreeItem>[];
|
||||
commentEntries.sort(
|
||||
(c1, c2) => c1.creationTimestamp.compareTo(c2.creationTimestamp));
|
||||
for (final entry in commentEntries) {
|
||||
|
@ -97,11 +107,14 @@ class FriendicaArchiveService extends ChangeNotifier {
|
|||
final treeEntry = FriendicaEntryTreeItem(entry, parent == null);
|
||||
parent?.addChild(treeEntry);
|
||||
entryTrees[entry.id] = treeEntry;
|
||||
commentTreeEntries.add(treeEntry);
|
||||
}
|
||||
|
||||
_postEntries.clear();
|
||||
_postEntries
|
||||
.addAll(entryTrees.values.where((element) => !element.isOrphaned));
|
||||
_postEntries.addAll(postTreeEntries);
|
||||
|
||||
_allComments.clear();
|
||||
_allComments.addAll(commentTreeEntries);
|
||||
|
||||
_orphanedCommentEntries.clear();
|
||||
_orphanedCommentEntries
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_contact.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_timeline_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/services/friendica_connections.dart';
|
||||
|
||||
class TopInteractorsGenerator {
|
||||
final _interactors = <String, InteractorItem>{};
|
||||
final _processedEntryIds = <String>{};
|
||||
|
||||
void clear() {
|
||||
_interactors.clear();
|
||||
_processedEntryIds.clear();
|
||||
}
|
||||
|
||||
void processEntry(
|
||||
FriendicaTimelineEntry item, FriendicaConnections contacts) {
|
||||
if (_processedEntryIds.contains(item.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_processedEntryIds.add(item.id);
|
||||
|
||||
if (item.parentAuthorId.isNotEmpty) {
|
||||
final interactorItem =
|
||||
_getInteractorItemById(item.parentAuthorId, contacts);
|
||||
_interactors[item.parentAuthorId] =
|
||||
interactorItem.incrementResharedOrCommentedOn();
|
||||
}
|
||||
|
||||
for (final like in item.likes) {
|
||||
final interactorItem =
|
||||
_interactors[like.id] ?? InteractorItem(contact: like);
|
||||
_interactors[like.id] = interactorItem.incrementLike();
|
||||
}
|
||||
|
||||
for (final dislike in item.dislikes) {
|
||||
final interactorItem =
|
||||
_interactors[dislike.id] ?? InteractorItem(contact: dislike);
|
||||
_interactors[dislike.id] = interactorItem.incrementDislike();
|
||||
}
|
||||
}
|
||||
|
||||
List<InteractorItem> getTopCommentReshare(int threshold) {
|
||||
final forResult = List.of(_interactors.values);
|
||||
forResult.sort((i1, i2) =>
|
||||
i2.resharedOrCommentedOn.compareTo(i1.resharedOrCommentedOn));
|
||||
return forResult.take(threshold).toList();
|
||||
}
|
||||
|
||||
List<InteractorItem> getTopLikes(int threshold) {
|
||||
final forResult = List.of(_interactors.values);
|
||||
forResult.sort((i1, i2) => i2.likeCount.compareTo(i1.likeCount));
|
||||
return forResult.take(threshold).toList();
|
||||
}
|
||||
|
||||
List<InteractorItem> getTopDislikes(int threshold) {
|
||||
final forResult = List.of(_interactors.values);
|
||||
forResult.sort((i1, i2) => i2.dislikeCount.compareTo(i1.dislikeCount));
|
||||
return forResult.take(threshold).toList();
|
||||
}
|
||||
|
||||
InteractorItem _getInteractorItemById(
|
||||
String id, FriendicaConnections contacts) {
|
||||
if (_interactors.containsKey(id)) {
|
||||
return _interactors[id]!;
|
||||
}
|
||||
|
||||
final contact = contacts.getById(id).fold(
|
||||
onSuccess: (contact) => contact,
|
||||
onError: (error) => FriendicaContact(
|
||||
status: ConnectionStatus.none,
|
||||
name: '',
|
||||
id: id,
|
||||
profileUrl: Uri(),
|
||||
network: 'network'));
|
||||
return InteractorItem(contact: contact);
|
||||
}
|
||||
}
|
||||
|
||||
class InteractorItem {
|
||||
final FriendicaContact contact;
|
||||
final int resharedOrCommentedOn;
|
||||
final int likeCount;
|
||||
final int dislikeCount;
|
||||
|
||||
InteractorItem(
|
||||
{required this.contact,
|
||||
this.resharedOrCommentedOn = 0,
|
||||
this.likeCount = 0,
|
||||
this.dislikeCount = 0});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InteractorItem{contact: $contact, resharedOrCommentedOn: $resharedOrCommentedOn, likeCount: $likeCount, dislikeCount: $dislikeCount}';
|
||||
}
|
||||
|
||||
InteractorItem copy(
|
||||
{FriendicaContact? contact,
|
||||
int? resharedOrCommentedOn,
|
||||
int? likeCount,
|
||||
int? dislikeCount}) {
|
||||
return InteractorItem(
|
||||
contact: contact ?? this.contact,
|
||||
resharedOrCommentedOn:
|
||||
resharedOrCommentedOn ?? this.resharedOrCommentedOn,
|
||||
likeCount: likeCount ?? this.likeCount,
|
||||
dislikeCount: dislikeCount ?? this.dislikeCount);
|
||||
}
|
||||
|
||||
InteractorItem incrementResharedOrCommentedOn() =>
|
||||
copy(resharedOrCommentedOn: this.resharedOrCommentedOn + 1);
|
||||
|
||||
InteractorItem incrementLike() => copy(likeCount: this.likeCount + 1);
|
||||
|
||||
InteractorItem incrementDislike() =>
|
||||
copy(dislikeCount: this.dislikeCount + 1);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:html/parser.dart' show parse;
|
||||
|
||||
class WordMapGenerator {
|
||||
final _words = <String, int>{};
|
||||
final int minimumWordSize;
|
||||
|
@ -16,7 +18,14 @@ class WordMapGenerator {
|
|||
}
|
||||
|
||||
void processEntry(String text) {
|
||||
final wordsFromText = text
|
||||
final topLevelText = parse(text)
|
||||
.body
|
||||
?.nodes
|
||||
.where((element) => element.nodeType == 3)
|
||||
.join(' ') ??
|
||||
text;
|
||||
|
||||
final wordsFromText = topLevelText
|
||||
.toLowerCase()
|
||||
.replaceAll(RegExp(r'[^\w]+'), ' ')
|
||||
.replaceAll(RegExp(r'[_]+'), ' ')
|
||||
|
|
Loading…
Reference in a new issue