mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 12:23:31 +00:00
Merge branch 'circle-vs-channel-awareness' into 'main'
Circle vs channel awareness See merge request mysocialportal/relatica!61
This commit is contained in:
commit
19cca28692
19 changed files with 353 additions and 150 deletions
|
@ -13,12 +13,18 @@ if (flutterRoot == null) {
|
|||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
flutterVersionCode = '3'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
flutterVersionName = '0.9.0'
|
||||
}
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
@ -53,11 +59,27 @@ android {
|
|||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
if (System.getenv()["CI"]) { // CI=true is exported by Codemagic
|
||||
storeFile file(System.getenv()["CM_KEYSTORE_PATH"])
|
||||
storePassword System.getenv()["CM_KEYSTORE_PASSWORD"]
|
||||
keyAlias System.getenv()["CM_KEY_ALIAS"]
|
||||
keyPassword System.getenv()["CM_KEY_PASSWORD"]
|
||||
} else {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
50
codemagic.yaml
Normal file
50
codemagic.yaml
Normal file
|
@ -0,0 +1,50 @@
|
|||
workflows:
|
||||
android-workflow:
|
||||
name: Android Workflow
|
||||
instance_type: mac_mini_m1
|
||||
max_build_duration: 120
|
||||
environment:
|
||||
android_signing:
|
||||
- codemagickeystore
|
||||
groups:
|
||||
- google_play # <-- (Includes GCLOUD_SERVICE_ACCOUNT_CREDENTIALS)
|
||||
vars:
|
||||
PACKAGE_NAME: "social.myportal.relatica" # <-- Put your package name here
|
||||
GOOGLE_PLAY_TRACK: "internal"
|
||||
flutter: stable
|
||||
scripts:
|
||||
- name: Set up local.properties
|
||||
script: |
|
||||
echo "flutter.sdk=$HOME/programs/flutter" > "$CM_BUILD_DIR/android/local.properties"
|
||||
- name: Get Flutter packages
|
||||
script: |
|
||||
flutter packages pub get
|
||||
# - name: Flutter analyze
|
||||
# script: |
|
||||
# flutter analyze
|
||||
# ignore_failure: true
|
||||
- name: Flutter unit tests
|
||||
script: |
|
||||
flutter test
|
||||
ignore_failure: true
|
||||
- name: Build AAB with Flutter
|
||||
script: |
|
||||
BUILD_NUMBER=$(($(google-play get-latest-build-number --package-name "$PACKAGE_NAME" --tracks="$GOOGLE_PLAY_TRACK") + 1))
|
||||
flutter build appbundle --release \
|
||||
--build-name=0.9.0 \
|
||||
--build-number=$PROJECT_BUILD_NUMBER
|
||||
artifacts:
|
||||
- build/**/outputs/**/*.aab
|
||||
- build/**/outputs/**/mapping.txt
|
||||
- flutter_drive.log
|
||||
publishing:
|
||||
email:
|
||||
recipients:
|
||||
- codemagic@myportal.social
|
||||
notify:
|
||||
success: true
|
||||
failure: false
|
||||
google_play:
|
||||
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
|
||||
track: $GOOGLE_PLAY_TRACK
|
||||
submit_as_draft: true
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../models/visibility.dart' as v;
|
||||
import '../models/timeline_grouping_list_data.dart';
|
||||
import '../services/connections_manager.dart';
|
||||
|
||||
Future<bool?> showVisibilityDialog(
|
||||
|
@ -8,7 +9,9 @@ Future<bool?> showVisibilityDialog(
|
|||
ConnectionsManager cm,
|
||||
v.Visibility visibility,
|
||||
) async {
|
||||
final circlesMap = {for (var item in cm.getMyCircles()) item.id: item};
|
||||
final circlesMap = {
|
||||
for (var item in cm.getGroupingListData(GroupingType.circle)) item.id: item
|
||||
};
|
||||
|
||||
final allowedCircles = visibility.allowedCircleIds.map((c) {
|
||||
if (c == '~') {
|
||||
|
|
|
@ -1,27 +1,31 @@
|
|||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../../models/circle_data.dart';
|
||||
import '../../models/connection.dart';
|
||||
import '../../models/exec_error.dart';
|
||||
import '../../models/timeline_grouping_list_data.dart';
|
||||
|
||||
abstract class ICirclesRepo {
|
||||
void clear();
|
||||
|
||||
void addAllCircles(List<CircleData> circles);
|
||||
void addAllCircles(List<TimelineGroupingListData> circles);
|
||||
|
||||
void addConnectionToCircle(CircleData circle, Connection connection);
|
||||
void addConnectionToCircle(
|
||||
TimelineGroupingListData circle, Connection connection);
|
||||
|
||||
void clearMyCircles();
|
||||
|
||||
void upsertCircle(CircleData circle);
|
||||
void upsertCircle(TimelineGroupingListData circle);
|
||||
|
||||
void deleteCircle(CircleData circle);
|
||||
void deleteCircle(TimelineGroupingListData circle);
|
||||
|
||||
List<CircleData> getMyCircles();
|
||||
List<TimelineGroupingListData> getMyCircles();
|
||||
|
||||
Result<List<Connection>, ExecError> getCircleMembers(CircleData circle);
|
||||
Result<List<Connection>, ExecError> getCircleMembers(
|
||||
TimelineGroupingListData circle);
|
||||
|
||||
Result<List<CircleData>, ExecError> getCirclesForUser(String id);
|
||||
Result<List<TimelineGroupingListData>, ExecError> getCirclesForUser(
|
||||
String id);
|
||||
|
||||
bool updateConnectionCircleData(String id, List<CircleData> currentCircless);
|
||||
bool updateConnectionCircleData(
|
||||
String id, List<TimelineGroupingListData> currentCircless);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../../models/circle_data.dart';
|
||||
import '../../models/connection.dart';
|
||||
import '../../models/exec_error.dart';
|
||||
import '../../models/timeline_grouping_list_data.dart';
|
||||
import '../interfaces/circles_repo_intf.dart';
|
||||
|
||||
class MemoryCirclesRepo implements ICirclesRepo {
|
||||
final _circlesForConnection = <String, List<CircleData>>{};
|
||||
final _circlesForConnection = <String, List<TimelineGroupingListData>>{};
|
||||
final _connectionsForCircle = <String, Set<Connection>>{};
|
||||
final _myCircles = <CircleData>{};
|
||||
final _myCircles = <TimelineGroupingListData>{};
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
|
@ -18,7 +18,8 @@ class MemoryCirclesRepo implements ICirclesRepo {
|
|||
}
|
||||
|
||||
@override
|
||||
Result<List<CircleData>, ExecError> getCirclesForUser(String id) {
|
||||
Result<List<TimelineGroupingListData>, ExecError> getCirclesForUser(
|
||||
String id) {
|
||||
if (!_circlesForConnection.containsKey(id)) {
|
||||
return Result.error(ExecError(
|
||||
type: ErrorType.notFound,
|
||||
|
@ -30,12 +31,13 @@ class MemoryCirclesRepo implements ICirclesRepo {
|
|||
}
|
||||
|
||||
@override
|
||||
List<CircleData> getMyCircles() {
|
||||
List<TimelineGroupingListData> getMyCircles() {
|
||||
return _myCircles.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Result<List<Connection>, ExecError> getCircleMembers(CircleData circle) {
|
||||
Result<List<Connection>, ExecError> getCircleMembers(
|
||||
TimelineGroupingListData circle) {
|
||||
if (_connectionsForCircle.containsKey(circle.id)) {
|
||||
return Result.ok(_connectionsForCircle[circle.id]!.toList());
|
||||
}
|
||||
|
@ -52,31 +54,33 @@ class MemoryCirclesRepo implements ICirclesRepo {
|
|||
}
|
||||
|
||||
@override
|
||||
void addConnectionToCircle(CircleData circle, Connection connection) {
|
||||
void addConnectionToCircle(
|
||||
TimelineGroupingListData circle, Connection connection) {
|
||||
_connectionsForCircle.putIfAbsent(circle.id, () => {}).add(connection);
|
||||
_circlesForConnection[connection.id]?.add(circle);
|
||||
}
|
||||
|
||||
@override
|
||||
void addAllCircles(List<CircleData> circle) {
|
||||
void addAllCircles(List<TimelineGroupingListData> circle) {
|
||||
_myCircles.addAll(circle);
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateConnectionCircleData(String id, List<CircleData> currentCircles) {
|
||||
bool updateConnectionCircleData(
|
||||
String id, List<TimelineGroupingListData> currentCircles) {
|
||||
_circlesForConnection[id] = currentCircles;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void upsertCircle(CircleData circle) {
|
||||
void upsertCircle(TimelineGroupingListData circle) {
|
||||
_connectionsForCircle.putIfAbsent(circle.id, () => {});
|
||||
_myCircles.remove(circle);
|
||||
_myCircles.add(circle);
|
||||
}
|
||||
|
||||
@override
|
||||
void deleteCircle(CircleData circle) {
|
||||
void deleteCircle(TimelineGroupingListData circle) {
|
||||
for (final conCircles in _circlesForConnection.values) {
|
||||
conCircles.remove(circle);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import 'package:result_monad/result_monad.dart';
|
|||
import '../friendica_client/paged_response.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/auth/profile.dart';
|
||||
import '../models/circle_data.dart';
|
||||
import '../models/connection.dart';
|
||||
import '../models/direct_message.dart';
|
||||
import '../models/exec_error.dart';
|
||||
|
@ -21,6 +20,7 @@ import '../models/media_attachment_uploads/image_types_enum.dart';
|
|||
import '../models/search_results.dart';
|
||||
import '../models/search_types.dart';
|
||||
import '../models/timeline_entry.dart';
|
||||
import '../models/timeline_grouping_list_data.dart';
|
||||
import '../models/timeline_identifiers.dart';
|
||||
import '../models/user_notification.dart';
|
||||
import '../models/visibility.dart';
|
||||
|
@ -28,13 +28,13 @@ import '../serializers/friendica/direct_message_friendica_extensions.dart';
|
|||
import '../serializers/friendica/gallery_data_friendica_extensions.dart';
|
||||
import '../serializers/friendica/image_entry_friendica_extensions.dart';
|
||||
import '../serializers/friendica/visibility_friendica_extensions.dart';
|
||||
import '../serializers/mastodon/circle_data_mastodon_extensions.dart';
|
||||
import '../serializers/mastodon/connection_mastodon_extensions.dart';
|
||||
import '../serializers/mastodon/follow_request_mastodon_extensions.dart';
|
||||
import '../serializers/mastodon/instance_info_mastodon_extensions.dart';
|
||||
import '../serializers/mastodon/notification_mastodon_extension.dart';
|
||||
import '../serializers/mastodon/search_result_mastodon_extensions.dart';
|
||||
import '../serializers/mastodon/timeline_entry_mastodon_extensions.dart';
|
||||
import '../serializers/mastodon/timeline_grouping_list_data.dart';
|
||||
import '../serializers/mastodon/visibility_mastodon_extensions.dart';
|
||||
import '../services/fediverse_server_validator.dart';
|
||||
import '../services/network_status_service.dart';
|
||||
|
@ -168,18 +168,20 @@ class GalleryClient extends FriendicaClient {
|
|||
}
|
||||
}
|
||||
|
||||
class CirclesClient extends FriendicaClient {
|
||||
static final _logger = Logger('$CirclesClient');
|
||||
class TimelineGroupingListClient extends FriendicaClient {
|
||||
static final _logger = Logger('$TimelineGroupingListClient');
|
||||
|
||||
CirclesClient(super.credentials) : super();
|
||||
TimelineGroupingListClient(super.credentials) : super();
|
||||
|
||||
FutureResult<List<CircleData>, ExecError> getCircles() async {
|
||||
_logger.finest(() => 'Getting circle (Mastodon List) data');
|
||||
FutureResult<List<TimelineGroupingListData>, ExecError>
|
||||
getTimelineGroupingListData() async {
|
||||
_logger.finest(() => 'Getting timeline grouping data (Mastodon List) data');
|
||||
final url = 'https://$serverName/api/v1/lists';
|
||||
final request = Uri.parse(url);
|
||||
return (await _getApiListRequest(request).andThenSuccessAsync(
|
||||
(listsJson) async => listsJson.data
|
||||
.map((json) => CircleDataMastodonExtensions.fromJson(json))
|
||||
.map((json) =>
|
||||
TimelineGroupingListDataMastodonExtensions.fromJson(json))
|
||||
.toList()))
|
||||
.mapError((error) => error is ExecError
|
||||
? error
|
||||
|
@ -187,7 +189,7 @@ class CirclesClient extends FriendicaClient {
|
|||
}
|
||||
|
||||
FutureResult<PagedResponse<List<Connection>>, ExecError> getCircleMembers(
|
||||
CircleData circleData,
|
||||
TimelineGroupingListData circleData,
|
||||
PagingData page,
|
||||
) async {
|
||||
_networkStatusService.startConnectionUpdateStatus();
|
||||
|
@ -206,7 +208,8 @@ class CirclesClient extends FriendicaClient {
|
|||
.execErrorCast();
|
||||
}
|
||||
|
||||
FutureResult<CircleData, ExecError> createCircle(String title) async {
|
||||
FutureResult<TimelineGroupingListData, ExecError> createCircle(
|
||||
String title) async {
|
||||
_logger.finest(() => 'Creating circle (Mastodon List) of name $title');
|
||||
final url = 'https://$serverName/api/v1/lists';
|
||||
final body = {
|
||||
|
@ -217,11 +220,11 @@ class CirclesClient extends FriendicaClient {
|
|||
body,
|
||||
headers: _headers,
|
||||
).andThenSuccessAsync((data) async =>
|
||||
CircleDataMastodonExtensions.fromJson(jsonDecode(data)));
|
||||
TimelineGroupingListDataMastodonExtensions.fromJson(jsonDecode(data)));
|
||||
return result.execErrorCast();
|
||||
}
|
||||
|
||||
FutureResult<CircleData, ExecError> renameCircle(
|
||||
FutureResult<TimelineGroupingListData, ExecError> renameCircle(
|
||||
String id, String title) async {
|
||||
_logger.finest(() => 'Reanming circle (Mastodon List) to name $title');
|
||||
final url = 'https://$serverName/api/v1/lists/$id';
|
||||
|
@ -234,12 +237,13 @@ class CirclesClient extends FriendicaClient {
|
|||
headers: _headers,
|
||||
).andThenSuccessAsync((data) async {
|
||||
final json = jsonDecode(data);
|
||||
return CircleDataMastodonExtensions.fromJson(json);
|
||||
return TimelineGroupingListDataMastodonExtensions.fromJson(json);
|
||||
});
|
||||
return result.execErrorCast();
|
||||
}
|
||||
|
||||
FutureResult<bool, ExecError> deleteCircle(CircleData circleData) async {
|
||||
FutureResult<bool, ExecError> deleteCircle(
|
||||
TimelineGroupingListData circleData) async {
|
||||
_logger.finest(
|
||||
() => 'Reanming circle (Mastodon List) to name ${circleData.name}');
|
||||
final url = 'https://$serverName/api/v1/lists/${circleData.id}';
|
||||
|
@ -247,21 +251,22 @@ class CirclesClient extends FriendicaClient {
|
|||
return result.mapValue((_) => true).execErrorCast();
|
||||
}
|
||||
|
||||
FutureResult<List<CircleData>, ExecError> getMemberCirclesForConnection(
|
||||
String connectionId) async {
|
||||
FutureResult<List<TimelineGroupingListData>, ExecError>
|
||||
getMemberCirclesForConnection(String connectionId) async {
|
||||
_logger.finest(() =>
|
||||
'Getting circles (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.data
|
||||
.map((json) => CircleDataMastodonExtensions.fromJson(json))
|
||||
.map((json) =>
|
||||
TimelineGroupingListDataMastodonExtensions.fromJson(json))
|
||||
.toList()))
|
||||
.mapError((error) => error as ExecError);
|
||||
}
|
||||
|
||||
FutureResult<bool, ExecError> addConnectionToCircle(
|
||||
CircleData circle,
|
||||
TimelineGroupingListData circle,
|
||||
Connection connection,
|
||||
) async {
|
||||
_logger.finest(() => 'Adding connection to circle');
|
||||
|
@ -275,7 +280,7 @@ class CirclesClient extends FriendicaClient {
|
|||
}
|
||||
|
||||
FutureResult<bool, ExecError> removeConnectionFromCircle(
|
||||
CircleData circle,
|
||||
TimelineGroupingListData circle,
|
||||
Connection connection,
|
||||
) async {
|
||||
_logger.finest(() => 'Adding connection to circle');
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
class CircleData {
|
||||
static final followersPseudoCircle = CircleData('~', 'Followers');
|
||||
|
||||
final String id;
|
||||
|
||||
final String name;
|
||||
|
||||
CircleData(this.id, this.name);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CircleData{id: $id, name: $name}';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is CircleData && runtimeType == other.runtimeType && id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
}
|
35
lib/models/timeline_grouping_list_data.dart
Normal file
35
lib/models/timeline_grouping_list_data.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
enum GroupingType {
|
||||
channel,
|
||||
circle,
|
||||
group,
|
||||
}
|
||||
|
||||
class TimelineGroupingListData {
|
||||
static const followersPseudoCircle =
|
||||
TimelineGroupingListData('~', 'Followers', GroupingType.circle);
|
||||
|
||||
static const empty = TimelineGroupingListData('', '', GroupingType.circle);
|
||||
|
||||
final String id;
|
||||
|
||||
final String name;
|
||||
|
||||
final GroupingType groupingType;
|
||||
|
||||
const TimelineGroupingListData(this.id, this.name, this.groupingType);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CircleData{id: $id, name: $name, type: ${groupingType.name}}';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TimelineGroupingListData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
}
|
|
@ -8,9 +8,9 @@ import '../controls/linear_status_indicator.dart';
|
|||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/status_and_refresh_button.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/circle_data.dart';
|
||||
import '../models/connection.dart';
|
||||
import '../models/exec_error.dart';
|
||||
import '../models/timeline_grouping_list_data.dart';
|
||||
import '../routes.dart';
|
||||
import '../services/connections_manager.dart';
|
||||
import '../services/network_status_service.dart';
|
||||
|
@ -29,15 +29,17 @@ class CircleAddUsersScreen extends StatefulWidget {
|
|||
class _CircleAddUsersScreenState extends State<CircleAddUsersScreen> {
|
||||
static final _logger = Logger('$CircleAddUsersScreen');
|
||||
var filterText = '';
|
||||
late CircleData circleData;
|
||||
late TimelineGroupingListData circleData;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final manager =
|
||||
getIt<ActiveProfileSelector<ConnectionsManager>>().activeEntry.value;
|
||||
circleData =
|
||||
manager.getMyCircles().where((g) => g.id == widget.circleId).first;
|
||||
circleData = manager
|
||||
.getGroupingListData(GroupingType.circle)
|
||||
.where((g) => g.id == widget.circleId)
|
||||
.first;
|
||||
}
|
||||
|
||||
Future<void> addUserToCircle(
|
||||
|
|
|
@ -9,8 +9,8 @@ import '../controls/responsive_max_width.dart';
|
|||
import '../controls/standard_appbar.dart';
|
||||
import '../controls/status_and_refresh_button.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/circle_data.dart';
|
||||
import '../models/connection.dart';
|
||||
import '../models/timeline_grouping_list_data.dart';
|
||||
import '../routes.dart';
|
||||
import '../services/connections_manager.dart';
|
||||
import '../services/network_status_service.dart';
|
||||
|
@ -31,7 +31,7 @@ class _CircleEditorScreenState extends State<CircleEditorScreen> {
|
|||
var processingUpdate = false;
|
||||
var allowNameEditing = false;
|
||||
var filterText = '';
|
||||
late CircleData circleData;
|
||||
late TimelineGroupingListData circleData;
|
||||
|
||||
Future<void> updateCircleName(
|
||||
BuildContext context, ConnectionsManager manager) async {
|
||||
|
@ -93,7 +93,7 @@ class _CircleEditorScreenState extends State<CircleEditorScreen> {
|
|||
final manager =
|
||||
getIt<ActiveProfileSelector<ConnectionsManager>>().activeEntry.value;
|
||||
circleData = manager
|
||||
.getMyCircles()
|
||||
.getGroupingListData(GroupingType.circle)
|
||||
.where(
|
||||
(g) => g.id == widget.circleId,
|
||||
)
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/standard_appbar.dart';
|
||||
import '../models/timeline_grouping_list_data.dart';
|
||||
import '../routes.dart';
|
||||
import '../services/connections_manager.dart';
|
||||
import '../utils/active_profile_selector.dart';
|
||||
|
@ -17,7 +18,7 @@ class CircleManagementScreen extends StatelessWidget {
|
|||
.watch<ActiveProfileSelector<ConnectionsManager>>()
|
||||
.activeEntry
|
||||
.value;
|
||||
final circles = manager.getMyCircles();
|
||||
final circles = manager.getGroupingListData(GroupingType.circle);
|
||||
circles.sort((g1, g2) => g1.name.compareTo(g2.name));
|
||||
return Scaffold(
|
||||
appBar: StandardAppBar.build(
|
||||
|
|
|
@ -17,12 +17,12 @@ import '../controls/standard_appbar.dart';
|
|||
import '../controls/timeline/status_header_control.dart';
|
||||
import '../controls/visibility_dialog.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/circle_data.dart';
|
||||
import '../models/exec_error.dart';
|
||||
import '../models/image_entry.dart';
|
||||
import '../models/link_preview_data.dart';
|
||||
import '../models/media_attachment_uploads/new_entry_media_items.dart';
|
||||
import '../models/timeline_entry.dart';
|
||||
import '../models/timeline_grouping_list_data.dart';
|
||||
import '../models/visibility.dart';
|
||||
import '../serializers/friendica/link_preview_friendica_extensions.dart';
|
||||
import '../services/connections_manager.dart';
|
||||
|
@ -58,7 +58,7 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
final existingMediaItems = <ImageEntry>[];
|
||||
final focusNode = FocusNode();
|
||||
Visibility visibility = Visibility.public();
|
||||
CircleData? currentCircle;
|
||||
TimelineGroupingListData? currentCircle;
|
||||
|
||||
var isSubmitting = false;
|
||||
|
||||
|
@ -599,21 +599,23 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
final circles = context
|
||||
.watch<ActiveProfileSelector<TimelineManager>>()
|
||||
.activeEntry
|
||||
.andThen((tm) => tm.getCircles())
|
||||
.andThen((tm) => tm.getTimelineGroupingListData(GroupingType.circle))
|
||||
.getValueOrElse(() => []);
|
||||
circles.sort((g1, g2) => g1.name.compareTo(g2.name));
|
||||
|
||||
final circleMenuItems = <DropdownMenuItem<CircleData>>[];
|
||||
final circleMenuItems = <DropdownMenuItem<TimelineGroupingListData>>[];
|
||||
circleMenuItems.add(DropdownMenuItem(
|
||||
value: CircleData.followersPseudoCircle,
|
||||
child: Text(CircleData.followersPseudoCircle.name)));
|
||||
circleMenuItems.add(DropdownMenuItem(
|
||||
value: CircleData('', ''), enabled: false, child: const Divider()));
|
||||
value: TimelineGroupingListData.followersPseudoCircle,
|
||||
child: Text(TimelineGroupingListData.followersPseudoCircle.name)));
|
||||
circleMenuItems.add(const DropdownMenuItem(
|
||||
value: TimelineGroupingListData.empty,
|
||||
enabled: false,
|
||||
child: Divider()));
|
||||
circleMenuItems.addAll(circles.map((g) => DropdownMenuItem(
|
||||
value: g,
|
||||
child: Text(g.name),
|
||||
)));
|
||||
if (currentCircle != CircleData.followersPseudoCircle &&
|
||||
if (currentCircle != TimelineGroupingListData.followersPseudoCircle &&
|
||||
!circles.contains(currentCircle)) {
|
||||
currentCircle = null;
|
||||
}
|
||||
|
@ -660,7 +662,7 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
const HorizontalPadding(),
|
||||
if (visibility.type == VisibilityType.private)
|
||||
Expanded(
|
||||
child: DropdownButton<CircleData>(
|
||||
child: DropdownButton<TimelineGroupingListData>(
|
||||
value: currentCircle,
|
||||
isExpanded: true,
|
||||
onChanged: widget.forEditing
|
||||
|
|
|
@ -3,17 +3,18 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relatica/controls/focus_mode_status_headline.dart';
|
||||
import 'package:relatica/riverpod_controllers/focus_mode.dart';
|
||||
|
||||
import '../controls/app_bottom_nav_bar.dart';
|
||||
import '../controls/focus_mode_status_headline.dart';
|
||||
import '../controls/linear_status_indicator.dart';
|
||||
import '../controls/login_aware_cached_network_image.dart';
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/standard_app_drawer.dart';
|
||||
import '../controls/timeline/timeline_panel.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/timeline_grouping_list_data.dart';
|
||||
import '../models/timeline_identifiers.dart';
|
||||
import '../riverpod_controllers/focus_mode.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import '../services/network_status_service.dart';
|
||||
import '../services/timeline_manager.dart';
|
||||
|
@ -114,38 +115,85 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
|
|||
.activeEntry
|
||||
.value;
|
||||
|
||||
final circles = manager.getCircles().getValueOrElse(() => []).toList();
|
||||
final circles = manager
|
||||
.getTimelineGroupingListData(GroupingType.circle)
|
||||
.getValueOrElse(() => [])
|
||||
.toList();
|
||||
circles.sort((g1, g2) => g1.name.compareTo(g2.name));
|
||||
|
||||
final groups = manager
|
||||
.getTimelineGroupingListData(GroupingType.group)
|
||||
.getValueOrElse(() => [])
|
||||
.toList();
|
||||
groups.sort((g1, g2) => g1.name.compareTo(g2.name));
|
||||
|
||||
final channels = manager
|
||||
.getTimelineGroupingListData(GroupingType.channel)
|
||||
.getValueOrElse(() => [])
|
||||
.toList();
|
||||
channels.sort((g1, g2) => g1.name.compareTo(g2.name));
|
||||
|
||||
final items = [
|
||||
...standardTypes
|
||||
.map((t) => TimelineIdentifiers(timeline: t))
|
||||
.map((e) => DropdownMenuItem(value: e, child: Text(e.toLabel()))),
|
||||
const DropdownMenuItem(
|
||||
value: null,
|
||||
enabled: false,
|
||||
child: Divider(),
|
||||
),
|
||||
const DropdownMenuItem(
|
||||
if (circles.isNotEmpty) ...[
|
||||
const DropdownMenuItem(
|
||||
value: null,
|
||||
enabled: false,
|
||||
child: Text(
|
||||
'Circles',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontStyle: FontStyle.italic,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
)),
|
||||
...circles
|
||||
.map((c) => TimelineIdentifiers(
|
||||
timeline: TimelineType.circle, auxData: c.id, label: c.name))
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
e.toLabel(),
|
||||
overflow: TextOverflow.fade,
|
||||
))),
|
||||
child: Divider(),
|
||||
),
|
||||
const DropdownMenuItem(
|
||||
value: null,
|
||||
enabled: false,
|
||||
child: Text(
|
||||
'Circles',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontStyle: FontStyle.italic,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
)),
|
||||
..._timelineGroupingListDataCollectionToDropdown(circles),
|
||||
],
|
||||
if (groups.isNotEmpty) ...[
|
||||
const DropdownMenuItem(
|
||||
value: null,
|
||||
enabled: false,
|
||||
child: Divider(),
|
||||
),
|
||||
const DropdownMenuItem(
|
||||
value: null,
|
||||
enabled: false,
|
||||
child: Text(
|
||||
'Groups',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontStyle: FontStyle.italic,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
)),
|
||||
..._timelineGroupingListDataCollectionToDropdown(groups),
|
||||
],
|
||||
if (channels.isNotEmpty) ...[
|
||||
const DropdownMenuItem(
|
||||
value: null,
|
||||
enabled: false,
|
||||
child: Divider(),
|
||||
),
|
||||
const DropdownMenuItem(
|
||||
value: null,
|
||||
enabled: false,
|
||||
child: Text(
|
||||
'Channels',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontStyle: FontStyle.italic,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
)),
|
||||
..._timelineGroupingListDataCollectionToDropdown(channels),
|
||||
],
|
||||
];
|
||||
|
||||
if (items.where((i) => i.value == currentTimeline).isEmpty) {
|
||||
|
@ -167,3 +215,17 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
List<DropdownMenuItem<TimelineIdentifiers>>
|
||||
_timelineGroupingListDataCollectionToDropdown(
|
||||
List<TimelineGroupingListData> circles) =>
|
||||
circles
|
||||
.map((c) => TimelineIdentifiers(
|
||||
timeline: TimelineType.circle, auxData: c.id, label: c.name))
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
e.toLabel(),
|
||||
overflow: TextOverflow.fade,
|
||||
)))
|
||||
.toList();
|
||||
|
|
|
@ -6,8 +6,8 @@ import '../controls/html_text_viewer_control.dart';
|
|||
import '../controls/login_aware_cached_network_image.dart';
|
||||
import '../controls/padding.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/circle_data.dart';
|
||||
import '../models/connection.dart';
|
||||
import '../models/timeline_grouping_list_data.dart';
|
||||
import '../routes.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import '../services/blocks_manager.dart';
|
||||
|
@ -152,12 +152,12 @@ class _UserProfileScreenState extends State<UserProfileScreen> {
|
|||
Connection profile,
|
||||
ConnectionsManager manager,
|
||||
) {
|
||||
final myCircles = manager.getMyCircles();
|
||||
final myCircles = manager.getGroupingListData(GroupingType.circle);
|
||||
final usersCircles = manager.getCirclesForUser(profile.id).fold(
|
||||
onSuccess: (circles) => circles.toSet(),
|
||||
onError: (error) {
|
||||
buildSnackbar(context, 'Error getting circle data: $error');
|
||||
return <CircleData>{};
|
||||
return <TimelineGroupingListData>{};
|
||||
});
|
||||
myCircles.sort((g1, g2) => g1.name.compareTo(g2.name));
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import '../../models/circle_data.dart';
|
||||
|
||||
extension CircleDataMastodonExtensions on CircleData {
|
||||
static CircleData fromJson(Map<String, dynamic> json) => CircleData(
|
||||
json['id'],
|
||||
json['title'],
|
||||
);
|
||||
}
|
27
lib/serializers/mastodon/timeline_grouping_list_data.dart
Normal file
27
lib/serializers/mastodon/timeline_grouping_list_data.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
import '../../models/timeline_grouping_list_data.dart';
|
||||
|
||||
extension TimelineGroupingListDataMastodonExtensions
|
||||
on TimelineGroupingListData {
|
||||
static TimelineGroupingListData fromJson(Map<String, dynamic> json) {
|
||||
final id = json['id']?.toString() ?? '';
|
||||
final typeString = json['replies_policy']?.toString() ?? '';
|
||||
|
||||
late final GroupingType type;
|
||||
if (typeString == 'followed') {
|
||||
if (id.startsWith('channel')) {
|
||||
type = GroupingType.channel;
|
||||
} else if (id.startsWith('group')) {
|
||||
type = GroupingType.group;
|
||||
} else {
|
||||
type = GroupingType.circle;
|
||||
}
|
||||
} else {
|
||||
type = GroupingType.circle;
|
||||
}
|
||||
return TimelineGroupingListData(
|
||||
id,
|
||||
json['title'],
|
||||
type,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:relatica/models/circle_data.dart';
|
||||
|
||||
import '../../models/timeline_grouping_list_data.dart';
|
||||
import '../../models/visibility.dart';
|
||||
|
||||
extension VisibilityMastodonExtensions on Visibility {
|
||||
|
@ -15,7 +14,7 @@ extension VisibilityMastodonExtensions on Visibility {
|
|||
if (!onComment && hasDetails) {
|
||||
final circleId =
|
||||
allowedCircleIds.firstOrNull ?? allowedUserIds.firstOrNull;
|
||||
if (circleId == CircleData.followersPseudoCircle.id) {
|
||||
if (circleId == TimelineGroupingListData.followersPseudoCircle.id) {
|
||||
return 'private';
|
||||
}
|
||||
|
||||
|
|
|
@ -11,9 +11,9 @@ import '../friendica_client/friendica_client.dart';
|
|||
import '../friendica_client/paging_data.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/auth/profile.dart';
|
||||
import '../models/circle_data.dart';
|
||||
import '../models/connection.dart';
|
||||
import '../models/exec_error.dart';
|
||||
import '../models/timeline_grouping_list_data.dart';
|
||||
import '../utils/active_profile_selector.dart';
|
||||
import 'persistent_info_service.dart';
|
||||
|
||||
|
@ -238,16 +238,20 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
List<CircleData> getMyCircles() {
|
||||
List<TimelineGroupingListData> getGroupingListData(GroupingType type) {
|
||||
if (circlesNotInitialized) {
|
||||
circlesNotInitialized = false;
|
||||
_updateMyCircles(true);
|
||||
_updateMyGroupingListData(true);
|
||||
}
|
||||
|
||||
return circlesRepo.getMyCircles();
|
||||
return circlesRepo
|
||||
.getMyCircles()
|
||||
.where((e) => e.groupingType == type)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Result<List<Connection>, ExecError> getCircleMembers(CircleData circle) {
|
||||
Result<List<Connection>, ExecError> getCircleMembers(
|
||||
TimelineGroupingListData circle) {
|
||||
return circlesRepo
|
||||
.getCircleMembers(circle)
|
||||
.transform(
|
||||
|
@ -258,8 +262,9 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
.execErrorCast();
|
||||
}
|
||||
|
||||
FutureResult<CircleData, ExecError> createCircle(String newName) async {
|
||||
final result = await CirclesClient(profile)
|
||||
FutureResult<TimelineGroupingListData, ExecError> createCircle(
|
||||
String newName) async {
|
||||
final result = await TimelineGroupingListClient(profile)
|
||||
.createCircle(newName)
|
||||
.withResultAsync((newCircle) async {
|
||||
circlesRepo.upsertCircle(newCircle);
|
||||
|
@ -268,9 +273,9 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
return result.execErrorCast();
|
||||
}
|
||||
|
||||
FutureResult<CircleData, ExecError> renameCircle(
|
||||
FutureResult<TimelineGroupingListData, ExecError> renameCircle(
|
||||
String id, String newName) async {
|
||||
final result = await CirclesClient(profile)
|
||||
final result = await TimelineGroupingListClient(profile)
|
||||
.renameCircle(id, newName)
|
||||
.withResultAsync((renamedCircle) async {
|
||||
circlesRepo.upsertCircle(renamedCircle);
|
||||
|
@ -279,8 +284,9 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
return result.execErrorCast();
|
||||
}
|
||||
|
||||
FutureResult<bool, ExecError> deleteCircle(CircleData circleData) async {
|
||||
final result = await CirclesClient(profile)
|
||||
FutureResult<bool, ExecError> deleteCircle(
|
||||
TimelineGroupingListData circleData) async {
|
||||
final result = await TimelineGroupingListClient(profile)
|
||||
.deleteCircle(circleData)
|
||||
.withResultAsync((_) async {
|
||||
circlesRepo.deleteCircle(circleData);
|
||||
|
@ -290,12 +296,12 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
}
|
||||
|
||||
void refreshCircles() {
|
||||
_updateMyCircles(true);
|
||||
_updateMyGroupingListData(true);
|
||||
}
|
||||
|
||||
Future<void> refreshCircleMemberships(CircleData circle) async {
|
||||
Future<void> refreshCircleMemberships(TimelineGroupingListData circle) async {
|
||||
var page = PagingData(limit: 50);
|
||||
final client = CirclesClient(profile);
|
||||
final client = TimelineGroupingListClient(profile);
|
||||
final allResults = <Connection>{};
|
||||
var moreResults = true;
|
||||
while (moreResults) {
|
||||
|
@ -319,7 +325,8 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
Result<List<CircleData>, ExecError> getCirclesForUser(String id) {
|
||||
Result<List<TimelineGroupingListData>, ExecError> getCirclesForUser(
|
||||
String id) {
|
||||
final result = circlesRepo.getCirclesForUser(id);
|
||||
if (result.isSuccess) {
|
||||
_logger.finer("Circles for user $id: $result");
|
||||
|
@ -335,9 +342,9 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
}
|
||||
|
||||
FutureResult<bool, ExecError> addUserToCircle(
|
||||
CircleData circle, Connection connection) async {
|
||||
TimelineGroupingListData circle, Connection connection) async {
|
||||
_logger.finest('Adding ${connection.name} to circle: ${circle.name}');
|
||||
return await CirclesClient(profile)
|
||||
return await TimelineGroupingListClient(profile)
|
||||
.addConnectionToCircle(circle, connection)
|
||||
.withResultAsync((_) async => await refreshCircleMemberships(circle))
|
||||
.withResult((_) => notifyListeners())
|
||||
|
@ -349,9 +356,9 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
}
|
||||
|
||||
FutureResult<bool, ExecError> removeUserFromCircle(
|
||||
CircleData circle, Connection connection) async {
|
||||
TimelineGroupingListData circle, Connection connection) async {
|
||||
_logger.finest('Removing ${connection.name} from circle: ${circle.name}');
|
||||
return CirclesClient(profile)
|
||||
return TimelineGroupingListClient(profile)
|
||||
.removeConnectionFromCircle(circle, connection)
|
||||
.withResultAsync((_) async => await refreshCircleMemberships(circle))
|
||||
.withResult((_) => notifyListeners())
|
||||
|
@ -395,7 +402,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
Connection connection, {
|
||||
bool withNotifications = true,
|
||||
}) async {
|
||||
await _updateMyCircles(false);
|
||||
await _updateMyGroupingListData(false);
|
||||
await _refreshCircleListData(connection.id, false);
|
||||
await _refreshConnection(connection, false);
|
||||
if (withNotifications) {
|
||||
|
@ -405,7 +412,9 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
|
||||
Future<void> _refreshCircleListData(String id, bool withNotification) async {
|
||||
_logger.finest('Refreshing member list data for Connection $id');
|
||||
await CirclesClient(profile).getMemberCirclesForConnection(id).match(
|
||||
await TimelineGroupingListClient(profile)
|
||||
.getMemberCirclesForConnection(id)
|
||||
.match(
|
||||
onSuccess: (circles) {
|
||||
circlesRepo.updateConnectionCircleData(id, circles);
|
||||
if (withNotification) {
|
||||
|
@ -436,9 +445,11 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _updateMyCircles(bool withNotification) async {
|
||||
Future<void> _updateMyGroupingListData(bool withNotification) async {
|
||||
_logger.finest('Refreshing my circles list');
|
||||
await CirclesClient(profile).getCircles().match(
|
||||
await TimelineGroupingListClient(profile)
|
||||
.getTimelineGroupingListData()
|
||||
.match(
|
||||
onSuccess: (circles) {
|
||||
_logger.finest('Got updated circles:${circles.map((e) => e.name)}');
|
||||
circlesRepo.clearMyCircles();
|
||||
|
|
|
@ -5,13 +5,13 @@ import 'package:result_monad/result_monad.dart';
|
|||
import '../data/interfaces/circles_repo_intf.dart';
|
||||
import '../friendica_client/friendica_client.dart';
|
||||
import '../models/auth/profile.dart';
|
||||
import '../models/circle_data.dart';
|
||||
import '../models/entry_tree_item.dart';
|
||||
import '../models/exec_error.dart';
|
||||
import '../models/image_entry.dart';
|
||||
import '../models/media_attachment_uploads/new_entry_media_items.dart';
|
||||
import '../models/timeline.dart';
|
||||
import '../models/timeline_entry.dart';
|
||||
import '../models/timeline_grouping_list_data.dart';
|
||||
import '../models/timeline_identifiers.dart';
|
||||
import '../models/visibility.dart';
|
||||
import 'entry_manager_service.dart';
|
||||
|
@ -42,19 +42,25 @@ class TimelineManager extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
Result<List<CircleData>, ExecError> getCircles() {
|
||||
Result<List<TimelineGroupingListData>, ExecError> getTimelineGroupingListData(
|
||||
GroupingType type) {
|
||||
if (circlesNotInitialized) {
|
||||
_refreshCircleData();
|
||||
circlesNotInitialized = false;
|
||||
return Result.ok([]);
|
||||
}
|
||||
|
||||
return Result.ok(circlesRepo.getMyCircles());
|
||||
return Result.ok(circlesRepo
|
||||
.getMyCircles()
|
||||
.where((e) => e.groupingType == type)
|
||||
.toList());
|
||||
}
|
||||
|
||||
Future<void> _refreshCircleData() async {
|
||||
_logger.finer('Refreshing member circle data ');
|
||||
await CirclesClient(profile).getCircles().match(
|
||||
await TimelineGroupingListClient(profile)
|
||||
.getTimelineGroupingListData()
|
||||
.match(
|
||||
onSuccess: (circles) {
|
||||
circlesRepo.addAllCircles(circles);
|
||||
notifyListeners();
|
||||
|
|
Loading…
Reference in a new issue