diff --git a/lib/api/interface/libcp.dart b/lib/api/interface/libcp.dart index b632e19..0628b96 100644 --- a/lib/api/interface/libcp.dart +++ b/lib/api/interface/libcp.dart @@ -43,6 +43,12 @@ abstract class ICypherpost { required String inviteCode, }); + R selfInviteCode({ + required String hostname, + required int socks5, + required String socialRoot, + }); + R leaveServer({ required String hostname, required int socks5, diff --git a/lib/api/libcp.dart b/lib/api/libcp.dart index d98ee36..0de4b2c 100644 --- a/lib/api/libcp.dart +++ b/lib/api/libcp.dart @@ -151,6 +151,24 @@ class LibCypherpost implements ICypherpost { return R(result: InvitationDetail.fromJson(resp)); } + @override + R selfInviteCode({ + required String hostname, + required int socks5, + required String socialRoot, + }) { + final resp = _libcp.selfInvitation( + hostname: hostname, + socks5: socks5, + socialRoot: socialRoot, + ); + + if (resp.contains('error')) { + return R(error: SMError.fromJson(resp).error); + } else + return R(result: InvitationDetail.fromJson(resp)); + } + @override R leaveServer({ required String hostname, diff --git a/lib/cubit/network/discover.dart b/lib/cubit/network/discover.dart index 50761a8..6d9270a 100644 --- a/lib/cubit/network/discover.dart +++ b/lib/cubit/network/discover.dart @@ -92,7 +92,7 @@ class DiscoverCubit extends Cubit { } final networkMembers = NetworkMembers( - id: state.network.id!, + id: state.network.id, members: members.result!, ); await saveToStorage(networkMembers); diff --git a/lib/cubit/network/overview.dart b/lib/cubit/network/overview.dart index 6f2efbb..eac1c8c 100644 --- a/lib/cubit/network/overview.dart +++ b/lib/cubit/network/overview.dart @@ -6,7 +6,9 @@ import 'package:sats/api/libcp.dart'; import 'package:sats/cubit/logger.dart'; import 'package:sats/cubit/social-root.dart'; import 'package:sats/cubit/tor.dart'; +import 'package:sats/model/network-chat-history.dart'; import 'package:sats/model/network-identity.dart'; +import 'package:sats/model/network-members.dart'; import 'package:sats/model/result.dart'; import 'package:sats/pkg/interface/clipboard.dart'; import 'package:sats/pkg/interface/storage.dart'; @@ -25,6 +27,10 @@ class OverviewState with _$OverviewState { @Default('') String error, @Default('') String loading, @Default('') String displayedInviteSecret, + @Default(0) int latestGenesis, + @Default([]) List verifiedPosts, + @Default([]) List corruptedPostIds, + @Default([]) List members, }) = _OverviewState; const OverviewState._(); @@ -50,21 +56,50 @@ class OverviewCubit extends Cubit { Future load() async { try { - // load from storage and update from network - all posts emit( state.copyWith( - displayedInviteSecret: (state.network.lastInviteSecret == null) - ? '' - : state.network.lastInviteSecret!, - pubkey: _socialRoot.state.key!.pubkey, + loading: 'fromStorage', + ), + ); + final storedHistory = _storage.getItem( + StoreKeys.ChatHistory.name, + state.network.id!, + ); + if (storedHistory.hasError) return; + + final storedMembers = _storage.getItem( + StoreKeys.Members.name, + state.network.id!, + ); + if (storedMembers.hasError) return; + await Future.delayed(const Duration(milliseconds: 300)); + + emit( + state.copyWith( + verifiedPosts: storedHistory.result!.verifiedPosts + .map((e) => Verified.fromJson(e)) + .toList(), + corruptedPostIds: storedHistory.result!.corruptedPostIds, + latestGenesis: storedHistory.result!.latestGenesis, + members: storedMembers.result!.members, loading: '', ), ); } catch (e, s) { - _logger.logException(e, 'NetworkOverview.load', s); + _logger.logException(e, 'OverviewCubit.load', s); } } + MemberIdentity? usernameByPubkey(String pubkey) { + return state.members.firstWhere( + (m) => m.pubkey == pubkey, + orElse: () => MemberIdentity( + username: '*notfound*', + pubkey: pubkey, + ), + ); + } + Future generateInvite() async { try { emit( @@ -116,7 +151,7 @@ class OverviewCubit extends Cubit { ), ); } catch (e, s) { - _logger.logException(e, 'NetworkOverview.genInvite', s); + _logger.logException(e, 'OverviewCubit.genInvite', s); } } @@ -155,7 +190,7 @@ class OverviewCubit extends Cubit { ), ); } catch (e, s) { - _logger.logException(e, 'NetworkOverview.leave', s); + _logger.logException(e, 'OverviewCubit.leave', s); } } @@ -180,6 +215,73 @@ class OverviewCubit extends Cubit { } } + Future fetchAllPosts() async { + emit( + state.copyWith( + loading: 'fetching', + error: '', + ), + ); + final socks5 = _torCubit.state.socks5Port; + final socialRoot = _socialRoot.state.key!.xprv; + + final sorted = await compute(getPosts, { + 'hostname': 'https://' + state.network.hostname, + 'socks5': socks5.toString(), + 'socialRoot': socialRoot, + 'genesisFilter': state.latestGenesis.toString(), + }); + + if (sorted.hasError) { + emit( + state.copyWith( + error: sorted.error!, + loading: '', + ), + ); + return; + } + + final allVerifiedPosts = state.verifiedPosts + sorted.result!.verified!; + + final history = NetworkChatHistory( + verifiedPosts: allVerifiedPosts.map((e) => e.toJson()).toList(), + latestGenesis: sorted.result!.latestGenesis! + 1, + corruptedPostIds: state.corruptedPostIds + sorted.result!.corrupted!, + ); + + await updateNetworkPosts(history); + await load(); + emit( + state.copyWith( + loading: '', + ), + ); + } + + void removeDuplicatePosts() {} + + Future updateNetworkPosts(NetworkChatHistory history) async { + try { + final status = await _storage.saveItemAt( + StoreKeys.ChatHistory.name, + state.network.id!, + history, + ); + if (status.hasError) { + emit( + state.copyWith( + error: couldNotSaveError, + loading: '', + ), + ); + return; + } + } catch (e, s) { + _logger.logException(e, 'OverviewCubit.load', s); + } + } + void purgeServerModels() { try { final status = _storage.deleteItemAt( @@ -233,3 +335,14 @@ R leave(dynamic data) { ); return resp; } + +R getPosts(dynamic data) { + final obj = data as Map; + final resp = LibCypherpost().getAllPosts( + hostname: obj['hostname']!, + socks5: int.parse(obj['socks5']!), + socialRoot: obj['socialRoot']!, + genesisFilter: int.parse(obj['genesisFilter']!), + ); + return resp; +} diff --git a/lib/cubit/network/overview.freezed.dart b/lib/cubit/network/overview.freezed.dart index 801124c..ac1b71c 100644 --- a/lib/cubit/network/overview.freezed.dart +++ b/lib/cubit/network/overview.freezed.dart @@ -22,6 +22,10 @@ mixin _$OverviewState { String get error => throw _privateConstructorUsedError; String get loading => throw _privateConstructorUsedError; String get displayedInviteSecret => throw _privateConstructorUsedError; + int get latestGenesis => throw _privateConstructorUsedError; + List get verifiedPosts => throw _privateConstructorUsedError; + List get corruptedPostIds => throw _privateConstructorUsedError; + List get members => throw _privateConstructorUsedError; @JsonKey(ignore: true) $OverviewStateCopyWith get copyWith => @@ -39,7 +43,11 @@ abstract class $OverviewStateCopyWith<$Res> { bool showInfo, String error, String loading, - String displayedInviteSecret}); + String displayedInviteSecret, + int latestGenesis, + List verifiedPosts, + List corruptedPostIds, + List members}); $NetworkIdentityCopyWith<$Res> get network; } @@ -61,6 +69,10 @@ class _$OverviewStateCopyWithImpl<$Res> Object? error = freezed, Object? loading = freezed, Object? displayedInviteSecret = freezed, + Object? latestGenesis = freezed, + Object? verifiedPosts = freezed, + Object? corruptedPostIds = freezed, + Object? members = freezed, }) { return _then(_value.copyWith( network: network == freezed @@ -87,6 +99,22 @@ class _$OverviewStateCopyWithImpl<$Res> ? _value.displayedInviteSecret : displayedInviteSecret // ignore: cast_nullable_to_non_nullable as String, + latestGenesis: latestGenesis == freezed + ? _value.latestGenesis + : latestGenesis // ignore: cast_nullable_to_non_nullable + as int, + verifiedPosts: verifiedPosts == freezed + ? _value.verifiedPosts + : verifiedPosts // ignore: cast_nullable_to_non_nullable + as List, + corruptedPostIds: corruptedPostIds == freezed + ? _value.corruptedPostIds + : corruptedPostIds // ignore: cast_nullable_to_non_nullable + as List, + members: members == freezed + ? _value.members + : members // ignore: cast_nullable_to_non_nullable + as List, )); } @@ -111,7 +139,11 @@ abstract class _$$_OverviewStateCopyWith<$Res> bool showInfo, String error, String loading, - String displayedInviteSecret}); + String displayedInviteSecret, + int latestGenesis, + List verifiedPosts, + List corruptedPostIds, + List members}); @override $NetworkIdentityCopyWith<$Res> get network; @@ -136,6 +168,10 @@ class __$$_OverviewStateCopyWithImpl<$Res> Object? error = freezed, Object? loading = freezed, Object? displayedInviteSecret = freezed, + Object? latestGenesis = freezed, + Object? verifiedPosts = freezed, + Object? corruptedPostIds = freezed, + Object? members = freezed, }) { return _then(_$_OverviewState( network: network == freezed @@ -162,6 +198,22 @@ class __$$_OverviewStateCopyWithImpl<$Res> ? _value.displayedInviteSecret : displayedInviteSecret // ignore: cast_nullable_to_non_nullable as String, + latestGenesis: latestGenesis == freezed + ? _value.latestGenesis + : latestGenesis // ignore: cast_nullable_to_non_nullable + as int, + verifiedPosts: verifiedPosts == freezed + ? _value._verifiedPosts + : verifiedPosts // ignore: cast_nullable_to_non_nullable + as List, + corruptedPostIds: corruptedPostIds == freezed + ? _value._corruptedPostIds + : corruptedPostIds // ignore: cast_nullable_to_non_nullable + as List, + members: members == freezed + ? _value._members + : members // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -175,8 +227,15 @@ class _$_OverviewState extends _OverviewState with DiagnosticableTreeMixin { this.showInfo = false, this.error = '', this.loading = '', - this.displayedInviteSecret = ''}) - : super._(); + this.displayedInviteSecret = '', + this.latestGenesis = 0, + final List verifiedPosts = const [], + final List corruptedPostIds = const [], + final List members = const []}) + : _verifiedPosts = verifiedPosts, + _corruptedPostIds = corruptedPostIds, + _members = members, + super._(); @override final NetworkIdentity network; @@ -194,10 +253,36 @@ class _$_OverviewState extends _OverviewState with DiagnosticableTreeMixin { @override @JsonKey() final String displayedInviteSecret; + @override + @JsonKey() + final int latestGenesis; + final List _verifiedPosts; + @override + @JsonKey() + List get verifiedPosts { + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_verifiedPosts); + } + + final List _corruptedPostIds; + @override + @JsonKey() + List get corruptedPostIds { + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_corruptedPostIds); + } + + final List _members; + @override + @JsonKey() + List get members { + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_members); + } @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'OverviewState(network: $network, pubkey: $pubkey, showInfo: $showInfo, error: $error, loading: $loading, displayedInviteSecret: $displayedInviteSecret)'; + return 'OverviewState(network: $network, pubkey: $pubkey, showInfo: $showInfo, error: $error, loading: $loading, displayedInviteSecret: $displayedInviteSecret, latestGenesis: $latestGenesis, verifiedPosts: $verifiedPosts, corruptedPostIds: $corruptedPostIds, members: $members)'; } @override @@ -210,8 +295,11 @@ class _$_OverviewState extends _OverviewState with DiagnosticableTreeMixin { ..add(DiagnosticsProperty('showInfo', showInfo)) ..add(DiagnosticsProperty('error', error)) ..add(DiagnosticsProperty('loading', loading)) - ..add( - DiagnosticsProperty('displayedInviteSecret', displayedInviteSecret)); + ..add(DiagnosticsProperty('displayedInviteSecret', displayedInviteSecret)) + ..add(DiagnosticsProperty('latestGenesis', latestGenesis)) + ..add(DiagnosticsProperty('verifiedPosts', verifiedPosts)) + ..add(DiagnosticsProperty('corruptedPostIds', corruptedPostIds)) + ..add(DiagnosticsProperty('members', members)); } @override @@ -225,7 +313,14 @@ class _$_OverviewState extends _OverviewState with DiagnosticableTreeMixin { const DeepCollectionEquality().equals(other.error, error) && const DeepCollectionEquality().equals(other.loading, loading) && const DeepCollectionEquality() - .equals(other.displayedInviteSecret, displayedInviteSecret)); + .equals(other.displayedInviteSecret, displayedInviteSecret) && + const DeepCollectionEquality() + .equals(other.latestGenesis, latestGenesis) && + const DeepCollectionEquality() + .equals(other._verifiedPosts, _verifiedPosts) && + const DeepCollectionEquality() + .equals(other._corruptedPostIds, _corruptedPostIds) && + const DeepCollectionEquality().equals(other._members, _members)); } @override @@ -236,7 +331,11 @@ class _$_OverviewState extends _OverviewState with DiagnosticableTreeMixin { const DeepCollectionEquality().hash(showInfo), const DeepCollectionEquality().hash(error), const DeepCollectionEquality().hash(loading), - const DeepCollectionEquality().hash(displayedInviteSecret)); + const DeepCollectionEquality().hash(displayedInviteSecret), + const DeepCollectionEquality().hash(latestGenesis), + const DeepCollectionEquality().hash(_verifiedPosts), + const DeepCollectionEquality().hash(_corruptedPostIds), + const DeepCollectionEquality().hash(_members)); @JsonKey(ignore: true) @override @@ -251,7 +350,11 @@ abstract class _OverviewState extends OverviewState { final bool showInfo, final String error, final String loading, - final String displayedInviteSecret}) = _$_OverviewState; + final String displayedInviteSecret, + final int latestGenesis, + final List verifiedPosts, + final List corruptedPostIds, + final List members}) = _$_OverviewState; const _OverviewState._() : super._(); @override @@ -267,6 +370,14 @@ abstract class _OverviewState extends OverviewState { @override String get displayedInviteSecret => throw _privateConstructorUsedError; @override + int get latestGenesis => throw _privateConstructorUsedError; + @override + List get verifiedPosts => throw _privateConstructorUsedError; + @override + List get corruptedPostIds => throw _privateConstructorUsedError; + @override + List get members => throw _privateConstructorUsedError; + @override @JsonKey(ignore: true) _$$_OverviewStateCopyWith<_$_OverviewState> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/cubit/networks.dart b/lib/cubit/networks.dart index e54881b..8ed7b73 100644 --- a/lib/cubit/networks.dart +++ b/lib/cubit/networks.dart @@ -7,6 +7,7 @@ import 'package:sats/cubit/logger.dart'; import 'package:sats/cubit/social-root.dart'; import 'package:sats/cubit/tor.dart'; import 'package:sats/model/network-identity.dart'; +import 'package:sats/model/network-members.dart'; import 'package:sats/model/result.dart'; import 'package:sats/pkg/interface/clipboard.dart'; import 'package:sats/pkg/interface/storage.dart'; @@ -271,7 +272,101 @@ class NetworksCubit extends Cubit { } } - Future resyncExistingUser() async {} + Future resyncExistingUser() async { + emit( + state.copyWith( + loading: 'ping', + ), + ); + + final socks5 = _torCubit.state.socks5Port; + final socialRoot = _socialRoot.state.key!.xprv; + + late R serverId; + if (state.name != '') { + serverId = await compute(serverIdentity, { + 'hostname': 'https://' + state.hostname!, + 'socks5': socks5.toString(), + 'socialRoot': socialRoot, + }); + + if (serverId.hasError) { + emit( + state.copyWith( + error: 'Could not find host!', + loading: '', + ), + ); + return; + } + + emit( + state.copyWith( + loading: 'resync', + ), + ); + final inviteDetail = await compute(selfInviteCode, { + 'hostname': 'https://' + state.hostname!, + 'socks5': socks5.toString(), + 'socialRoot': socialRoot, + }); + + if (inviteDetail.hasError) { + emit( + state.copyWith( + error: inviteDetail.error!, + loading: '', + ), + ); + return; + } + + final members = await compute(getMembers, { + 'hostname': 'https://' + state.hostname!, + 'socks5': socks5.toString(), + 'socialRoot': socialRoot, + }); + + if (members.hasError) { + emit( + state.copyWith( + error: members.error!, + loading: '', + ), + ); + return; + } + + final myIdentity = members.result!.firstWhere( + (m) => m.pubkey == _socialRoot.state.key!.pubkey, + orElse: () => MemberIdentity( + username: '*notfound*', + pubkey: _socialRoot.state.key!.pubkey, + ), + ); + + final networkServerId = NetworkIdentity( + hostname: state.hostname!, + name: (state.name == null) ? serverId.result!.name : state.name!, + kind: (state.kind == null) ? serverId.result!.kind : state.kind!, + serverPubkey: (state.serverPubkey == null) + ? serverId.result!.pubkey + : state.serverPubkey!, + username: myIdentity.username!, + inviteCode: inviteDetail.result!.inviteCode, + inviteCount: inviteDetail.result!.count, + ); + + await updateServerIdAndMembersStorage(networkServerId, members.result!); + await load(); + emit( + state.copyWith( + joined: true, + ), + ); + } + } + Future updateServerIdStorage(NetworkIdentity serverId) async { try { final savedid = await _storage.saveItem( @@ -299,6 +394,56 @@ class NetworksCubit extends Cubit { } } + Future updateServerIdAndMembersStorage( + NetworkIdentity serverId, + List members, + ) async { + try { + final savedid = await _storage.saveItem( + StoreKeys.Networks.name, + serverId, + ); + if (savedid.hasError) { + emit( + state.copyWith( + error: couldNotSaveError, + loading: '', + ), + ); + return; + } + final id = savedid.result!; + final newId = serverId.copyWith(id: id); + await _storage.saveItemAt( + StoreKeys.Networks.name, + id, + newId, + ); + + final networkMembers = NetworkMembers( + id: id, + members: members, + ); + final status = await _storage.saveItemAt( + StoreKeys.Members.name, + id, + networkMembers, + ); + + if (status.hasError) { + emit( + state.copyWith( + error: couldNotSaveError, + loading: '', + ), + ); + return; + } + } catch (e, s) { + _logger.logException(e, 'Discover.load', s); + } + } + void clear() => emit( NetworksState( networks: state.networks, @@ -329,3 +474,23 @@ R joinHostServer(dynamic data) { ); return resp; } + +R selfInviteCode(dynamic data) { + final obj = data as Map; + final resp = LibCypherpost().selfInviteCode( + hostname: obj['hostname']!, + socks5: int.parse(obj['socks5']!), + socialRoot: obj['socialRoot']!, + ); + return resp; +} + +R> getMembers(dynamic data) { + final obj = data as Map; + final resp = LibCypherpost().getMembers( + hostname: obj['hostname']!, + socks5: int.parse(obj['socks5']!), + socialRoot: obj['socialRoot']!, + ); + return resp; +} diff --git a/lib/model/network-chat-history.dart b/lib/model/network-chat-history.dart index feceb0e..8de8471 100644 --- a/lib/model/network-chat-history.dart +++ b/lib/model/network-chat-history.dart @@ -1,6 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hive/hive.dart'; -import 'package:sats/model/cypherpost.dart'; part 'network-chat-history.g.dart'; part 'network-chat-history.freezed.dart'; @@ -9,9 +8,10 @@ part 'network-chat-history.freezed.dart'; class NetworkChatHistory with _$NetworkChatHistory { @HiveType(typeId: 13, adapterName: 'NetworkChatHistoryClassAdapter') const factory NetworkChatHistory({ - @HiveField(0) required String hostname, - @HiveField(1) required String counterParty, - @HiveField(2) required List posts, + @HiveField(0) int? id, + @HiveField(1) required List> verifiedPosts, + @HiveField(2) required int latestGenesis, + @HiveField(3) required List corruptedPostIds, }) = _NetworkChatHistory; const NetworkChatHistory._(); } diff --git a/lib/model/network-chat-history.freezed.dart b/lib/model/network-chat-history.freezed.dart index dd2b483..0bc7a12 100644 --- a/lib/model/network-chat-history.freezed.dart +++ b/lib/model/network-chat-history.freezed.dart @@ -17,11 +17,14 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$NetworkChatHistory { @HiveField(0) - String get hostname => throw _privateConstructorUsedError; + int? get id => throw _privateConstructorUsedError; @HiveField(1) - String get counterParty => throw _privateConstructorUsedError; + List> get verifiedPosts => + throw _privateConstructorUsedError; @HiveField(2) - List get posts => throw _privateConstructorUsedError; + int get latestGenesis => throw _privateConstructorUsedError; + @HiveField(3) + List get corruptedPostIds => throw _privateConstructorUsedError; @JsonKey(ignore: true) $NetworkChatHistoryCopyWith get copyWith => @@ -34,9 +37,10 @@ abstract class $NetworkChatHistoryCopyWith<$Res> { NetworkChatHistory value, $Res Function(NetworkChatHistory) then) = _$NetworkChatHistoryCopyWithImpl<$Res>; $Res call( - {@HiveField(0) String hostname, - @HiveField(1) String counterParty, - @HiveField(2) List posts}); + {@HiveField(0) int? id, + @HiveField(1) List> verifiedPosts, + @HiveField(2) int latestGenesis, + @HiveField(3) List corruptedPostIds}); } /// @nodoc @@ -50,23 +54,28 @@ class _$NetworkChatHistoryCopyWithImpl<$Res> @override $Res call({ - Object? hostname = freezed, - Object? counterParty = freezed, - Object? posts = freezed, + Object? id = freezed, + Object? verifiedPosts = freezed, + Object? latestGenesis = freezed, + Object? corruptedPostIds = freezed, }) { return _then(_value.copyWith( - hostname: hostname == freezed - ? _value.hostname - : hostname // ignore: cast_nullable_to_non_nullable - as String, - counterParty: counterParty == freezed - ? _value.counterParty - : counterParty // ignore: cast_nullable_to_non_nullable - as String, - posts: posts == freezed - ? _value.posts - : posts // ignore: cast_nullable_to_non_nullable - as List, + id: id == freezed + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + verifiedPosts: verifiedPosts == freezed + ? _value.verifiedPosts + : verifiedPosts // ignore: cast_nullable_to_non_nullable + as List>, + latestGenesis: latestGenesis == freezed + ? _value.latestGenesis + : latestGenesis // ignore: cast_nullable_to_non_nullable + as int, + corruptedPostIds: corruptedPostIds == freezed + ? _value.corruptedPostIds + : corruptedPostIds // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -79,9 +88,10 @@ abstract class _$$_NetworkChatHistoryCopyWith<$Res> __$$_NetworkChatHistoryCopyWithImpl<$Res>; @override $Res call( - {@HiveField(0) String hostname, - @HiveField(1) String counterParty, - @HiveField(2) List posts}); + {@HiveField(0) int? id, + @HiveField(1) List> verifiedPosts, + @HiveField(2) int latestGenesis, + @HiveField(3) List corruptedPostIds}); } /// @nodoc @@ -97,23 +107,28 @@ class __$$_NetworkChatHistoryCopyWithImpl<$Res> @override $Res call({ - Object? hostname = freezed, - Object? counterParty = freezed, - Object? posts = freezed, + Object? id = freezed, + Object? verifiedPosts = freezed, + Object? latestGenesis = freezed, + Object? corruptedPostIds = freezed, }) { return _then(_$_NetworkChatHistory( - hostname: hostname == freezed - ? _value.hostname - : hostname // ignore: cast_nullable_to_non_nullable - as String, - counterParty: counterParty == freezed - ? _value.counterParty - : counterParty // ignore: cast_nullable_to_non_nullable - as String, - posts: posts == freezed - ? _value._posts - : posts // ignore: cast_nullable_to_non_nullable - as List, + id: id == freezed + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + verifiedPosts: verifiedPosts == freezed + ? _value._verifiedPosts + : verifiedPosts // ignore: cast_nullable_to_non_nullable + as List>, + latestGenesis: latestGenesis == freezed + ? _value.latestGenesis + : latestGenesis // ignore: cast_nullable_to_non_nullable + as int, + corruptedPostIds: corruptedPostIds == freezed + ? _value._corruptedPostIds + : corruptedPostIds // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -123,29 +138,39 @@ class __$$_NetworkChatHistoryCopyWithImpl<$Res> @HiveType(typeId: 13, adapterName: 'NetworkChatHistoryClassAdapter') class _$_NetworkChatHistory extends _NetworkChatHistory { const _$_NetworkChatHistory( - {@HiveField(0) required this.hostname, - @HiveField(1) required this.counterParty, - @HiveField(2) required final List posts}) - : _posts = posts, + {@HiveField(0) this.id, + @HiveField(1) required final List> verifiedPosts, + @HiveField(2) required this.latestGenesis, + @HiveField(3) required final List corruptedPostIds}) + : _verifiedPosts = verifiedPosts, + _corruptedPostIds = corruptedPostIds, super._(); @override @HiveField(0) - final String hostname; + final int? id; + final List> _verifiedPosts; @override @HiveField(1) - final String counterParty; - final List _posts; + List> get verifiedPosts { + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_verifiedPosts); + } + @override @HiveField(2) - List get posts { + final int latestGenesis; + final List _corruptedPostIds; + @override + @HiveField(3) + List get corruptedPostIds { // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_posts); + return EqualUnmodifiableListView(_corruptedPostIds); } @override String toString() { - return 'NetworkChatHistory(hostname: $hostname, counterParty: $counterParty, posts: $posts)'; + return 'NetworkChatHistory(id: $id, verifiedPosts: $verifiedPosts, latestGenesis: $latestGenesis, corruptedPostIds: $corruptedPostIds)'; } @override @@ -153,18 +178,22 @@ class _$_NetworkChatHistory extends _NetworkChatHistory { return identical(this, other) || (other.runtimeType == runtimeType && other is _$_NetworkChatHistory && - const DeepCollectionEquality().equals(other.hostname, hostname) && + const DeepCollectionEquality().equals(other.id, id) && const DeepCollectionEquality() - .equals(other.counterParty, counterParty) && - const DeepCollectionEquality().equals(other._posts, _posts)); + .equals(other._verifiedPosts, _verifiedPosts) && + const DeepCollectionEquality() + .equals(other.latestGenesis, latestGenesis) && + const DeepCollectionEquality() + .equals(other._corruptedPostIds, _corruptedPostIds)); } @override int get hashCode => Object.hash( runtimeType, - const DeepCollectionEquality().hash(hostname), - const DeepCollectionEquality().hash(counterParty), - const DeepCollectionEquality().hash(_posts)); + const DeepCollectionEquality().hash(id), + const DeepCollectionEquality().hash(_verifiedPosts), + const DeepCollectionEquality().hash(latestGenesis), + const DeepCollectionEquality().hash(_corruptedPostIds)); @JsonKey(ignore: true) @override @@ -175,21 +204,26 @@ class _$_NetworkChatHistory extends _NetworkChatHistory { abstract class _NetworkChatHistory extends NetworkChatHistory { const factory _NetworkChatHistory( - {@HiveField(0) required final String hostname, - @HiveField(1) required final String counterParty, - @HiveField(2) required final List posts}) = + {@HiveField(0) final int? id, + @HiveField(1) required final List> verifiedPosts, + @HiveField(2) required final int latestGenesis, + @HiveField(3) required final List corruptedPostIds}) = _$_NetworkChatHistory; const _NetworkChatHistory._() : super._(); @override @HiveField(0) - String get hostname => throw _privateConstructorUsedError; + int? get id => throw _privateConstructorUsedError; @override @HiveField(1) - String get counterParty => throw _privateConstructorUsedError; + List> get verifiedPosts => + throw _privateConstructorUsedError; @override @HiveField(2) - List get posts => throw _privateConstructorUsedError; + int get latestGenesis => throw _privateConstructorUsedError; + @override + @HiveField(3) + List get corruptedPostIds => throw _privateConstructorUsedError; @override @JsonKey(ignore: true) _$$_NetworkChatHistoryCopyWith<_$_NetworkChatHistory> get copyWith => diff --git a/lib/model/network-chat-history.g.dart b/lib/model/network-chat-history.g.dart index 6c8db32..89e8196 100644 --- a/lib/model/network-chat-history.g.dart +++ b/lib/model/network-chat-history.g.dart @@ -18,22 +18,27 @@ class NetworkChatHistoryClassAdapter for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; return _$_NetworkChatHistory( - hostname: fields[0] as String, - counterParty: fields[1] as String, - posts: (fields[2] as List).cast(), + id: fields[0] as int?, + verifiedPosts: (fields[1] as List) + .map((dynamic e) => (e as Map).cast()) + .toList(), + latestGenesis: fields[2] as int, + corruptedPostIds: (fields[3] as List).cast(), ); } @override void write(BinaryWriter writer, _$_NetworkChatHistory obj) { writer - ..writeByte(3) + ..writeByte(4) ..writeByte(0) - ..write(obj.hostname) - ..writeByte(1) - ..write(obj.counterParty) + ..write(obj.id) ..writeByte(2) - ..write(obj.posts); + ..write(obj.latestGenesis) + ..writeByte(1) + ..write(obj.verifiedPosts) + ..writeByte(3) + ..write(obj.corruptedPostIds); } @override diff --git a/lib/pkg/storage.dart b/lib/pkg/storage.dart index e5489be..a779277 100644 --- a/lib/pkg/storage.dart +++ b/lib/pkg/storage.dart @@ -9,6 +9,7 @@ import 'package:sats/model/blockchain.dart'; import 'package:sats/model/fees.dart'; import 'package:sats/model/master.dart'; import 'package:sats/model/member-identity.dart'; +import 'package:sats/model/network-chat-history.dart'; import 'package:sats/model/network-identity.dart'; import 'package:sats/model/network-members.dart'; import 'package:sats/model/node.dart'; @@ -34,6 +35,7 @@ enum StoreKeys { SocialRoot, Networks, Members, + ChatHistory, } extension StoreKeysFunctions on StoreKeys { @@ -49,6 +51,7 @@ extension StoreKeysFunctions on StoreKeys { StoreKeys.SocialRoot: 'socialRoot', StoreKeys.Networks: 'networks', StoreKeys.Members: 'members', + StoreKeys.ChatHistory: 'chatHistory', }[this]!; } @@ -67,6 +70,7 @@ Future initializeHive() async { Hive.registerAdapter(NetworkIdentityClassAdapter()); // typeId: 11 Hive.registerAdapter(MemberIdentityAdapter()); // typeId: 12 Hive.registerAdapter(NetworkMembersClassAdapter()); // typeId: 12 + Hive.registerAdapter(NetworkChatHistoryClassAdapter()); // typeId: 13 const secureStorage = FlutterSecureStorage(); final encryprionKey = await secureStorage.read(key: 'key'); @@ -124,6 +128,10 @@ Future initializeHive() async { StoreKeys.Members.name, encryptionCipher: HiveAesCipher(encryptionKey), ); + await Hive.openBox( + StoreKeys.ChatHistory.name, + encryptionCipher: HiveAesCipher(encryptionKey), + ); await Hive.openBox( 'storage', encryptionCipher: HiveAesCipher(encryptionKey), diff --git a/lib/ui/component/Network/ChatCard.dart b/lib/ui/component/Network/ChatCard.dart index 4f2586a..d9a4e75 100644 --- a/lib/ui/component/Network/ChatCard.dart +++ b/lib/ui/component/Network/ChatCard.dart @@ -1,23 +1,26 @@ import 'package:flutter/material.dart'; -import 'package:sats/model/cypherpost.dart'; +import 'package:libcpclient/outputs.dart'; +import 'package:sats/cubit/network/overview.dart'; import 'package:sats/pkg/extensions.dart'; class ChatCard extends StatelessWidget { const ChatCard({ Key? key, this.isSelection = false, - required this.member, + required this.history, }) : super(key: key); - final CypherPostIdentity member; + final Verified history; final bool isSelection; @override Widget build(BuildContext context) { + final overviewCubit = context.select((OverviewCubit dc) => dc); + return GestureDetector( onTap: () { if (!isSelection) { - // context.push('/chat-directory'); + // context.push('/network-chat'); } }, child: Material( @@ -54,7 +57,10 @@ class ChatCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - '@' + member.username, + '@' + + overviewCubit + .usernameByPubkey(history.counterParty!)! + .username!, style: context.fonts.subtitle1!.copyWith( color: context.colours.onBackground, fontSize: 16, diff --git a/lib/ui/component/Network/ChatList.dart b/lib/ui/component/Network/ChatList.dart index c08495c..410bd9b 100644 --- a/lib/ui/component/Network/ChatList.dart +++ b/lib/ui/component/Network/ChatList.dart @@ -1,17 +1,20 @@ import 'package:flutter/material.dart'; +import 'package:sats/cubit/network/overview.dart'; import 'package:sats/model/cypherpost-mock.dart'; +import 'package:sats/pkg/extensions.dart'; import 'package:sats/ui/component/Network/ChatCard.dart'; class ChatList extends StatelessWidget { @override Widget build(BuildContext c) { + final history = c.select((OverviewCubit dc) => dc.state.verifiedPosts); return SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Column( children: [ - for (var m in members) ...[ + for (var h in history) ...[ const SizedBox(height: 2), - ChatCard(member: m), + ChatCard(history: h), const SizedBox(height: 2), ], ], diff --git a/lib/ui/component/Network/JoinLoader.dart b/lib/ui/component/Network/JoinLoader.dart index 616b1b4..e5777cf 100644 --- a/lib/ui/component/Network/JoinLoader.dart +++ b/lib/ui/component/Network/JoinLoader.dart @@ -27,6 +27,10 @@ class JoinLoader extends StatelessWidget { { return const Loading(text: 'Requesting invite code...'); } + case 'resync': + { + return const Loading(text: 'Resyncing with host...'); + } default: { return Container(); diff --git a/lib/ui/component/Network/NetworkJoinForm.dart b/lib/ui/component/Network/NetworkJoinForm.dart index b2197c6..0151a0d 100644 --- a/lib/ui/component/Network/NetworkJoinForm.dart +++ b/lib/ui/component/Network/NetworkJoinForm.dart @@ -225,8 +225,7 @@ class _NetworkJoinFormState extends State { primary: c.colours.primary, ), onPressed: () async { - networksCubit.resyncExistingUser(); - resyncWarning(c); + await networksCubit.resyncExistingUser(); }, child: Text('RE-SYNC'.toUpperCase()), ), @@ -279,39 +278,3 @@ Future inviteCodeHelp(BuildContext context) async { }, ); } - -Future resyncWarning(BuildContext context) async { - return showDialog( - context: context, - barrierDismissible: true, - builder: (BuildContext context) { - return AlertDialog( - contentPadding: const EdgeInsets.all(24.0), - insetPadding: const EdgeInsets.all(24.0), - backgroundColor: context.colours.onPrimaryContainer, - elevation: 24.0, - title: Text( - 'RESYNC IDENTITY', - style: context.fonts.headline5!.copyWith( - fontWeight: FontWeight.bold, - color: context.colours.onPrimary, - ), - ), - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'This feature is currently unavailable. It will be added in the next release.', - style: context.fonts.bodyMedium!.copyWith( - fontWeight: FontWeight.normal, - color: context.colours.onPrimary, - ), - ), - const SizedBox(height: 12), - ], - ), - ), - ); - }, - ); -} diff --git a/lib/ui/screen/NetworkOverview.dart b/lib/ui/screen/NetworkOverview.dart index 0836ab3..d77b4ad 100644 --- a/lib/ui/screen/NetworkOverview.dart +++ b/lib/ui/screen/NetworkOverview.dart @@ -12,11 +12,15 @@ import 'package:sats/ui/component/Network/OverviewLoader.dart'; class _NetworkOverview extends StatelessWidget { @override Widget build(BuildContext c) { + final overviewCubit = c.select((OverviewCubit oc) => oc); + return Scaffold( body: SafeArea( child: RefreshIndicator( displacement: 10.0, - onRefresh: () async {}, + onRefresh: () async { + await overviewCubit.fetchAllPosts(); + }, child: BlocBuilder( builder: (context, overviewState) { return CustomScrollView( diff --git a/packages/libcpclient/lib/cpclient.dart b/packages/libcpclient/lib/cpclient.dart index 4efbeb7..17d2071 100644 --- a/packages/libcpclient/lib/cpclient.dart +++ b/packages/libcpclient/lib/cpclient.dart @@ -103,6 +103,20 @@ class LibCPClientFFI { return resp; } + String selfInvitation({ + required String hostname, + required int socks5, + required String socialRoot, + }) { + final func = binary.lookupFunction('self_invite'); + final resp = func( + hostname.toNativeUtf8(), + socks5.toString().toNativeUtf8(), + socialRoot.toNativeUtf8(), + ).toDartString(); + return resp; + } + String leaveServer({ required String hostname, required int socks5, diff --git a/packages/libcpclient/update-core.sh b/packages/libcpclient/update-core.sh index bf3b34e..8461bce 100755 --- a/packages/libcpclient/update-core.sh +++ b/packages/libcpclient/update-core.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -RELEASE=v0.1.7 +RELEASE=v0.1.10 rm -rf releases mkdir -p releases diff --git a/test/lib/api/libcp_test.dart b/test/lib/api/libcp_test.dart index 0bca3cd..3418883 100644 --- a/test/lib/api/libcp_test.dart +++ b/test/lib/api/libcp_test.dart @@ -17,6 +17,8 @@ void main() { const username = 'ika0009'; const recipient = '76015c9bbf0b6d45a953a3ff4dcf6142624481ad747b45e1f775db1e0463c02e'; + const txprv = + 'xprv9s21ZrQH143K43j1kVYWahN4T3aAJNspThDrQxsLoN5Wmu96U6cysT4dVwPpn3ZdK2MKp57axqAXzSDe57pudJjLDh59KzoszabQSFnsGMu'; late LibCypherpost libcp; @@ -110,8 +112,8 @@ void main() { final postsAsChat = libcp.getAllPosts( hostname: hostname, socks5: socks5, - socialRoot: expectedSocialRoot, - genesisFilter: 0, + socialRoot: txprv, + genesisFilter: 1672859711718 + 1, ); inspect(postsAsChat.result);