kopia lustrzana https://gitlab.com/mysocialportal/relatica
				
				
				
			First cut at group timelines
							rodzic
							
								
									0704539a47
								
							
						
					
					
						commit
						3ede9ed04c
					
				| 
						 | 
				
			
			@ -8,9 +8,11 @@ import 'models/TimelineIdentifiers.dart';
 | 
			
		|||
import 'models/connection.dart';
 | 
			
		||||
import 'models/credentials.dart';
 | 
			
		||||
import 'models/exec_error.dart';
 | 
			
		||||
import 'models/group_data.dart';
 | 
			
		||||
import 'models/timeline_entry.dart';
 | 
			
		||||
import 'models/user_notification.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/timeline_entry_mastodon_extensions.dart';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -66,6 +68,32 @@ class FriendicaClient {
 | 
			
		|||
    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(
 | 
			
		||||
      {String userId = '', int page = 1, int count = 10}) async {
 | 
			
		||||
    _logger.finest(() => 'Getting user timeline for $userId');
 | 
			
		||||
| 
						 | 
				
			
			@ -298,7 +326,8 @@ class FriendicaClient {
 | 
			
		|||
        return 'timelines/public';
 | 
			
		||||
      case TimelineType.local:
 | 
			
		||||
        return 'timelines/public';
 | 
			
		||||
      case TimelineType.tag:
 | 
			
		||||
      case TimelineType.group:
 | 
			
		||||
        return 'timelines/list/${type.auxData}';
 | 
			
		||||
      case TimelineType.profile:
 | 
			
		||||
        return '/accounts/${type.auxData}/statuses';
 | 
			
		||||
      case TimelineType.self:
 | 
			
		||||
| 
						 | 
				
			
			@ -311,10 +340,10 @@ class FriendicaClient {
 | 
			
		|||
      case TimelineType.home:
 | 
			
		||||
      case TimelineType.global:
 | 
			
		||||
      case TimelineType.profile:
 | 
			
		||||
      case TimelineType.group:
 | 
			
		||||
        return '';
 | 
			
		||||
      case TimelineType.local:
 | 
			
		||||
        return 'local=true';
 | 
			
		||||
      case TimelineType.tag:
 | 
			
		||||
      case TimelineType.self:
 | 
			
		||||
        throw UnimplementedError('These types are not supported yet');
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,9 +2,26 @@ enum TimelineType {
 | 
			
		|||
  home,
 | 
			
		||||
  global,
 | 
			
		||||
  local,
 | 
			
		||||
  tag,
 | 
			
		||||
  group,
 | 
			
		||||
  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 {
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +37,8 @@ class TimelineIdentifiers {
 | 
			
		|||
  factory TimelineIdentifiers.home() =>
 | 
			
		||||
      TimelineIdentifiers(timeline: TimelineType.home);
 | 
			
		||||
 | 
			
		||||
  factory TimelineIdentifiers.profile(String profileId) => TimelineIdentifiers(
 | 
			
		||||
  factory TimelineIdentifiers.profile(String profileId) =>
 | 
			
		||||
      TimelineIdentifiers(
 | 
			
		||||
        timeline: TimelineType.profile,
 | 
			
		||||
        auxData: profileId,
 | 
			
		||||
      );
 | 
			
		||||
| 
						 | 
				
			
			@ -35,10 +53,10 @@ class TimelineIdentifiers {
 | 
			
		|||
  @override
 | 
			
		||||
  bool operator ==(Object other) =>
 | 
			
		||||
      identical(this, other) ||
 | 
			
		||||
      other is TimelineIdentifiers &&
 | 
			
		||||
          runtimeType == other.runtimeType &&
 | 
			
		||||
          timeline == other.timeline &&
 | 
			
		||||
          auxData == other.auxData;
 | 
			
		||||
          other is TimelineIdentifiers &&
 | 
			
		||||
              runtimeType == other.runtimeType &&
 | 
			
		||||
              timeline == other.timeline &&
 | 
			
		||||
              auxData == other.auxData;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode => timeline.hashCode ^ auxData.hashCode;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(
 | 
			
		||||
        padding: const EdgeInsets.all(8.0),
 | 
			
		||||
        child: Center(
 | 
			
		||||
          child: Column(
 | 
			
		||||
            crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
            mainAxisAlignment: MainAxisAlignment.start,
 | 
			
		||||
            children: [
 | 
			
		||||
              if (isComment && parentEntry != null)
 | 
			
		||||
                buildCommentPreview(context, parentEntry!),
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                controller: spoilerController,
 | 
			
		||||
                decoration: InputDecoration(
 | 
			
		||||
                  labelText: '$statusType Spoiler Text (optional)',
 | 
			
		||||
                  border: OutlineInputBorder(
 | 
			
		||||
                    borderSide: BorderSide(
 | 
			
		||||
                      color: Theme.of(context).backgroundColor,
 | 
			
		||||
          child: SingleChildScrollView(
 | 
			
		||||
            child: Column(
 | 
			
		||||
              crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
              mainAxisAlignment: MainAxisAlignment.start,
 | 
			
		||||
              children: [
 | 
			
		||||
                if (isComment && parentEntry != null)
 | 
			
		||||
                  buildCommentPreview(context, parentEntry!),
 | 
			
		||||
                TextFormField(
 | 
			
		||||
                  controller: spoilerController,
 | 
			
		||||
                  decoration: InputDecoration(
 | 
			
		||||
                    labelText: '$statusType Spoiler Text (optional)',
 | 
			
		||||
                    border: OutlineInputBorder(
 | 
			
		||||
                      borderSide: BorderSide(
 | 
			
		||||
                        color: Theme.of(context).backgroundColor,
 | 
			
		||||
                      ),
 | 
			
		||||
                      borderRadius: BorderRadius.circular(5.0),
 | 
			
		||||
                    ),
 | 
			
		||||
                    borderRadius: BorderRadius.circular(5.0),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              const VerticalPadding(),
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                maxLines: 10,
 | 
			
		||||
                controller: contentController,
 | 
			
		||||
                decoration: InputDecoration(
 | 
			
		||||
                  labelText: '$statusType Content',
 | 
			
		||||
                  alignLabelWithHint: true,
 | 
			
		||||
                  border: OutlineInputBorder(
 | 
			
		||||
                    borderSide: BorderSide(
 | 
			
		||||
                      color: Theme.of(context).backgroundColor,
 | 
			
		||||
                const VerticalPadding(),
 | 
			
		||||
                TextFormField(
 | 
			
		||||
                  maxLines: 10,
 | 
			
		||||
                  controller: contentController,
 | 
			
		||||
                  decoration: InputDecoration(
 | 
			
		||||
                    labelText: '$statusType Content',
 | 
			
		||||
                    alignLabelWithHint: true,
 | 
			
		||||
                    border: OutlineInputBorder(
 | 
			
		||||
                      borderSide: BorderSide(
 | 
			
		||||
                        color: Theme.of(context).backgroundColor,
 | 
			
		||||
                      ),
 | 
			
		||||
                      borderRadius: BorderRadius.circular(5.0),
 | 
			
		||||
                    ),
 | 
			
		||||
                    borderRadius: BorderRadius.circular(5.0),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              const VerticalPadding(),
 | 
			
		||||
              Row(
 | 
			
		||||
                mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
                children: [
 | 
			
		||||
                  ElevatedButton(
 | 
			
		||||
                    onPressed: () async => createStatus(context, manager),
 | 
			
		||||
                    child: const Text('Submit'),
 | 
			
		||||
                  ),
 | 
			
		||||
                  const HorizontalPadding(),
 | 
			
		||||
                  ElevatedButton(
 | 
			
		||||
                    onPressed: () {
 | 
			
		||||
                      context.pop();
 | 
			
		||||
                    },
 | 
			
		||||
                    child: const Text('Cancel'),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
                const VerticalPadding(),
 | 
			
		||||
                Row(
 | 
			
		||||
                  mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    ElevatedButton(
 | 
			
		||||
                      onPressed: () async => createStatus(context, manager),
 | 
			
		||||
                      child: const Text('Submit'),
 | 
			
		||||
                    ),
 | 
			
		||||
                    const HorizontalPadding(),
 | 
			
		||||
                    ElevatedButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        context.pop();
 | 
			
		||||
                      },
 | 
			
		||||
                      child: const Text('Cancel'),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,14 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:go_router/go_router.dart';
 | 
			
		||||
import 'package:logging/logging.dart';
 | 
			
		||||
import 'package:provider/provider.dart';
 | 
			
		||||
 | 
			
		||||
import '../controls/app_bottom_nav_bar.dart';
 | 
			
		||||
import '../controls/padding.dart';
 | 
			
		||||
import '../controls/timeline/timeline_panel.dart';
 | 
			
		||||
import '../models/TimelineIdentifiers.dart';
 | 
			
		||||
import '../models/group_data.dart';
 | 
			
		||||
import '../services/timeline_manager.dart';
 | 
			
		||||
 | 
			
		||||
class HomeScreen extends StatefulWidget {
 | 
			
		||||
  const HomeScreen({super.key});
 | 
			
		||||
| 
						 | 
				
			
			@ -18,18 +22,58 @@ class _HomeScreenState extends State<HomeScreen> {
 | 
			
		|||
 | 
			
		||||
  final postText = TextEditingController();
 | 
			
		||||
  var currentType = TimelineType.home;
 | 
			
		||||
  GroupData? currentGroup;
 | 
			
		||||
  final types = [
 | 
			
		||||
    TimelineType.home,
 | 
			
		||||
    TimelineType.global,
 | 
			
		||||
    TimelineType.local,
 | 
			
		||||
    TimelineType.group,
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    _logger.finest('Build');
 | 
			
		||||
    final groups = context
 | 
			
		||||
        .watch<TimelineManager>()
 | 
			
		||||
        .getGroups()
 | 
			
		||||
        .getValueOrElse(() => [])
 | 
			
		||||
        .toList();
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      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: [
 | 
			
		||||
          IconButton(
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
| 
						 | 
				
			
			@ -41,23 +85,11 @@ class _HomeScreenState extends State<HomeScreen> {
 | 
			
		|||
      ),
 | 
			
		||||
      body: Column(
 | 
			
		||||
        children: [
 | 
			
		||||
          DropdownButton<TimelineType>(
 | 
			
		||||
              value: currentType,
 | 
			
		||||
              items: types
 | 
			
		||||
                  .map((e) => DropdownMenuItem<TimelineType>(
 | 
			
		||||
                        value: e,
 | 
			
		||||
                        child: Text(e.name),
 | 
			
		||||
                      ))
 | 
			
		||||
                  .toList(),
 | 
			
		||||
              onChanged: (value) {
 | 
			
		||||
                setState(() {
 | 
			
		||||
                  currentType = value!;
 | 
			
		||||
                });
 | 
			
		||||
              }),
 | 
			
		||||
          Expanded(
 | 
			
		||||
              child: TimelinePanel(
 | 
			
		||||
            timeline: TimelineIdentifiers(
 | 
			
		||||
              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 '../globals.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 _connectionsByName = <String, Connection>{};
 | 
			
		||||
  final _connectionsByProfileUrl = <Uri, Connection>{};
 | 
			
		||||
  final _listsForConnection = <String, List<GroupData>>{};
 | 
			
		||||
 | 
			
		||||
  int get length => _connectionsById.length;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +21,7 @@ class ConnectionsManager {
 | 
			
		|||
    _connectionsById.clear();
 | 
			
		||||
    _connectionsByName.clear();
 | 
			
		||||
    _connectionsByProfileUrl.clear();
 | 
			
		||||
    _listsForConnection.clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool addConnection(Connection connection) {
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +45,28 @@ class ConnectionsManager {
 | 
			
		|||
    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) {
 | 
			
		||||
    final result = _connectionsById[id];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,10 @@ import '../globals.dart';
 | 
			
		|||
import '../models/TimelineIdentifiers.dart';
 | 
			
		||||
import '../models/entry_tree_item.dart';
 | 
			
		||||
import '../models/exec_error.dart';
 | 
			
		||||
import '../models/group_data.dart';
 | 
			
		||||
import '../models/timeline.dart';
 | 
			
		||||
import '../models/timeline_entry.dart';
 | 
			
		||||
import 'auth_service.dart';
 | 
			
		||||
import 'entry_manager_service.dart';
 | 
			
		||||
 | 
			
		||||
enum TimelineRefreshType {
 | 
			
		||||
| 
						 | 
				
			
			@ -21,12 +23,43 @@ class TimelineManager extends ChangeNotifier {
 | 
			
		|||
 | 
			
		||||
  final cachedTimelines = <TimelineIdentifiers, Timeline>{};
 | 
			
		||||
 | 
			
		||||
  final _groups = <String, GroupData>{};
 | 
			
		||||
 | 
			
		||||
  void clear() {
 | 
			
		||||
    cachedTimelines.clear();
 | 
			
		||||
    _groups.clear();
 | 
			
		||||
    getIt<EntryManagerService>().clear();
 | 
			
		||||
    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,
 | 
			
		||||
      {String spoilerText = '', String inReplyToId = ''}) async {
 | 
			
		||||
    final result = await getIt<EntryManagerService>().createNewStatus(
 | 
			
		||||
| 
						 | 
				
			
			@ -110,7 +143,9 @@ class TimelineManager extends ChangeNotifier {
 | 
			
		|||
    late final int highestId;
 | 
			
		||||
    switch (refreshType) {
 | 
			
		||||
      case TimelineRefreshType.refresh:
 | 
			
		||||
        lowestId = timeline.highestStatusId + 1;
 | 
			
		||||
        lowestId = timeline.highestStatusId == 0
 | 
			
		||||
            ? timeline.highestStatusId
 | 
			
		||||
            : timeline.highestStatusId + 1;
 | 
			
		||||
        highestId = 0;
 | 
			
		||||
        break;
 | 
			
		||||
      case TimelineRefreshType.loadOlder:
 | 
			
		||||
| 
						 | 
				
			
			@ -153,16 +188,8 @@ class TimelineManager extends ChangeNotifier {
 | 
			
		|||
    notifyListeners();
 | 
			
		||||
    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
 | 
			
		||||
// Have a purge caches button to start that over from scratch
 | 
			
		||||
// 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
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue