mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 15:53:32 +00:00
First cut at group timelines
This commit is contained in:
parent
0704539a47
commit
3ede9ed04c
8 changed files with 236 additions and 78 deletions
|
@ -8,9 +8,11 @@ import 'models/TimelineIdentifiers.dart';
|
||||||
import 'models/connection.dart';
|
import 'models/connection.dart';
|
||||||
import 'models/credentials.dart';
|
import 'models/credentials.dart';
|
||||||
import 'models/exec_error.dart';
|
import 'models/exec_error.dart';
|
||||||
|
import 'models/group_data.dart';
|
||||||
import 'models/timeline_entry.dart';
|
import 'models/timeline_entry.dart';
|
||||||
import 'models/user_notification.dart';
|
import 'models/user_notification.dart';
|
||||||
import 'serializers/friendica/connection_friendica_extensions.dart';
|
import 'serializers/friendica/connection_friendica_extensions.dart';
|
||||||
|
import 'serializers/mastodon/group_data_mastodon_extensions.dart';
|
||||||
import 'serializers/mastodon/notification_mastodon_extension.dart';
|
import 'serializers/mastodon/notification_mastodon_extension.dart';
|
||||||
import 'serializers/mastodon/timeline_entry_mastodon_extensions.dart';
|
import 'serializers/mastodon/timeline_entry_mastodon_extensions.dart';
|
||||||
|
|
||||||
|
@ -66,6 +68,32 @@ class FriendicaClient {
|
||||||
return response.mapValue((value) => true);
|
return response.mapValue((value) => true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FutureResult<List<GroupData>, ExecError> getGroups() async {
|
||||||
|
_logger.finest(() => 'Getting group (Mastodon List) data');
|
||||||
|
final url = 'https://$serverName/api/v1/lists';
|
||||||
|
final request = Uri.parse(url);
|
||||||
|
return (await _getApiListRequest(request).andThenSuccessAsync(
|
||||||
|
(listsJson) async => listsJson
|
||||||
|
.map((json) => GroupDataMastodonExtensions.fromJson(json))
|
||||||
|
.toList()))
|
||||||
|
.mapError((error) => error is ExecError
|
||||||
|
? error
|
||||||
|
: ExecError(type: ErrorType.localError, message: error.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureResult<List<GroupData>, ExecError> getMemberGroupsForConnection(
|
||||||
|
String connectionId) async {
|
||||||
|
_logger.finest(() =>
|
||||||
|
'Getting groups (Mastodon Lists) containing connection: $connectionId');
|
||||||
|
final url = 'https://$serverName/api/v1/accounts/$connectionId/lists';
|
||||||
|
final request = Uri.parse(url);
|
||||||
|
return (await _getApiListRequest(request).andThenSuccessAsync(
|
||||||
|
(listsJson) async => listsJson
|
||||||
|
.map((json) => GroupDataMastodonExtensions.fromJson(json))
|
||||||
|
.toList()))
|
||||||
|
.mapError((error) => error as ExecError);
|
||||||
|
}
|
||||||
|
|
||||||
FutureResult<List<TimelineEntry>, ExecError> getUserTimeline(
|
FutureResult<List<TimelineEntry>, ExecError> getUserTimeline(
|
||||||
{String userId = '', int page = 1, int count = 10}) async {
|
{String userId = '', int page = 1, int count = 10}) async {
|
||||||
_logger.finest(() => 'Getting user timeline for $userId');
|
_logger.finest(() => 'Getting user timeline for $userId');
|
||||||
|
@ -298,7 +326,8 @@ class FriendicaClient {
|
||||||
return 'timelines/public';
|
return 'timelines/public';
|
||||||
case TimelineType.local:
|
case TimelineType.local:
|
||||||
return 'timelines/public';
|
return 'timelines/public';
|
||||||
case TimelineType.tag:
|
case TimelineType.group:
|
||||||
|
return 'timelines/list/${type.auxData}';
|
||||||
case TimelineType.profile:
|
case TimelineType.profile:
|
||||||
return '/accounts/${type.auxData}/statuses';
|
return '/accounts/${type.auxData}/statuses';
|
||||||
case TimelineType.self:
|
case TimelineType.self:
|
||||||
|
@ -311,10 +340,10 @@ class FriendicaClient {
|
||||||
case TimelineType.home:
|
case TimelineType.home:
|
||||||
case TimelineType.global:
|
case TimelineType.global:
|
||||||
case TimelineType.profile:
|
case TimelineType.profile:
|
||||||
|
case TimelineType.group:
|
||||||
return '';
|
return '';
|
||||||
case TimelineType.local:
|
case TimelineType.local:
|
||||||
return 'local=true';
|
return 'local=true';
|
||||||
case TimelineType.tag:
|
|
||||||
case TimelineType.self:
|
case TimelineType.self:
|
||||||
throw UnimplementedError('These types are not supported yet');
|
throw UnimplementedError('These types are not supported yet');
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,26 @@ enum TimelineType {
|
||||||
home,
|
home,
|
||||||
global,
|
global,
|
||||||
local,
|
local,
|
||||||
tag,
|
group,
|
||||||
profile,
|
profile,
|
||||||
self,
|
self;
|
||||||
|
|
||||||
|
String toLabel() {
|
||||||
|
switch (this) {
|
||||||
|
case TimelineType.home:
|
||||||
|
return 'Home';
|
||||||
|
case TimelineType.global:
|
||||||
|
return 'Global';
|
||||||
|
case TimelineType.local:
|
||||||
|
return 'Local';
|
||||||
|
case TimelineType.group:
|
||||||
|
return 'Group';
|
||||||
|
case TimelineType.profile:
|
||||||
|
return 'Profile';
|
||||||
|
case TimelineType.self:
|
||||||
|
return 'Self';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimelineIdentifiers {
|
class TimelineIdentifiers {
|
||||||
|
@ -20,7 +37,8 @@ class TimelineIdentifiers {
|
||||||
factory TimelineIdentifiers.home() =>
|
factory TimelineIdentifiers.home() =>
|
||||||
TimelineIdentifiers(timeline: TimelineType.home);
|
TimelineIdentifiers(timeline: TimelineType.home);
|
||||||
|
|
||||||
factory TimelineIdentifiers.profile(String profileId) => TimelineIdentifiers(
|
factory TimelineIdentifiers.profile(String profileId) =>
|
||||||
|
TimelineIdentifiers(
|
||||||
timeline: TimelineType.profile,
|
timeline: TimelineType.profile,
|
||||||
auxData: profileId,
|
auxData: profileId,
|
||||||
);
|
);
|
||||||
|
@ -35,10 +53,10 @@ class TimelineIdentifiers {
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is TimelineIdentifiers &&
|
other is TimelineIdentifiers &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
timeline == other.timeline &&
|
timeline == other.timeline &&
|
||||||
auxData == other.auxData;
|
auxData == other.auxData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => timeline.hashCode ^ auxData.hashCode;
|
int get hashCode => timeline.hashCode ^ auxData.hashCode;
|
||||||
|
|
11
lib/models/group_data.dart
Normal file
11
lib/models/group_data.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
class GroupData {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
GroupData(this.id, this.name);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'GroupData{id: $id, name: $name}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,57 +77,59 @@ class _EditorScreenState extends State<EditorScreen> {
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: SingleChildScrollView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
if (isComment && parentEntry != null)
|
children: [
|
||||||
buildCommentPreview(context, parentEntry!),
|
if (isComment && parentEntry != null)
|
||||||
TextFormField(
|
buildCommentPreview(context, parentEntry!),
|
||||||
controller: spoilerController,
|
TextFormField(
|
||||||
decoration: InputDecoration(
|
controller: spoilerController,
|
||||||
labelText: '$statusType Spoiler Text (optional)',
|
decoration: InputDecoration(
|
||||||
border: OutlineInputBorder(
|
labelText: '$statusType Spoiler Text (optional)',
|
||||||
borderSide: BorderSide(
|
border: OutlineInputBorder(
|
||||||
color: Theme.of(context).backgroundColor,
|
borderSide: BorderSide(
|
||||||
|
color: Theme.of(context).backgroundColor,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(5.0),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(5.0),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const VerticalPadding(),
|
||||||
const VerticalPadding(),
|
TextFormField(
|
||||||
TextFormField(
|
maxLines: 10,
|
||||||
maxLines: 10,
|
controller: contentController,
|
||||||
controller: contentController,
|
decoration: InputDecoration(
|
||||||
decoration: InputDecoration(
|
labelText: '$statusType Content',
|
||||||
labelText: '$statusType Content',
|
alignLabelWithHint: true,
|
||||||
alignLabelWithHint: true,
|
border: OutlineInputBorder(
|
||||||
border: OutlineInputBorder(
|
borderSide: BorderSide(
|
||||||
borderSide: BorderSide(
|
color: Theme.of(context).backgroundColor,
|
||||||
color: Theme.of(context).backgroundColor,
|
),
|
||||||
|
borderRadius: BorderRadius.circular(5.0),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(5.0),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const VerticalPadding(),
|
||||||
const VerticalPadding(),
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
children: [
|
||||||
children: [
|
ElevatedButton(
|
||||||
ElevatedButton(
|
onPressed: () async => createStatus(context, manager),
|
||||||
onPressed: () async => createStatus(context, manager),
|
child: const Text('Submit'),
|
||||||
child: const Text('Submit'),
|
),
|
||||||
),
|
const HorizontalPadding(),
|
||||||
const HorizontalPadding(),
|
ElevatedButton(
|
||||||
ElevatedButton(
|
onPressed: () {
|
||||||
onPressed: () {
|
context.pop();
|
||||||
context.pop();
|
},
|
||||||
},
|
child: const Text('Cancel'),
|
||||||
child: const Text('Cancel'),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../controls/app_bottom_nav_bar.dart';
|
import '../controls/app_bottom_nav_bar.dart';
|
||||||
|
import '../controls/padding.dart';
|
||||||
import '../controls/timeline/timeline_panel.dart';
|
import '../controls/timeline/timeline_panel.dart';
|
||||||
import '../models/TimelineIdentifiers.dart';
|
import '../models/TimelineIdentifiers.dart';
|
||||||
|
import '../models/group_data.dart';
|
||||||
|
import '../services/timeline_manager.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
const HomeScreen({super.key});
|
const HomeScreen({super.key});
|
||||||
|
@ -18,18 +22,58 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
|
||||||
final postText = TextEditingController();
|
final postText = TextEditingController();
|
||||||
var currentType = TimelineType.home;
|
var currentType = TimelineType.home;
|
||||||
|
GroupData? currentGroup;
|
||||||
final types = [
|
final types = [
|
||||||
TimelineType.home,
|
TimelineType.home,
|
||||||
TimelineType.global,
|
TimelineType.global,
|
||||||
TimelineType.local,
|
TimelineType.local,
|
||||||
|
TimelineType.group,
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_logger.finest('Build');
|
_logger.finest('Build');
|
||||||
|
final groups = context
|
||||||
|
.watch<TimelineManager>()
|
||||||
|
.getGroups()
|
||||||
|
.getValueOrElse(() => [])
|
||||||
|
.toList();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Home'),
|
backgroundColor: Theme.of(context).canvasColor,
|
||||||
|
title: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
DropdownButton<TimelineType>(
|
||||||
|
value: currentType,
|
||||||
|
items: types
|
||||||
|
.map((e) => DropdownMenuItem<TimelineType>(
|
||||||
|
value: e,
|
||||||
|
child: Text(e.toLabel()),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
currentType = value!;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
const HorizontalPadding(),
|
||||||
|
if (currentType == TimelineType.group)
|
||||||
|
DropdownButton<GroupData>(
|
||||||
|
value: currentGroup,
|
||||||
|
items: groups
|
||||||
|
.map((g) => DropdownMenuItem<GroupData>(
|
||||||
|
value: g,
|
||||||
|
child: Text(g.name),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
currentGroup = value;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -41,23 +85,11 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
DropdownButton<TimelineType>(
|
|
||||||
value: currentType,
|
|
||||||
items: types
|
|
||||||
.map((e) => DropdownMenuItem<TimelineType>(
|
|
||||||
value: e,
|
|
||||||
child: Text(e.name),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
currentType = value!;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TimelinePanel(
|
child: TimelinePanel(
|
||||||
timeline: TimelineIdentifiers(
|
timeline: TimelineIdentifiers(
|
||||||
timeline: currentType,
|
timeline: currentType,
|
||||||
|
auxData: currentGroup?.id ?? '',
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import '../../models/group_data.dart';
|
||||||
|
|
||||||
|
extension GroupDataMastodonExtensions on GroupData {
|
||||||
|
static GroupData fromJson(Map<String, dynamic> json) => GroupData(
|
||||||
|
json['id'],
|
||||||
|
json['title'],
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,11 +1,19 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:result_monad/result_monad.dart';
|
import 'package:result_monad/result_monad.dart';
|
||||||
|
|
||||||
|
import '../globals.dart';
|
||||||
import '../models/connection.dart';
|
import '../models/connection.dart';
|
||||||
|
import '../models/exec_error.dart';
|
||||||
|
import '../models/group_data.dart';
|
||||||
|
import 'auth_service.dart';
|
||||||
|
|
||||||
class ConnectionsManager {
|
class ConnectionsManager extends ChangeNotifier {
|
||||||
|
static final _logger = Logger('$ConnectionsManager');
|
||||||
final _connectionsById = <String, Connection>{};
|
final _connectionsById = <String, Connection>{};
|
||||||
final _connectionsByName = <String, Connection>{};
|
final _connectionsByName = <String, Connection>{};
|
||||||
final _connectionsByProfileUrl = <Uri, Connection>{};
|
final _connectionsByProfileUrl = <Uri, Connection>{};
|
||||||
|
final _listsForConnection = <String, List<GroupData>>{};
|
||||||
|
|
||||||
int get length => _connectionsById.length;
|
int get length => _connectionsById.length;
|
||||||
|
|
||||||
|
@ -13,6 +21,7 @@ class ConnectionsManager {
|
||||||
_connectionsById.clear();
|
_connectionsById.clear();
|
||||||
_connectionsByName.clear();
|
_connectionsByName.clear();
|
||||||
_connectionsByProfileUrl.clear();
|
_connectionsByProfileUrl.clear();
|
||||||
|
_listsForConnection.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool addConnection(Connection connection) {
|
bool addConnection(Connection connection) {
|
||||||
|
@ -36,6 +45,28 @@ class ConnectionsManager {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<List<GroupData>, ExecError> getListsForUser(String id) {
|
||||||
|
final result = _listsForConnection[id] ?? [];
|
||||||
|
_refreshConnectionListData(id);
|
||||||
|
return Result.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshConnectionListData(String id) async {
|
||||||
|
_logger.finest('Refreshing member list data for Connection $id');
|
||||||
|
await getIt<AuthService>()
|
||||||
|
.currentClient
|
||||||
|
.andThenAsync((client) => client.getMemberGroupsForConnection(id))
|
||||||
|
.match(
|
||||||
|
onSuccess: (lists) {
|
||||||
|
_listsForConnection[id] = lists;
|
||||||
|
notifyListeners();
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
_logger.severe('Error getting list data for $id: $error');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Result<Connection, String> getById(String id) {
|
Result<Connection, String> getById(String id) {
|
||||||
final result = _connectionsById[id];
|
final result = _connectionsById[id];
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,10 @@ import '../globals.dart';
|
||||||
import '../models/TimelineIdentifiers.dart';
|
import '../models/TimelineIdentifiers.dart';
|
||||||
import '../models/entry_tree_item.dart';
|
import '../models/entry_tree_item.dart';
|
||||||
import '../models/exec_error.dart';
|
import '../models/exec_error.dart';
|
||||||
|
import '../models/group_data.dart';
|
||||||
import '../models/timeline.dart';
|
import '../models/timeline.dart';
|
||||||
import '../models/timeline_entry.dart';
|
import '../models/timeline_entry.dart';
|
||||||
|
import 'auth_service.dart';
|
||||||
import 'entry_manager_service.dart';
|
import 'entry_manager_service.dart';
|
||||||
|
|
||||||
enum TimelineRefreshType {
|
enum TimelineRefreshType {
|
||||||
|
@ -21,12 +23,43 @@ class TimelineManager extends ChangeNotifier {
|
||||||
|
|
||||||
final cachedTimelines = <TimelineIdentifiers, Timeline>{};
|
final cachedTimelines = <TimelineIdentifiers, Timeline>{};
|
||||||
|
|
||||||
|
final _groups = <String, GroupData>{};
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
cachedTimelines.clear();
|
cachedTimelines.clear();
|
||||||
|
_groups.clear();
|
||||||
getIt<EntryManagerService>().clear();
|
getIt<EntryManagerService>().clear();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<List<GroupData>, ExecError> getGroups() {
|
||||||
|
if (_groups.isEmpty) {
|
||||||
|
_refreshGroupData();
|
||||||
|
return Result.ok([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(_groups.values.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshGroupData() async {
|
||||||
|
_logger.finest('Refreshing member group data ');
|
||||||
|
await getIt<AuthService>()
|
||||||
|
.currentClient
|
||||||
|
.andThenAsync((client) => client.getGroups())
|
||||||
|
.match(
|
||||||
|
onSuccess: (groups) {
|
||||||
|
_groups.clear();
|
||||||
|
for (final group in groups) {
|
||||||
|
_groups[group.id] = group;
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
_logger.severe('Error getting list data: $error');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
FutureResult<bool, ExecError> createNewStatus(String text,
|
FutureResult<bool, ExecError> createNewStatus(String text,
|
||||||
{String spoilerText = '', String inReplyToId = ''}) async {
|
{String spoilerText = '', String inReplyToId = ''}) async {
|
||||||
final result = await getIt<EntryManagerService>().createNewStatus(
|
final result = await getIt<EntryManagerService>().createNewStatus(
|
||||||
|
@ -110,7 +143,9 @@ class TimelineManager extends ChangeNotifier {
|
||||||
late final int highestId;
|
late final int highestId;
|
||||||
switch (refreshType) {
|
switch (refreshType) {
|
||||||
case TimelineRefreshType.refresh:
|
case TimelineRefreshType.refresh:
|
||||||
lowestId = timeline.highestStatusId + 1;
|
lowestId = timeline.highestStatusId == 0
|
||||||
|
? timeline.highestStatusId
|
||||||
|
: timeline.highestStatusId + 1;
|
||||||
highestId = 0;
|
highestId = 0;
|
||||||
break;
|
break;
|
||||||
case TimelineRefreshType.loadOlder:
|
case TimelineRefreshType.loadOlder:
|
||||||
|
@ -153,16 +188,8 @@ class TimelineManager extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// All statuses get dumped into the entity mangager and get full assembled posts out of it
|
|
||||||
// Timeline keeps track of posts level only so can query timeline manager for those
|
|
||||||
// Should put backing store on timelines and entity manager so can recover from restart faster
|
// Should put backing store on timelines and entity manager so can recover from restart faster
|
||||||
// Have a purge caches button to start that over from scratch
|
// Have a purge caches button to start that over from scratch
|
||||||
// Should have a contacts manager with backing store as well
|
// Should have a contacts manager with backing store as well
|
||||||
// Timeline view is new control that knows how to load timeline, scrolling around with refresh and get more
|
|
||||||
// Timeline Item view displays itself and children
|
|
||||||
// Has "Add Comment" value
|
|
||||||
// Has like/dislke
|
|
||||||
// Has reshare/quote reshare (if can get that working somehow)
|
|
||||||
// If our own has delete
|
// If our own has delete
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue