diff --git a/lib/models/file.dart b/lib/models/file.dart index 007f20a0..e6559d6e 100644 --- a/lib/models/file.dart +++ b/lib/models/file.dart @@ -55,6 +55,7 @@ sealed class SnCloudFile with _$SnCloudFile { required DateTime createdAt, required DateTime updatedAt, required DateTime? deletedAt, + String? url, }) = _SnCloudFile; factory SnCloudFile.fromJson(Map json) => diff --git a/lib/models/file.freezed.dart b/lib/models/file.freezed.dart index f34e17a1..d7063918 100644 --- a/lib/models/file.freezed.dart +++ b/lib/models/file.freezed.dart @@ -281,7 +281,7 @@ as String?, /// @nodoc mixin _$SnCloudFile { - String get id; String get name; String? get description; Map? get fileMeta; Map? get userMeta; SnFilePool? get pool; List get sensitiveMarks; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; + String get id; String get name; String? get description; Map? get fileMeta; Map? get userMeta; SnFilePool? get pool; List get sensitiveMarks; String? get mimeType; String? get hash; int get size; DateTime? get uploadedAt; String? get uploadedTo; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String? get url; /// Create a copy of SnCloudFile /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -294,16 +294,16 @@ $SnCloudFileCopyWith get copyWith => _$SnCloudFileCopyWithImpl Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(fileMeta),const DeepCollectionEquality().hash(userMeta),pool,const DeepCollectionEquality().hash(sensitiveMarks),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt); +int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(fileMeta),const DeepCollectionEquality().hash(userMeta),pool,const DeepCollectionEquality().hash(sensitiveMarks),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt,url); @override String toString() { - return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, pool: $pool, sensitiveMarks: $sensitiveMarks, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; + return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, pool: $pool, sensitiveMarks: $sensitiveMarks, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, url: $url)'; } @@ -314,7 +314,7 @@ abstract mixin class $SnCloudFileCopyWith<$Res> { factory $SnCloudFileCopyWith(SnCloudFile value, $Res Function(SnCloudFile) _then) = _$SnCloudFileCopyWithImpl; @useResult $Res call({ - String id, String name, String? description, Map? fileMeta, Map? userMeta, SnFilePool? pool, List sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt + String id, String name, String? description, Map? fileMeta, Map? userMeta, SnFilePool? pool, List sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? url }); @@ -331,7 +331,7 @@ class _$SnCloudFileCopyWithImpl<$Res> /// Create a copy of SnCloudFile /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? pool = freezed,Object? sensitiveMarks = null,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? pool = freezed,Object? sensitiveMarks = null,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? url = freezed,}) { return _then(_self.copyWith( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable @@ -348,7 +348,8 @@ as DateTime?,uploadedTo: freezed == uploadedTo ? _self.uploadedTo : uploadedTo / as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable -as DateTime?, +as DateTime?,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?, )); } /// Create a copy of SnCloudFile @@ -442,10 +443,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String? description, Map? fileMeta, Map? userMeta, SnFilePool? pool, List sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String? description, Map? fileMeta, Map? userMeta, SnFilePool? pool, List sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? url)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _SnCloudFile() when $default != null: -return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.pool,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: +return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.pool,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.url);case _: return orElse(); } @@ -463,10 +464,10 @@ return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userM /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String name, String? description, Map? fileMeta, Map? userMeta, SnFilePool? pool, List sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String id, String name, String? description, Map? fileMeta, Map? userMeta, SnFilePool? pool, List sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? url) $default,) {final _that = this; switch (_that) { case _SnCloudFile(): -return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.pool,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);} +return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.pool,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.url);} } /// A variant of `when` that fallback to returning `null` /// @@ -480,10 +481,10 @@ return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userM /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String? description, Map? fileMeta, Map? userMeta, SnFilePool? pool, List sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String? description, Map? fileMeta, Map? userMeta, SnFilePool? pool, List sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? url)? $default,) {final _that = this; switch (_that) { case _SnCloudFile() when $default != null: -return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.pool,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: +return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userMeta,_that.pool,_that.sensitiveMarks,_that.mimeType,_that.hash,_that.size,_that.uploadedAt,_that.uploadedTo,_that.createdAt,_that.updatedAt,_that.deletedAt,_that.url);case _: return null; } @@ -495,7 +496,7 @@ return $default(_that.id,_that.name,_that.description,_that.fileMeta,_that.userM @JsonSerializable() class _SnCloudFile implements SnCloudFile { - const _SnCloudFile({required this.id, required this.name, required this.description, required final Map? fileMeta, required final Map? userMeta, required this.pool, final List sensitiveMarks = const [], required this.mimeType, required this.hash, required this.size, required this.uploadedAt, required this.uploadedTo, required this.createdAt, required this.updatedAt, required this.deletedAt}): _fileMeta = fileMeta,_userMeta = userMeta,_sensitiveMarks = sensitiveMarks; + const _SnCloudFile({required this.id, required this.name, required this.description, required final Map? fileMeta, required final Map? userMeta, required this.pool, final List sensitiveMarks = const [], required this.mimeType, required this.hash, required this.size, required this.uploadedAt, required this.uploadedTo, required this.createdAt, required this.updatedAt, required this.deletedAt, this.url}): _fileMeta = fileMeta,_userMeta = userMeta,_sensitiveMarks = sensitiveMarks; factory _SnCloudFile.fromJson(Map json) => _$SnCloudFileFromJson(json); @override final String id; @@ -535,6 +536,7 @@ class _SnCloudFile implements SnCloudFile { @override final DateTime createdAt; @override final DateTime updatedAt; @override final DateTime? deletedAt; +@override final String? url; /// Create a copy of SnCloudFile /// with the given fields replaced by the non-null parameter values. @@ -549,16 +551,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._fileMeta, _fileMeta)&&const DeepCollectionEquality().equals(other._userMeta, _userMeta)&&(identical(other.pool, pool) || other.pool == pool)&&const DeepCollectionEquality().equals(other._sensitiveMarks, _sensitiveMarks)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCloudFile&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&const DeepCollectionEquality().equals(other._fileMeta, _fileMeta)&&const DeepCollectionEquality().equals(other._userMeta, _userMeta)&&(identical(other.pool, pool) || other.pool == pool)&&const DeepCollectionEquality().equals(other._sensitiveMarks, _sensitiveMarks)&&(identical(other.mimeType, mimeType) || other.mimeType == mimeType)&&(identical(other.hash, hash) || other.hash == hash)&&(identical(other.size, size) || other.size == size)&&(identical(other.uploadedAt, uploadedAt) || other.uploadedAt == uploadedAt)&&(identical(other.uploadedTo, uploadedTo) || other.uploadedTo == uploadedTo)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.url, url) || other.url == url)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(_fileMeta),const DeepCollectionEquality().hash(_userMeta),pool,const DeepCollectionEquality().hash(_sensitiveMarks),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt); +int get hashCode => Object.hash(runtimeType,id,name,description,const DeepCollectionEquality().hash(_fileMeta),const DeepCollectionEquality().hash(_userMeta),pool,const DeepCollectionEquality().hash(_sensitiveMarks),mimeType,hash,size,uploadedAt,uploadedTo,createdAt,updatedAt,deletedAt,url); @override String toString() { - return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, pool: $pool, sensitiveMarks: $sensitiveMarks, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; + return 'SnCloudFile(id: $id, name: $name, description: $description, fileMeta: $fileMeta, userMeta: $userMeta, pool: $pool, sensitiveMarks: $sensitiveMarks, mimeType: $mimeType, hash: $hash, size: $size, uploadedAt: $uploadedAt, uploadedTo: $uploadedTo, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, url: $url)'; } @@ -569,7 +571,7 @@ abstract mixin class _$SnCloudFileCopyWith<$Res> implements $SnCloudFileCopyWith factory _$SnCloudFileCopyWith(_SnCloudFile value, $Res Function(_SnCloudFile) _then) = __$SnCloudFileCopyWithImpl; @override @useResult $Res call({ - String id, String name, String? description, Map? fileMeta, Map? userMeta, SnFilePool? pool, List sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt + String id, String name, String? description, Map? fileMeta, Map? userMeta, SnFilePool? pool, List sensitiveMarks, String? mimeType, String? hash, int size, DateTime? uploadedAt, String? uploadedTo, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String? url }); @@ -586,7 +588,7 @@ class __$SnCloudFileCopyWithImpl<$Res> /// Create a copy of SnCloudFile /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? pool = freezed,Object? sensitiveMarks = null,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? description = freezed,Object? fileMeta = freezed,Object? userMeta = freezed,Object? pool = freezed,Object? sensitiveMarks = null,Object? mimeType = freezed,Object? hash = freezed,Object? size = null,Object? uploadedAt = freezed,Object? uploadedTo = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? url = freezed,}) { return _then(_SnCloudFile( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable @@ -603,7 +605,8 @@ as DateTime?,uploadedTo: freezed == uploadedTo ? _self.uploadedTo : uploadedTo / as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable -as DateTime?, +as DateTime?,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?, )); } diff --git a/lib/models/file.g.dart b/lib/models/file.g.dart index a1f95113..8ddb4fa6 100644 --- a/lib/models/file.g.dart +++ b/lib/models/file.g.dart @@ -55,6 +55,7 @@ _SnCloudFile _$SnCloudFileFromJson(Map json) => _SnCloudFile( deletedAt: json['deleted_at'] == null ? null : DateTime.parse(json['deleted_at'] as String), + url: json['url'] as String?, ); Map _$SnCloudFileToJson(_SnCloudFile instance) => @@ -74,6 +75,7 @@ Map _$SnCloudFileToJson(_SnCloudFile instance) => 'created_at': instance.createdAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(), + 'url': instance.url, }; _SnCloudFileIndex _$SnCloudFileIndexFromJson(Map json) => diff --git a/lib/screens/account/profile.dart b/lib/screens/account/profile.dart index e0cb2d17..3057a2f2 100644 --- a/lib/screens/account/profile.dart +++ b/lib/screens/account/profile.dart @@ -476,9 +476,9 @@ class _AccountPublisherList extends StatelessWidget { subtitle: Text( publisher.bio.isNotEmpty ? publisher.bio - .split('\n') - .where((line) => line.trim().isNotEmpty) - .join('\n') + .split('\n') + .where((line) => line.trim().isNotEmpty) + .join('\n') : 'descriptionNone'.tr(), maxLines: 3, overflow: TextOverflow.ellipsis, @@ -550,16 +550,14 @@ class _AccountAction extends StatelessWidget { ), ), onPressed: relationshipAction, - label: - Text( - accountRelationship.value == null - ? 'addFriendShort' - : 'added', - ).tr(), - icon: - accountRelationship.value == null - ? const Icon(Symbols.person_add) - : const Icon(Symbols.person_check), + label: Text( + accountRelationship.value == null + ? 'addFriendShort' + : 'added', + ).tr(), + icon: accountRelationship.value == null + ? const Icon(Symbols.person_add) + : const Icon(Symbols.person_check), ), ), if (accountRelationship.value == null || @@ -579,16 +577,14 @@ class _AccountAction extends StatelessWidget { ), ), onPressed: blockAction, - label: - Text( - accountRelationship.value == null - ? 'blockUser' - : 'unblockUser', - ).tr(), - icon: - accountRelationship.value == null - ? const Icon(Symbols.block) - : const Icon(Symbols.person_cancel), + label: Text( + accountRelationship.value == null + ? 'blockUser' + : 'unblockUser', + ).tr(), + icon: accountRelationship.value == null + ? const Icon(Symbols.block) + : const Icon(Symbols.person_cancel), ), ), ], @@ -600,13 +596,12 @@ class _AccountAction extends StatelessWidget { child: FilledButton.icon( onPressed: directMessageAction, icon: const Icon(Symbols.message), - label: - Text( - accountChat.value == null - ? 'createDirectMessage' - : 'gotoDirectMessage', - maxLines: 1, - ).tr(), + label: Text( + accountChat.value == null + ? 'createDirectMessage' + : 'gotoDirectMessage', + maxLines: 1, + ).tr(), ), ), IconButton.filled( @@ -664,7 +659,7 @@ Future accountAppbarForcegroundColor(Ref ref, String uname) async { if (account.profile.background == null) return null; final colors = await ColorExtractionService.getColorsFromImage( CloudImageWidget.provider( - fileId: account.profile.background!.id, + file: account.profile.background!, serverUrl: ref.watch(serverUrlProvider), ), ); @@ -838,262 +833,256 @@ class AccountProfileScreen extends HookConsumerWidget { final accountPublishers = ref.watch(accountPublishersProvider(data.id)); return AppScaffold( isNoBackground: false, - appBar: - isWideScreen(context) - ? AppBar( - foregroundColor: appbarColor.value, - leading: PageBackButton( - color: appbarColor.value, + appBar: isWideScreen(context) + ? AppBar( + foregroundColor: appbarColor.value, + leading: PageBackButton( + color: appbarColor.value, + shadows: [appbarShadow], + ), + title: Text( + data.nick, + style: TextStyle( + color: + appbarColor.value ?? + Theme.of(context).appBarTheme.foregroundColor, shadows: [appbarShadow], ), - title: Text( - data.nick, - style: TextStyle( - color: - appbarColor.value ?? - Theme.of(context).appBarTheme.foregroundColor, - shadows: [appbarShadow], - ), - ), - ) - : null, - body: - isWideScreen(context) - ? Row( - children: [ - Flexible( - child: CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: _AccountBasicInfo( - data: data, - uname: name, - accountDeveloper: accountDeveloper, - ).padding(horizontal: 4, top: 20), - ), - if (data.badges.isNotEmpty) - SliverToBoxAdapter( - child: Card( - child: BadgeList( - badges: data.badges, - ).padding(horizontal: 26, vertical: 20), - ).padding(left: 2, right: 4), - ), - SliverToBoxAdapter( - child: Column( - spacing: 12, - children: [ - LevelingProgressCard( - level: data.profile.level, - experience: data.profile.experience, - progress: data.profile.levelingProgress, - ).padding(left: 2, right: 4), - if (data.profile.verification != null) - Card( - margin: EdgeInsets.zero, - child: VerificationStatusCard( - mark: data.profile.verification!, - ), - ), - ], - ).padding(horizontal: 4, top: 8), - ), - SliverToBoxAdapter( - child: _AccountProfileBio( - data: data, - ).padding(top: 4), - ), - if (data.profile.links.isNotEmpty) - SliverToBoxAdapter( - child: _AccountProfileLinks(data: data), - ), - if (data.contacts.any((c) => c.isPublic)) - SliverToBoxAdapter( - child: _AccountProfileContacts(data: data), - ), - SliverToBoxAdapter( - child: _AccountProfileDetail(data: data), - ), - ], - ), - ), - Flexible( - child: CustomScrollView( - slivers: [ - SliverGap(18), - SliverToBoxAdapter( - child: ActivityPresenceWidget( - uname: name, - ).padding(horizontal: 4, top: 4, bottom: 4), - ), - SliverToBoxAdapter( - child: _AccountPublisherList( - publishers: accountPublishers.value ?? [], - ), - ), - if (user.value != null && !isCurrentUser) - SliverToBoxAdapter( - child: _AccountAction( - data: data, - accountRelationship: accountRelationship, - accountChat: accountChat, - relationshipAction: relationshipAction, - blockAction: blockAction, - directMessageAction: directMessageAction, - ), - ), + ), + ) + : null, + body: isWideScreen(context) + ? Row( + children: [ + Flexible( + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: _AccountBasicInfo( + data: data, + uname: name, + accountDeveloper: accountDeveloper, + ).padding(horizontal: 4, top: 20), + ), + if (data.badges.isNotEmpty) SliverToBoxAdapter( child: Card( - child: FortuneGraphWidget( - events: accountEvents, - eventCalandarUser: data.name, - margin: EdgeInsets.zero, - ), + child: BadgeList( + badges: data.badges, + ).padding(horizontal: 26, vertical: 20), + ).padding(left: 2, right: 4), + ), + SliverToBoxAdapter( + child: Column( + spacing: 12, + children: [ + LevelingProgressCard( + level: data.profile.level, + experience: data.profile.experience, + progress: data.profile.levelingProgress, + ).padding(left: 2, right: 4), + if (data.profile.verification != null) + Card( + margin: EdgeInsets.zero, + child: VerificationStatusCard( + mark: data.profile.verification!, + ), + ), + ], + ).padding(horizontal: 4, top: 8), + ), + SliverToBoxAdapter( + child: _AccountProfileBio( + data: data, + ).padding(top: 4), + ), + if (data.profile.links.isNotEmpty) + SliverToBoxAdapter( + child: _AccountProfileLinks(data: data), + ), + if (data.contacts.any((c) => c.isPublic)) + SliverToBoxAdapter( + child: _AccountProfileContacts(data: data), + ), + SliverToBoxAdapter( + child: _AccountProfileDetail(data: data), + ), + ], + ), + ), + Flexible( + child: CustomScrollView( + slivers: [ + SliverGap(18), + SliverToBoxAdapter( + child: ActivityPresenceWidget( + uname: name, + ).padding(horizontal: 4, top: 4, bottom: 4), + ), + SliverToBoxAdapter( + child: _AccountPublisherList( + publishers: accountPublishers.value ?? [], + ), + ), + if (user.value != null && !isCurrentUser) + SliverToBoxAdapter( + child: _AccountAction( + data: data, + accountRelationship: accountRelationship, + accountChat: accountChat, + relationshipAction: relationshipAction, + blockAction: blockAction, + directMessageAction: directMessageAction, ), ), - ], - ), - ), - ], - ).padding(horizontal: 24) - : CustomScrollView( - slivers: [ - SliverAppBar( - foregroundColor: appbarColor.value, - expandedHeight: 180, - pinned: true, - leading: PageBackButton( - color: appbarColor.value, - shadows: [appbarShadow], - ), - flexibleSpace: Stack( - children: [ - Positioned.fill( - child: - data.profile.background?.id != null - ? CloudImageWidget( - file: data.profile.background, - ) - : Container( - color: - Theme.of( - context, - ).appBarTheme.backgroundColor, - ), - ), - FlexibleSpaceBar( - title: Text( - data.nick, - style: TextStyle( - color: - appbarColor.value ?? - Theme.of( - context, - ).appBarTheme.foregroundColor, - shadows: [appbarShadow], - ), + SliverToBoxAdapter( + child: Card( + child: FortuneGraphWidget( + events: accountEvents, + eventCalandarUser: data.name, + margin: EdgeInsets.zero, ), ), - ], - ), + ), + ], ), - SliverToBoxAdapter( - child: _AccountBasicInfo( - data: data, - uname: name, - accountDeveloper: accountDeveloper, - ).padding(horizontal: 4, top: 8), + ), + ], + ).padding(horizontal: 24) + : CustomScrollView( + slivers: [ + SliverAppBar( + foregroundColor: appbarColor.value, + expandedHeight: 180, + pinned: true, + leading: PageBackButton( + color: appbarColor.value, + shadows: [appbarShadow], ), - if (data.badges.isNotEmpty) - SliverToBoxAdapter( - child: Card( - child: BadgeList( - badges: data.badges, - ).padding(horizontal: 26, vertical: 20), - ).padding(horizontal: 4), - ), - SliverToBoxAdapter( - child: Column( - children: [ - LevelingProgressCard( - level: data.profile.level, - experience: data.profile.experience, - progress: data.profile.levelingProgress, - ).padding(top: 8, horizontal: 8, bottom: 4), - if (data.profile.verification != null) - Card( - child: VerificationStatusCard( - mark: data.profile.verification!, - ), - ).padding(horizontal: 4), - ], - ), + flexibleSpace: Stack( + children: [ + Positioned.fill( + child: data.profile.background?.id != null + ? CloudImageWidget( + file: data.profile.background, + ) + : Container( + color: Theme.of( + context, + ).appBarTheme.backgroundColor, + ), + ), + FlexibleSpaceBar( + title: Text( + data.nick, + style: TextStyle( + color: + appbarColor.value ?? + Theme.of( + context, + ).appBarTheme.foregroundColor, + shadows: [appbarShadow], + ), + ), + ), + ], ), - SliverToBoxAdapter( - child: _AccountProfileBio( - data: data, - ).padding(horizontal: 4), - ), - if (data.profile.links.isNotEmpty) - SliverToBoxAdapter( - child: _AccountProfileLinks( - data: data, - ).padding(horizontal: 4), - ), - if (data.contacts.any((c) => c.isPublic)) - SliverToBoxAdapter( - child: _AccountProfileContacts( - data: data, - ).padding(horizontal: 4), - ), - SliverToBoxAdapter( - child: ActivityPresenceWidget( - uname: name, - ).padding(horizontal: 8, top: 4, bottom: 4), - ), - SliverToBoxAdapter( - child: _AccountPublisherList( - publishers: accountPublishers.value ?? [], - ).padding(horizontal: 4), - ), - SliverToBoxAdapter( - child: _AccountProfileDetail( - data: data, - ).padding(horizontal: 4), - ), - if (user.value != null && !isCurrentUser) - SliverToBoxAdapter( - child: _AccountAction( - data: data, - accountRelationship: accountRelationship, - accountChat: accountChat, - relationshipAction: relationshipAction, - blockAction: blockAction, - directMessageAction: directMessageAction, - ).padding(horizontal: 4), - ), + ), + SliverToBoxAdapter( + child: _AccountBasicInfo( + data: data, + uname: name, + accountDeveloper: accountDeveloper, + ).padding(horizontal: 4, top: 8), + ), + if (data.badges.isNotEmpty) SliverToBoxAdapter( child: Card( - child: FortuneGraphWidget( - events: accountEvents, - eventCalandarUser: data.name, - ), + child: BadgeList( + badges: data.badges, + ).padding(horizontal: 26, vertical: 20), ).padding(horizontal: 4), ), - ], - ), + SliverToBoxAdapter( + child: Column( + children: [ + LevelingProgressCard( + level: data.profile.level, + experience: data.profile.experience, + progress: data.profile.levelingProgress, + ).padding(top: 8, horizontal: 8, bottom: 4), + if (data.profile.verification != null) + Card( + child: VerificationStatusCard( + mark: data.profile.verification!, + ), + ).padding(horizontal: 4), + ], + ), + ), + SliverToBoxAdapter( + child: _AccountProfileBio( + data: data, + ).padding(horizontal: 4), + ), + if (data.profile.links.isNotEmpty) + SliverToBoxAdapter( + child: _AccountProfileLinks( + data: data, + ).padding(horizontal: 4), + ), + if (data.contacts.any((c) => c.isPublic)) + SliverToBoxAdapter( + child: _AccountProfileContacts( + data: data, + ).padding(horizontal: 4), + ), + SliverToBoxAdapter( + child: ActivityPresenceWidget( + uname: name, + ).padding(horizontal: 8, top: 4, bottom: 4), + ), + SliverToBoxAdapter( + child: _AccountPublisherList( + publishers: accountPublishers.value ?? [], + ).padding(horizontal: 4), + ), + SliverToBoxAdapter( + child: _AccountProfileDetail( + data: data, + ).padding(horizontal: 4), + ), + if (user.value != null && !isCurrentUser) + SliverToBoxAdapter( + child: _AccountAction( + data: data, + accountRelationship: accountRelationship, + accountChat: accountChat, + relationshipAction: relationshipAction, + blockAction: blockAction, + directMessageAction: directMessageAction, + ).padding(horizontal: 4), + ), + SliverToBoxAdapter( + child: Card( + child: FortuneGraphWidget( + events: accountEvents, + eventCalandarUser: data.name, + ), + ).padding(horizontal: 4), + ), + ], + ), ); }, - error: - (error, stackTrace) => AppScaffold( - appBar: AppBar(leading: const PageBackButton()), - body: Center(child: Text(error.toString())), - ), - loading: - () => AppScaffold( - appBar: AppBar(leading: const PageBackButton()), - body: Center(child: CircularProgressIndicator()), - ), + error: (error, stackTrace) => AppScaffold( + appBar: AppBar(leading: const PageBackButton()), + body: Center(child: Text(error.toString())), + ), + loading: () => AppScaffold( + appBar: AppBar(leading: const PageBackButton()), + body: Center(child: CircularProgressIndicator()), + ), ); } } diff --git a/lib/screens/posts/publisher_profile.dart b/lib/screens/posts/publisher_profile.dart index 78359cb2..2a155e76 100644 --- a/lib/screens/posts/publisher_profile.dart +++ b/lib/screens/posts/publisher_profile.dart @@ -393,7 +393,7 @@ Future publisherAppbarForcegroundColor(Ref ref, String pubName) async { if (publisher.background == null) return null; final colors = await ColorExtractionService.getColorsFromImage( CloudImageWidget.provider( - fileId: publisher.background!.id, + file: publisher.background!, serverUrl: ref.watch(serverUrlProvider), ), ); diff --git a/lib/screens/realm/realm_detail.dart b/lib/screens/realm/realm_detail.dart index c5740ae1..dd7d29f5 100644 --- a/lib/screens/realm/realm_detail.dart +++ b/lib/screens/realm/realm_detail.dart @@ -33,7 +33,7 @@ final realmAppbarForegroundColorProvider = FutureProvider.autoDispose if (realm?.background == null) return null; final colors = await ColorExtractionService.getColorsFromImage( CloudImageWidget.provider( - fileId: realm!.background!.id, + file: realm!.background!, serverUrl: ref.watch(serverUrlProvider), ), ); diff --git a/lib/widgets/activitypub/actor_profile.dart b/lib/widgets/activitypub/actor_profile.dart index 9979798e..4166ca06 100644 --- a/lib/widgets/activitypub/actor_profile.dart +++ b/lib/widgets/activitypub/actor_profile.dart @@ -3,11 +3,11 @@ import 'package:flutter/material.dart'; import 'package:island/models/activitypub.dart'; import 'package:material_symbols_icons/symbols.dart'; -class ActorAvatarWidget extends StatelessWidget { +class ActorPictureWidget extends StatelessWidget { final SnActivityPubActor actor; final double radius; - const ActorAvatarWidget({super.key, required this.actor, this.radius = 16}); + const ActorPictureWidget({super.key, required this.actor, this.radius = 16}); @override Widget build(BuildContext context) { @@ -29,10 +29,12 @@ class ActorAvatarWidget extends StatelessWidget { backgroundImage: CachedNetworkImageProvider(avatarUrl), radius: radius, backgroundColor: Theme.of(context).colorScheme.surfaceContainer, - child: Icon( - Symbols.person, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), + child: avatarUrl.isNotEmpty + ? null + : Icon( + Symbols.person, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ), Positioned( right: 0, diff --git a/lib/widgets/alert.dart b/lib/widgets/alert.dart index 697a5e12..5bafaff2 100644 --- a/lib/widgets/alert.dart +++ b/lib/widgets/alert.dart @@ -4,11 +4,13 @@ import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/main.dart'; import 'package:island/talker.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:top_snackbar_flutter/top_snack_bar.dart'; +import 'package:url_launcher/url_launcher.dart'; void showSnackBar(String message, {SnackBarAction? action}) { final context = globalOverlay.currentState!.context; @@ -373,3 +375,20 @@ Future showConfirmAlert( ); return result ?? false; } + +Future openExternalLink(Uri url, WidgetRef ref) async { + final whitelistDomains = ['solian.app', 'solsynth.dev']; + if (whitelistDomains.any( + (domain) => url.host == domain || url.host.endsWith('.$domain'), + )) { + await launchUrl(url, mode: LaunchMode.externalApplication); + } else { + final value = await showConfirmAlert( + 'openLinkConfirmDescription'.tr(args: [url.toString()]), + 'openLinkConfirm'.tr(), + ); + if (value) { + await launchUrl(url, mode: LaunchMode.externalApplication); + } + } +} diff --git a/lib/widgets/content/cloud_file_lightbox.dart b/lib/widgets/content/cloud_file_lightbox.dart index c7deb371..cbfc6c4a 100644 --- a/lib/widgets/content/cloud_file_lightbox.dart +++ b/lib/widgets/content/cloud_file_lightbox.dart @@ -62,7 +62,7 @@ class CloudFileLightbox extends HookConsumerWidget { controller: photoViewController, heroAttributes: PhotoViewHeroAttributes(tag: heroTag), imageProvider: CloudImageWidget.provider( - fileId: item.id, + file: item, serverUrl: serverUrl, original: showOriginal.value, ), @@ -118,20 +118,21 @@ class CloudFileLightbox extends HookConsumerWidget { onPressed: showInfoSheet, shadows: WhiteShadows.standard, ), - FileActionButton.more( - onPressed: () { - final router = GoRouter.of(context); - Navigator.of(context).pop(context); - Future(() { - router.pushNamed( - 'fileDetail', - pathParameters: {'id': item.id}, - extra: item, - ); - }); - }, - shadows: WhiteShadows.standard, - ), + if (item.url != null) + FileActionButton.more( + onPressed: () { + final router = GoRouter.of(context); + Navigator.of(context).pop(context); + Future(() { + router.pushNamed( + 'fileDetail', + pathParameters: {'id': item.id}, + extra: item, + ); + }); + }, + shadows: WhiteShadows.standard, + ), ], showExtraOnLeft: true, ), diff --git a/lib/widgets/content/cloud_files.dart b/lib/widgets/content/cloud_files.dart index e49839ac..cab4e02a 100644 --- a/lib/widgets/content/cloud_files.dart +++ b/lib/widgets/content/cloud_files.dart @@ -41,7 +41,7 @@ class CloudFileWidget extends HookConsumerWidget { appSettingsProvider.select((s) => s.dataSavingMode), ); final serverUrl = ref.watch(serverUrlProvider); - final uri = '$serverUrl/drive/files/${item.id}'; + final uri = item.url ?? '$serverUrl/drive/files/${item.id}'; final unlocked = useState(false); @@ -529,7 +529,7 @@ class CloudImageWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final serverUrl = ref.watch(serverUrlProvider); - final uri = '$serverUrl/drive/files/${file?.id ?? fileId}'; + final uri = file?.url ?? '$serverUrl/drive/files/${file?.id ?? fileId}'; return AspectRatio( aspectRatio: aspectRatio, @@ -540,13 +540,15 @@ class CloudImageWidget extends ConsumerWidget { } static ImageProvider provider({ - required String fileId, + required SnCloudFile file, required String serverUrl, bool original = false, }) { - final uri = original - ? '$serverUrl/drive/files/$fileId?original=true' - : '$serverUrl/drive/files/$fileId'; + final uri = + file.url ?? + (original + ? '$serverUrl/drive/files/${file.id}?original=true' + : '$serverUrl/drive/files/${file.id}'); return CachedNetworkImageProvider(uri); } } diff --git a/lib/widgets/content/file_viewer_contents.dart b/lib/widgets/content/file_viewer_contents.dart index 1507bccb..6c23412f 100644 --- a/lib/widgets/content/file_viewer_contents.dart +++ b/lib/widgets/content/file_viewer_contents.dart @@ -167,7 +167,7 @@ class ImageFileContent extends HookConsumerWidget { ), controller: photoViewController, imageProvider: CloudImageWidget.provider( - fileId: item.id, + file: item, serverUrl: ref.watch(serverUrlProvider), original: showOriginal.value, ), diff --git a/lib/widgets/content/markdown.dart b/lib/widgets/content/markdown.dart index 3251b959..3141d3c3 100644 --- a/lib/widgets/content/markdown.dart +++ b/lib/widgets/content/markdown.dart @@ -21,7 +21,6 @@ import 'package:markdown/markdown.dart' as markdown; import 'package:markdown_widget/markdown_widget.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:uuid/uuid.dart'; import 'image.dart'; @@ -139,7 +138,7 @@ class MarkdownTextContent extends HookConsumerWidget { style: linkStyle ?? TextStyle(color: Theme.of(context).colorScheme.primary), - onTap: (href) { + onTap: (href) async { final url = Uri.tryParse(href); if (url != null) { if (url.scheme == 'solian') { @@ -147,22 +146,7 @@ class MarkdownTextContent extends HookConsumerWidget { context.push(fullPath); return; } - final whitelistDomains = ['solian.app', 'solsynth.dev']; - if (whitelistDomains.any( - (domain) => - url.host == domain || url.host.endsWith('.$domain'), - )) { - launchUrl(url, mode: LaunchMode.externalApplication); - return; - } - showConfirmAlert( - 'openLinkConfirmDescription'.tr(args: [url.toString()]), - 'openLinkConfirm'.tr(), - ).then((value) { - if (value) { - launchUrl(url, mode: LaunchMode.externalApplication); - } - }); + await openExternalLink(url, ref); } else { showSnackBar( 'brokenLink'.tr(args: [href]), diff --git a/lib/widgets/post/post_shared.dart b/lib/widgets/post/post_shared.dart index ed989277..d613e329 100644 --- a/lib/widgets/post/post_shared.dart +++ b/lib/widgets/post/post_shared.dart @@ -146,7 +146,7 @@ class PostReplyPreview extends HookConsumerWidget { } // Handle actor case if (post.actor != null) { - return ActorAvatarWidget(actor: post.actor!, radius: radius); + return ActorPictureWidget(actor: post.actor!, radius: radius); } // Fallback return ProfilePictureWidget(fileId: null, radius: radius); @@ -452,7 +452,7 @@ class ReferencedPostWidget extends StatelessWidget { } // Handle actor case if (post.actor != null) { - return ActorAvatarWidget(actor: post.actor!, radius: radius); + return ActorPictureWidget(actor: post.actor!, radius: radius); } // Fallback return ProfilePictureWidget(fileId: null, radius: radius); @@ -657,7 +657,7 @@ class ReferencedPostWidget extends StatelessWidget { } } -class PostHeader extends StatelessWidget { +class PostHeader extends HookConsumerWidget { final SnPost item; final bool isFullPost; final Widget? trailing; @@ -695,7 +695,7 @@ class PostHeader extends StatelessWidget { } // Handle actor case if (post.actor != null) { - return ActorAvatarWidget(actor: post.actor!, radius: radius); + return ActorPictureWidget(actor: post.actor!, radius: radius); } // Fallback return ProfilePictureWidget(fileId: null, radius: radius); @@ -746,7 +746,7 @@ class PostHeader extends StatelessWidget { } @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return Column( children: [ Row( @@ -756,12 +756,12 @@ class PostHeader extends StatelessWidget { GestureDetector( onTap: isInteractive && _getPublisherName(item) != null ? () { - context.pushNamed( - 'publisherProfile', - pathParameters: { - 'name': _getPublisherName(item) as String, - }, - ); + if (item.publisher != null) { + context.pushNamed( + 'publisherProfile', + pathParameters: {'name': item.publisher!.name}, + ); + } } : null, child: _buildProfilePicture(context, item, radius: 16),