Merge branch 'prep-v0.2.0' into 'main'

Prep v0.2.0

See merge request mysocialportal/relatica!22
This commit is contained in:
HankG 2023-03-15 17:40:14 +00:00
commit e7c99b5021
21 changed files with 359 additions and 313 deletions

View file

@ -1,6 +1,33 @@
# Relatica Change Log
## Version 0.1.0b3 (beta)
## Version 0.2.0 (beta), 15 March 2023
* Changes
* Uses Google's Material 3 UI instead of Material 2
* Drawer idiom replaces the menu screen idiom. Drawer contains list of logged in accounts at the top to quickly
switch between, followed by the menu items previously on the menu screen.
* "Sign In Screen" now also the "Manage Accounts" screen
* Username/password settings split the username/servername field so can use the email address as the login like
with
the website
* Have sections listing logged in and logged out accounts.
* Can permanently delete previous credentials if no longer want them.
* Added back swiping down for refresh on timelines, notifications, and contacts. Also changed the progress indicator
to be a linear bar at the top
* Unread notifications counts no longer appear.
* "New Post" button is now a floating action button at the bottom right of the timeline
* Fixes
* Toggle like status on nested comments no longer generates an error.
* New Features
* Support for OAuth Login as well as username/password
* Support for multiple concurrent logins and switching between them
* Added paging to notifications
* Ability to list who liked or reshared a post by clicking on the respective summary.
* Unread direct messages indicators show up in the notifications panel
* Data Pages manager allows for re-requesting data from server based on previous calls. Currently used only for
notifications.
## Version 0.1.0b3 (beta), 30 January 2023
* Changes
* The timeline names have been changed to:
@ -17,6 +44,8 @@
* Keyboard type on login screen for username field is now "email" type
* New Features
* A combined network status/refresh icon on timeline, notifications, and gallery panel
* More detailed user profile screen for users which now includes things like their description, handle and common
name, etc.
* Paging in gallery list to incremental load of larger galleries (also sorted newest first)
* Image View Screen has a Carousel feature for swiping through images of a post, gallery, etc.
* Can copy post/comment Raw HTML text, image descriptions, and Direct Message text content
@ -25,7 +54,7 @@
* Dialog Box "Menu" on posts/comments to more easily decide to navigate to it internally (Posts only), open in the
system's default browser, or copy the URL
## Version 0.1.0b2 (beta)
## Version 0.1.0b2 (beta), 27 January 2023
* Fixes
* Scrollwheel zooming now works correctly on desktop
@ -47,7 +76,7 @@
* Start a new conversation by searching for a known contact "type @ and start typing like in
posts/comments"
## Version 0.1.0 (beta)
## Version 0.1.0 (beta), 20 January 2023
* Initial public release, as an early beta.
* Major working features (at least in initial implementation versions):

View file

@ -2,17 +2,19 @@
A Flutter application for interfacing with the Friendica social network.
<img src="screenshots/v.0.1.0/windows/RelaticaFirstScreenshot.png" alt="Relatica v0.1.0 on Windows Screenshot" width="300px"/>
<img src="screenshots/v0.2.0/linux/relatica_v0.2.0_home.png" alt="Relatica v0.2.0 on Linux Home Screen Screenshot" width="300px"/>
<img src="screenshots/v0.2.0/linux/relatica_v0.2.0_drawer.png" alt="Relatica v0.2.0 on Linux Home Expanded Drawer Screenshot" width="300px"/>
## Project Objectives
* Have a native app client on mobile (Apple and Android) and desktop (Linux, Mac, and Windows)
* Providing a simpler UX for people to interact with Friendica
* Providing a better low-bandwidth environment experience than the web-app version running in a browser
* Reduce server side loading in the new fediverse era by doing techniques like leveraging paging of comments, local caching, and lazy loading.
* Reduce server side loading in the new fediverse era by doing techniques like leveraging paging of comments, local
caching, and lazy loading.
* Provide more intuitive mechanisms for adding things like Content Warning/Spoiler text and image ALT-text
## Current Status
The project is currently in an early-beta state. If you'd like to use it at this time please
@ -22,7 +24,6 @@ It is possible to install it now by following the [install instructions](install
If you would like to contribute please see [this Developers Notes](developers.md) section.
## Community and Support
[Relatica Community Matrix Discussion Room](https://matrix.to/#/#relatica-community:myportal.social)
@ -30,6 +31,7 @@ If you would like to contribute please see [this Developers Notes](developers.md
[Issue Tracker](https://gitlab.com/mysocialportal/relatica/-/issues)
### Things I could use help from community on:
* More coders and testers are always welcome
* CI/CD
* Packaging for Linux operating systems using Flatpak, Snap, etc. (or individual packages per operating system)
@ -39,4 +41,5 @@ If you would like to contribute please see [this Developers Notes](developers.md
* Spreading the word
## License
Relatica is licensed with the [Mozilla Public License 2.0 (MPL)](LICENSE) copyleft license.

View file

@ -55,7 +55,8 @@ wanted to lay out some expectations before getting into the small details
### Things that work
* Logging in with username/password. These are stored using the OS specific key vaults
* Logging in with username/password and OAuth. These are stored using the OS specific key vaults
* Having multiple accounts logged in at the same time and switching between them
* Writing **public** posts
* Typing @ brings up a list of all known fediverse accounts that the app has ever seen as you
type (but not all that your server has seen)
@ -84,13 +85,14 @@ wanted to lay out some expectations before getting into the small details
specific buttons (this will probably change in the near future)
* Refresh notifications and contacts gets updates to that respective data (this may
change in the near future)
* Show list of who liked and reshared posts/comments
* Detailed information about users, such as description fields
### Big things I want to have working in the near future:
* Show list of who liked and reshared posts/comments
* More timeline types like Comments, "By Activity", etc.
* Better timeline UX to allow for "in-fill"
* More detailed profile information for users and logged in user
* Fine grain details about users (last post, last update)
* Better data display on larger format displays by doing things like:
* Allowing images/thumbnails to be larger
* Limiting maximum width of timeline columns
@ -124,7 +126,6 @@ wanted to lay out some expectations before getting into the small details
* Nitter replacement of Twitter links
* User configurable Server blocking
* Server-side searching tied into profiles, posts, hashtags
* OAuth logins
* Being able to ignore/unignore users
* Deleting images and entire galleries
* Events
@ -134,7 +135,6 @@ wanted to lay out some expectations before getting into the small details
* Account creation through the application
* Creating new forums through the application
* Site administration
* Two-factor authentication
* Adding videos or files to posts
* Multi-account logins
@ -143,13 +143,13 @@ wanted to lay out some expectations before getting into the small details
### Broken and hopefully fixed in the very near future:
* Resharing of Diaspora federated posts is currently broken server side. All other posts should be
reshareable. (Already fixed in Friendica but not in the stable release yet)
reshareable. (Fixed in Friendica 2023.03)
* Content Warnings/Spoiler Text on *posts* aren't federating over to Mastodon well so only use it on
Comments for now
Comments for now (fixed on servers running Friendica 2023.03)
* ALT text on images should not have any quotation marks as it breaks when federating over to
Diaspora for the time being
* Portrait videos overflow their boxes in the timeline
* Blocked/ignored user's content is still returned by the API
* Blocked/ignored user's content is still returned by the API (Fixed in Friendica 2023.03)
* Paging for some of the endpoints either isn't wired in yet or is not working as needed server
side. That includes things like:
* Friend requests
@ -164,19 +164,12 @@ wanted to lay out some expectations before getting into the small details
* The only post type that is supported right now are public posts therefore all posts you write
through the app will have this privacy level.
* Notifications need to be manually refreshed.
* Responsiveness can be laggy. Sometimes hitting buttons doesn't seem to do something but it is
doing a network request. I know I need to improve that
* In galleries you need to double click on the picture to open the preview. It is remnants of an
experiment I was doing on more consistent UX which feels broken so I'm changing.
* Sometimes timelines get confused so swapping between the different groups/timelines creates a
muddled display. Restarting the app fixes this.
* Some images within posts, usually graphical emojis, are rendered drastically larger than they
should be.
* On Linux you will need to re-enter your credentials each time you use the app for the first time
after logging in.
* Groups are listed by the order they were created not alphabetically in the drop down menu
* Liking a nested comment can appear to lock up (it stays grayed out). Navigating back and forth
fixes that.
* On Linux you will need to login to the key manager and unlock it before opening the app. Some Linux versions do this
automatically while others do not.
* The "in fill" problem: Timelines fill only at the ends with at most 20 posts per call. So let's
say you logged in at 09:00 and the initial pulls went from 07:00 to 09:00:
```

View file

@ -6,12 +6,11 @@ For more information about the current beta testing program
# Latest Binaries:
* [Android v0.1.0b3](https://mysocialportal-relatica.nyc3.cdn.digitaloceanspaces.com/v0.1.0/relatica_v0.1.0b3.apk.zip)
* iPhone/iPad v0.1.0b3: This is only available through TestFlight. Please contact me for access.
* [Windows (Intel) v0.1.0b3](https://mysocialportal-relatica.nyc3.cdn.digitaloceanspaces.com/v0.1.0/Relatica_v0.1.0b3_x64_win.zip)
* [macOS v0.1.0b3](https://mysocialportal-relatica.nyc3.cdn.digitaloceanspaces.com/v0.1.0/Relatica_v0.1.0b3_mac.zip)
also through TestFlight
* [Linux (Intel Ubuntu 22) v0.1.0b3](https://mysocialportal-relatica.nyc3.cdn.digitaloceanspaces.com/v0.1.0/relatica_v0.1.0b3_linux_x64_ubuntu22.zip)
* [Android v0.2.0](https://mysocialportal-relatica.nyc3.cdn.digitaloceanspaces.com/v0.2.0%2Frelatica_v0.2.0.apk.zip)
* iPhone/iPad v0.2.0: This is only available through TestFlight. Please contact me for access.
* [Windows (Intel) v0.2.0](https://mysocialportal-relatica.nyc3.cdn.digitaloceanspaces.com/v0.2.0%2FRelatica_v0.2.0_x64_win.zip)
* macOS v0.2.0 This is only available through TestFlight. Please contact me for access.
* [Linux (Intel Ubuntu 22) v0.2.0](https://mysocialportal-relatica.nyc3.cdn.digitaloceanspaces.com/v0.2.0%2Frelatica_v0.2.0_linux_x64_ubuntu22.zip)
## Mobile

View file

@ -42,12 +42,7 @@ class AppBottomNavBar extends StatelessWidget {
switch (newButton) {
case NavBarButtons.timelines:
try {
Navigator.of(context)
.popUntil(ModalRoute.withName(ScreenPaths.timelines));
} catch (e) {
context.go(ScreenPaths.timelines);
}
break;
case NavBarButtons.notifications:
context.pushNamed(ScreenPaths.notifications);

View file

@ -9,7 +9,8 @@ import '../services/auth_service.dart';
class StandardAppDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Drawer(
return SafeArea(
child: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
@ -17,7 +18,7 @@ class StandardAppDrawer extends StatelessWidget {
(p) => ListTile(
onTap: () async {
await getIt<AccountsService>().setActiveProfile(p);
if (context.mounted) {
if (context.mounted && context.canPop()) {
context.pop();
}
},
@ -68,6 +69,7 @@ class StandardAppDrawer extends StatelessWidget {
// }),
],
),
),
);
}

View file

@ -24,7 +24,16 @@ class TimelinePanel extends StatelessWidget {
return Center(child: Text('Error getting timeline: ${result.error}'));
}
final items = result.value;
return ListView.separated(
return RefreshIndicator(
onRefresh: () async {
manager.updateTimeline(
timeline,
TimelineRefreshType.refresh,
);
return;
},
child: ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
if (index == 0) {
return TextButton(
@ -41,8 +50,8 @@ class TimelinePanel extends StatelessWidget {
}
final itemIndex = index - 1;
final item = items[itemIndex];
_logger
.finest('Building item: $itemIndex: ${item.entry.toShortString()}');
_logger.finest(
'Building item: $itemIndex: ${item.entry.toShortString()}');
return PostControl(
originalItem: item,
scrollToId: item.id,
@ -53,6 +62,7 @@ class TimelinePanel extends StatelessWidget {
},
separatorBuilder: (context, index) => Divider(),
itemCount: items.length + 2,
),
);
}
}

View file

@ -17,7 +17,11 @@ class ObjectBoxCache {
final docsDir = await getApplicationSupportDirectory();
final path = p.join(docsDir.path, baseDir, subDir);
try {
Directory(path).createSync(recursive: true);
} catch (e) {
_logger.severe('Error creating ObjectCachePathDirectory: $e');
}
_logger.info('ObjectBoxCache path: $path');
final store = await openStore(
directory: path, macosApplicationGroup: 'T69YZGT58U.relatica');

View file

@ -87,7 +87,7 @@ Future<void> dependencyInjectionInitialization() async {
Future<void> updateProfileDependencyInjectors(Profile profile) async {
final objectBox = await ObjectBoxCache.create(
baseDir: 'profileboxcaches',
subDir: '${profile.username}_${profile.serverName}',
subDir: '${profile.id}_${profile.serverName}',
);
final connectionReposSelector =
getIt<ActiveProfileSelector<IConnectionsRepo>>();

View file

@ -41,9 +41,11 @@ class PagesManager<TResult, TID> {
}
final result = await onRequest(PagingData(limit: limit));
if (result.isSuccess) {
if (result.value.previous != null || result.value.next != null) {
final newPage = result.value.map((data) => idMapper(data));
_pages.add(newPage);
}
}
return result;
}

View file

@ -5,9 +5,7 @@ import 'package:provider/provider.dart';
import '../controls/app_bottom_nav_bar.dart';
import '../controls/current_profile_button.dart';
import '../controls/padding.dart';
import '../controls/standard_app_drawer.dart';
import '../controls/status_and_refresh_button.dart';
import '../globals.dart';
import '../models/connection.dart';
import '../routes.dart';
@ -45,10 +43,11 @@ class _ContactsScreenState extends State<ContactsScreen> {
);
late Widget body;
if (contacts.isEmpty) {
body = const SingleChildScrollView(
body = SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Text('No Contacts'),
);
child: Center(
child: Text('No contacts'),
));
} else {
body = ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
@ -66,31 +65,12 @@ class _ContactsScreenState extends State<ContactsScreen> {
separatorBuilder: (context, index) => const Divider(),
itemCount: contacts.length);
}
final profileButton = buildCurrentProfileButton(context);
return Scaffold(
drawer: StandardAppDrawer(),
body: SafeArea(
child: RefreshIndicator(
onRefresh: () async {
if (nss.connectionUpdateStatus.value) {
return;
}
await manager.updateAllContacts();
},
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
if (profileButton != null) ...[
SizedBox(width: 70.0, child: profileButton),
const HorizontalPadding(),
],
Expanded(
child: TextField(
return SafeArea(
child: Scaffold(
appBar: AppBar(
leading: buildCurrentProfileButton(context),
title: TextField(
onChanged: (value) {
setState(() {
filterText = value.toLowerCase();
@ -108,19 +88,28 @@ class _ContactsScreenState extends State<ContactsScreen> {
),
),
),
const HorizontalPadding(),
StatusAndRefreshButton(
drawer: StandardAppDrawer(),
body: SafeArea(
child: RefreshIndicator(
onRefresh: () async {
if (nss.connectionUpdateStatus.value) {
return;
}
manager.updateAllContacts();
return;
},
child: Column(
children: [
ValueListenableBuilder(
valueListenable: nss.connectionUpdateStatus,
refreshFunction: () async =>
await manager.updateAllContacts(),
)
],
),
),
const VerticalPadding(),
Expanded(
child: body,
),
builder: (context2, executing, _) {
if (executing) {
return const LinearProgressIndicator();
}
return const SizedBox();
}),
Expanded(child: body),
],
),
),
@ -128,6 +117,7 @@ class _ContactsScreenState extends State<ContactsScreen> {
bottomNavigationBar: AppBottomNavBar(
currentButton: NavBarButtons.contacts,
),
),
);
}
}

View file

@ -7,7 +7,6 @@ import 'package:provider/provider.dart';
import '../controls/app_bottom_nav_bar.dart';
import '../controls/padding.dart';
import '../controls/standard_app_drawer.dart';
import '../controls/status_and_refresh_button.dart';
import '../controls/timeline/timeline_panel.dart';
import '../globals.dart';
import '../models/TimelineIdentifiers.dart';
@ -72,6 +71,22 @@ class _HomeScreenState extends State<HomeScreen> {
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (currentType == TimelineType.group)
PopupMenuButton<TimelineType>(
initialValue: currentType,
// Callback that sets the selected popup menu item.
onSelected: (value) {
setState(() {
currentType = value;
});
},
itemBuilder: (BuildContext context) => types
.map((e) => PopupMenuItem<TimelineType>(
value: e,
child: Text(e.toLabel()),
))
.toList()),
if (currentType != TimelineType.group)
DropdownButton<TimelineType>(
value: currentType,
items: types
@ -85,7 +100,9 @@ class _HomeScreenState extends State<HomeScreen> {
currentType = value!;
});
}),
const HorizontalPadding(),
const HorizontalPadding(
width: 5.0,
),
if (currentType == TimelineType.group)
DropdownButton<GroupData>(
value: currentGroup,
@ -102,36 +119,32 @@ class _HomeScreenState extends State<HomeScreen> {
}),
],
),
actions: [
StatusAndRefreshButton(
valueListenable: nss.timelineLoadingStatus,
refreshFunction: () async => await manager.updateTimeline(
currentTimeline, TimelineRefreshType.refresh),
buttonColor: Theme.of(context).textTheme.bodyLarge?.color,
),
IconButton(
onPressed: () {
context.push('/post/new');
},
icon: Icon(
Icons.edit,
color: Theme.of(context).textTheme.bodyLarge?.color,
),
),
],
),
body: Column(
body: Center(
child: Column(
children: [
Expanded(
child: TimelinePanel(
timeline: currentTimeline,
)),
ValueListenableBuilder(
valueListenable: nss.timelineLoadingStatus,
builder: (context2, executing, _) {
if (executing) {
return const LinearProgressIndicator();
}
return const SizedBox();
}),
Expanded(child: TimelinePanel(timeline: currentTimeline)),
],
),
),
drawer: StandardAppDrawer(),
bottomNavigationBar: const AppBottomNavBar(
currentButton: NavBarButtons.timelines,
),
);
floatingActionButton: FloatingActionButton.small(
onPressed: () {
context.push('/post/new');
},
child: Icon(Icons.add),
));
}
}

View file

@ -34,7 +34,6 @@ class NotificationsScreen extends StatelessWidget {
StatusAndRefreshButton(
valueListenable: nss.notificationsUpdateStatus,
refreshFunction: () async => manager.updateNotifications(),
busyColor: Theme.of(context).colorScheme.background,
),
IconButton(
onPressed: () async => _clearAllNotifications(context, manager),
@ -42,7 +41,6 @@ class NotificationsScreen extends StatelessWidget {
),
];
if (notifications.isEmpty) {
manager.updateNotifications();
title = 'Notifications';
body = Center(
child: Column(
@ -52,12 +50,15 @@ class NotificationsScreen extends StatelessWidget {
));
} else {
final unreadCount = notifications.where((e) => !e.dismissed).length;
title = 'Notifications ($unreadCount)';
title =
'Notifications'; //TODO wire in the summary count data if has that endpoint
body = RefreshIndicator(
onRefresh: () async {
manager.updateNotifications();
return;
},
child: ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
if (index == 0) {
return TextButton(

View file

@ -136,6 +136,7 @@ class _SignInScreenState extends State<SignInScreen> {
),
const VerticalPadding(),
TextFormField(
autocorrect: false,
readOnly: existingAccount,
autovalidateMode: AutovalidateMode.onUserInteraction,
controller: serverNameController,

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:relatica/utils/active_profile_selector.dart';
import '../controls/padding.dart';
import '../globals.dart';
@ -40,7 +41,10 @@ class _UserProfileScreenState extends State<UserProfileScreen> {
@override
Widget build(BuildContext context) {
final manager = context.watch<ConnectionsManager>();
final manager = context
.watch<ActiveProfileSelector<ConnectionsManager>>()
.activeEntry
.value;
final body = manager.getById(widget.userId).fold(onSuccess: (profile) {
final notMyProfile =
getIt<AccountsService>().currentProfile.userId != profile.id;

View file

@ -14,10 +14,12 @@ import 'auth_service.dart';
class DirectMessageService extends ChangeNotifier {
static final _logger = Logger('$DirectMessageService');
final _threads = <String, DirectMessageThread>{};
var _firstLoading = true;
List<DirectMessageThread> getThreads({bool unreadyOnly = false}) {
if (_threads.isEmpty) {
if (_threads.isEmpty && _firstLoading) {
updateThreads();
_firstLoading = false;
}
if (unreadyOnly) {

View file

@ -68,8 +68,7 @@ class EntryManagerService extends ChangeNotifier {
FutureResult<bool, ExecError> deleteEntryById(String id) async {
_logger.finest('Delete entry: $id');
final result =
await StatusesClient(getIt<AccountsService>().currentProfile)
final result = await StatusesClient(getIt<AccountsService>().currentProfile)
.deleteEntryById(id);
if (result.isFailure) {
return result.errorCast();
@ -80,7 +79,8 @@ class EntryManagerService extends ChangeNotifier {
return Result.ok(true);
}
FutureResult<bool, ExecError> createNewStatus(String text, {
FutureResult<bool, ExecError> createNewStatus(
String text, {
String spoilerText = '',
String inReplyToId = '',
required NewEntryMediaItems mediaItems,
@ -129,8 +129,7 @@ class EntryManagerService extends ChangeNotifier {
}
}
final result =
await StatusesClient(getIt<AccountsService>().currentProfile)
final result = await StatusesClient(getIt<AccountsService>().currentProfile)
.createNewStatus(
text: text,
spoilerText: spoilerText,
@ -184,12 +183,10 @@ class EntryManagerService extends ChangeNotifier {
}
itemsResult.value.sort((t1, t2) => t1.id.compareTo(t2.id));
final updatedPosts = await processNewItems(
itemsResult.value, client.profile.userId, client);
final updatedPosts =
await processNewItems(itemsResult.value, client.profile.userId, client);
_logger.finest(() {
final postCount = _entries.values
.where((e) => e.parentId.isEmpty)
.length;
final postCount = _entries.values.where((e) => e.parentId.isEmpty).length;
final commentCount = _entries.length - postCount;
final orphanCount = _entries.values
.where(
@ -200,9 +197,11 @@ class EntryManagerService extends ChangeNotifier {
return Result.ok(updatedPosts);
}
Future<List<EntryTreeItem>> processNewItems(List<TimelineEntry> items,
Future<List<EntryTreeItem>> processNewItems(
List<TimelineEntry> items,
String currentId,
FriendicaClient? client,) async {
FriendicaClient? client,
) async {
items.sort((i1, i2) => int.parse(i1.id).compareTo(int.parse(i2.id)));
final allSeenItems = [...items];
for (final item in items) {
@ -229,9 +228,7 @@ class EntryManagerService extends ChangeNotifier {
await StatusesClient(getIt<AccountsService>().currentProfile)
.getPostOrComment(o.id, fullContext: true)
.andThenSuccessAsync((items) async {
final parentPostId = items
.firstWhere((e) => e.parentId.isEmpty)
.id;
final parentPostId = items.firstWhere((e) => e.parentId.isEmpty).id;
_parentPostIds[o.id] = parentPostId;
allSeenItems.addAll(items);
for (final item in items) {
@ -301,9 +298,7 @@ class EntryManagerService extends ChangeNotifier {
.toList();
_logger.finest(
'Completed processing new items ${client == null
? 'sub level'
: 'top level'}');
'Completed processing new items ${client == null ? 'sub level' : 'top level'}');
return updatedPosts;
}
@ -312,8 +307,7 @@ class EntryManagerService extends ChangeNotifier {
final client = StatusesClient(getIt<AccountsService>().currentProfile);
final result = await client
.getPostOrComment(id, fullContext: false)
.andThenAsync((rootItems) async =>
await client
.andThenAsync((rootItems) async => await client
.getPostOrComment(id, fullContext: true)
.andThenSuccessAsync(
(contextItems) async => [...rootItems, ...contextItems]))
@ -375,10 +369,9 @@ class EntryManagerService extends ChangeNotifier {
return Result.ok(true);
}
FutureResult<EntryTreeItem, ExecError> toggleFavorited(String id,
bool newStatus) async {
final client =
InteractionsClient(getIt<AccountsService>().currentProfile);
FutureResult<EntryTreeItem, ExecError> toggleFavorited(
String id, bool newStatus) async {
final client = InteractionsClient(getIt<AccountsService>().currentProfile);
final result = await client.changeFavoriteStatus(id, newStatus);
if (result.isFailure) {
return result.errorCast();
@ -388,7 +381,7 @@ class EntryManagerService extends ChangeNotifier {
_entries[update.id] = update;
final node = update.parentId.isEmpty
? _postNodes[update.id]!
: _postNodes[update.parentId]!.getChildById(update.id)!;
: _postNodes[_parentPostIds[update.parentId]]!;
notifyListeners();
return Result.ok(_nodeToTreeItem(node, client.profile.userId));

View file

@ -22,8 +22,13 @@ class NotificationsManager extends ChangeNotifier {
idMapper: (nn) => nn.map((n) => n.id).toList(),
onRequest: _clientGetNotificationsRequest,
);
var _firstLoad = true;
List<UserNotification> get notifications {
if (_notifications.isEmpty && _firstLoad) {
updateNotifications();
_firstLoad = false;
}
final result = List<UserNotification>.from(_notifications.values);
result.sort((n1, n2) {
if (n1.dismissed == n2.dismissed) {
@ -46,7 +51,7 @@ class NotificationsManager extends ChangeNotifier {
}
FutureResult<List<UserNotification>, ExecError> updateNotifications() async {
const initialPull = 25;
const initialPull = 100;
final nn = <UserNotification>[];
if (_pm.pages.isEmpty) {
final result = await _pm.initialize(initialPull);

View file

@ -2,7 +2,7 @@ name: relatica
description: A mobile and desktop client for interacting with the Friendica social network
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.1.0+1
version: 0.2.0+1
environment:
sdk: '>=2.18.2 <3.0.0'

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB