kopia lustrzana https://gitlab.com/mysocialportal/relatica
231 wiersze
6.7 KiB
Dart
231 wiersze
6.7 KiB
Dart
import 'package:logging/logging.dart';
|
|
import 'package:result_monad/result_monad.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
import 'package:stack_trace/stack_trace.dart';
|
|
|
|
import '../models/auth/profile.dart';
|
|
import '../models/exec_error.dart';
|
|
import '../models/media_attachment.dart';
|
|
import '../models/networking/paging_data.dart';
|
|
import '../models/timeline.dart';
|
|
import '../models/timeline_entry.dart';
|
|
import '../models/timeline_identifiers.dart';
|
|
import '../riverpod_controllers/networking/friendica_timelines_client_services.dart';
|
|
import 'entry_tree_item_services.dart';
|
|
|
|
part 'timeline_services.g.dart';
|
|
|
|
enum TimelineRefreshType {
|
|
refresh,
|
|
loadOlder,
|
|
loadNewer,
|
|
}
|
|
|
|
final _tcLogger = Logger('TimelineCleanerProvider');
|
|
|
|
@Riverpod(keepAlive: true)
|
|
class TimelineMaintainer extends _$TimelineMaintainer {
|
|
@override
|
|
List<TimelineIdentifiers> build(Profile profile) {
|
|
return [];
|
|
}
|
|
|
|
void add(TimelineIdentifiers timelineId) {
|
|
_tcLogger.fine('Adding timeline: $timelineId');
|
|
state.add(timelineId);
|
|
ref.notifyListeners();
|
|
}
|
|
|
|
void sweep(String statusId) {
|
|
for (final t in state) {
|
|
final result = ref
|
|
.read(timelineManagerProvider(profile, t).notifier)
|
|
.removeTimelineEntry(statusId);
|
|
_tcLogger.finest(
|
|
'Attempting to remove $statusId from $t but was it there? $result');
|
|
}
|
|
}
|
|
|
|
void refreshAll() async {
|
|
const typesToRefresh = [
|
|
TimelineType.home,
|
|
TimelineType.global,
|
|
TimelineType.local,
|
|
TimelineType.circle,
|
|
];
|
|
|
|
final nonUserTimelines =
|
|
state.where((t) => typesToRefresh.contains(t.timeline)).toList();
|
|
for (final t in nonUserTimelines) {
|
|
ref.invalidate(timelineManagerProvider(profile, t));
|
|
}
|
|
}
|
|
|
|
Future<void> loadNewerForPersonalTimelines() async {
|
|
const standardMyTimelines = [
|
|
TimelineType.home,
|
|
TimelineType.global,
|
|
TimelineType.local,
|
|
TimelineType.self,
|
|
];
|
|
final openMyTimelines =
|
|
state.where((i) => standardMyTimelines.contains(i.timeline)).toList();
|
|
if (openMyTimelines.isEmpty) {
|
|
_tcLogger.finest(
|
|
'No personal timelines open that need checking for new entries');
|
|
}
|
|
|
|
for (final t in openMyTimelines) {
|
|
await ref
|
|
.read(timelineManagerProvider(profile, t).notifier)
|
|
.updateTimeline(TimelineRefreshType.loadNewer);
|
|
}
|
|
}
|
|
}
|
|
|
|
final _tmpLogger = Logger('TimelineManagerProvider');
|
|
|
|
@Riverpod(keepAlive: true)
|
|
class TimelineManager extends _$TimelineManager {
|
|
@override
|
|
Timeline build(Profile profile, TimelineIdentifiers timelineId) {
|
|
_tmpLogger.info('Building for $profile for $timelineId');
|
|
Future.microtask(() async =>
|
|
ref.read(timelineMaintainerProvider(profile).notifier).add(timelineId));
|
|
return Timeline(timelineId);
|
|
}
|
|
|
|
Future<void> updateTimeline(
|
|
TimelineRefreshType refreshType,
|
|
) async {
|
|
_tmpLogger.info(
|
|
'Updating w/$refreshType for timeline $timelineId for profile $profile ');
|
|
late final int lowestId;
|
|
late final int highestId;
|
|
late final InsertionType insertionType;
|
|
|
|
switch (refreshType) {
|
|
case TimelineRefreshType.refresh:
|
|
lowestId = 0;
|
|
highestId = 0;
|
|
insertionType = InsertionType.end;
|
|
break;
|
|
case TimelineRefreshType.loadOlder:
|
|
lowestId = state.lowestStatusId;
|
|
highestId = 0;
|
|
insertionType = InsertionType.end;
|
|
break;
|
|
case TimelineRefreshType.loadNewer:
|
|
lowestId = 0;
|
|
highestId = state.highestStatusId;
|
|
insertionType = InsertionType.beginning;
|
|
break;
|
|
}
|
|
(await ref
|
|
.read(timelineUpdaterProvider(profile).notifier)
|
|
.updateTimeline(timelineId, lowestId, highestId))
|
|
.match(onSuccess: (posts) {
|
|
_tmpLogger
|
|
.finest('Posts returned for adding to $timelineId: ${posts.length}');
|
|
final oldState = state;
|
|
final newState = state = state.addOrUpdate(
|
|
posts.map((p) => p.id).toList(),
|
|
insertionType: insertionType,
|
|
);
|
|
_tmpLogger.finest(
|
|
'Old post count: ${oldState.posts.length}, New Post count: ${newState.posts.length}');
|
|
_tmpLogger.finest('Old state == New state? ${oldState == newState}');
|
|
}, onError: (error) {
|
|
_tmpLogger.severe(
|
|
'Error updating timeline: $timelineId}',
|
|
Trace.current(),
|
|
);
|
|
});
|
|
}
|
|
|
|
bool removeTimelineEntry(String id) {
|
|
final hadItem = state.removeTimelineEntry(id);
|
|
if (hadItem) {
|
|
ref.notifyListeners();
|
|
}
|
|
_tmpLogger.finest('Remove entry $id? $hadItem');
|
|
|
|
return hadItem;
|
|
}
|
|
}
|
|
|
|
@riverpod
|
|
class UserMediaTimeline extends _$UserMediaTimeline {
|
|
static const limit = 50;
|
|
var nextPage = const PagingData(limit: limit);
|
|
var media = <MediaAttachment>[];
|
|
|
|
@override
|
|
Future<Result<List<MediaAttachment>, ExecError>> build(
|
|
Profile profile, String accountId) async {
|
|
await updateTimeline(reset: true, withNotification: false);
|
|
return Result.ok(media);
|
|
}
|
|
|
|
Future<Result<List<MediaAttachment>, ExecError>> updateTimeline(
|
|
{bool reset = true, bool withNotification = true}) async {
|
|
if (reset) {
|
|
nextPage = const PagingData(limit: limit);
|
|
media.clear();
|
|
}
|
|
|
|
final result = await ref.watch(
|
|
userMediaTimelineClientProvider(profile, accountId, page: nextPage)
|
|
.future);
|
|
return result
|
|
.withResult((result) {
|
|
for (final entries in result.data) {
|
|
media.addAll(entries.mediaAttachments);
|
|
}
|
|
nextPage = result.next!;
|
|
if (withNotification) {
|
|
ref.notifyListeners();
|
|
}
|
|
})
|
|
.transform((_) => media)
|
|
.execErrorCast();
|
|
}
|
|
}
|
|
|
|
@riverpod
|
|
class UserPostsAndCommentsTimeline extends _$UserPostsAndCommentsTimeline {
|
|
static const limit = 50;
|
|
var nextPage = const PagingData(limit: limit);
|
|
var entries = <TimelineEntry>[];
|
|
|
|
@override
|
|
Future<Result<List<TimelineEntry>, ExecError>> build(
|
|
Profile profile, String accountId) async {
|
|
await updateTimeline(reset: true, withNotification: false);
|
|
return Result.ok(entries);
|
|
}
|
|
|
|
Future<Result<List<TimelineEntry>, ExecError>> updateTimeline(
|
|
{bool reset = true, bool withNotification = true}) async {
|
|
if (reset) {
|
|
nextPage = const PagingData(limit: limit);
|
|
entries.clear();
|
|
}
|
|
|
|
final result = await ref.watch(userPostsAndCommentsTimelineClientProvider(
|
|
profile, accountId,
|
|
page: nextPage)
|
|
.future);
|
|
return result
|
|
.withResult((result) {
|
|
entries.addAll(result.data);
|
|
nextPage = result.next!;
|
|
if (withNotification) {
|
|
ref.notifyListeners();
|
|
}
|
|
})
|
|
.transform((_) => entries)
|
|
.execErrorCast();
|
|
}
|
|
}
|