diff --git a/.gitignore b/.gitignore index 33ae96a89..4ba6b0062 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ app.*.map.json # builder **/node_modules/ +**/build/ # Release /private_keys/ \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2976cc0df..0e72126e1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -36,6 +36,11 @@ PODS: - DKImagePickerController/PhotoGallery - Flutter - Flutter (1.0.0) + - flutter_image_compress_common (1.0.0): + - Flutter + - Mantle + - SDWebImage + - SDWebImageWebPCoder - flutter_secure_storage (6.0.0): - Flutter - FMDB (2.7.5): @@ -45,6 +50,21 @@ PODS: - Flutter - image_gallery_saver (2.0.2): - Flutter + - libwebp (1.3.2): + - libwebp/demux (= 1.3.2) + - libwebp/mux (= 1.3.2) + - libwebp/sharpyuv (= 1.3.2) + - libwebp/webp (= 1.3.2) + - libwebp/demux (1.3.2): + - libwebp/webp + - libwebp/mux (1.3.2): + - libwebp/demux + - libwebp/sharpyuv (1.3.2) + - libwebp/webp (1.3.2): + - libwebp/sharpyuv + - Mantle (2.2.0): + - Mantle/extobjc (= 2.2.0) + - Mantle/extobjc (2.2.0) - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -57,6 +77,9 @@ PODS: - SDWebImage (5.15.8): - SDWebImage/Core (= 5.15.8) - SDWebImage/Core (5.15.8) + - SDWebImageWebPCoder (0.11.0): + - libwebp (~> 1.0) + - SDWebImage/Core (~> 5.15) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -73,6 +96,7 @@ DEPENDENCIES: - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) + - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_editor_common (from `.symlinks/plugins/image_editor_common/ios`) - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) @@ -90,7 +114,10 @@ SPEC REPOS: - DKImagePickerController - DKPhotoGallery - FMDB + - libwebp + - Mantle - SDWebImage + - SDWebImageWebPCoder - SwiftyGif EXTERNAL SOURCES: @@ -100,6 +127,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: Flutter + flutter_image_compress_common: + :path: ".symlinks/plugins/flutter_image_compress_common/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" image_editor_common: @@ -129,15 +158,19 @@ SPEC CHECKSUMS: DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: ce3938a0df3cc1ef404671531facef740d03f920 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43 image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb + libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 + Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1 SDWebImage: cb032eba469c54e0000e78bcb0a13cdde0a52798 + SDWebImageWebPCoder: 295a6573c512f54ad2dd58098e64e17dcf008499 share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028 shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a diff --git a/lib/const.dart b/lib/const.dart new file mode 100644 index 000000000..6b5599df2 --- /dev/null +++ b/lib/const.dart @@ -0,0 +1 @@ +const misskeyIOReactionDelay = 1500; diff --git a/lib/extensions/text_editing_controller_extension.dart b/lib/extensions/text_editing_controller_extension.dart index eff5b0156..40c1b3d1b 100644 --- a/lib/extensions/text_editing_controller_extension.dart +++ b/lib/extensions/text_editing_controller_extension.dart @@ -1,25 +1,80 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:miria/model/input_completion_type.dart'; extension TextEditingControllerExtension on TextEditingController { - bool get isIncludeBeforeColon { - if (selection.base.offset == -1) return false; - return text.substring(0, selection.base.offset).contains(":"); + String? get textBeforeSelection { + final baseOffset = selection.baseOffset; + if (baseOffset < 0) { + return null; + } + return text.substring(0, baseOffset); } - bool get isEmojiScope { - final position = selection.base.offset; - final startPosition = text.substring(0, position).lastIndexOf(":") + 1; + String? get emojiQuery { + final textBeforeSelection = this.textBeforeSelection; + if (textBeforeSelection == null) { + return null; + } + final lastColonIndex = textBeforeSelection.lastIndexOf(":"); + if (lastColonIndex < 0) { + return null; + } if (RegExp(r':[a-zA-z_0-9]+?:$') - .hasMatch(text.substring(0, startPosition))) { - return true; + .hasMatch(text.substring(0, lastColonIndex + 1))) { + return null; + } else { + return textBeforeSelection.substring(lastColonIndex + 1); + } + } + + String? get mfmFnQuery { + final textBeforeSelection = this.textBeforeSelection; + if (textBeforeSelection == null) { + return null; + } + final lastOpenTagIndex = textBeforeSelection.lastIndexOf(r"$["); + if (lastOpenTagIndex < 0) { + return null; + } + final query = textBeforeSelection.substring(lastOpenTagIndex + 2); + if (RegExp(r"^[a-z234]*$").hasMatch(query)) { + return query; + } else { + return null; } - return false; } - String get emojiSearchValue { - final position = selection.base.offset; - final startPosition = text.substring(0, position).lastIndexOf(":") + 1; - return text.substring(startPosition, position); + String? get hashtagQuery { + final textBeforeSelection = this.textBeforeSelection; + if (textBeforeSelection == null) { + return null; + } + final lastHashIndex = textBeforeSelection.lastIndexOf("#"); + if (lastHashIndex < 0) { + return null; + } + final query = textBeforeSelection.substring(lastHashIndex + 1); + if (query.contains(RegExp(r"""[ \u3000\t.,!?'"#:/[\]【】()「」()<>]"""))) { + return null; + } else { + return query; + } + } + + InputCompletionType get inputCompletionType { + final emojiQuery = this.emojiQuery; + if (emojiQuery != null) { + return Emoji(emojiQuery); + } + final mfmFnQuery = this.mfmFnQuery; + if (mfmFnQuery != null) { + return MfmFn(mfmFnQuery); + } + final hashtagQuery = this.hashtagQuery; + if (hashtagQuery != null) { + return Hashtag(hashtagQuery); + } + return Basic(); } void insert(String insertText, {String? afterText}) { diff --git a/lib/extensions/users_lists_show_response_extension.dart b/lib/extensions/users_lists_show_response_extension.dart new file mode 100644 index 000000000..2525bd78c --- /dev/null +++ b/lib/extensions/users_lists_show_response_extension.dart @@ -0,0 +1,13 @@ +import 'package:misskey_dart/misskey_dart.dart'; + +extension UsersListsShowResponseExtension on UsersListsShowResponse { + UsersList toUsersList() { + return UsersList( + id: id, + createdAt: createdAt, + name: name, + userIds: userIds, + isPublic: isPublic, + ); + } +} diff --git a/lib/model/antenna_settings.dart b/lib/model/antenna_settings.dart new file mode 100644 index 000000000..588309e11 --- /dev/null +++ b/lib/model/antenna_settings.dart @@ -0,0 +1,36 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +part 'antenna_settings.freezed.dart'; + +@freezed +class AntennaSettings with _$AntennaSettings { + const factory AntennaSettings({ + @Default("") String name, + @Default(AntennaSource.all) AntennaSource src, + String? userListId, + @Default([]) List> keywords, + @Default([]) List> excludeKeywords, + @Default([]) List users, + @Default(false) bool caseSensitive, + @Default(false) bool withReplies, + @Default(false) bool withFile, + @Default(false) bool notify, + }) = _AntennaSettings; + const AntennaSettings._(); + + factory AntennaSettings.fromAntenna(Antenna antenna) { + return AntennaSettings( + name: antenna.name, + src: antenna.src, + userListId: antenna.userListId, + keywords: antenna.keywords, + excludeKeywords: antenna.excludeKeywords, + users: antenna.users, + caseSensitive: antenna.caseSensitive, + withReplies: antenna.withReplies, + withFile: antenna.withFile, + notify: antenna.notify, + ); + } +} diff --git a/lib/model/antenna_settings.freezed.dart b/lib/model/antenna_settings.freezed.dart new file mode 100644 index 000000000..3a1bc6857 --- /dev/null +++ b/lib/model/antenna_settings.freezed.dart @@ -0,0 +1,364 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'antenna_settings.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$AntennaSettings { + String get name => throw _privateConstructorUsedError; + AntennaSource get src => throw _privateConstructorUsedError; + String? get userListId => throw _privateConstructorUsedError; + List> get keywords => throw _privateConstructorUsedError; + List> get excludeKeywords => throw _privateConstructorUsedError; + List get users => throw _privateConstructorUsedError; + bool get caseSensitive => throw _privateConstructorUsedError; + bool get withReplies => throw _privateConstructorUsedError; + bool get withFile => throw _privateConstructorUsedError; + bool get notify => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AntennaSettingsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AntennaSettingsCopyWith<$Res> { + factory $AntennaSettingsCopyWith( + AntennaSettings value, $Res Function(AntennaSettings) then) = + _$AntennaSettingsCopyWithImpl<$Res, AntennaSettings>; + @useResult + $Res call( + {String name, + AntennaSource src, + String? userListId, + List> keywords, + List> excludeKeywords, + List users, + bool caseSensitive, + bool withReplies, + bool withFile, + bool notify}); +} + +/// @nodoc +class _$AntennaSettingsCopyWithImpl<$Res, $Val extends AntennaSettings> + implements $AntennaSettingsCopyWith<$Res> { + _$AntennaSettingsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? src = null, + Object? userListId = freezed, + Object? keywords = null, + Object? excludeKeywords = null, + Object? users = null, + Object? caseSensitive = null, + Object? withReplies = null, + Object? withFile = null, + Object? notify = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + src: null == src + ? _value.src + : src // ignore: cast_nullable_to_non_nullable + as AntennaSource, + userListId: freezed == userListId + ? _value.userListId + : userListId // ignore: cast_nullable_to_non_nullable + as String?, + keywords: null == keywords + ? _value.keywords + : keywords // ignore: cast_nullable_to_non_nullable + as List>, + excludeKeywords: null == excludeKeywords + ? _value.excludeKeywords + : excludeKeywords // ignore: cast_nullable_to_non_nullable + as List>, + users: null == users + ? _value.users + : users // ignore: cast_nullable_to_non_nullable + as List, + caseSensitive: null == caseSensitive + ? _value.caseSensitive + : caseSensitive // ignore: cast_nullable_to_non_nullable + as bool, + withReplies: null == withReplies + ? _value.withReplies + : withReplies // ignore: cast_nullable_to_non_nullable + as bool, + withFile: null == withFile + ? _value.withFile + : withFile // ignore: cast_nullable_to_non_nullable + as bool, + notify: null == notify + ? _value.notify + : notify // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_AntennaSettingsCopyWith<$Res> + implements $AntennaSettingsCopyWith<$Res> { + factory _$$_AntennaSettingsCopyWith( + _$_AntennaSettings value, $Res Function(_$_AntennaSettings) then) = + __$$_AntennaSettingsCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String name, + AntennaSource src, + String? userListId, + List> keywords, + List> excludeKeywords, + List users, + bool caseSensitive, + bool withReplies, + bool withFile, + bool notify}); +} + +/// @nodoc +class __$$_AntennaSettingsCopyWithImpl<$Res> + extends _$AntennaSettingsCopyWithImpl<$Res, _$_AntennaSettings> + implements _$$_AntennaSettingsCopyWith<$Res> { + __$$_AntennaSettingsCopyWithImpl( + _$_AntennaSettings _value, $Res Function(_$_AntennaSettings) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? src = null, + Object? userListId = freezed, + Object? keywords = null, + Object? excludeKeywords = null, + Object? users = null, + Object? caseSensitive = null, + Object? withReplies = null, + Object? withFile = null, + Object? notify = null, + }) { + return _then(_$_AntennaSettings( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + src: null == src + ? _value.src + : src // ignore: cast_nullable_to_non_nullable + as AntennaSource, + userListId: freezed == userListId + ? _value.userListId + : userListId // ignore: cast_nullable_to_non_nullable + as String?, + keywords: null == keywords + ? _value._keywords + : keywords // ignore: cast_nullable_to_non_nullable + as List>, + excludeKeywords: null == excludeKeywords + ? _value._excludeKeywords + : excludeKeywords // ignore: cast_nullable_to_non_nullable + as List>, + users: null == users + ? _value._users + : users // ignore: cast_nullable_to_non_nullable + as List, + caseSensitive: null == caseSensitive + ? _value.caseSensitive + : caseSensitive // ignore: cast_nullable_to_non_nullable + as bool, + withReplies: null == withReplies + ? _value.withReplies + : withReplies // ignore: cast_nullable_to_non_nullable + as bool, + withFile: null == withFile + ? _value.withFile + : withFile // ignore: cast_nullable_to_non_nullable + as bool, + notify: null == notify + ? _value.notify + : notify // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$_AntennaSettings extends _AntennaSettings { + const _$_AntennaSettings( + {this.name = "", + this.src = AntennaSource.all, + this.userListId, + final List> keywords = const [], + final List> excludeKeywords = const [], + final List users = const [], + this.caseSensitive = false, + this.withReplies = false, + this.withFile = false, + this.notify = false}) + : _keywords = keywords, + _excludeKeywords = excludeKeywords, + _users = users, + super._(); + + @override + @JsonKey() + final String name; + @override + @JsonKey() + final AntennaSource src; + @override + final String? userListId; + final List> _keywords; + @override + @JsonKey() + List> get keywords { + if (_keywords is EqualUnmodifiableListView) return _keywords; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_keywords); + } + + final List> _excludeKeywords; + @override + @JsonKey() + List> get excludeKeywords { + if (_excludeKeywords is EqualUnmodifiableListView) return _excludeKeywords; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_excludeKeywords); + } + + final List _users; + @override + @JsonKey() + List get users { + if (_users is EqualUnmodifiableListView) return _users; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_users); + } + + @override + @JsonKey() + final bool caseSensitive; + @override + @JsonKey() + final bool withReplies; + @override + @JsonKey() + final bool withFile; + @override + @JsonKey() + final bool notify; + + @override + String toString() { + return 'AntennaSettings(name: $name, src: $src, userListId: $userListId, keywords: $keywords, excludeKeywords: $excludeKeywords, users: $users, caseSensitive: $caseSensitive, withReplies: $withReplies, withFile: $withFile, notify: $notify)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_AntennaSettings && + (identical(other.name, name) || other.name == name) && + (identical(other.src, src) || other.src == src) && + (identical(other.userListId, userListId) || + other.userListId == userListId) && + const DeepCollectionEquality().equals(other._keywords, _keywords) && + const DeepCollectionEquality() + .equals(other._excludeKeywords, _excludeKeywords) && + const DeepCollectionEquality().equals(other._users, _users) && + (identical(other.caseSensitive, caseSensitive) || + other.caseSensitive == caseSensitive) && + (identical(other.withReplies, withReplies) || + other.withReplies == withReplies) && + (identical(other.withFile, withFile) || + other.withFile == withFile) && + (identical(other.notify, notify) || other.notify == notify)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + name, + src, + userListId, + const DeepCollectionEquality().hash(_keywords), + const DeepCollectionEquality().hash(_excludeKeywords), + const DeepCollectionEquality().hash(_users), + caseSensitive, + withReplies, + withFile, + notify); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_AntennaSettingsCopyWith<_$_AntennaSettings> get copyWith => + __$$_AntennaSettingsCopyWithImpl<_$_AntennaSettings>(this, _$identity); +} + +abstract class _AntennaSettings extends AntennaSettings { + const factory _AntennaSettings( + {final String name, + final AntennaSource src, + final String? userListId, + final List> keywords, + final List> excludeKeywords, + final List users, + final bool caseSensitive, + final bool withReplies, + final bool withFile, + final bool notify}) = _$_AntennaSettings; + const _AntennaSettings._() : super._(); + + @override + String get name; + @override + AntennaSource get src; + @override + String? get userListId; + @override + List> get keywords; + @override + List> get excludeKeywords; + @override + List get users; + @override + bool get caseSensitive; + @override + bool get withReplies; + @override + bool get withFile; + @override + bool get notify; + @override + @JsonKey(ignore: true) + _$$_AntennaSettingsCopyWith<_$_AntennaSettings> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/model/clip_settings.dart b/lib/model/clip_settings.dart new file mode 100644 index 000000000..dcba4f988 --- /dev/null +++ b/lib/model/clip_settings.dart @@ -0,0 +1,22 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +part 'clip_settings.freezed.dart'; + +@freezed +class ClipSettings with _$ClipSettings { + const factory ClipSettings({ + @Default("") String name, + String? description, + @Default(false) bool isPublic, + }) = _ClipSettings; + const ClipSettings._(); + + factory ClipSettings.fromClip(Clip clip) { + return ClipSettings( + name: clip.name ?? "", + description: clip.description, + isPublic: clip.isPublic, + ); + } +} diff --git a/lib/model/clip_settings.freezed.dart b/lib/model/clip_settings.freezed.dart new file mode 100644 index 000000000..a30888b3f --- /dev/null +++ b/lib/model/clip_settings.freezed.dart @@ -0,0 +1,174 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'clip_settings.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$ClipSettings { + String get name => throw _privateConstructorUsedError; + String? get description => throw _privateConstructorUsedError; + bool get isPublic => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $ClipSettingsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ClipSettingsCopyWith<$Res> { + factory $ClipSettingsCopyWith( + ClipSettings value, $Res Function(ClipSettings) then) = + _$ClipSettingsCopyWithImpl<$Res, ClipSettings>; + @useResult + $Res call({String name, String? description, bool isPublic}); +} + +/// @nodoc +class _$ClipSettingsCopyWithImpl<$Res, $Val extends ClipSettings> + implements $ClipSettingsCopyWith<$Res> { + _$ClipSettingsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? description = freezed, + Object? isPublic = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + isPublic: null == isPublic + ? _value.isPublic + : isPublic // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_ClipSettingsCopyWith<$Res> + implements $ClipSettingsCopyWith<$Res> { + factory _$$_ClipSettingsCopyWith( + _$_ClipSettings value, $Res Function(_$_ClipSettings) then) = + __$$_ClipSettingsCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, String? description, bool isPublic}); +} + +/// @nodoc +class __$$_ClipSettingsCopyWithImpl<$Res> + extends _$ClipSettingsCopyWithImpl<$Res, _$_ClipSettings> + implements _$$_ClipSettingsCopyWith<$Res> { + __$$_ClipSettingsCopyWithImpl( + _$_ClipSettings _value, $Res Function(_$_ClipSettings) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? description = freezed, + Object? isPublic = null, + }) { + return _then(_$_ClipSettings( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + isPublic: null == isPublic + ? _value.isPublic + : isPublic // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$_ClipSettings extends _ClipSettings { + const _$_ClipSettings( + {this.name = "", this.description, this.isPublic = false}) + : super._(); + + @override + @JsonKey() + final String name; + @override + final String? description; + @override + @JsonKey() + final bool isPublic; + + @override + String toString() { + return 'ClipSettings(name: $name, description: $description, isPublic: $isPublic)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_ClipSettings && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.isPublic, isPublic) || + other.isPublic == isPublic)); + } + + @override + int get hashCode => Object.hash(runtimeType, name, description, isPublic); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_ClipSettingsCopyWith<_$_ClipSettings> get copyWith => + __$$_ClipSettingsCopyWithImpl<_$_ClipSettings>(this, _$identity); +} + +abstract class _ClipSettings extends ClipSettings { + const factory _ClipSettings( + {final String name, + final String? description, + final bool isPublic}) = _$_ClipSettings; + const _ClipSettings._() : super._(); + + @override + String get name; + @override + String? get description; + @override + bool get isPublic; + @override + @JsonKey(ignore: true) + _$$_ClipSettingsCopyWith<_$_ClipSettings> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/model/image_file.dart b/lib/model/image_file.dart index b818e3ef3..5ed33c7b4 100644 --- a/lib/model/image_file.dart +++ b/lib/model/image_file.dart @@ -3,10 +3,13 @@ import 'dart:typed_data'; sealed class MisskeyPostFile { final String fileName; final bool isNsfw; - final String caption; + final String? caption; - const MisskeyPostFile( - {required this.fileName, required this.isNsfw, required this.caption}); + const MisskeyPostFile({ + required this.fileName, + this.isNsfw = false, + this.caption, + }); } class ImageFile extends MisskeyPostFile { @@ -14,8 +17,8 @@ class ImageFile extends MisskeyPostFile { const ImageFile({ required this.data, required super.fileName, - required super.isNsfw, - required super.caption, + super.isNsfw, + super.caption, }); } @@ -26,10 +29,10 @@ class ImageFileAlreadyPostedFile extends MisskeyPostFile { const ImageFileAlreadyPostedFile({ required this.data, required this.id, - required this.isEdited, + this.isEdited = false, required super.fileName, - required super.isNsfw, - required super.caption, + super.isNsfw, + super.caption, }); } @@ -38,8 +41,8 @@ class UnknownFile extends MisskeyPostFile { const UnknownFile({ required this.data, required super.fileName, - required super.isNsfw, - required super.caption, + super.isNsfw, + super.caption, }); } @@ -50,9 +53,9 @@ class UnknownAlreadyPostedFile extends MisskeyPostFile { const UnknownAlreadyPostedFile({ required this.url, required this.id, - required this.isEdited, + this.isEdited = false, required super.fileName, - required super.isNsfw, - required super.caption, + super.isNsfw, + super.caption, }); } diff --git a/lib/model/input_completion_type.dart b/lib/model/input_completion_type.dart new file mode 100644 index 000000000..66c003873 --- /dev/null +++ b/lib/model/input_completion_type.dart @@ -0,0 +1,23 @@ +sealed class InputCompletionType { + const InputCompletionType(); +} + +class Basic extends InputCompletionType {} + +class Emoji extends InputCompletionType { + const Emoji(this.query); + + final String query; +} + +class MfmFn extends InputCompletionType { + const MfmFn(this.query); + + final String query; +} + +class Hashtag extends InputCompletionType { + const Hashtag(this.query); + + final String query; +} diff --git a/lib/model/tab_setting.dart b/lib/model/tab_setting.dart index 91df3c860..ece13aed3 100644 --- a/lib/model/tab_setting.dart +++ b/lib/model/tab_setting.dart @@ -22,6 +22,9 @@ class TabSetting with _$TabSetting { /// タブ種別 required TabType tabType, + /// ロールタイムラインのノートの場合、ロールID + String? roleId, + /// チャンネルのノートの場合、チャンネルID String? channelId, diff --git a/lib/model/tab_setting.freezed.dart b/lib/model/tab_setting.freezed.dart index 40f6fa171..218a40d51 100644 --- a/lib/model/tab_setting.freezed.dart +++ b/lib/model/tab_setting.freezed.dart @@ -26,6 +26,9 @@ mixin _$TabSetting { /// タブ種別 TabType get tabType => throw _privateConstructorUsedError; + /// ロールタイムラインのノートの場合、ロールID + String? get roleId => throw _privateConstructorUsedError; + /// チャンネルのノートの場合、チャンネルID String? get channelId => throw _privateConstructorUsedError; @@ -60,6 +63,7 @@ abstract class $TabSettingCopyWith<$Res> { $Res call( {@IconDataConverter() TabIcon icon, TabType tabType, + String? roleId, String? channelId, String? listId, String? antennaId, @@ -87,6 +91,7 @@ class _$TabSettingCopyWithImpl<$Res, $Val extends TabSetting> $Res call({ Object? icon = null, Object? tabType = null, + Object? roleId = freezed, Object? channelId = freezed, Object? listId = freezed, Object? antennaId = freezed, @@ -104,6 +109,10 @@ class _$TabSettingCopyWithImpl<$Res, $Val extends TabSetting> ? _value.tabType : tabType // ignore: cast_nullable_to_non_nullable as TabType, + roleId: freezed == roleId + ? _value.roleId + : roleId // ignore: cast_nullable_to_non_nullable + as String?, channelId: freezed == channelId ? _value.channelId : channelId // ignore: cast_nullable_to_non_nullable @@ -163,6 +172,7 @@ abstract class _$$_TabSettingCopyWith<$Res> $Res call( {@IconDataConverter() TabIcon icon, TabType tabType, + String? roleId, String? channelId, String? listId, String? antennaId, @@ -190,6 +200,7 @@ class __$$_TabSettingCopyWithImpl<$Res> $Res call({ Object? icon = null, Object? tabType = null, + Object? roleId = freezed, Object? channelId = freezed, Object? listId = freezed, Object? antennaId = freezed, @@ -207,6 +218,10 @@ class __$$_TabSettingCopyWithImpl<$Res> ? _value.tabType : tabType // ignore: cast_nullable_to_non_nullable as TabType, + roleId: freezed == roleId + ? _value.roleId + : roleId // ignore: cast_nullable_to_non_nullable + as String?, channelId: freezed == channelId ? _value.channelId : channelId // ignore: cast_nullable_to_non_nullable @@ -242,6 +257,7 @@ class _$_TabSetting extends _TabSetting { const _$_TabSetting( {@IconDataConverter() required this.icon, required this.tabType, + this.roleId, this.channelId, this.listId, this.antennaId, @@ -262,6 +278,10 @@ class _$_TabSetting extends _TabSetting { @override final TabType tabType; + /// ロールタイムラインのノートの場合、ロールID + @override + final String? roleId; + /// チャンネルのノートの場合、チャンネルID @override final String? channelId; @@ -292,7 +312,7 @@ class _$_TabSetting extends _TabSetting { @override String toString() { - return 'TabSetting(icon: $icon, tabType: $tabType, channelId: $channelId, listId: $listId, antennaId: $antennaId, isSubscribe: $isSubscribe, name: $name, account: $account, renoteDisplay: $renoteDisplay)'; + return 'TabSetting(icon: $icon, tabType: $tabType, roleId: $roleId, channelId: $channelId, listId: $listId, antennaId: $antennaId, isSubscribe: $isSubscribe, name: $name, account: $account, renoteDisplay: $renoteDisplay)'; } @override @@ -302,6 +322,7 @@ class _$_TabSetting extends _TabSetting { other is _$_TabSetting && (identical(other.icon, icon) || other.icon == icon) && (identical(other.tabType, tabType) || other.tabType == tabType) && + (identical(other.roleId, roleId) || other.roleId == roleId) && (identical(other.channelId, channelId) || other.channelId == channelId) && (identical(other.listId, listId) || other.listId == listId) && @@ -321,6 +342,7 @@ class _$_TabSetting extends _TabSetting { runtimeType, icon, tabType, + roleId, channelId, listId, antennaId, @@ -347,6 +369,7 @@ abstract class _TabSetting extends TabSetting { const factory _TabSetting( {@IconDataConverter() required final TabIcon icon, required final TabType tabType, + final String? roleId, final String? channelId, final String? listId, final String? antennaId, @@ -368,6 +391,10 @@ abstract class _TabSetting extends TabSetting { TabType get tabType; @override + /// ロールタイムラインのノートの場合、ロールID + String? get roleId; + @override + /// チャンネルのノートの場合、チャンネルID String? get channelId; @override diff --git a/lib/model/tab_setting.g.dart b/lib/model/tab_setting.g.dart index 6d467e2c7..64258833f 100644 --- a/lib/model/tab_setting.g.dart +++ b/lib/model/tab_setting.g.dart @@ -10,6 +10,7 @@ _$_TabSetting _$$_TabSettingFromJson(Map json) => _$_TabSetting( icon: const IconDataConverter().fromJson(json['icon']), tabType: $enumDecode(_$TabTypeEnumMap, json['tabType']), + roleId: json['roleId'] as String?, channelId: json['channelId'] as String?, listId: json['listId'] as String?, antennaId: json['antennaId'] as String?, @@ -23,6 +24,7 @@ Map _$$_TabSettingToJson(_$_TabSetting instance) => { 'icon': const IconDataConverter().toJson(instance.icon), 'tabType': _$TabTypeEnumMap[instance.tabType]!, + 'roleId': instance.roleId, 'channelId': instance.channelId, 'listId': instance.listId, 'antennaId': instance.antennaId, @@ -37,6 +39,7 @@ const _$TabTypeEnumMap = { TabType.homeTimeline: 'homeTimeline', TabType.globalTimeline: 'globalTimeline', TabType.hybridTimeline: 'hybridTimeline', + TabType.roleTimeline: 'roleTimeline', TabType.channel: 'channel', TabType.userList: 'userList', TabType.antenna: 'antenna', diff --git a/lib/model/tab_type.dart b/lib/model/tab_type.dart index 970cbf901..ea0fe45d8 100644 --- a/lib/model/tab_type.dart +++ b/lib/model/tab_type.dart @@ -8,6 +8,7 @@ enum TabType { homeTimeline("ホームタイムライン"), globalTimeline("グローバルタイムライン"), hybridTimeline("ソーシャルタイムライン"), + roleTimeline("ロールタイムライン"), channel("チャンネル"), userList("リスト"), antenna("アンテナ"), @@ -27,6 +28,8 @@ enum TabType { return globalTimeLineProvider(setting); case TabType.hybridTimeline: return hybridTimeLineProvider(setting); //FIXME + case TabType.roleTimeline: + return roleTimelineProvider(setting); case TabType.channel: return channelTimelineProvider(setting); case TabType.userList: diff --git a/lib/model/users_list_settings.dart b/lib/model/users_list_settings.dart new file mode 100644 index 000000000..8765b5c8b --- /dev/null +++ b/lib/model/users_list_settings.dart @@ -0,0 +1,20 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +part 'users_list_settings.freezed.dart'; + +@freezed +class UsersListSettings with _$UsersListSettings { + const factory UsersListSettings({ + @Default("") String name, + @Default(false) bool isPublic, + }) = _UsersListSettings; + const UsersListSettings._(); + + factory UsersListSettings.fromUsersList(UsersList list) { + return UsersListSettings( + name: list.name ?? "", + isPublic: list.isPublic ?? false, + ); + } +} diff --git a/lib/model/users_list_settings.freezed.dart b/lib/model/users_list_settings.freezed.dart new file mode 100644 index 000000000..b9345f48b --- /dev/null +++ b/lib/model/users_list_settings.freezed.dart @@ -0,0 +1,155 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'users_list_settings.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$UsersListSettings { + String get name => throw _privateConstructorUsedError; + bool get isPublic => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $UsersListSettingsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UsersListSettingsCopyWith<$Res> { + factory $UsersListSettingsCopyWith( + UsersListSettings value, $Res Function(UsersListSettings) then) = + _$UsersListSettingsCopyWithImpl<$Res, UsersListSettings>; + @useResult + $Res call({String name, bool isPublic}); +} + +/// @nodoc +class _$UsersListSettingsCopyWithImpl<$Res, $Val extends UsersListSettings> + implements $UsersListSettingsCopyWith<$Res> { + _$UsersListSettingsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? isPublic = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + isPublic: null == isPublic + ? _value.isPublic + : isPublic // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_UsersListSettingsCopyWith<$Res> + implements $UsersListSettingsCopyWith<$Res> { + factory _$$_UsersListSettingsCopyWith(_$_UsersListSettings value, + $Res Function(_$_UsersListSettings) then) = + __$$_UsersListSettingsCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, bool isPublic}); +} + +/// @nodoc +class __$$_UsersListSettingsCopyWithImpl<$Res> + extends _$UsersListSettingsCopyWithImpl<$Res, _$_UsersListSettings> + implements _$$_UsersListSettingsCopyWith<$Res> { + __$$_UsersListSettingsCopyWithImpl( + _$_UsersListSettings _value, $Res Function(_$_UsersListSettings) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? isPublic = null, + }) { + return _then(_$_UsersListSettings( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + isPublic: null == isPublic + ? _value.isPublic + : isPublic // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$_UsersListSettings extends _UsersListSettings { + const _$_UsersListSettings({this.name = "", this.isPublic = false}) + : super._(); + + @override + @JsonKey() + final String name; + @override + @JsonKey() + final bool isPublic; + + @override + String toString() { + return 'UsersListSettings(name: $name, isPublic: $isPublic)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_UsersListSettings && + (identical(other.name, name) || other.name == name) && + (identical(other.isPublic, isPublic) || + other.isPublic == isPublic)); + } + + @override + int get hashCode => Object.hash(runtimeType, name, isPublic); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_UsersListSettingsCopyWith<_$_UsersListSettings> get copyWith => + __$$_UsersListSettingsCopyWithImpl<_$_UsersListSettings>( + this, _$identity); +} + +abstract class _UsersListSettings extends UsersListSettings { + const factory _UsersListSettings({final String name, final bool isPublic}) = + _$_UsersListSettings; + const _UsersListSettings._() : super._(); + + @override + String get name; + @override + bool get isPublic; + @override + @JsonKey(ignore: true) + _$$_UsersListSettingsCopyWith<_$_UsersListSettings> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/providers.dart b/lib/providers.dart index a4312b98c..368dec969 100644 --- a/lib/providers.dart +++ b/lib/providers.dart @@ -17,6 +17,7 @@ import 'package:miria/repository/main_stream_repository.dart'; import 'package:miria/repository/global_time_line_repository.dart'; import 'package:miria/repository/home_time_line_repository.dart'; import 'package:miria/repository/local_time_line_repository.dart'; +import 'package:miria/repository/role_timeline_repository.dart'; import 'package:miria/repository/note_repository.dart'; import 'package:miria/repository/tab_settings_repository.dart'; import 'package:miria/repository/time_line_repository.dart'; @@ -29,8 +30,11 @@ import 'package:misskey_dart/misskey_dart.dart'; final dioProvider = Provider((ref) => Dio()); final fileSystemProvider = Provider((ref) => const LocalFileSystem()); -final misskeyProvider = Provider.family( - (ref, account) => Misskey(token: account.token, host: account.host)); +final misskeyProvider = Provider.family((ref, account) => + Misskey( + token: account.token, + host: account.host, + socketConnectionTimeout: const Duration(seconds: 20))); final localTimeLineProvider = ChangeNotifierProvider.family( @@ -82,6 +86,20 @@ final hybridTimeLineProvider = ref.read(emojiRepositoryProvider(tabSetting.account)), )); +final roleTimelineProvider = + ChangeNotifierProvider.family( + (ref, tabSetting) => RoleTimelineRepository( + ref.read(misskeyProvider(tabSetting.account)), + tabSetting.account, + ref.read(notesProvider(tabSetting.account)), + ref.read(mainStreamRepositoryProvider(tabSetting.account)), + ref.read(generalSettingsRepositoryProvider), + tabSetting, + ref.read(mainStreamRepositoryProvider(tabSetting.account)), + ref.read(accountRepository), + ref.read(emojiRepositoryProvider(tabSetting.account)), + )); + final channelTimelineProvider = ChangeNotifierProvider.family( (ref, tabSetting) => ChannelTimelineRepository( @@ -137,7 +155,8 @@ final favoriteProvider = ChangeNotifierProvider.autoDispose ref.read(misskeyProvider(account)), ref.read(notesProvider(account)))); final notesProvider = ChangeNotifierProvider.family( - (ref, account) => NoteRepository(ref.read(misskeyProvider(account)))); + (ref, account) => + NoteRepository(ref.read(misskeyProvider(account)), account)); //TODO: アカウント毎である必要はない ホスト毎 //TODO: のつもりだったけど、絵文字にロールが関係するようになるとアカウント毎になる @@ -194,5 +213,6 @@ final noteCreateProvider = StateNotifierProvider.family ref.read(fileSystemProvider), ref.read(dioProvider), ref.read(misskeyProvider(account)), - ref.read(errorEventProvider.notifier)), + ref.read(errorEventProvider.notifier), + ref.read(notesProvider(account))), ); diff --git a/lib/repository/account_repository.dart b/lib/repository/account_repository.dart index 6f045ef69..06a56d9e0 100644 --- a/lib/repository/account_repository.dart +++ b/lib/repository/account_repository.dart @@ -88,6 +88,7 @@ class AccountRepository extends ChangeNotifier { notifyListeners(); } + //一つ目のアカウントが追加されたときに自動で追加されるタブ Future _addIfTabSettingNothing() async { if (_account.length == 1) { final account = _account.first; @@ -150,7 +151,8 @@ class AccountRepository extends ChangeNotifier { final version = nodeInfoResult["software"]["version"]; - final endpoints = await Misskey(host: server, token: null).endpoints(); + final endpoints = + await reader(misskeyProvider(Account.demoAccount(server))).endpoints(); if (!endpoints.contains("emojis")) { throw SpecifiedException("Miriaと互換性のないソフトウェアです。\n$software $version"); } diff --git a/lib/repository/antenna_timeline_repository.dart b/lib/repository/antenna_timeline_repository.dart index 44d18e22c..e564f2aeb 100644 --- a/lib/repository/antenna_timeline_repository.dart +++ b/lib/repository/antenna_timeline_repository.dart @@ -24,14 +24,15 @@ class AntennaTimelineRepository extends SocketTimelineRepository { required FutureOr Function(String id, TimelineReacted reaction) onUnreacted, required FutureOr Function(String id, TimelineVoted vote) onVoted, + required FutureOr Function(String id, NoteEdited note) onUpdated, }) { return misskey.antennaStream( - antennaId: tabSetting.antennaId!, - onNoteReceived: onReceived, - onReacted: onReacted, - onUnreacted: onUnreacted, - onVoted: onVoted, - ); + antennaId: tabSetting.antennaId!, + onNoteReceived: onReceived, + onReacted: onReacted, + onUnreacted: onUnreacted, + onVoted: onVoted, + onUpdated: onUpdated); } @override diff --git a/lib/repository/channel_time_line_repository.dart b/lib/repository/channel_time_line_repository.dart index afb3dad7c..e2e8906a8 100644 --- a/lib/repository/channel_time_line_repository.dart +++ b/lib/repository/channel_time_line_repository.dart @@ -24,14 +24,15 @@ class ChannelTimelineRepository extends SocketTimelineRepository { required FutureOr Function(String id, TimelineReacted reaction) onUnreacted, required FutureOr Function(String id, TimelineVoted vote) onVoted, + required FutureOr Function(String id, NoteEdited note) onUpdated, }) { return misskey.channelStream( - channelId: tabSetting.channelId!, - onNoteReceived: onReceived, - onReacted: onReacted, - onUnreacted: onUnreacted, - onVoted: onVoted, - ); + channelId: tabSetting.channelId!, + onNoteReceived: onReceived, + onReacted: onReacted, + onUnreacted: onUnreacted, + onVoted: onVoted, + onUpdated: onUpdated); } @override diff --git a/lib/repository/home_time_line_repository.dart b/lib/repository/home_time_line_repository.dart index 421839a01..2e952a222 100644 --- a/lib/repository/home_time_line_repository.dart +++ b/lib/repository/home_time_line_repository.dart @@ -24,13 +24,14 @@ class HomeTimeLineRepository extends SocketTimelineRepository { required FutureOr Function(String id, TimelineReacted reaction) onUnreacted, required FutureOr Function(String id, TimelineVoted vote) onVoted, + required FutureOr Function(String id, NoteEdited note) onUpdated, }) { return misskey.homeTimelineStream( - onNoteReceived: onReceived, - onReacted: onReacted, - onUnreacted: onUnreacted, - onVoted: onVoted, - ); + onNoteReceived: onReceived, + onReacted: onReacted, + onUnreacted: onUnreacted, + onVoted: onVoted, + onUpdated: onUpdated); } @override diff --git a/lib/repository/hybrid_timeline_repository.dart b/lib/repository/hybrid_timeline_repository.dart index 7a2db6ef1..16e17ab71 100644 --- a/lib/repository/hybrid_timeline_repository.dart +++ b/lib/repository/hybrid_timeline_repository.dart @@ -24,13 +24,14 @@ class HybridTimelineRepository extends SocketTimelineRepository { required FutureOr Function(String id, TimelineReacted reaction) onUnreacted, required FutureOr Function(String id, TimelineVoted vote) onVoted, + required FutureOr Function(String id, NoteEdited note) onUpdated, }) { return misskey.hybridTimelineStream( - onNoteReceived: onReceived, - onReacted: onReacted, - onUnreacted: onUnreacted, - onVoted: onVoted, - ); + onNoteReceived: onReceived, + onReacted: onReacted, + onUnreacted: onUnreacted, + onVoted: onVoted, + onUpdated: onUpdated); } @override diff --git a/lib/repository/import_export_repository.dart b/lib/repository/import_export_repository.dart index 32ca93f12..b2891e688 100644 --- a/lib/repository/import_export_repository.dart +++ b/lib/repository/import_export_repository.dart @@ -24,27 +24,40 @@ class ImportExportRepository extends ChangeNotifier { ImportExportRepository(this.reader); Future import(BuildContext context, Account account) async { - final folder = await showDialog( - barrierDismissible: false, + final result = await showDialog( context: context, - builder: (context2) => WillPopScope( - onWillPop: () async => false, - child: FolderSelectDialog( - account: account, - fileShowTarget: "miria.json.unknown", - ), + builder: (context2) => FolderSelectDialog( + account: account, + fileShowTarget: const ["miria.json", "miria.json.unknown"], + confirmationText: "このフォルダーからインポートする", ), ); + if (result == null) return; - final alreadyExists = await reader(misskeyProvider(account)) - .drive - .files - .find(DriveFilesFindRequest( - name: "miria.json.unknown", folderId: folder?.id)); + final folder = result.folder; + Iterable alreadyExists = + await reader(misskeyProvider(account)).drive.files.find( + DriveFilesFindRequest( + name: "miria.json", + folderId: folder?.id, + ), + ); + + if (!context.mounted) return; if (alreadyExists.isEmpty) { - await SimpleMessageDialog.show(context, "ここにMiriaの設定ファイルあれへんかったわ"); - return; + alreadyExists = await reader(misskeyProvider(account)).drive.files.find( + DriveFilesFindRequest( + name: "miria.json.unknown", + folderId: folder?.id, + ), + ); + + if (!context.mounted) return; + if (alreadyExists.isEmpty) { + await SimpleMessageDialog.show(context, "ここにMiriaの設定ファイルあれへんかったわ"); + return; + } } final importFile = alreadyExists.first; @@ -77,9 +90,11 @@ class ImportExportRepository extends ChangeNotifier { final tabSettings = []; for (final tabSetting in json["tabSettings"]) { - final account = accounts.firstWhereOrNull((element) => - tabSetting["account"]["host"] == element.host && - tabSetting["account"]["userId"] == element.userId); + final account = accounts.firstWhereOrNull( + (element) => + tabSetting["account"]["host"] == element.host && + tabSetting["account"]["userId"] == element.userId, + ); if (account == null) { continue; @@ -90,43 +105,51 @@ class ImportExportRepository extends ChangeNotifier { (tabSetting as Map) ..remove("account") ..addEntries( - [MapEntry("account", jsonDecode(jsonEncode(account.toJson())))]); + [MapEntry("account", jsonDecode(jsonEncode(account.toJson())))], + ); tabSettings.add(TabSetting.fromJson(tabSetting)); } reader(tabSettingsRepositoryProvider).save(tabSettings); + if (!context.mounted) return; await SimpleMessageDialog.show(context, "インポート終わったで。"); + + if (!context.mounted) return; context.router ..removeWhere((route) => true) ..push(const SplashRoute()); } Future export(BuildContext context, Account account) async { - final folder = await showDialog( - barrierDismissible: false, + final result = await showDialog( context: context, - builder: (context2) => WillPopScope( - onWillPop: () async => false, - child: FolderSelectDialog( - account: account, - fileShowTarget: "miria.json.unknown", - ), + builder: (context2) => FolderSelectDialog( + account: account, + fileShowTarget: const ["miria.json", "miria.json.unknown"], + confirmationText: "このフォルダーに保存する", ), ); + if (result == null) return; + + final folder = result.folder; - final alreadyExists = await reader(misskeyProvider(account)) - .drive - .files - .find(DriveFilesFindRequest( - name: "miria.json.unknown", folderId: folder?.id)); + final alreadyExists = + await reader(misskeyProvider(account)).drive.files.find( + DriveFilesFindRequest( + name: "miria.json.unknown", + folderId: folder?.id, + ), + ); + if (!context.mounted) return; if (alreadyExists.isNotEmpty) { final alreadyConfirm = await SimpleConfirmDialog.show( - context: context, - message: "ここにもうあるけど上書きするか?", - primary: "上書きする", - secondary: "やっぱやめた"); + context: context, + message: "ここにもうあるけど上書きするか?", + primary: "上書きする", + secondary: "やっぱやめた", + ); if (alreadyConfirm != true) return; for (final element in alreadyExists) { @@ -138,13 +161,11 @@ class ImportExportRepository extends ChangeNotifier { } final data = ExportedSetting( - generalSettings: reader(generalSettingsRepositoryProvider).settings, - tabSettings: - reader(tabSettingsRepositoryProvider).tabSettings.toList(), - accountSettings: reader(accountSettingsRepositoryProvider) - .accountSettings - .toList()) - .toJson(); + generalSettings: reader(generalSettingsRepositoryProvider).settings, + tabSettings: reader(tabSettingsRepositoryProvider).tabSettings.toList(), + accountSettings: + reader(accountSettingsRepositoryProvider).accountSettings.toList(), + ).toJson(); // 外に漏れると困るので for (final element in data["tabSettings"] as List) { @@ -154,13 +175,16 @@ class ImportExportRepository extends ChangeNotifier { } await reader(misskeyProvider(account)).drive.files.createAsBinary( - DriveFilesCreateRequest( + DriveFilesCreateRequest( folderId: folder?.id, name: "miria.json", comment: "Miria設定ファイル", - force: true), - Uint8List.fromList(utf8.encode(jsonEncode(data)))); + force: true, + ), + Uint8List.fromList(utf8.encode(jsonEncode(data))), + ); + if (!context.mounted) return; await SimpleMessageDialog.show(context, "エクスポート終わったで"); } } diff --git a/lib/repository/local_time_line_repository.dart b/lib/repository/local_time_line_repository.dart index b2339242b..246aefa48 100644 --- a/lib/repository/local_time_line_repository.dart +++ b/lib/repository/local_time_line_repository.dart @@ -24,12 +24,14 @@ class LocalTimeLineRepository extends SocketTimelineRepository { required FutureOr Function(String id, TimelineReacted reaction) onUnreacted, required FutureOr Function(String id, TimelineVoted vote) onVoted, + required FutureOr Function(String id, NoteEdited note) onUpdated, }) { return misskey.localTimelineStream( onNoteReceived: onReceived, onReacted: onReacted, onUnreacted: onUnreacted, onVoted: onVoted, + onUpdated: onUpdated, ); } diff --git a/lib/repository/main_stream_repository.dart b/lib/repository/main_stream_repository.dart index 24bce0fde..fad3bffbd 100644 --- a/lib/repository/main_stream_repository.dart +++ b/lib/repository/main_stream_repository.dart @@ -50,7 +50,7 @@ class MainStreamRepository extends ChangeNotifier { notifyListeners(); } - void connect() { + Future connect() async { socketController = misskey.mainStream( onReadAllNotifications: () { hasUnreadNotification = false; @@ -81,10 +81,8 @@ class MainStreamRepository extends ChangeNotifier { accountRepository.createUnreadAnnouncement(account, announcement); }, ); - misskey.startStreaming(); - Future(() async { - await confirmNotification(); - }); + await misskey.startStreaming(); + confirmNotification(); } Future reconnect() async { @@ -97,10 +95,11 @@ class MainStreamRepository extends ChangeNotifier { } isReconnecting = true; try { + print("main stream repository's socket controller will be disconnect"); socketController?.disconnect(); socketController = null; await misskey.streamingService.restart(); - connect(); + await connect(); } finally { isReconnecting = false; } diff --git a/lib/repository/note_repository.dart b/lib/repository/note_repository.dart index 1343e1b50..1d58c802d 100644 --- a/lib/repository/note_repository.dart +++ b/lib/repository/note_repository.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:miria/model/account.dart'; import 'package:misskey_dart/misskey_dart.dart'; part 'note_repository.freezed.dart'; @@ -11,6 +12,8 @@ class NoteStatus with _$NoteStatus { required bool isLongVisible, required bool isReactionedRenote, required bool isLongVisibleInitialized, + required bool isIncludeMuteWord, + required bool isMuteOpened, }) = _NoteStatus; } @@ -19,7 +22,23 @@ class NoteRepository extends ChangeNotifier { final Map _notes = {}; final Map _noteStatuses = {}; - NoteRepository(this.misskey); + final List> muteWordContents = []; + final List muteWordRegExps = []; + + NoteRepository(this.misskey, Account account) { + for (final muteWord in account.i.mutedWords) { + final content = muteWord.content; + final regExp = muteWord.regExp; + if (content != null) { + muteWordContents.add(content); + } + if (regExp != null) { + try { + muteWordRegExps.add(RegExp(regExp.substring(1, regExp.length - 1))); + } catch (e) {} + } + } + } Map get notes => _notes; @@ -43,11 +62,17 @@ class NoteRepository extends ChangeNotifier { : (note.myReaction ?? (note.reactions.isNotEmpty ? registeredNote?.myReaction : null)), ); - _noteStatuses[note.id] ??= const NoteStatus( + _noteStatuses[note.id] ??= NoteStatus( isCwOpened: false, isLongVisible: false, isReactionedRenote: false, - isLongVisibleInitialized: false); + isLongVisibleInitialized: false, + isIncludeMuteWord: muteWordContents.any((e) => e.every((e2) => + note.text?.contains(e2) == true || + note.cw?.contains(e2) == true)) || + muteWordRegExps.any((e) => + note.text?.contains(e) == true || note.cw?.contains(e) == true), + isMuteOpened: false); final renote = note.renote; final reply = note.reply; if (renote != null) { diff --git a/lib/repository/note_repository.freezed.dart b/lib/repository/note_repository.freezed.dart index 8d46aec81..acc105aa1 100644 --- a/lib/repository/note_repository.freezed.dart +++ b/lib/repository/note_repository.freezed.dart @@ -20,6 +20,8 @@ mixin _$NoteStatus { bool get isLongVisible => throw _privateConstructorUsedError; bool get isReactionedRenote => throw _privateConstructorUsedError; bool get isLongVisibleInitialized => throw _privateConstructorUsedError; + bool get isIncludeMuteWord => throw _privateConstructorUsedError; + bool get isMuteOpened => throw _privateConstructorUsedError; @JsonKey(ignore: true) $NoteStatusCopyWith get copyWith => @@ -36,7 +38,9 @@ abstract class $NoteStatusCopyWith<$Res> { {bool isCwOpened, bool isLongVisible, bool isReactionedRenote, - bool isLongVisibleInitialized}); + bool isLongVisibleInitialized, + bool isIncludeMuteWord, + bool isMuteOpened}); } /// @nodoc @@ -56,6 +60,8 @@ class _$NoteStatusCopyWithImpl<$Res, $Val extends NoteStatus> Object? isLongVisible = null, Object? isReactionedRenote = null, Object? isLongVisibleInitialized = null, + Object? isIncludeMuteWord = null, + Object? isMuteOpened = null, }) { return _then(_value.copyWith( isCwOpened: null == isCwOpened @@ -74,6 +80,14 @@ class _$NoteStatusCopyWithImpl<$Res, $Val extends NoteStatus> ? _value.isLongVisibleInitialized : isLongVisibleInitialized // ignore: cast_nullable_to_non_nullable as bool, + isIncludeMuteWord: null == isIncludeMuteWord + ? _value.isIncludeMuteWord + : isIncludeMuteWord // ignore: cast_nullable_to_non_nullable + as bool, + isMuteOpened: null == isMuteOpened + ? _value.isMuteOpened + : isMuteOpened // ignore: cast_nullable_to_non_nullable + as bool, ) as $Val); } } @@ -90,7 +104,9 @@ abstract class _$$_NoteStatusCopyWith<$Res> {bool isCwOpened, bool isLongVisible, bool isReactionedRenote, - bool isLongVisibleInitialized}); + bool isLongVisibleInitialized, + bool isIncludeMuteWord, + bool isMuteOpened}); } /// @nodoc @@ -108,6 +124,8 @@ class __$$_NoteStatusCopyWithImpl<$Res> Object? isLongVisible = null, Object? isReactionedRenote = null, Object? isLongVisibleInitialized = null, + Object? isIncludeMuteWord = null, + Object? isMuteOpened = null, }) { return _then(_$_NoteStatus( isCwOpened: null == isCwOpened @@ -126,6 +144,14 @@ class __$$_NoteStatusCopyWithImpl<$Res> ? _value.isLongVisibleInitialized : isLongVisibleInitialized // ignore: cast_nullable_to_non_nullable as bool, + isIncludeMuteWord: null == isIncludeMuteWord + ? _value.isIncludeMuteWord + : isIncludeMuteWord // ignore: cast_nullable_to_non_nullable + as bool, + isMuteOpened: null == isMuteOpened + ? _value.isMuteOpened + : isMuteOpened // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -137,7 +163,9 @@ class _$_NoteStatus with DiagnosticableTreeMixin implements _NoteStatus { {required this.isCwOpened, required this.isLongVisible, required this.isReactionedRenote, - required this.isLongVisibleInitialized}); + required this.isLongVisibleInitialized, + required this.isIncludeMuteWord, + required this.isMuteOpened}); @override final bool isCwOpened; @@ -147,10 +175,14 @@ class _$_NoteStatus with DiagnosticableTreeMixin implements _NoteStatus { final bool isReactionedRenote; @override final bool isLongVisibleInitialized; + @override + final bool isIncludeMuteWord; + @override + final bool isMuteOpened; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'NoteStatus(isCwOpened: $isCwOpened, isLongVisible: $isLongVisible, isReactionedRenote: $isReactionedRenote, isLongVisibleInitialized: $isLongVisibleInitialized)'; + return 'NoteStatus(isCwOpened: $isCwOpened, isLongVisible: $isLongVisible, isReactionedRenote: $isReactionedRenote, isLongVisibleInitialized: $isLongVisibleInitialized, isIncludeMuteWord: $isIncludeMuteWord, isMuteOpened: $isMuteOpened)'; } @override @@ -162,7 +194,9 @@ class _$_NoteStatus with DiagnosticableTreeMixin implements _NoteStatus { ..add(DiagnosticsProperty('isLongVisible', isLongVisible)) ..add(DiagnosticsProperty('isReactionedRenote', isReactionedRenote)) ..add(DiagnosticsProperty( - 'isLongVisibleInitialized', isLongVisibleInitialized)); + 'isLongVisibleInitialized', isLongVisibleInitialized)) + ..add(DiagnosticsProperty('isIncludeMuteWord', isIncludeMuteWord)) + ..add(DiagnosticsProperty('isMuteOpened', isMuteOpened)); } @override @@ -178,12 +212,22 @@ class _$_NoteStatus with DiagnosticableTreeMixin implements _NoteStatus { other.isReactionedRenote == isReactionedRenote) && (identical( other.isLongVisibleInitialized, isLongVisibleInitialized) || - other.isLongVisibleInitialized == isLongVisibleInitialized)); + other.isLongVisibleInitialized == isLongVisibleInitialized) && + (identical(other.isIncludeMuteWord, isIncludeMuteWord) || + other.isIncludeMuteWord == isIncludeMuteWord) && + (identical(other.isMuteOpened, isMuteOpened) || + other.isMuteOpened == isMuteOpened)); } @override - int get hashCode => Object.hash(runtimeType, isCwOpened, isLongVisible, - isReactionedRenote, isLongVisibleInitialized); + int get hashCode => Object.hash( + runtimeType, + isCwOpened, + isLongVisible, + isReactionedRenote, + isLongVisibleInitialized, + isIncludeMuteWord, + isMuteOpened); @JsonKey(ignore: true) @override @@ -197,7 +241,9 @@ abstract class _NoteStatus implements NoteStatus { {required final bool isCwOpened, required final bool isLongVisible, required final bool isReactionedRenote, - required final bool isLongVisibleInitialized}) = _$_NoteStatus; + required final bool isLongVisibleInitialized, + required final bool isIncludeMuteWord, + required final bool isMuteOpened}) = _$_NoteStatus; @override bool get isCwOpened; @@ -208,6 +254,10 @@ abstract class _NoteStatus implements NoteStatus { @override bool get isLongVisibleInitialized; @override + bool get isIncludeMuteWord; + @override + bool get isMuteOpened; + @override @JsonKey(ignore: true) _$$_NoteStatusCopyWith<_$_NoteStatus> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/repository/role_timeline_repository.dart b/lib/repository/role_timeline_repository.dart new file mode 100644 index 000000000..74a7645c9 --- /dev/null +++ b/lib/repository/role_timeline_repository.dart @@ -0,0 +1,45 @@ +import 'dart:async'; + +import 'package:miria/repository/socket_timeline_repository.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +class RoleTimelineRepository extends SocketTimelineRepository { + RoleTimelineRepository( + super.misskey, + super.account, + super.noteRepository, + super.globalNotificationRepository, + super.generalSettingsRepository, + super.tabSetting, + super.mainStreamRepository, + super.accountRepository, + super.emojiRepository, + ); + + @override + SocketController createSocketController({ + required void Function(Note note) onReceived, + required FutureOr Function(String id, TimelineReacted reaction) + onReacted, + required FutureOr Function(String id, TimelineReacted reaction) + onUnreacted, + required FutureOr Function(String id, TimelineVoted vote) onVoted, + required FutureOr Function(String id, NoteEdited note) onUpdated, + }) { + return misskey.roleTimelineStream( + roleId: tabSetting.roleId!, + onNoteReceived: onReceived, + onReacted: onReacted, + onVoted: onVoted, + ); + } + + @override + Future> requestNotes({String? untilId}) async { + return await misskey.roles.notes(RolesNotesRequest( + roleId: tabSetting.roleId!, + limit: 30, + untilId: untilId, + )); + } +} diff --git a/lib/repository/socket_timeline_repository.dart b/lib/repository/socket_timeline_repository.dart index 4117c599f..48d60fd33 100644 --- a/lib/repository/socket_timeline_repository.dart +++ b/lib/repository/socket_timeline_repository.dart @@ -44,6 +44,7 @@ abstract class SocketTimelineRepository extends TimelineRepository { required FutureOr Function(String id, TimelineReacted reaction) onUnreacted, required FutureOr Function(String id, TimelineVoted vote) onVoted, + required FutureOr Function(String id, NoteEdited vote) onUpdated, }); void reloadLatestNotes() { @@ -78,85 +79,88 @@ abstract class SocketTimelineRepository extends TimelineRepository { } @override - void startTimeLine() { - Future(() async { - try { - await emojiRepository.loadFromSourceIfNeed(); - await accountRepository.loadFromSourceIfNeed(tabSetting.account); - await mainStreamRepository.reconnect(); - isLoading = false; - error = null; - notifyListeners(); - } catch (e, s) { - error = (e, s); - isLoading = false; - notifyListeners(); - } + Future startTimeLine() async { + try { + await emojiRepository.loadFromSourceIfNeed(); + await accountRepository.loadFromSourceIfNeed(tabSetting.account); + await mainStreamRepository.reconnect(); + isLoading = false; + error = null; + notifyListeners(); + } catch (e, s) { + error = (e, s); + isLoading = false; + notifyListeners(); + } - if (socketController != null) { - socketController?.disconnect(); - } + if (socketController != null) { + socketController?.disconnect(); + } - socketController = createSocketController( - onReceived: (note) { - newerNotes.add(note); + socketController = createSocketController( + onReceived: (note) { + newerNotes.add(note); - notifyListeners(); - }, - onReacted: (id, value) { - final registeredNote = noteRepository.notes[id]; - if (registeredNote == null) return; - final reaction = Map.of(registeredNote.reactions); - reaction[value.reaction] = (reaction[value.reaction] ?? 0) + 1; - final emoji = value.emoji; - final reactionEmojis = Map.of(registeredNote.reactionEmojis); - if (emoji != null && !value.reaction.endsWith("@.:")) { - reactionEmojis[emoji.name] = emoji.url; - } - noteRepository.registerNote(registeredNote.copyWith( - reactions: reaction, - reactionEmojis: reactionEmojis, - myReaction: value.userId == account.i.id - ? emoji?.name - : registeredNote.myReaction)); - }, - onUnreacted: (id, value) { - final registeredNote = noteRepository.notes[id]; - if (registeredNote == null) return; - final reaction = Map.of(registeredNote.reactions); - reaction[value.reaction] = - max((reaction[value.reaction] ?? 0) - 1, 0); - if (reaction[value.reaction] == 0) { - reaction.remove(value.reaction); - } - final emoji = value.emoji; - final reactionEmojis = Map.of(registeredNote.reactionEmojis); - if (emoji != null && !value.reaction.endsWith("@.:")) { - reactionEmojis[emoji.name] = emoji.url; - } - noteRepository.registerNote(registeredNote.copyWith( - reactions: reaction, - reactionEmojis: reactionEmojis, - myReaction: value.userId == account.i.id - ? "" - : registeredNote.myReaction)); - }, - onVoted: (id, value) { - final registeredNote = noteRepository.notes[id]; - if (registeredNote == null) return; - - final poll = registeredNote.poll; - if (poll == null) return; + notifyListeners(); + }, + onReacted: (id, value) { + final registeredNote = noteRepository.notes[id]; + if (registeredNote == null) return; + final reaction = Map.of(registeredNote.reactions); + reaction[value.reaction] = (reaction[value.reaction] ?? 0) + 1; + final emoji = value.emoji; + final reactionEmojis = Map.of(registeredNote.reactionEmojis); + if (emoji != null && !value.reaction.endsWith("@.:")) { + reactionEmojis[emoji.name] = emoji.url; + } + noteRepository.registerNote(registeredNote.copyWith( + reactions: reaction, + reactionEmojis: reactionEmojis, + myReaction: value.userId == account.i.id + ? (emoji?.name != null ? ":${emoji?.name}:" : null) + : registeredNote.myReaction)); + }, + onUnreacted: (id, value) { + final registeredNote = noteRepository.notes[id]; + if (registeredNote == null) return; + final reaction = Map.of(registeredNote.reactions); + reaction[value.reaction] = max((reaction[value.reaction] ?? 0) - 1, 0); + if (reaction[value.reaction] == 0) { + reaction.remove(value.reaction); + } + final emoji = value.emoji; + final reactionEmojis = Map.of(registeredNote.reactionEmojis); + if (emoji != null && !value.reaction.endsWith("@.:")) { + reactionEmojis[emoji.name] = emoji.url; + } + noteRepository.registerNote(registeredNote.copyWith( + reactions: reaction, + reactionEmojis: reactionEmojis, + myReaction: + value.userId == account.i.id ? "" : registeredNote.myReaction)); + }, + onVoted: (id, value) { + final registeredNote = noteRepository.notes[id]; + if (registeredNote == null) return; - final choices = poll.choices.toList(); - choices[value.choice] = choices[value.choice] - .copyWith(votes: choices[value.choice].votes + 1); - noteRepository.registerNote( - registeredNote.copyWith(poll: poll.copyWith(choices: choices))); - }, - ); - misskey.startStreaming(); + final poll = registeredNote.poll; + if (poll == null) return; + final choices = poll.choices.toList(); + choices[value.choice] = choices[value.choice] + .copyWith(votes: choices[value.choice].votes + 1); + noteRepository.registerNote( + registeredNote.copyWith(poll: poll.copyWith(choices: choices))); + }, + onUpdated: (id, value) { + final note = noteRepository.notes[id]; + if (note == null) return; + noteRepository.registerNote(note.copyWith( + text: value.text, cw: value.cw, updatedAt: DateTime.now())); + }, + ); + await misskey.startStreaming(); + Future(() async { if (olderNotes.isEmpty) { try { final resultNotes = await requestNotes(); @@ -183,12 +187,15 @@ abstract class SocketTimelineRepository extends TimelineRepository { Future reconnect() async { if (isReconnecting) return; isReconnecting = true; + notifyListeners(); try { await super.reconnect(); socketController = null; - startTimeLine(); + await startTimeLine(); + error = null; } finally { isReconnecting = false; + notifyListeners(); } } diff --git a/lib/repository/user_list_time_line_repository.dart b/lib/repository/user_list_time_line_repository.dart index 577fffa22..d97c46095 100644 --- a/lib/repository/user_list_time_line_repository.dart +++ b/lib/repository/user_list_time_line_repository.dart @@ -24,12 +24,14 @@ class UserListTimelineRepository extends SocketTimelineRepository { required FutureOr Function(String id, TimelineReacted reaction) onUnreacted, required FutureOr Function(String id, TimelineVoted vote) onVoted, + required FutureOr Function(String id, NoteEdited note) onUpdated, }) { return misskey.userListStream( listId: tabSetting.listId!, onNoteReceived: onReceived, onReacted: onReacted, onVoted: onVoted, + onUpdated: onUpdated, ); } diff --git a/lib/router/app_router.dart b/lib/router/app_router.dart index 6eaab8b04..6acccbb40 100644 --- a/lib/router/app_router.dart +++ b/lib/router/app_router.dart @@ -36,6 +36,7 @@ import 'package:miria/view/time_line_page/time_line_page.dart'; import 'package:miria/view/user_page/user_followee.dart'; import 'package:miria/view/user_page/user_follower.dart'; import 'package:miria/view/user_page/user_page.dart'; +import 'package:miria/view/users_list_page/users_list_detail_page.dart'; import 'package:miria/view/users_list_page/users_list_page.dart'; import 'package:miria/view/users_list_page/users_list_timeline_page.dart'; import 'package:miria/view/splash_page/splash_page.dart'; @@ -66,6 +67,7 @@ class AppRouter extends _$AppRouter { AutoRoute(page: AntennaNotesRoute.page), AutoRoute(page: UsersListRoute.page), AutoRoute(page: UsersListTimelineRoute.page), + AutoRoute(page: UsersListDetailRoute.page), AutoRoute(page: NotificationRoute.page), AutoRoute(page: FavoritedNoteRoute.page), AutoRoute(page: ClipListRoute.page), diff --git a/lib/router/app_router.gr.dart b/lib/router/app_router.gr.dart index 330c2f00e..5b179e22b 100644 --- a/lib/router/app_router.gr.dart +++ b/lib/router/app_router.gr.dart @@ -96,7 +96,8 @@ abstract class _$AppRouter extends RootStackRouter { channel: args.channel, reply: args.reply, renote: args.renote, - deletedNote: args.deletedNote, + note: args.note, + noteCreationMode: args.noteCreationMode, ), ); }, @@ -156,6 +157,16 @@ abstract class _$AppRouter extends RootStackRouter { ), ); }, + AnnouncementRoute.name: (routeData) { + final args = routeData.argsAs(); + return AutoRoutePage( + routeData: routeData, + child: AnnouncementPage( + key: args.key, + account: args.account, + ), + ); + }, SplashRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, @@ -218,7 +229,7 @@ abstract class _$AppRouter extends RootStackRouter { routeData: routeData, child: UsersListTimelinePage( args.account, - args.listId, + args.list, key: args.key, ), ); @@ -233,6 +244,17 @@ abstract class _$AppRouter extends RootStackRouter { ), ); }, + UsersListDetailRoute.name: (routeData) { + final args = routeData.argsAs(); + return AutoRoutePage( + routeData: routeData, + child: UsersListDetailPage( + key: args.key, + account: args.account, + listId: args.listId, + ), + ); + }, ChannelDetailRoute.name: (routeData) { final args = routeData.argsAs(); return AutoRoutePage( @@ -387,16 +409,6 @@ abstract class _$AppRouter extends RootStackRouter { ), ); }, - AnnouncementRoute.name: (routeData) { - final args = routeData.argsAs(); - return AutoRoutePage( - routeData: routeData, - child: AnnouncementPage( - key: args.key, - account: args.account, - ), - ); - }, }; } @@ -668,7 +680,8 @@ class NoteCreateRoute extends PageRouteInfo { CommunityChannel? channel, Note? reply, Note? renote, - Note? deletedNote, + Note? note, + NoteCreationMode? noteCreationMode, List? children, }) : super( NoteCreateRoute.name, @@ -680,7 +693,8 @@ class NoteCreateRoute extends PageRouteInfo { channel: channel, reply: reply, renote: renote, - deletedNote: deletedNote, + note: note, + noteCreationMode: noteCreationMode, ), initialChildren: children, ); @@ -700,7 +714,8 @@ class NoteCreateRouteArgs { this.channel, this.reply, this.renote, - this.deletedNote, + this.note, + this.noteCreationMode, }); final Key? key; @@ -717,11 +732,13 @@ class NoteCreateRouteArgs { final Note? renote; - final Note? deletedNote; + final Note? note; + + final NoteCreationMode? noteCreationMode; @override String toString() { - return 'NoteCreateRouteArgs{key: $key, initialAccount: $initialAccount, initialText: $initialText, initialMediaFiles: $initialMediaFiles, channel: $channel, reply: $reply, renote: $renote, deletedNote: $deletedNote}'; + return 'NoteCreateRouteArgs{key: $key, initialAccount: $initialAccount, initialText: $initialText, initialMediaFiles: $initialMediaFiles, channel: $channel, reply: $reply, renote: $renote, note: $note, noteCreationMode: $noteCreationMode}'; } } @@ -944,6 +961,44 @@ class PhotoEditRouteArgs { } } +/// generated route for +/// [AnnouncementPage] +class AnnouncementRoute extends PageRouteInfo { + AnnouncementRoute({ + Key? key, + required Account account, + List? children, + }) : super( + AnnouncementRoute.name, + args: AnnouncementRouteArgs( + key: key, + account: account, + ), + initialChildren: children, + ); + + static const String name = 'AnnouncementRoute'; + + static const PageInfo page = + PageInfo(name); +} + +class AnnouncementRouteArgs { + const AnnouncementRouteArgs({ + this.key, + required this.account, + }); + + final Key? key; + + final Account account; + + @override + String toString() { + return 'AnnouncementRouteArgs{key: $key, account: $account}'; + } +} + /// generated route for /// [SplashPage] class SplashRoute extends PageRouteInfo { @@ -1155,14 +1210,14 @@ class ReactionDeckRouteArgs { class UsersListTimelineRoute extends PageRouteInfo { UsersListTimelineRoute({ required Account account, - required String listId, + required UsersList list, Key? key, List? children, }) : super( UsersListTimelineRoute.name, args: UsersListTimelineRouteArgs( account: account, - listId: listId, + list: list, key: key, ), initialChildren: children, @@ -1177,19 +1232,19 @@ class UsersListTimelineRoute extends PageRouteInfo { class UsersListTimelineRouteArgs { const UsersListTimelineRouteArgs({ required this.account, - required this.listId, + required this.list, this.key, }); final Account account; - final String listId; + final UsersList list; final Key? key; @override String toString() { - return 'UsersListTimelineRouteArgs{account: $account, listId: $listId, key: $key}'; + return 'UsersListTimelineRouteArgs{account: $account, list: $list, key: $key}'; } } @@ -1231,6 +1286,49 @@ class UsersListRouteArgs { } } +/// generated route for +/// [UsersListDetailPage] +class UsersListDetailRoute extends PageRouteInfo { + UsersListDetailRoute({ + Key? key, + required Account account, + required String listId, + List? children, + }) : super( + UsersListDetailRoute.name, + args: UsersListDetailRouteArgs( + key: key, + account: account, + listId: listId, + ), + initialChildren: children, + ); + + static const String name = 'UsersListDetailRoute'; + + static const PageInfo page = + PageInfo(name); +} + +class UsersListDetailRouteArgs { + const UsersListDetailRouteArgs({ + this.key, + required this.account, + required this.listId, + }); + + final Key? key; + + final Account account; + + final String listId; + + @override + String toString() { + return 'UsersListDetailRouteArgs{key: $key, account: $account, listId: $listId}'; + } +} + /// generated route for /// [ChannelDetailPage] class ChannelDetailRoute extends PageRouteInfo { @@ -1762,41 +1860,3 @@ class FavoritedNoteRouteArgs { return 'FavoritedNoteRouteArgs{key: $key, account: $account}'; } } - -/// generated route for -/// [AnnouncementPage] -class AnnouncementRoute extends PageRouteInfo { - AnnouncementRoute({ - Key? key, - required Account account, - List? children, - }) : super( - AnnouncementRoute.name, - args: AnnouncementRouteArgs( - key: key, - account: account, - ), - initialChildren: children, - ); - - static const String name = 'AnnouncementRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class AnnouncementRouteArgs { - const AnnouncementRouteArgs({ - this.key, - required this.account, - }); - - final Key? key; - - final Account account; - - @override - String toString() { - return 'AnnouncementRouteArgs{key: $key, account: $account}'; - } -} diff --git a/lib/state_notifier/note_create_page/note_create_state_notifier.dart b/lib/state_notifier/note_create_page/note_create_state_notifier.dart index 944b5aa09..d90c7216b 100644 --- a/lib/state_notifier/note_create_page/note_create_state_notifier.dart +++ b/lib/state_notifier/note_create_page/note_create_state_notifier.dart @@ -1,20 +1,22 @@ -import 'dart:io'; import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:file/file.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:mfm_parser/mfm_parser.dart'; import 'package:miria/model/account.dart'; import 'package:miria/model/image_file.dart'; +import 'package:miria/repository/note_repository.dart'; import 'package:miria/view/common/error_dialog_handler.dart'; import 'package:miria/view/dialogs/simple_message_dialog.dart'; import 'package:miria/view/note_create_page/drive_file_select_dialog.dart'; import 'package:miria/view/note_create_page/drive_modal_sheet.dart'; import 'package:miria/view/note_create_page/file_settings_dialog.dart'; +import 'package:miria/view/note_create_page/note_create_page.dart'; import 'package:miria/view/user_select_dialog.dart'; import 'package:misskey_dart/misskey_dart.dart'; @@ -68,6 +70,8 @@ class NoteCreate with _$NoteCreate { int? voteDuration, @Default(VoteExpireDurationType.seconds) VoteExpireDurationType voteDurationType, + NoteCreationMode? noteCreationMode, + String? noteId, }) = _NoteCreate; } @@ -83,6 +87,7 @@ class NoteCreateNotifier extends StateNotifier { FileSystem fileSystem; Dio dio; Misskey misskey; + NoteRepository noteRepository; StateNotifier<(Object? error, BuildContext? context)> errorNotifier; NoteCreateNotifier( @@ -91,6 +96,7 @@ class NoteCreateNotifier extends StateNotifier { this.dio, this.misskey, this.errorNotifier, + this.noteRepository, ); /// 初期化する @@ -98,9 +104,10 @@ class NoteCreateNotifier extends StateNotifier { CommunityChannel? channel, String? initialText, List? initialMediaFiles, - Note? deletedNote, + Note? note, Note? renote, Note? reply, + NoteCreationMode? noteCreationMode, ) async { var resultState = state; @@ -121,84 +128,84 @@ class NoteCreateNotifier extends StateNotifier { resultState = resultState.copyWith(text: initialText); } if (initialMediaFiles != null && initialMediaFiles.isNotEmpty == true) { - resultState = resultState.copyWith(files: [ - for (final media in initialMediaFiles) - if (media.toLowerCase().endsWith("jpg") || - media.toLowerCase().endsWith("png") || - media.toLowerCase().endsWith("gif") || - media.toLowerCase().endsWith("webp")) - ImageFile( - data: await fileSystem.file(media).readAsBytes(), - fileName: media.substring( - media.lastIndexOf(Platform.pathSeparator) + 1, media.length), - isNsfw: false, - caption: "", - ) - else - UnknownFile( - data: await fileSystem.file(media).readAsBytes(), - fileName: media.substring( - media.lastIndexOf(Platform.pathSeparator) + 1, - media.length, - ), - isNsfw: false, - caption: "", - ) - ]); + resultState = resultState.copyWith( + files: await Future.wait( + initialMediaFiles.map((media) async { + final file = fileSystem.file(media); + final contents = await file.readAsBytes(); + final fileName = file.basename; + final extension = fileName.split(".").last.toLowerCase(); + if (["jpg", "png", "gif", "webp"].contains(extension)) { + return ImageFile( + data: contents, + fileName: fileName, + ); + } else { + return UnknownFile( + data: contents, + fileName: fileName, + ); + } + }), + ), + ); } // 削除されたノートの反映 - if (deletedNote != null) { + if (note != null) { final files = []; - for (final file in deletedNote.files) { + for (final file in note.files) { if (file.type.startsWith("image")) { final response = await dio.get(file.url, options: Options(responseType: ResponseType.bytes)); - files.add(ImageFileAlreadyPostedFile( + files.add( + ImageFileAlreadyPostedFile( fileName: file.name, data: response.data, id: file.id, isNsfw: file.isSensitive, - caption: file.comment ?? "", - isEdited: false)); + caption: file.comment, + ), + ); } else { - files.add(UnknownAlreadyPostedFile( - url: file.url, - id: file.id, - fileName: file.name, - isNsfw: file.isSensitive, - caption: file.comment ?? "", - isEdited: false, - )); + files.add( + UnknownAlreadyPostedFile( + url: file.url, + id: file.id, + fileName: file.name, + isNsfw: file.isSensitive, + caption: file.comment, + ), + ); } } - final deletedNoteChannel = deletedNote.channel; + final deletedNoteChannel = note.channel; resultState = resultState.copyWith( - noteVisibility: deletedNote.visibility, - localOnly: deletedNote.localOnly, + noteVisibility: note.visibility, + localOnly: note.localOnly, files: files, channel: deletedNoteChannel != null ? NoteCreateChannel( id: deletedNoteChannel.id, name: deletedNoteChannel.name) : null, - cwText: deletedNote.cw ?? "", - isCw: deletedNote.cw?.isNotEmpty == true, - text: deletedNote.text ?? "", - reactionAcceptance: deletedNote.reactionAcceptance, + cwText: note.cw ?? "", + isCw: note.cw?.isNotEmpty == true, + text: note.text ?? "", + reactionAcceptance: note.reactionAcceptance, replyTo: [ - for (final userId in deletedNote.mentions) + for (final userId in note.mentions) (await misskey.users.show(UsersShowRequest(userId: userId))) .toUser() ], - isVote: deletedNote.poll != null, - isVoteMultiple: deletedNote.poll?.multiple ?? false, + isVote: note.poll != null, + isVoteMultiple: note.poll?.multiple ?? false, voteExpireType: VoteExpireType.date, - voteContentCount: - deletedNote.poll?.choices.map((e) => e.text).length ?? 2, - voteContent: - deletedNote.poll?.choices.map((e) => e.text).toList() ?? [], - voteDate: deletedNote.poll?.expiresAt, + voteContentCount: note.poll?.choices.map((e) => e.text).length ?? 2, + voteContent: note.poll?.choices.map((e) => e.text).toList() ?? [], + voteDate: note.poll?.expiresAt, + noteCreationMode: noteCreationMode, + noteId: note.id, ); state = resultState; return; @@ -277,6 +284,20 @@ class NoteCreateNotifier extends StateNotifier { for (final file in state.files) { switch (file) { case ImageFile(): + final fileName = file.fileName.toLowerCase(); + var imageData = file.data; + try { + if (fileName.endsWith("jpg") || + fileName.endsWith("jpeg") || + fileName.endsWith("tiff") || + fileName.endsWith("tif")) { + imageData = + await FlutterImageCompress.compressWithList(file.data); + } + } catch (e) { + print("failed to compress file"); + } + final response = await misskey.drive.files.createAsBinary( DriveFilesCreateRequest( force: true, @@ -284,7 +305,7 @@ class NoteCreateNotifier extends StateNotifier { isSensitive: file.isNsfw, comment: file.caption, ), - file.data, + imageData, ); fileIds.add(response.id); @@ -390,19 +411,30 @@ class NoteCreateNotifier extends StateNotifier { ? voteDuration : null); - await misskey.notes.create(NotesCreateRequest( - visibility: state.noteVisibility, - text: postText, - cw: state.isCw ? state.cwText : null, - localOnly: state.localOnly, - replyId: state.reply?.id, - renoteId: state.renote?.id, - channelId: state.channel?.id, - fileIds: fileIds.isEmpty ? null : fileIds, - visibleUserIds: visibleUserIds.toSet().toList(), //distinct list - reactionAcceptance: state.reactionAcceptance, - poll: state.isVote ? poll : null, - )); + if (state.noteCreationMode == NoteCreationMode.update) { + await misskey.notes.update(NotesUpdateRequest( + noteId: state.noteId!, + text: postText ?? "", + cw: state.isCw ? state.cwText : null, + )); + noteRepository.registerNote(noteRepository.notes[state.noteId!]! + .copyWith( + text: postText ?? "", cw: state.isCw ? state.cwText : null)); + } else { + await misskey.notes.create(NotesCreateRequest( + visibility: state.noteVisibility, + text: postText, + cw: state.isCw ? state.cwText : null, + localOnly: state.localOnly, + replyId: state.reply?.id, + renoteId: state.renote?.id, + channelId: state.channel?.id, + fileIds: fileIds.isEmpty ? null : fileIds, + visibleUserIds: visibleUserIds.toSet().toList(), //distinct list + reactionAcceptance: state.reactionAcceptance, + poll: state.isVote ? poll : null, + )); + } if (!mounted) return; state = state.copyWith(isNoteSending: NoteSendStatus.finished); } catch (e) { @@ -418,53 +450,75 @@ class NoteCreateNotifier extends StateNotifier { if (result == DriveModalSheetReturnValue.drive) { if (!mounted) return; - final result = await showDialog( - context: context, - builder: (context) => DriveFileSelectDialog(account: state.account)); + final result = await showDialog?>( + context: context, + builder: (context) => DriveFileSelectDialog( + account: state.account, + allowMultiple: true, + ), + ); if (result == null) return; - if (result.type.startsWith("image")) { - final fileContentResponse = await dio.get(result.url, - options: Options(responseType: ResponseType.bytes)); - - state = state.copyWith(files: [ - ...state.files, - ImageFileAlreadyPostedFile( - data: fileContentResponse.data, - fileName: result.name, - id: result.id, - isEdited: false, - isNsfw: result.isSensitive, - caption: result.comment ?? "", - ) - ]); - } else { - state = state.copyWith(files: [ + final files = await Future.wait( + result.map((file) async { + if (file.type.startsWith("image")) { + final fileContentResponse = await dio.get( + file.url, + options: Options(responseType: ResponseType.bytes), + ); + return ImageFileAlreadyPostedFile( + data: fileContentResponse.data!, + id: file.id, + fileName: file.name, + isNsfw: file.isSensitive, + caption: file.comment, + ); + } + return UnknownAlreadyPostedFile( + url: file.url, + id: file.id, + fileName: file.name, + isNsfw: file.isSensitive, + caption: file.comment, + ); + }), + ); + if (!mounted) return; + state = state.copyWith( + files: [ ...state.files, - UnknownAlreadyPostedFile( - url: result.url, - id: result.id, - isEdited: false, - fileName: result.name, - isNsfw: result.isSensitive, - caption: result.comment ?? "") - ]); - } + ...files, + ], + ); } else if (result == DriveModalSheetReturnValue.upload) { - final result = await FilePicker.platform.pickFiles(type: FileType.image); - if (result == null) return; + final result = await FilePicker.platform.pickFiles( + type: FileType.image, + allowMultiple: true, + ); + if (result == null || result.files.isEmpty) return; + + final fsFiles = result.files.map((file) { + final path = file.path; + if (path != null) { + return fileSystem.file(path); + } + return null; + }).nonNulls; + final files = await Future.wait( + fsFiles.map( + (file) async => ImageFile( + data: await file.readAsBytes(), + fileName: file.basename, + ), + ), + ); - final path = result.files.single.path; - if (path == null) return; if (!mounted) return; - state = state.copyWith(files: [ - ...state.files, - ImageFile( - data: await fileSystem.file(path).readAsBytes(), - fileName: path.substring( - path.lastIndexOf(Platform.pathSeparator) + 1, path.length), - isNsfw: false, - caption: "") - ]); + state = state.copyWith( + files: [ + ...state.files, + ...files, + ], + ); } } diff --git a/lib/state_notifier/note_create_page/note_create_state_notifier.freezed.dart b/lib/state_notifier/note_create_page/note_create_state_notifier.freezed.dart index 38a78bfc1..9520a7259 100644 --- a/lib/state_notifier/note_create_page/note_create_state_notifier.freezed.dart +++ b/lib/state_notifier/note_create_page/note_create_state_notifier.freezed.dart @@ -40,6 +40,8 @@ mixin _$NoteCreate { int? get voteDuration => throw _privateConstructorUsedError; VoteExpireDurationType get voteDurationType => throw _privateConstructorUsedError; + NoteCreationMode? get noteCreationMode => throw _privateConstructorUsedError; + String? get noteId => throw _privateConstructorUsedError; @JsonKey(ignore: true) $NoteCreateCopyWith get copyWith => @@ -74,7 +76,9 @@ abstract class $NoteCreateCopyWith<$Res> { bool isVoteMultiple, DateTime? voteDate, int? voteDuration, - VoteExpireDurationType voteDurationType}); + VoteExpireDurationType voteDurationType, + NoteCreationMode? noteCreationMode, + String? noteId}); $AccountCopyWith<$Res> get account; $NoteCreateChannelCopyWith<$Res>? get channel; @@ -117,6 +121,8 @@ class _$NoteCreateCopyWithImpl<$Res, $Val extends NoteCreate> Object? voteDate = freezed, Object? voteDuration = freezed, Object? voteDurationType = null, + Object? noteCreationMode = freezed, + Object? noteId = freezed, }) { return _then(_value.copyWith( account: null == account @@ -207,6 +213,14 @@ class _$NoteCreateCopyWithImpl<$Res, $Val extends NoteCreate> ? _value.voteDurationType : voteDurationType // ignore: cast_nullable_to_non_nullable as VoteExpireDurationType, + noteCreationMode: freezed == noteCreationMode + ? _value.noteCreationMode + : noteCreationMode // ignore: cast_nullable_to_non_nullable + as NoteCreationMode?, + noteId: freezed == noteId + ? _value.noteId + : noteId // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } @@ -285,7 +299,9 @@ abstract class _$$_NoteCreateCopyWith<$Res> bool isVoteMultiple, DateTime? voteDate, int? voteDuration, - VoteExpireDurationType voteDurationType}); + VoteExpireDurationType voteDurationType, + NoteCreationMode? noteCreationMode, + String? noteId}); @override $AccountCopyWith<$Res> get account; @@ -330,6 +346,8 @@ class __$$_NoteCreateCopyWithImpl<$Res> Object? voteDate = freezed, Object? voteDuration = freezed, Object? voteDurationType = null, + Object? noteCreationMode = freezed, + Object? noteId = freezed, }) { return _then(_$_NoteCreate( account: null == account @@ -420,6 +438,14 @@ class __$$_NoteCreateCopyWithImpl<$Res> ? _value.voteDurationType : voteDurationType // ignore: cast_nullable_to_non_nullable as VoteExpireDurationType, + noteCreationMode: freezed == noteCreationMode + ? _value.noteCreationMode + : noteCreationMode // ignore: cast_nullable_to_non_nullable + as NoteCreationMode?, + noteId: freezed == noteId + ? _value.noteId + : noteId // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -449,7 +475,9 @@ class _$_NoteCreate implements _NoteCreate { this.isVoteMultiple = false, this.voteDate, this.voteDuration, - this.voteDurationType = VoteExpireDurationType.seconds}) + this.voteDurationType = VoteExpireDurationType.seconds, + this.noteCreationMode, + this.noteId}) : _replyTo = replyTo, _files = files, _voteContent = voteContent; @@ -528,10 +556,14 @@ class _$_NoteCreate implements _NoteCreate { @override @JsonKey() final VoteExpireDurationType voteDurationType; + @override + final NoteCreationMode? noteCreationMode; + @override + final String? noteId; @override String toString() { - return 'NoteCreate(account: $account, noteVisibility: $noteVisibility, localOnly: $localOnly, replyTo: $replyTo, files: $files, channel: $channel, reply: $reply, renote: $renote, reactionAcceptance: $reactionAcceptance, isCw: $isCw, cwText: $cwText, text: $text, isTextFocused: $isTextFocused, isNoteSending: $isNoteSending, isVote: $isVote, voteContent: $voteContent, voteContentCount: $voteContentCount, voteExpireType: $voteExpireType, isVoteMultiple: $isVoteMultiple, voteDate: $voteDate, voteDuration: $voteDuration, voteDurationType: $voteDurationType)'; + return 'NoteCreate(account: $account, noteVisibility: $noteVisibility, localOnly: $localOnly, replyTo: $replyTo, files: $files, channel: $channel, reply: $reply, renote: $renote, reactionAcceptance: $reactionAcceptance, isCw: $isCw, cwText: $cwText, text: $text, isTextFocused: $isTextFocused, isNoteSending: $isNoteSending, isVote: $isVote, voteContent: $voteContent, voteContentCount: $voteContentCount, voteExpireType: $voteExpireType, isVoteMultiple: $isVoteMultiple, voteDate: $voteDate, voteDuration: $voteDuration, voteDurationType: $voteDurationType, noteCreationMode: $noteCreationMode, noteId: $noteId)'; } @override @@ -572,7 +604,10 @@ class _$_NoteCreate implements _NoteCreate { (identical(other.voteDuration, voteDuration) || other.voteDuration == voteDuration) && (identical(other.voteDurationType, voteDurationType) || - other.voteDurationType == voteDurationType)); + other.voteDurationType == voteDurationType) && + (identical(other.noteCreationMode, noteCreationMode) || + other.noteCreationMode == noteCreationMode) && + (identical(other.noteId, noteId) || other.noteId == noteId)); } @override @@ -599,7 +634,9 @@ class _$_NoteCreate implements _NoteCreate { isVoteMultiple, voteDate, voteDuration, - voteDurationType + voteDurationType, + noteCreationMode, + noteId ]); @JsonKey(ignore: true) @@ -632,7 +669,9 @@ abstract class _NoteCreate implements NoteCreate { final bool isVoteMultiple, final DateTime? voteDate, final int? voteDuration, - final VoteExpireDurationType voteDurationType}) = _$_NoteCreate; + final VoteExpireDurationType voteDurationType, + final NoteCreationMode? noteCreationMode, + final String? noteId}) = _$_NoteCreate; @override Account get account; @@ -679,6 +718,10 @@ abstract class _NoteCreate implements NoteCreate { @override VoteExpireDurationType get voteDurationType; @override + NoteCreationMode? get noteCreationMode; + @override + String? get noteId; + @override @JsonKey(ignore: true) _$$_NoteCreateCopyWith<_$_NoteCreate> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/view/antenna_page/antenna_list.dart b/lib/view/antenna_page/antenna_list.dart index 50b120b07..db42da2b9 100644 --- a/lib/view/antenna_page/antenna_list.dart +++ b/lib/view/antenna_page/antenna_list.dart @@ -1,26 +1,63 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/providers.dart'; import 'package:miria/router/app_router.dart'; +import 'package:miria/view/antenna_page/antenna_page.dart'; import 'package:miria/view/common/account_scope.dart'; -import 'package:miria/view/common/futable_list_builder.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:misskey_dart/misskey_dart.dart'; +import 'package:miria/view/common/error_detail.dart'; +import 'package:miria/view/common/error_dialog_handler.dart'; +import 'package:miria/view/dialogs/simple_confirm_dialog.dart'; class AntennaList extends ConsumerWidget { const AntennaList({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - return FutureListView( - future: - ref.read(misskeyProvider(AccountScope.of(context))).antennas.list(), - builder: (context, element) { - return ListTile( - onTap: () => context.pushRoute(AntennaNotesRoute( - antenna: element, account: AccountScope.of(context))), - title: Text(element.name), - ); - }); + final account = AccountScope.of(context); + final misskey = ref.watch(misskeyProvider(account)); + final antennas = ref.watch(antennasListNotifierProvider(misskey)); + + return antennas.when( + data: (antennas) { + return ListView.builder( + itemCount: antennas.length, + itemBuilder: (context, index) { + final antenna = antennas[index]; + return ListTile( + title: Text(antenna.name), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + final result = await SimpleConfirmDialog.show( + context: context, + message: "このアンテナを削除しますか?", + primary: "削除する", + secondary: "やめる", + ); + if (!context.mounted) return; + if (result ?? false) { + await ref + .read( + antennasListNotifierProvider(misskey).notifier, + ) + .delete(antenna.id) + .expectFailure(context); + } + }, + ), + onTap: () => context.pushRoute( + AntennaNotesRoute( + antenna: antenna, + account: account, + ), + ), + ); + }, + ); + }, + error: (e, st) => Center(child: ErrorDetail(error: e, stackTrace: st)), + loading: () => const Center(child: CircularProgressIndicator()), + ); } } diff --git a/lib/view/antenna_page/antenna_notes_page.dart b/lib/view/antenna_page/antenna_notes_page.dart index 61e709584..e1c0db009 100644 --- a/lib/view/antenna_page/antenna_notes_page.dart +++ b/lib/view/antenna_page/antenna_notes_page.dart @@ -1,9 +1,15 @@ import 'package:auto_route/annotations.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/model/account.dart'; +import 'package:miria/model/antenna_settings.dart'; +import 'package:miria/providers.dart'; import 'package:miria/view/antenna_page/antenna_notes.dart'; +import 'package:miria/view/antenna_page/antenna_page.dart'; +import 'package:miria/view/antenna_page/antenna_settings_dialog.dart'; import 'package:miria/view/common/account_scope.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/view/common/error_dialog_handler.dart'; import 'package:misskey_dart/misskey_dart.dart'; @RoutePage() @@ -16,18 +22,50 @@ class AntennaNotesPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final misskey = ref.watch(misskeyProvider(account)); + final antenna = ref.watch( + antennasListNotifierProvider(misskey).select( + (antennas) => antennas.valueOrNull + ?.firstWhereOrNull((e) => e.id == this.antenna.id), + ), + ) ?? + this.antenna; + return AccountScope( account: account, child: Scaffold( - appBar: AppBar( - title: Text(antenna.name), - ), - body: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: AntennaNotes( - antennaId: antenna.id, + appBar: AppBar( + title: Text(antenna.name), + actions: [ + IconButton( + icon: const Icon(Icons.settings), + onPressed: () async { + final settings = await showDialog( + context: context, + builder: (context) => AntennaSettingsDialog( + title: const Text("編集"), + initialSettings: AntennaSettings.fromAntenna(antenna), + account: account, + ), + ); + if (!context.mounted) return; + if (settings != null) { + ref + .read(antennasListNotifierProvider(misskey).notifier) + .updateAntenna(antenna.id, settings) + .expectFailure(context); + } + }, ), - )), + ], + ), + body: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: AntennaNotes( + antennaId: antenna.id, + ), + ), + ), ); } } diff --git a/lib/view/antenna_page/antenna_page.dart b/lib/view/antenna_page/antenna_page.dart index 981379dfe..b64a1ad20 100644 --- a/lib/view/antenna_page/antenna_page.dart +++ b/lib/view/antenna_page/antenna_page.dart @@ -1,9 +1,92 @@ import 'package:auto_route/annotations.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/model/account.dart'; +import 'package:miria/model/antenna_settings.dart'; +import 'package:miria/providers.dart'; import 'package:miria/view/antenna_page/antenna_list.dart'; +import 'package:miria/view/antenna_page/antenna_settings_dialog.dart'; import 'package:miria/view/common/account_scope.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/view/common/error_dialog_handler.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +final antennasListNotifierProvider = AutoDisposeAsyncNotifierProviderFamily< + AntennasListNotifier, List, Misskey>(AntennasListNotifier.new); + +class AntennasListNotifier + extends AutoDisposeFamilyAsyncNotifier, Misskey> { + @override + Future> build(Misskey arg) async { + final response = await _misskey.antennas.list(); + return response.toList(); + } + + Misskey get _misskey => arg; + + Future create(AntennaSettings settings) async { + final antenna = await _misskey.antennas.create( + AntennasCreateRequest( + name: settings.name, + src: settings.src, + keywords: settings.keywords, + excludeKeywords: settings.excludeKeywords, + users: settings.users, + caseSensitive: settings.caseSensitive, + withReplies: settings.withReplies, + withFile: settings.withFile, + notify: settings.notify, + ), + ); + state = AsyncValue.data([...?state.valueOrNull, antenna]); + } + + Future delete(String antennaId) async { + await _misskey.antennas.delete(AntennasDeleteRequest(antennaId: antennaId)); + state = AsyncValue.data( + state.valueOrNull?.where((e) => e.id != antennaId).toList() ?? [], + ); + } + + Future updateAntenna( + String antennaId, + AntennaSettings settings, + ) async { + await _misskey.antennas.update( + AntennasUpdateRequest( + antennaId: antennaId, + name: settings.name, + src: settings.src, + keywords: settings.keywords, + excludeKeywords: settings.excludeKeywords, + users: settings.users, + caseSensitive: settings.caseSensitive, + withReplies: settings.withReplies, + withFile: settings.withFile, + notify: settings.notify, + ), + ); + state = AsyncValue.data( + state.valueOrNull + ?.map( + (antenna) => (antenna.id == antennaId) + ? antenna.copyWith( + name: settings.name, + src: settings.src, + keywords: settings.keywords, + excludeKeywords: settings.excludeKeywords, + users: settings.users, + caseSensitive: settings.caseSensitive, + withReplies: settings.withReplies, + withFile: settings.withFile, + notify: settings.notify, + ) + : antenna, + ) + .toList() ?? + [], + ); + } +} @RoutePage() class AntennaPage extends ConsumerWidget { @@ -13,15 +96,39 @@ class AntennaPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final misskey = ref.watch(misskeyProvider(account)); + return AccountScope( account: account, child: Scaffold( appBar: AppBar( title: const Text("アンテナ"), + actions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () async { + final settings = await showDialog( + context: context, + builder: (context) => AntennaSettingsDialog( + title: const Text("作成"), + account: account, + ), + ); + if (!context.mounted) return; + if (settings != null) { + await ref + .read(antennasListNotifierProvider(misskey).notifier) + .create(settings) + .expectFailure(context); + } + }, + ), + ], ), body: const Padding( - padding: EdgeInsets.only(left: 10, right: 10), - child: AntennaList()), + padding: EdgeInsets.only(left: 10, right: 10), + child: AntennaList(), + ), ), ); } diff --git a/lib/view/antenna_page/antenna_settings_dialog.dart b/lib/view/antenna_page/antenna_settings_dialog.dart new file mode 100644 index 000000000..d54c00cfe --- /dev/null +++ b/lib/view/antenna_page/antenna_settings_dialog.dart @@ -0,0 +1,340 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/extensions/user_extension.dart'; +import 'package:miria/model/account.dart'; +import 'package:miria/model/antenna_settings.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/view/user_select_dialog.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +final _formKeyProvider = Provider.autoDispose((ref) => GlobalKey()); + +final _initialSettingsProvider = Provider.autoDispose( + (ref) => throw UnimplementedError(), +); + +final _textControllerProvider = ChangeNotifierProvider.autoDispose( + (ref) => TextEditingController( + text: ref.watch( + _initialSettingsProvider.select( + (settings) => settings.users.join("\n"), + ), + ), + ), + dependencies: [_initialSettingsProvider], +); + +final _antennaSettingsNotifierProvider = + NotifierProvider.autoDispose<_AntennaSettingsNotifier, AntennaSettings>( + _AntennaSettingsNotifier.new, + dependencies: [_initialSettingsProvider], +); + +class _AntennaSettingsNotifier extends AutoDisposeNotifier { + @override + AntennaSettings build() { + return ref.watch(_initialSettingsProvider); + } + + void updateName(String? name) { + if (name != null) { + state = state.copyWith(name: name); + } + } + + void updateSrc(AntennaSource? src) { + if (src != null) { + state = state.copyWith(src: src); + } + } + + void updateUserList(UsersList? list) { + if (list != null) { + state = state.copyWith(userListId: list.id); + } + } + + void updateUsers(String? users) { + if (users != null) { + state = state.copyWith(users: users.trim().split("\n")); + } + } + + void updateKeywords(String? keywords) { + if (keywords != null) { + state = state.copyWith( + keywords: keywords.trim().split("\n").map((e) => e.split(" ")).toList(), + ); + } + } + + void updateExcludeKeywords(String? excludeKeywords) { + if (excludeKeywords != null) { + state = state.copyWith( + excludeKeywords: excludeKeywords + .trim() + .split("\n") + .map((e) => e.split(" ")) + .toList(), + ); + } + } + + void updateCaseSensitive(bool? caseSensitive) { + if (caseSensitive != null) { + state = state.copyWith(caseSensitive: caseSensitive); + } + } + + void updateWithReplies(bool? withReplies) { + if (withReplies != null) { + state = state.copyWith(withReplies: withReplies); + } + } + + void updateWithFile(bool? withFile) { + if (withFile != null) { + state = state.copyWith(withFile: withFile); + } + } +} + +final _usersListListProvider = FutureProvider.family, Misskey>( + (ref, misskey) async { + final response = await misskey.users.list.list(); + return response.toList(); + }, +); + +class AntennaSettingsDialog extends StatelessWidget { + const AntennaSettingsDialog({ + super.key, + this.title, + this.initialSettings = const AntennaSettings(), + required this.account, + }); + + final Widget? title; + final AntennaSettings initialSettings; + final Account account; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: title, + scrollable: true, + content: ProviderScope( + overrides: [ + _initialSettingsProvider.overrideWithValue(initialSettings), + ], + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + child: AntennaSettingsForm( + account: account, + ), + ), + ), + ); + } +} + +class AntennaSettingsForm extends ConsumerWidget { + const AntennaSettingsForm({ + super.key, + required this.account, + }); + + final Account account; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final formKey = ref.watch(_formKeyProvider); + final initialSettings = ref.watch(_initialSettingsProvider); + final settings = ref.watch(_antennaSettingsNotifierProvider); + final misskey = ref.watch(misskeyProvider(account)); + final list = ref.watch(_usersListListProvider(misskey)); + final controller = ref.watch(_textControllerProvider); + + return Form( + key: formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + initialValue: initialSettings.name, + maxLength: 100, + decoration: const InputDecoration( + labelText: "アンテナ名", + contentPadding: EdgeInsets.fromLTRB(12, 24, 12, 16), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return "入力してください"; + } + return null; + }, + onSaved: + ref.read(_antennaSettingsNotifierProvider.notifier).updateName, + ), + const SizedBox(height: 10), + const Text("アンテナのソース"), + const SizedBox(height: 5), + DropdownButtonFormField( + items: AntennaSource.values + .map( + (e) => DropdownMenuItem( + value: e, + child: Text( + switch (e) { + AntennaSource.home => "ホーム", + AntennaSource.all => "全て", + AntennaSource.users => "ユーザー", + AntennaSource.list => "リスト", + }, + ), + ), + ) + .toList(), + value: settings.src, + hint: const Text("ソースを選択"), + onChanged: + ref.read(_antennaSettingsNotifierProvider.notifier).updateSrc, + ), + const SizedBox(height: 10), + if (settings.src == AntennaSource.list) + DropdownButtonFormField( + items: list.valueOrNull + ?.map( + (list) => DropdownMenuItem( + value: list, + child: Text(list.name ?? ""), + ), + ) + .toList(), + validator: (value) { + if (value == null) { + return "選択してください"; + } + return null; + }, + value: list.valueOrNull + ?.firstWhereOrNull((e) => e.id == settings.userListId), + hint: const Text("リストを選択"), + onChanged: ref + .read(_antennaSettingsNotifierProvider.notifier) + .updateUserList, + ), + if (settings.src == AntennaSource.users) ...[ + TextFormField( + controller: controller, + minLines: 2, + maxLines: 20, + decoration: const InputDecoration( + labelText: "ユーザー", + hintText: "ユーザーネームを改行で区切って指定します", + contentPadding: EdgeInsets.fromLTRB(12, 24, 12, 16), + ), + onSaved: ref + .read(_antennaSettingsNotifierProvider.notifier) + .updateUsers, + ), + TextButton( + onPressed: () async { + final user = await showDialog( + context: context, + builder: (context) => UserSelectDialog(account: account), + ); + if (user == null) { + return; + } + if (!context.mounted) return; + if (!controller.text.endsWith("\n") && + controller.text.isNotEmpty) { + controller.text += "\n"; + } + controller.text += "${user.acct}\n"; + }, + child: const Text("ユーザーを追加"), + ), + ], + const SizedBox(height: 10), + TextFormField( + initialValue: + initialSettings.keywords.map((e) => e.join(" ")).join("\n"), + minLines: 2, + maxLines: 20, + decoration: const InputDecoration( + labelText: "キーワード", + helperText: "スペースで区切った単語はAND条件で、改行で区切った行はOR条件で扱います", + helperMaxLines: 5, + contentPadding: EdgeInsets.fromLTRB(12, 24, 12, 16), + ), + // Misskey 2023.9.0 で条件が変更されるためバリデーションを行わない + // https://github.com/misskey-dev/misskey/pull/11469 + onSaved: ref + .read(_antennaSettingsNotifierProvider.notifier) + .updateKeywords, + ), + const SizedBox(height: 10), + TextFormField( + initialValue: initialSettings.excludeKeywords + .map((e) => e.join(" ")) + .join("\n"), + minLines: 2, + maxLines: 20, + decoration: const InputDecoration( + labelText: "除外キーワード", + helperText: "スペースで区切った単語はAND条件で、改行で区切った行はOR条件で扱います", + helperMaxLines: 5, + contentPadding: EdgeInsets.fromLTRB(12, 24, 12, 16), + ), + onSaved: ref + .read(_antennaSettingsNotifierProvider.notifier) + .updateExcludeKeywords, + ), + const SizedBox(height: 10), + CheckboxListTile( + title: const Text("大文字と小文字を区別する"), + value: settings.caseSensitive, + onChanged: ref + .read(_antennaSettingsNotifierProvider.notifier) + .updateCaseSensitive, + ), + CheckboxListTile( + title: const Text("リプライを受信する"), + value: settings.withReplies, + onChanged: ref + .read(_antennaSettingsNotifierProvider.notifier) + .updateWithReplies, + ), + CheckboxListTile( + title: const Text("ファイル付きのノートのみ受信する"), + value: settings.withFile, + onChanged: ref + .read(_antennaSettingsNotifierProvider.notifier) + .updateWithFile, + ), + // notifyは機能していない? + Center( + child: ElevatedButton( + child: const Text("決定"), + onPressed: () { + if (formKey.currentState!.validate()) { + formKey.currentState!.save(); + final settings = ref.read(_antennaSettingsNotifierProvider); + if (settings == initialSettings) { + Navigator.of(context).pop(); + } else { + Navigator.of(context).pop(settings); + } + } + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/view/channels_page/channel_detail_info.dart b/lib/view/channels_page/channel_detail_info.dart index 9fb74af4d..fb7dd364f 100644 --- a/lib/view/channels_page/channel_detail_info.dart +++ b/lib/view/channels_page/channel_detail_info.dart @@ -90,6 +90,9 @@ class ChannelDetailInfoState extends ConsumerState { @override Widget build(BuildContext context) { final data = this.data; + final isFavorited = data?.isFavorited; + final isFollowing = data?.isFollowing; + if (data == null) { if (error == null) { return const Center(child: CircularProgressIndicator()); @@ -149,23 +152,25 @@ class ChannelDetailInfoState extends ConsumerState { alignment: Alignment.centerRight, child: Wrap( children: [ - data.isFavorited - ? ElevatedButton.icon( - onPressed: unfavorite.expectFailure(context), - icon: const Icon(Icons.favorite_border), - label: const Text("お気に入り中")) - : OutlinedButton( - onPressed: favorite.expectFailure(context), - child: const Text("お気に入りにいれる")), + if (isFavorited != null) + isFavorited + ? ElevatedButton.icon( + onPressed: unfavorite.expectFailure(context), + icon: const Icon(Icons.favorite_border), + label: const Text("お気に入り中")) + : OutlinedButton( + onPressed: favorite.expectFailure(context), + child: const Text("お気に入りにいれる")), const Padding(padding: EdgeInsets.only(left: 10)), - data.isFollowing - ? ElevatedButton.icon( - onPressed: unfollow.expectFailure(context), - icon: const Icon(Icons.check), - label: const Text("フォローしています")) - : OutlinedButton( - onPressed: follow.expectFailure(context), - child: const Text("フォローする")) + if (isFollowing != null) + isFollowing + ? ElevatedButton.icon( + onPressed: unfollow.expectFailure(context), + icon: const Icon(Icons.check), + label: const Text("フォローしています")) + : OutlinedButton( + onPressed: follow.expectFailure(context), + child: const Text("フォローする")) ], )), MfmText(mfmText: data.description ?? ""), diff --git a/lib/view/clip_list_page/clip_detail_page.dart b/lib/view/clip_list_page/clip_detail_page.dart index 5a12660d5..f73ecde80 100644 --- a/lib/view/clip_list_page/clip_detail_page.dart +++ b/lib/view/clip_list_page/clip_detail_page.dart @@ -1,31 +1,64 @@ import 'package:auto_route/annotations.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/model/account.dart'; +import 'package:miria/model/clip_settings.dart'; +import 'package:miria/providers.dart'; import 'package:miria/view/clip_list_page/clip_detail_note_list.dart'; +import 'package:miria/view/clip_list_page/clip_list_page.dart'; +import 'package:miria/view/clip_list_page/clip_settings_dialog.dart'; import 'package:miria/view/common/account_scope.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/view/common/error_dialog_handler.dart'; @RoutePage() -class ClipDetailPage extends ConsumerStatefulWidget { +class ClipDetailPage extends ConsumerWidget { final Account account; final String id; const ClipDetailPage({super.key, required this.account, required this.id}); @override - ConsumerState createState() => ClipDetailPageState(); -} + Widget build(BuildContext context, WidgetRef ref) { + final misskey = ref.watch(misskeyProvider(account)); + final clip = ref.watch( + clipsListNotifierProvider(misskey).select( + (clips) => clips.valueOrNull?.firstWhereOrNull((e) => e.id == id), + ), + ); -class ClipDetailPageState extends ConsumerState { - @override - Widget build(BuildContext context) { return AccountScope( - account: widget.account, + account: account, child: Scaffold( - appBar: AppBar(), + appBar: AppBar( + title: Text(clip?.name ?? ""), + actions: [ + if (clip != null) + IconButton( + icon: const Icon(Icons.settings), + onPressed: () async { + final settings = await showDialog( + context: context, + builder: (context) => ClipSettingsDialog( + title: const Text("編集"), + initialSettings: ClipSettings.fromClip(clip), + ), + ); + if (!context.mounted) return; + if (settings != null) { + ref + .read(clipsListNotifierProvider(misskey).notifier) + .updateClip(clip.id, settings) + .expectFailure(context); + } + }, + ), + ], + ), body: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: ClipDetailNoteList(id: widget.id)), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: ClipDetailNoteList(id: id), + ), ), ); } diff --git a/lib/view/clip_list_page/clip_list_page.dart b/lib/view/clip_list_page/clip_list_page.dart index 03b34f429..7d2cf94b8 100644 --- a/lib/view/clip_list_page/clip_list_page.dart +++ b/lib/view/clip_list_page/clip_list_page.dart @@ -1,34 +1,142 @@ import 'package:auto_route/annotations.dart'; import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Clip; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/model/account.dart'; +import 'package:miria/model/clip_settings.dart'; import 'package:miria/providers.dart'; +import 'package:miria/view/clip_list_page/clip_settings_dialog.dart'; import 'package:miria/view/common/account_scope.dart'; import 'package:miria/view/common/clip_item.dart'; -import 'package:miria/view/common/futable_list_builder.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/view/common/error_detail.dart'; +import 'package:miria/view/common/error_dialog_handler.dart'; +import 'package:miria/view/dialogs/simple_confirm_dialog.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +final clipsListNotifierProvider = AutoDisposeAsyncNotifierProviderFamily< + ClipsListNotifier, List, Misskey>(ClipsListNotifier.new); + +class ClipsListNotifier + extends AutoDisposeFamilyAsyncNotifier, Misskey> { + @override + Future> build(Misskey arg) async { + final response = await _misskey.clips.list(); + return response.toList(); + } + + Misskey get _misskey => arg; + + Future create(ClipSettings settings) async { + final list = await _misskey.clips.create( + ClipsCreateRequest( + name: settings.name, + description: settings.description, + isPublic: settings.isPublic, + ), + ); + state = AsyncValue.data([...?state.valueOrNull, list]); + } + + Future delete(String clipId) async { + await _misskey.clips.delete(ClipsDeleteRequest(clipId: clipId)); + state = AsyncValue.data( + state.valueOrNull?.where((e) => e.id != clipId).toList() ?? [], + ); + } + + Future updateClip( + String clipId, + ClipSettings settings, + ) async { + final clip = await _misskey.clips.update( + ClipsUpdateRequest( + clipId: clipId, + name: settings.name, + description: settings.description, + isPublic: settings.isPublic, + ), + ); + state = AsyncValue.data( + state.valueOrNull + ?.map( + (e) => (e.id == clipId) ? clip : e, + ) + .toList() ?? + [], + ); + } +} @RoutePage() -class ClipListPage extends ConsumerStatefulWidget { +class ClipListPage extends ConsumerWidget { const ClipListPage({super.key, required this.account}); final Account account; @override - ConsumerState createState() => ClipListPageState(); -} + Widget build(BuildContext context, WidgetRef ref) { + final misskey = ref.watch(misskeyProvider(account)); + final clips = ref.watch(clipsListNotifierProvider(misskey)); -class ClipListPageState extends ConsumerState { - @override - Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text("クリップ一覧"), + appBar: AppBar( + title: const Text("クリップ一覧"), + actions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () async { + final settings = await showDialog( + context: context, + builder: (context) => const ClipSettingsDialog( + title: Text("作成"), + ), + ); + if (!context.mounted) return; + if (settings != null) { + ref + .read(clipsListNotifierProvider(misskey).notifier) + .create(settings) + .expectFailure(context); + } + }, + ), + ], + ), + body: clips.when( + data: (clips) => AccountScope( + account: account, + child: ListView.builder( + itemCount: clips.length, + itemBuilder: (context, index) { + final clip = clips[index]; + return ClipItem( + clip: clips[index], + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + final result = await SimpleConfirmDialog.show( + context: context, + message: "このクリップを削除しますか?", + primary: "削除する", + secondary: "やめる", + ); + if (!context.mounted) return; + if (result ?? false) { + await ref + .read( + clipsListNotifierProvider(misskey).notifier, + ) + .delete(clip.id) + .expectFailure(context); + } + }, + ), + ); + }, + ), ), - body: AccountScope( - account: widget.account, - child: FutureListView( - future: ref.read(misskeyProvider(widget.account)).clips.list(), - builder: (context, item) => ClipItem(clip: item), - ))); + error: (e, st) => Center(child: ErrorDetail(error: e, stackTrace: st)), + loading: () => const Center(child: CircularProgressIndicator()), + ), + ); } } diff --git a/lib/view/clip_list_page/clip_settings_dialog.dart b/lib/view/clip_list_page/clip_settings_dialog.dart new file mode 100644 index 000000000..35e5e185d --- /dev/null +++ b/lib/view/clip_list_page/clip_settings_dialog.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/model/clip_settings.dart'; + +final _formKeyProvider = Provider.autoDispose((ref) => GlobalKey()); + +final _initialSettingsProvider = Provider.autoDispose( + (ref) => throw UnimplementedError(), +); + +final _clipSettingsNotifierProvider = + NotifierProvider.autoDispose<_ClipSettingsNotifier, ClipSettings>( + _ClipSettingsNotifier.new, + dependencies: [_initialSettingsProvider], +); + +class _ClipSettingsNotifier extends AutoDisposeNotifier { + @override + ClipSettings build() { + return ref.watch(_initialSettingsProvider); + } + + void updateName(String? name) { + if (name != null) { + state = state.copyWith(name: name); + } + } + + void updateDescription(String? description) { + if (description != null) { + state = state.copyWith( + description: description.isEmpty ? null : description, + ); + } + } + + void updateIsPublic(bool? isPublic) { + if (isPublic != null) { + state = state.copyWith(isPublic: isPublic); + } + } +} + +class ClipSettingsDialog extends StatelessWidget { + const ClipSettingsDialog({ + super.key, + this.title, + this.initialSettings = const ClipSettings(), + }); + + final Widget? title; + final ClipSettings initialSettings; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: title, + scrollable: true, + content: ProviderScope( + overrides: [ + _initialSettingsProvider.overrideWithValue(initialSettings), + ], + child: const UsersListSettingsForm(), + ), + ); + } +} + +class UsersListSettingsForm extends ConsumerWidget { + const UsersListSettingsForm({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final formKey = ref.watch(_formKeyProvider); + final initialSettings = ref.watch(_initialSettingsProvider); + final settings = ref.watch(_clipSettingsNotifierProvider); + + return Form( + key: formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + initialValue: initialSettings.name, + maxLength: 100, + decoration: const InputDecoration( + labelText: "クリップ名", + contentPadding: EdgeInsets.fromLTRB(12, 24, 12, 16), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return "入力してください"; + } + return null; + }, + onSaved: + ref.read(_clipSettingsNotifierProvider.notifier).updateName, + ), + const SizedBox(height: 10), + TextFormField( + initialValue: initialSettings.description, + minLines: 2, + maxLines: null, + decoration: const InputDecoration( + labelText: "説明(省略可)", + contentPadding: EdgeInsets.fromLTRB(12, 24, 12, 16), + ), + onSaved: ref + .read(_clipSettingsNotifierProvider.notifier) + .updateDescription, + ), + CheckboxListTile( + title: const Text("パブリック"), + value: settings.isPublic, + onChanged: + ref.read(_clipSettingsNotifierProvider.notifier).updateIsPublic, + ), + ElevatedButton( + child: const Text("決定"), + onPressed: () { + if (formKey.currentState!.validate()) { + formKey.currentState!.save(); + final settings = ref.read(_clipSettingsNotifierProvider); + if (settings == initialSettings) { + Navigator.of(context).pop(); + } else { + Navigator.of(context).pop(settings); + } + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/view/common/clip_item.dart b/lib/view/common/clip_item.dart index 91f08f3e9..c4677402a 100644 --- a/lib/view/common/clip_item.dart +++ b/lib/view/common/clip_item.dart @@ -7,7 +7,13 @@ import 'package:misskey_dart/misskey_dart.dart'; class ClipItem extends StatelessWidget { final Clip clip; - const ClipItem({super.key, required this.clip}); + final Widget? trailing; + + const ClipItem({ + super.key, + required this.clip, + this.trailing, + }); @override Widget build(BuildContext context) { @@ -16,6 +22,7 @@ class ClipItem extends StatelessWidget { ClipDetailRoute(account: AccountScope.of(context), id: clip.id)), title: Text(clip.name ?? ""), subtitle: SimpleMfmText(clip.description ?? ""), + trailing: trailing, ); } } diff --git a/lib/view/common/error_detail.dart b/lib/view/common/error_detail.dart index dafbe2055..54a2ad222 100644 --- a/lib/view/common/error_detail.dart +++ b/lib/view/common/error_detail.dart @@ -1,3 +1,6 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; @@ -26,6 +29,12 @@ class ErrorDetail extends StatelessWidget { return Text("[${response.statusCode}] ${response.data}"); } } - return Text("不明なエラー:$error/*\n$stackTrace*/"); + if (e is WebSocketException) { + return Text("通信に失敗しました。\n$stackTrace"); + } + if (e is TimeoutException) { + return const Text("サーバーが応答してくれませんでした。"); + } + return Text("不明なエラー:$error\n$stackTrace"); } } diff --git a/lib/view/common/misskey_notes/custom_emoji.dart b/lib/view/common/misskey_notes/custom_emoji.dart index 7a89692f2..96f01283c 100644 --- a/lib/view/common/misskey_notes/custom_emoji.dart +++ b/lib/view/common/misskey_notes/custom_emoji.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:miria/model/general_settings.dart'; import 'package:miria/model/misskey_emoji_data.dart'; import 'package:miria/providers.dart'; +import 'package:miria/view/common/account_scope.dart'; import 'package:miria/view/common/misskey_notes/network_image.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/view/themes/app_theme.dart'; @@ -41,18 +42,20 @@ class CustomEmojiState extends ConsumerState { } /// カスタム絵文字のURLを解決する - Uri resolveCustomEmojiUrl(Uri uri) { - // 特例としてにじみすのみ、URLの変換をかける - if (uri.host == "media.nijimiss.app") { - return Uri( - scheme: "https", - host: "nijimiss.moe", - pathSegments: ["proxy", "image.webp"], - queryParameters: {"url": Uri.encodeFull(uri.toString()), "emoji": "1"}, - ); - } else { - return uri; - } + Uri resolveFallbackCustomEmojiUrl(CustomEmojiData emojiData) { + return Uri( + scheme: "https", + host: emojiData.isCurrentServer + ? AccountScope.of(context).host + : emojiData.hostedName + .replaceAll(RegExp(r'^\:(.+?)@'), "") + .replaceAll(":", ""), + pathSegments: ["proxy", "image.webp"], + queryParameters: { + "url": Uri.encodeFull(emojiData.url.toString()), + "emoji": "1" + }, + ); } @override @@ -75,11 +78,18 @@ class CustomEmojiState extends ConsumerState { isAttachTooltip: widget.isAttachTooltip, message: emojiData.hostedName, child: NetworkImageView( - url: resolveCustomEmojiUrl(emojiData.url).toString(), + url: emojiData.url.toString(), type: ImageType.customEmoji, - errorBuilder: (context, e, s) => Text( - emojiData.hostedName, - style: style, + errorBuilder: (context, e, s) => NetworkImageView( + url: resolveFallbackCustomEmojiUrl(emojiData).toString(), + type: ImageType.customEmoji, + loadingBuilder: (context, widget, chunk) => SizedBox( + height: scopedFontSize, + width: scopedFontSize, + ), + height: scopedFontSize, + errorBuilder: (context, e, s) => + Text(emojiData.hostedName, style: style), ), loadingBuilder: (context, widget, chunk) => SizedBox( height: scopedFontSize, diff --git a/lib/view/common/misskey_notes/misskey_note.dart b/lib/view/common/misskey_notes/misskey_note.dart index bdc0bd3b6..d2d8b37a8 100644 --- a/lib/view/common/misskey_notes/misskey_note.dart +++ b/lib/view/common/misskey_notes/misskey_note.dart @@ -5,6 +5,7 @@ import 'package:collection/collection.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:flutter/material.dart'; import 'package:mfm_parser/mfm_parser.dart' as parser; +import 'package:miria/const.dart'; import 'package:miria/extensions/date_time_extension.dart'; import 'package:miria/extensions/note_visibility_extension.dart'; import 'package:miria/extensions/user_extension.dart'; @@ -145,6 +146,7 @@ class MisskeyNoteState extends ConsumerState { bool isLongVisibleInitialized = false; List? displayTextNodes; + DateTime? latestUpdatedAt; bool shouldCollaposed(List node) { final result = nodeMaxTextLength(node); @@ -210,10 +212,34 @@ class MisskeyNoteState extends ConsumerState { return Container(); } + if (latestUpdatedAt != displayNote.updatedAt) { + latestUpdatedAt = displayNote.updatedAt; + displayTextNodes = null; + } + displayTextNodes ??= const parser.MfmParser().parse(displayNote.text ?? ""); final noteStatus = ref.watch(notesProvider(AccountScope.of(context)) .select((value) => value.noteStatuses[widget.note.id]))!; + + if (noteStatus.isIncludeMuteWord && !noteStatus.isMuteOpened) { + return SizedBox( + width: double.infinity, + child: GestureDetector( + onTap: () => ref + .read(notesProvider(AccountScope.of(context))) + .updateNoteStatus(displayNote.id, + (status) => status.copyWith(isMuteOpened: true)), + child: Padding( + padding: const EdgeInsets.only(top: 5.0, bottom: 5.0), + child: Text( + "${displayNote.user.name ?? displayNote.user.username}が何か言うとるわ", + style: Theme.of(context).textTheme.bodySmall, + ), + )), + ); + } + if (!noteStatus.isLongVisibleInitialized || widget.isForceUnvisibleRenote || widget.isForceUnvisibleReply || @@ -751,6 +777,10 @@ class MisskeyNoteState extends ConsumerState { .notes .reactions .delete(NotesReactionsDeleteRequest(noteId: displayNote.id)); + if (account.host == "misskey.io") { + await Future.delayed( + const Duration(milliseconds: misskeyIOReactionDelay)); + } await ref.read(notesProvider(account)).refresh(displayNote.id); return; } @@ -777,6 +807,10 @@ class MisskeyNoteState extends ConsumerState { if (selectedEmoji == null) return; await misskey.notes.reactions.create(NotesReactionsCreateRequest( noteId: displayNote.id, reaction: ":${selectedEmoji.baseName}:")); + if (account.host == "misskey.io") { + await Future.delayed( + const Duration(milliseconds: misskeyIOReactionDelay)); + } await note.refresh(displayNote.id); } } @@ -798,6 +832,12 @@ class NoteHeader1 extends StatelessWidget { child: Padding( padding: const EdgeInsets.only(top: 2), child: UserInformation(user: displayNote.user))), + if (displayNote.updatedAt != null) + Padding( + padding: const EdgeInsets.only(left: 5, right: 5), + child: Icon(Icons.edit, + size: Theme.of(context).textTheme.bodySmall?.fontSize, + color: Theme.of(context).textTheme.bodySmall?.color)), GestureDetector( onTap: () async => await _navigateDetailPage(context, displayNote, loginAs) @@ -871,6 +911,15 @@ class RenoteHeader extends StatelessWidget { ), ), ), + if (note.updatedAt != null) + Padding( + padding: const EdgeInsets.only(left: 5.0, right: 5.0), + child: Icon( + Icons.edit, + size: renoteTextStyle?.fontSize, + color: renoteTextStyle?.color, + ), + ), Text( note.createdAt.differenceNow, textAlign: TextAlign.right, @@ -948,7 +997,7 @@ class RenoteButton extends StatelessWidget { // 他人のノートで、ダイレクトまたはフォロワーのみへの公開の場合、リノート不可 if ((displayNote.visibility == NoteVisibility.specified || displayNote.visibility == NoteVisibility.followers) && - !(account.host == displayNote.user.host && + !(displayNote.user.host == null && account.userId == displayNote.user.username)) { return Icon( Icons.block, diff --git a/lib/view/common/misskey_notes/note_modal_sheet.dart b/lib/view/common/misskey_notes/note_modal_sheet.dart index 7c22a0d83..b310ef7fb 100644 --- a/lib/view/common/misskey_notes/note_modal_sheet.dart +++ b/lib/view/common/misskey_notes/note_modal_sheet.dart @@ -13,6 +13,7 @@ import 'package:miria/view/common/misskey_notes/clip_modal_sheet.dart'; import 'package:miria/view/common/misskey_notes/open_another_account.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/view/dialogs/simple_confirm_dialog.dart'; +import 'package:miria/view/note_create_page/note_create_page.dart'; import 'package:misskey_dart/misskey_dart.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; @@ -219,6 +220,19 @@ class NoteModalSheet extends ConsumerWidget { baseNote.renote != null && baseNote.poll == null && baseNote.files.isEmpty)) ...[ + if (account.i.policies.canEditNote) + ListTile( + title: const Text("編集する"), + onTap: () async { + Navigator.of(context).pop(); + context.pushRoute( + NoteCreateRoute( + initialAccount: account, + note: targetNote, + noteCreationMode: NoteCreationMode.update, + ), + ); + }), ListTile( title: const Text("削除する"), onTap: () async { @@ -263,7 +277,8 @@ class NoteModalSheet extends ConsumerWidget { context.pushRoute( NoteCreateRoute( initialAccount: account, - deletedNote: targetNote, + note: targetNote, + noteCreationMode: NoteCreationMode.recreate, ), ); } diff --git a/lib/view/common/misskey_notes/reaction_button.dart b/lib/view/common/misskey_notes/reaction_button.dart index 7adc384ed..395192a55 100644 --- a/lib/view/common/misskey_notes/reaction_button.dart +++ b/lib/view/common/misskey_notes/reaction_button.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:miria/const.dart'; import 'package:miria/model/account.dart'; import 'package:miria/model/misskey_emoji_data.dart'; import 'package:miria/providers.dart'; @@ -80,6 +81,10 @@ class ReactionButtonState extends ConsumerState { .notes .reactions .delete(NotesReactionsDeleteRequest(noteId: widget.noteId)); + if (account.host == "misskey.io") { + await Future.delayed( + const Duration(milliseconds: misskeyIOReactionDelay)); + } await ref.read(notesProvider(account)).refresh(widget.noteId); @@ -106,6 +111,13 @@ class ReactionButtonState extends ConsumerState { await ref.read(misskeyProvider(account)).notes.reactions.create( NotesReactionsCreateRequest( noteId: widget.noteId, reaction: reactionString)); + + // misskey.ioはただちにリアクションを反映してくれない + if (account.host == "misskey.io") { + await Future.delayed( + const Duration(milliseconds: misskeyIOReactionDelay)); + } + await ref.read(notesProvider(account)).refresh(widget.noteId); }, onLongPress: () { diff --git a/lib/view/common/note_create/custom_keyboard_list.dart b/lib/view/common/note_create/basic_keyboard.dart similarity index 50% rename from lib/view/common/note_create/custom_keyboard_list.dart rename to lib/view/common/note_create/basic_keyboard.dart index 975719c63..16921e3d2 100644 --- a/lib/view/common/note_create/custom_keyboard_list.dart +++ b/lib/view/common/note_create/basic_keyboard.dart @@ -1,12 +1,12 @@ import 'package:flutter/cupertino.dart'; -import 'custom_keyboard.dart'; +import 'custom_keyboard_button.dart'; -class CustomKeyboardList extends StatelessWidget { +class BasicKeyboard extends StatelessWidget { final TextEditingController controller; final FocusNode focusNode; - const CustomKeyboardList({ + const BasicKeyboard({ super.key, required this.controller, required this.focusNode, @@ -14,69 +14,85 @@ class CustomKeyboardList extends StatelessWidget { @override Widget build(BuildContext context) { - return Row(children: [ - CustomKeyboard( + return Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CustomKeyboardButton( keyboard: ":", displayText: ":", controller: controller, - focusNode: focusNode), - CustomKeyboard( + focusNode: focusNode, + ), + CustomKeyboardButton( keyboard: r"$[", afterInsert: "]", controller: controller, - focusNode: focusNode), - CustomKeyboard( + focusNode: focusNode, + ), + CustomKeyboardButton( keyboard: "**", afterInsert: "**", controller: controller, - focusNode: focusNode), - CustomKeyboard( + focusNode: focusNode, + ), + CustomKeyboardButton( keyboard: "", afterInsert: "", controller: controller, - focusNode: focusNode), - CustomKeyboard( + focusNode: focusNode, + ), + CustomKeyboardButton( keyboard: "", afterInsert: "", controller: controller, - focusNode: focusNode), - CustomKeyboard( + focusNode: focusNode, + ), + CustomKeyboardButton( keyboard: "***", afterInsert: "***", controller: controller, - focusNode: focusNode), - CustomKeyboard( + focusNode: focusNode, + ), + CustomKeyboardButton( keyboard: "~~", afterInsert: "~~", controller: controller, - focusNode: focusNode), - CustomKeyboard( - keyboard: "> ", - displayText: ">", - controller: controller, - focusNode: focusNode, - ), - CustomKeyboard( + focusNode: focusNode, + ), + CustomKeyboardButton( + keyboard: "> ", + displayText: ">", + controller: controller, + focusNode: focusNode, + ), + CustomKeyboardButton( keyboard: "
", afterInsert: "
", controller: controller, - focusNode: focusNode), - CustomKeyboard( + focusNode: focusNode, + ), + CustomKeyboardButton( keyboard: "`", afterInsert: "`", displayText: "`", controller: controller, - focusNode: focusNode), - CustomKeyboard( + focusNode: focusNode, + ), + CustomKeyboardButton( keyboard: "```\n", afterInsert: "\n```", controller: controller, - focusNode: focusNode), - CustomKeyboard( + focusNode: focusNode, + ), + CustomKeyboardButton( keyboard: "", afterInsert: "", controller: controller, - focusNode: focusNode) - ]); + focusNode: focusNode, + ), + ], + ); } } diff --git a/lib/view/common/note_create/custom_keyboard.dart b/lib/view/common/note_create/custom_keyboard_button.dart similarity index 62% rename from lib/view/common/note_create/custom_keyboard.dart rename to lib/view/common/note_create/custom_keyboard_button.dart index 9a02921b5..886ff2121 100644 --- a/lib/view/common/note_create/custom_keyboard.dart +++ b/lib/view/common/note_create/custom_keyboard_button.dart @@ -1,20 +1,22 @@ import 'package:flutter/material.dart'; import 'package:miria/extensions/text_editing_controller_extension.dart'; -class CustomKeyboard extends StatelessWidget { +class CustomKeyboardButton extends StatelessWidget { final String keyboard; final String displayText; final String? afterInsert; final TextEditingController controller; final FocusNode focusNode; + final void Function()? onTap; - const CustomKeyboard({ + const CustomKeyboardButton({ super.key, required this.keyboard, required this.controller, required this.focusNode, String? displayText, this.afterInsert, + this.onTap, }) : displayText = displayText ?? keyboard; void insert() { @@ -25,18 +27,19 @@ class CustomKeyboard extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () => insert(), + onTap: onTap ?? insert, child: Padding( padding: const EdgeInsets.all(5), child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: 32 * MediaQuery.of(context).textScaleFactor, - maxHeight: 32 * MediaQuery.of(context).textScaleFactor, - ), - child: Text( - keyboard, - textAlign: TextAlign.center, - )), + constraints: BoxConstraints( + minWidth: 32 * MediaQuery.of(context).textScaleFactor, + maxHeight: 32 * MediaQuery.of(context).textScaleFactor, + ), + child: Text( + keyboard, + textAlign: TextAlign.center, + ), + ), ), ); } diff --git a/lib/view/common/note_create/emoji_keyboard.dart b/lib/view/common/note_create/emoji_keyboard.dart new file mode 100644 index 000000000..626c4d592 --- /dev/null +++ b/lib/view/common/note_create/emoji_keyboard.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/model/account.dart'; +import 'package:miria/model/input_completion_type.dart'; +import 'package:miria/model/misskey_emoji_data.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/view/common/misskey_notes/custom_emoji.dart'; +import 'package:miria/view/common/note_create/input_completation.dart'; +import 'package:miria/view/reaction_picker_dialog/reaction_picker_dialog.dart'; + +final _filteredEmojisProvider = NotifierProvider.autoDispose + .family<_FilteredEmojis, List, Account>( + _FilteredEmojis.new, +); + +class _FilteredEmojis + extends AutoDisposeFamilyNotifier, Account> { + @override + List build(Account arg) { + ref.listen(inputCompletionTypeProvider, (_, type) { + _updateEmojis(type); + }); + return ref.read(emojiRepositoryProvider(arg)).defaultEmojis(); + } + + void _updateEmojis(InputCompletionType type) async { + if (type is Emoji) { + state = + await ref.read(emojiRepositoryProvider(arg)).searchEmojis(type.query); + } + } +} + +class EmojiKeyboard extends ConsumerWidget { + const EmojiKeyboard({ + super.key, + required this.account, + required this.controller, + required this.focusNode, + }); + + final Account account; + final TextEditingController controller; + final FocusNode focusNode; + + void insertEmoji(MisskeyEmojiData emoji, WidgetRef ref) { + final currentPosition = controller.selection.base.offset; + final text = controller.text; + + final beforeSearchText = + text.substring(0, text.substring(0, currentPosition).lastIndexOf(":")); + + final after = (currentPosition == text.length || currentPosition == -1) + ? "" + : text.substring(currentPosition, text.length); + + switch (emoji) { + case CustomEmojiData(): + controller.value = TextEditingValue( + text: "$beforeSearchText:${emoji.baseName}:$after", + selection: TextSelection.collapsed( + offset: beforeSearchText.length + emoji.baseName.length + 2, + ), + ); + break; + case UnicodeEmojiData(): + controller.value = TextEditingValue( + text: "$beforeSearchText${emoji.char}$after", + selection: TextSelection.collapsed( + offset: beforeSearchText.length + emoji.char.length, + ), + ); + break; + default: + return; + } + focusNode.requestFocus(); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final filteredEmojis = ref.watch(_filteredEmojisProvider(account)); + + return Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + for (final emoji in filteredEmojis) + GestureDetector( + onTap: () => insertEmoji(emoji, ref), + child: Padding( + padding: const EdgeInsets.all(5), + child: SizedBox( + height: 32 * MediaQuery.of(context).textScaleFactor, + child: CustomEmoji(emojiData: emoji), + ), + ), + ), + TextButton.icon( + onPressed: () async { + final selected = await showDialog( + context: context, + builder: (context2) => ReactionPickerDialog( + account: account, + isAcceptSensitive: true, + ), + ); + if (selected != null) { + insertEmoji(selected, ref); + } + }, + icon: const Icon(Icons.add_reaction_outlined), + label: const Text("他のん"), + ), + ], + ); + } +} diff --git a/lib/view/common/note_create/hashtag_keyboard.dart b/lib/view/common/note_create/hashtag_keyboard.dart new file mode 100644 index 000000000..c8fc99f10 --- /dev/null +++ b/lib/view/common/note_create/hashtag_keyboard.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/extensions/text_editing_controller_extension.dart'; +import 'package:miria/model/account.dart'; +import 'package:miria/model/input_completion_type.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/view/common/note_create/basic_keyboard.dart'; +import 'package:miria/view/common/note_create/custom_keyboard_button.dart'; +import 'package:miria/view/common/note_create/input_completation.dart'; +import 'package:misskey_dart/misskey_dart.dart' hide Hashtag; + +final _hashtagsSearchProvider = AsyncNotifierProviderFamily<_HashtagsSearch, + List, (String, String)>(_HashtagsSearch.new); + +class _HashtagsSearch + extends FamilyAsyncNotifier, (String, String)> { + @override + Future> build((String, String) arg) async { + final (host, query) = arg; + if (query.isEmpty) { + return []; + } else { + final response = await ref + .read(misskeyProvider(Account.demoAccount(host))) + .hashtags + .search( + HashtagsSearchRequest( + query: query, + limit: 30, + ), + ); + return response.toList(); + } + } +} + +final _filteredHashtagsProvider = NotifierProvider.autoDispose + .family<_FilteredHashtags, List, Account>(_FilteredHashtags.new); + +class _FilteredHashtags + extends AutoDisposeFamilyNotifier, Account> { + @override + List build(Account arg) { + ref.listen( + inputCompletionTypeProvider, + (_, type) { + _updateHashtags(type); + }, + fireImmediately: true, + ); + return []; + } + + void _updateHashtags(InputCompletionType type) async { + if (type is Hashtag) { + final query = type.query; + if (query.isEmpty) { + final response = await ref.read(misskeyProvider(arg)).hashtags.trend(); + state = response.map((hashtag) => hashtag.tag).toList(); + } else { + state = await ref.read( + _hashtagsSearchProvider((arg.host, query)).future, + ); + } + } + } +} + +class HashtagKeyboard extends ConsumerWidget { + const HashtagKeyboard({ + super.key, + required this.account, + required this.controller, + required this.focusNode, + }); + + final Account account; + final TextEditingController controller; + final FocusNode focusNode; + + void insertHashtag(String hashtag) { + final textBeforeSelection = controller.textBeforeSelection; + final lastHashIndex = textBeforeSelection!.lastIndexOf("#"); + final queryLength = textBeforeSelection.length - lastHashIndex - 1; + controller.insert("${hashtag.substring(queryLength)} "); + focusNode.requestFocus(); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final filteredHashtags = ref.watch(_filteredHashtagsProvider(account)); + + if (filteredHashtags.isEmpty) { + return BasicKeyboard( + controller: controller, + focusNode: focusNode, + ); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + for (final hashtag in filteredHashtags) + CustomKeyboardButton( + keyboard: hashtag, + controller: controller, + focusNode: focusNode, + onTap: () => insertHashtag(hashtag), + ), + ], + ); + } +} diff --git a/lib/view/common/note_create/input_completation.dart b/lib/view/common/note_create/input_completation.dart index 0cb23c261..5007f4e50 100644 --- a/lib/view/common/note_create/input_completation.dart +++ b/lib/view/common/note_create/input_completation.dart @@ -3,16 +3,17 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:miria/extensions/text_editing_controller_extension.dart'; -import 'package:miria/model/misskey_emoji_data.dart'; -import 'package:miria/providers.dart'; +import 'package:miria/model/input_completion_type.dart'; import 'package:miria/view/common/account_scope.dart'; -import 'package:miria/view/common/misskey_notes/custom_emoji.dart'; -import 'package:miria/view/common/note_create/custom_keyboard_list.dart'; +import 'package:miria/view/common/note_create/basic_keyboard.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:miria/view/reaction_picker_dialog/reaction_picker_dialog.dart'; +import 'package:miria/view/common/note_create/emoji_keyboard.dart'; +import 'package:miria/view/common/note_create/hashtag_keyboard.dart'; +import 'package:miria/view/common/note_create/mfm_fn_keyboard.dart'; + +final inputCompletionTypeProvider = + StateProvider.autoDispose((ref) => Basic()); -final inputComplementEmojiProvider = - StateProvider.autoDispose((ref) => []); final inputComplementDelayedProvider = Provider((ref) => 300); class InputComplement extends ConsumerStatefulWidget { @@ -32,65 +33,11 @@ class InputComplement extends ConsumerStatefulWidget { class InputComplementState extends ConsumerState { bool isClose = true; - void insertEmoji(MisskeyEmojiData emoji, WidgetRef ref) { - final currentPosition = widget.controller.selection.base.offset; - final text = widget.controller.text; - - final beforeSearchText = - text.substring(0, text.substring(0, currentPosition).lastIndexOf(":")); - - final after = (currentPosition == text.length || currentPosition == -1) - ? "" - : text.substring(currentPosition, text.length); - - switch (emoji) { - case CustomEmojiData(): - widget.controller.value = TextEditingValue( - text: "$beforeSearchText:${emoji.baseName}:$after", - selection: TextSelection.collapsed( - offset: beforeSearchText.length + emoji.baseName.length + 2)); - break; - case UnicodeEmojiData(): - widget.controller.value = TextEditingValue( - text: "$beforeSearchText${emoji.char}$after", - selection: TextSelection.collapsed(offset: emoji.char.length)); - - break; - default: - return; - } - - ref.read(inputComplementEmojiProvider.notifier).state = []; - ref.read(widget.focusNode).requestFocus(); - } - @override void initState() { super.initState(); - widget.controller.addListener(() { - if (widget.controller.isIncludeBeforeColon) { - if (widget.controller.isEmojiScope) { - if (ref.read(inputComplementEmojiProvider).isNotEmpty) { - ref.read(inputComplementEmojiProvider.notifier).state = []; - } - return; - } - - Future(() async { - final initialAccount = AccountScope.of(context); - final searchedEmojis = await (ref - .read(emojiRepositoryProvider(initialAccount)) - .searchEmojis(widget.controller.emojiSearchValue)); - ref.read(inputComplementEmojiProvider.notifier).state = - searchedEmojis; - }); - } else { - if (ref.read(inputComplementEmojiProvider).isNotEmpty) { - ref.read(inputComplementEmojiProvider.notifier).state = []; - } - } - }); + widget.controller.addListener(updateType); } @override @@ -100,22 +47,35 @@ class InputComplementState extends ConsumerState { isClose = !ref.read(widget.focusNode).hasFocus; } + @override + void dispose() { + widget.controller.removeListener(updateType); + + super.dispose(); + } + + void updateType() { + ref.read(inputCompletionTypeProvider.notifier).state = + widget.controller.inputCompletionType; + } + @override Widget build(BuildContext context) { - final filteredInputEmoji = ref.watch(inputComplementEmojiProvider); + final inputCompletionType = ref.watch(inputCompletionTypeProvider); + final focusNode = ref.watch(widget.focusNode); + final account = AccountScope.of(context); - ref.listen(widget.focusNode, (previous, next) { + ref.listen(widget.focusNode, (previous, next) async { if (!next.hasFocus) { - Future(() async { - await Future.delayed( - Duration(milliseconds: ref.read(inputComplementDelayedProvider))); - if (!mounted) return; - if (!ref.read(widget.focusNode).hasFocus) { - setState(() { - isClose = true; - }); - } - }); + await Future.delayed( + Duration(milliseconds: ref.read(inputComplementDelayedProvider)), + ); + if (!mounted) return; + if (!ref.read(widget.focusNode).hasFocus) { + setState(() { + isClose = true; + }); + } } else { setState(() { isClose = false; @@ -139,43 +99,26 @@ class InputComplementState extends ConsumerState { child: ConstrainedBox( constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (filteredInputEmoji.isNotEmpty) ...[ - for (final emoji in filteredInputEmoji) - GestureDetector( - onTap: () => insertEmoji(emoji, ref), - child: Padding( - padding: const EdgeInsets.all(5), - child: SizedBox( - height: 32 * - MediaQuery.of(context).textScaleFactor, - child: CustomEmoji(emojiData: emoji)), - ), - ), - TextButton.icon( - onPressed: () async { - final selected = await showDialog( - context: context, - builder: (context2) => ReactionPickerDialog( - account: AccountScope.of(context), - isAcceptSensitive: true, - )); - if (selected != null) { - insertEmoji(selected, ref); - } - }, - icon: const Icon(Icons.add_reaction_outlined), - label: const Text("他のん")) - ] else - CustomKeyboardList( - controller: widget.controller, - focusNode: ref.read(widget.focusNode), - ), - ]), + child: switch (inputCompletionType) { + Basic() => BasicKeyboard( + controller: widget.controller, + focusNode: focusNode, + ), + Emoji() => EmojiKeyboard( + account: account, + controller: widget.controller, + focusNode: focusNode, + ), + MfmFn() => MfmFnKeyboard( + controller: widget.controller, + focusNode: focusNode, + ), + Hashtag() => HashtagKeyboard( + account: account, + controller: widget.controller, + focusNode: focusNode, + ), + }, ), ), ), diff --git a/lib/view/common/note_create/mfm_fn_keyboard.dart b/lib/view/common/note_create/mfm_fn_keyboard.dart new file mode 100644 index 000000000..652dc6b2b --- /dev/null +++ b/lib/view/common/note_create/mfm_fn_keyboard.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/extensions/text_editing_controller_extension.dart'; +import 'package:miria/model/input_completion_type.dart'; +import 'package:miria/view/common/note_create/basic_keyboard.dart'; +import 'package:miria/view/common/note_create/custom_keyboard_button.dart'; +import 'package:miria/view/common/note_create/input_completation.dart'; + +const mfmFn = [ + "tada", + "jelly", + "twitch", + "shake", + "spin", + "jump", + "bounce", + "flip", + "x2", + "x3", + "x4", + "scale", + "position", + "fg", + "bg", + "font", + "blur", + "rainbow", + // "sparkle", + "rotate", +]; + +final _filteredMfmFnProvider = Provider.autoDispose>((ref) { + final type = ref.watch(inputCompletionTypeProvider); + if (type is MfmFn) { + return mfmFn.where((name) => name.startsWith(type.query)).toList(); + } else { + return mfmFn; + } +}); + +class MfmFnKeyboard extends ConsumerWidget { + const MfmFnKeyboard({ + super.key, + required this.controller, + required this.focusNode, + }); + + final TextEditingController controller; + final FocusNode focusNode; + + void insertMfmFn(String mfmFn) { + final textBeforeSelection = controller.textBeforeSelection; + final lastOpenTagIndex = textBeforeSelection!.lastIndexOf(r"$["); + final queryLength = textBeforeSelection.length - lastOpenTagIndex - 2; + controller.insert("${mfmFn.substring(queryLength)} "); + focusNode.requestFocus(); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final filteredMfmFn = ref.watch(_filteredMfmFnProvider); + + if (filteredMfmFn.isEmpty) { + return BasicKeyboard( + controller: controller, + focusNode: focusNode, + ); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + for (final mfmFn in filteredMfmFn) + CustomKeyboardButton( + keyboard: mfmFn, + controller: controller, + focusNode: focusNode, + onTap: () => insertMfmFn(mfmFn), + ), + ], + ); + } +} diff --git a/lib/view/explore_page/explore_highlight.dart b/lib/view/explore_page/explore_highlight.dart index 8c581feed..2ecb50f5b 100644 --- a/lib/view/explore_page/explore_highlight.dart +++ b/lib/view/explore_page/explore_highlight.dart @@ -64,13 +64,16 @@ class ExploreHighlightState extends ConsumerState { ref.read(notesProvider(account)).registerAll(note); return note.toList(); }, - nextFuture: (_, index) async { + nextFuture: (item, index) async { final Iterable note; if (isNote) { note = await ref .read(misskeyProvider(account)) .notes - .featured(NotesFeaturedRequest(offset: index)); + .featured(NotesFeaturedRequest( + offset: index, + untilId: item.id, + )); } else { note = await ref .read(misskeyProvider(account)) diff --git a/lib/view/note_create_page/drive_file_select_dialog.dart b/lib/view/note_create_page/drive_file_select_dialog.dart index 21d1ba439..d58be56fd 100644 --- a/lib/view/note_create_page/drive_file_select_dialog.dart +++ b/lib/view/note_create_page/drive_file_select_dialog.dart @@ -1,16 +1,20 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/model/account.dart'; import 'package:miria/providers.dart'; import 'package:miria/view/common/misskey_notes/network_image.dart'; import 'package:miria/view/common/pushable_listview.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/view/themes/app_theme.dart'; import 'package:misskey_dart/misskey_dart.dart'; class DriveFileSelectDialog extends ConsumerStatefulWidget { final Account account; + final bool allowMultiple; + const DriveFileSelectDialog({ super.key, required this.account, + this.allowMultiple = false, }); @override @@ -20,23 +24,41 @@ class DriveFileSelectDialog extends ConsumerStatefulWidget { class DriveFileSelectDialogState extends ConsumerState { final List path = []; + final List files = []; @override Widget build(BuildContext context) { return AlertDialog( - title: Row( - mainAxisSize: MainAxisSize.max, - children: [ - if (path.isNotEmpty) - IconButton( - onPressed: () { + title: AppBar( + leading: IconButton( + onPressed: path.isEmpty + ? null + : () { setState(() { path.removeLast(); }); }, - icon: const Icon(Icons.arrow_back)), - Expanded(child: Text(path.map((e) => e.name).join("/"))), + icon: const Icon(Icons.arrow_back), + ), + title: path.isEmpty + ? const Text("ファイルを選択") + : Text(path.map((e) => e.name).join("/")), + actions: [ + if (files.isNotEmpty) + Center( + child: Text( + "(${files.length})", + style: Theme.of(context).textTheme.titleMedium, + ), + ), + if (widget.allowMultiple) + IconButton( + onPressed: + files.isEmpty ? null : () => Navigator.of(context).pop(files), + icon: const Icon(Icons.check), + ), ], + backgroundColor: Colors.transparent, ), content: SizedBox( width: MediaQuery.of(context).size.width * 0.8, @@ -75,51 +97,84 @@ class DriveFileSelectDialogState extends ConsumerState { ); }), PushableListView( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - initializeFuture: () async { - final misskey = ref.read(misskeyProvider(widget.account)); - final response = await misskey.drive.files.files( - DriveFilesRequest( - folderId: path.isEmpty ? null : path.last.id)); - return response.toList(); - }, - nextFuture: (lastItem, _) async { - final misskey = ref.read(misskeyProvider(widget.account)); - final response = await misskey.drive.files.files( - DriveFilesRequest( - untilId: lastItem.id, - folderId: path.isEmpty ? null : path.last.id)); - return response.toList(); - }, - listKey: path.map((e) => e.id).join("/"), - itemBuilder: (context, item) { - return GestureDetector( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + initializeFuture: () async { + final misskey = ref.read(misskeyProvider(widget.account)); + final response = await misskey.drive.files.files( + DriveFilesRequest( + folderId: path.isEmpty ? null : path.last.id, + ), + ); + return response.toList(); + }, + nextFuture: (lastItem, _) async { + final misskey = ref.read(misskeyProvider(widget.account)); + final response = await misskey.drive.files.files( + DriveFilesRequest( + untilId: lastItem.id, + folderId: path.isEmpty ? null : path.last.id, + ), + ); + return response.toList(); + }, + listKey: path.map((e) => e.id).join("/"), + itemBuilder: (context, item) { + final isSelected = files.any((file) => file.id == item.id); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: InkWell( + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), onTap: () { - Navigator.of(context).pop(item); + if (widget.allowMultiple) { + setState(() { + if (isSelected) { + files.removeWhere((file) => file.id == item.id); + } else { + files.add(item); + } + }); + } else { + Navigator.of(context).pop(item); + } }, - child: Padding( + child: Container( padding: const EdgeInsets.all(10), + decoration: (widget.allowMultiple && isSelected) + ? BoxDecoration( + color: AppTheme.of(context) + .currentDisplayTabColor + .withOpacity(0.7), + borderRadius: BorderRadius.circular(5), + ) + : null, child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( - width: double.infinity, - height: 200, - child: item.thumbnailUrl == null - ? Container() - : NetworkImageView( + width: double.infinity, + height: 200, + child: item.thumbnailUrl == null + ? const SizedBox.shrink() + : ClipRRect( + borderRadius: BorderRadius.circular(5), + child: NetworkImageView( fit: BoxFit.cover, url: item.thumbnailUrl!, - type: ImageType.imageThumbnail)), + type: ImageType.imageThumbnail, + ), + ), + ), Text(item.name), ], ), ), - ); - }), + ), + ); + }, + ), ], ), ), diff --git a/lib/view/note_create_page/file_settings_dialog.dart b/lib/view/note_create_page/file_settings_dialog.dart index d43a10d1d..01eb59f8d 100644 --- a/lib/view/note_create_page/file_settings_dialog.dart +++ b/lib/view/note_create_page/file_settings_dialog.dart @@ -22,21 +22,22 @@ class FileSettingsDialog extends ConsumerStatefulWidget { } class FileSettingsDialogState extends ConsumerState { - final fileNameController = TextEditingController(); + late final TextEditingController fileNameController; + late final TextEditingController captionController; bool isNsfw = false; - final captionController = TextEditingController(); @override void initState() { super.initState(); - fileNameController.text = widget.file.fileName; - captionController.text = widget.file.caption; + fileNameController = TextEditingController(text: widget.file.fileName); + captionController = TextEditingController(text: widget.file.caption); isNsfw = widget.file.isNsfw; } @override void dispose() { + fileNameController.dispose(); captionController.dispose(); super.dispose(); } diff --git a/lib/view/note_create_page/note_create_page.dart b/lib/view/note_create_page/note_create_page.dart index bfdff0aaa..d3a2365d8 100644 --- a/lib/view/note_create_page/note_create_page.dart +++ b/lib/view/note_create_page/note_create_page.dart @@ -35,6 +35,8 @@ final noteInputTextProvider = final noteFocusProvider = ChangeNotifierProvider.autoDispose((ref) => FocusNode()); +enum NoteCreationMode { update, recreate } + @RoutePage() class NoteCreatePage extends ConsumerStatefulWidget { final Account initialAccount; @@ -43,7 +45,8 @@ class NoteCreatePage extends ConsumerStatefulWidget { final CommunityChannel? channel; final Note? reply; final Note? renote; - final Note? deletedNote; + final Note? note; + final NoteCreationMode? noteCreationMode; const NoteCreatePage({ super.key, @@ -53,7 +56,8 @@ class NoteCreatePage extends ConsumerStatefulWidget { this.channel, this.reply, this.renote, - this.deletedNote, + this.note, + this.noteCreationMode, }); @override @@ -80,12 +84,14 @@ class NoteCreatePageState extends ConsumerState { isFirstChangeDependenciesCalled = true; Future(() async { notifier.initialize( - widget.channel, - widget.initialText, - widget.initialMediaFiles, - widget.deletedNote, - widget.renote, - widget.reply); + widget.channel, + widget.initialText, + widget.initialMediaFiles, + widget.note, + widget.renote, + widget.reply, + widget.noteCreationMode, + ); ref.read(noteInputTextProvider).addListener(() { notifier.setContentText(ref.read(noteInputTextProvider).text); @@ -156,7 +162,10 @@ class NoteCreatePageState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - const NoteCreateSettingTop(), + if (widget.noteCreationMode != NoteCreationMode.update) + const NoteCreateSettingTop() + else + const Padding(padding: EdgeInsets.only(top: 30)), const ChannelArea(), const ReplyArea(), const ReplyToArea(), @@ -172,23 +181,30 @@ class NoteCreatePageState extends ConsumerState { ), Row( children: [ - IconButton( - onPressed: () async => - await notifier.chooseFile(context), - icon: const Icon(Icons.image)), - IconButton( - onPressed: () { - ref - .read(noteCreateProvider( - widget.initialAccount) - .notifier) - .toggleVote(); - }, - icon: const Icon(Icons.how_to_vote)), + if (widget.noteCreationMode != + NoteCreationMode.update) ...[ + IconButton( + onPressed: () async => + await notifier.chooseFile(context), + icon: const Icon(Icons.image)), + if (widget.noteCreationMode != + NoteCreationMode.update) + IconButton( + onPressed: () { + ref + .read(noteCreateProvider( + widget.initialAccount) + .notifier) + .toggleVote(); + }, + icon: const Icon(Icons.how_to_vote)), + ], const CwToggleButton(), - IconButton( - onPressed: () => notifier.addReplyUser(context), - icon: const Icon(Icons.mail_outline)), + if (widget.noteCreationMode != + NoteCreationMode.update) + IconButton( + onPressed: () => notifier.addReplyUser(context), + icon: const Icon(Icons.mail_outline)), IconButton( onPressed: () async { final selectedEmoji = @@ -220,9 +236,15 @@ class NoteCreatePageState extends ConsumerState { ], ), const MfmPreview(), - const FilePreview(), + if (widget.noteCreationMode != NoteCreationMode.update) + const FilePreview() + else if (widget.note?.files.isNotEmpty == true) + const Text("メディアがあります(編集はできません)"), const RenoteArea(), - const VoteArea(), + if (widget.noteCreationMode != NoteCreationMode.update) + const VoteArea() + else if (widget.note?.poll != null) + const Text("投票があります(編集はできません)"), ], ), ), diff --git a/lib/view/note_create_page/reaction_acceptance_dialog.dart b/lib/view/note_create_page/reaction_acceptance_dialog.dart index 8f81b5eff..12f1752ef 100644 --- a/lib/view/note_create_page/reaction_acceptance_dialog.dart +++ b/lib/view/note_create_page/reaction_acceptance_dialog.dart @@ -35,14 +35,12 @@ class ReactionAcceptanceDialog extends StatelessWidget { Navigator.of(context).pop(ReactionAcceptance.nonSensitiveOnly), leading: const Icon(Icons.shield_outlined), title: const Text("非センシティブのみ"), - subtitle: const Text("Misskey v13.13.1からの機能です。サーバーによっては使用できないことがあります。"), ), ListTile( onTap: () => Navigator.of(context).pop( ReactionAcceptance.nonSensitiveOnlyForLocalLikeOnlyForRemote), leading: const Icon(Icons.add_moderator_outlined), title: const Text("非センシティブのみ(リモートからはいいねのみ)"), - subtitle: const Text("Misskey v13.13.1からの機能です。サーバーによっては使用できないことがあります。"), ) ], ); diff --git a/lib/view/notification_page/notification_page.dart b/lib/view/notification_page/notification_page.dart index 9ca6704b2..a25d5f2e6 100644 --- a/lib/view/notification_page/notification_page.dart +++ b/lib/view/notification_page/notification_page.dart @@ -341,6 +341,23 @@ class NotificationItem extends ConsumerWidget { ], ), ); + case NoteNotification(): + return Padding( + padding: + const EdgeInsets.only(left: 10, top: 10, bottom: 10, right: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (notification.note?.user != null) + SimpleMfmText( + "${notification.note?.user.name ?? notification.note?.user.username}さんがノートしはったで", + emojis: notification.note?.user.emojis ?? {}, + ), + if (notification.note != null) + misskey_note.MisskeyNote(note: notification.note!) + ], + ), + ); } } diff --git a/lib/view/notification_page/notification_page_data.dart b/lib/view/notification_page/notification_page_data.dart index e179727f5..20b880ada 100644 --- a/lib/view/notification_page/notification_page_data.dart +++ b/lib/view/notification_page/notification_page_data.dart @@ -84,6 +84,15 @@ class PollNotification extends NotificationData { }); } +class NoteNotification extends NotificationData { + final Note? note; + NoteNotification({ + required this.note, + required super.createdAt, + required super.id, + }); +} + extension INotificationsResponseExtension on Iterable { List toNotificationData() { final resultList = []; @@ -206,6 +215,12 @@ extension INotificationsResponseExtension on Iterable { resultList.add(SimpleNotificationData( text: "テストやで", createdAt: element.createdAt, id: element.id)); + case NotificationType.note: + resultList.add(NoteNotification( + note: element.note, + createdAt: element.createdAt, + id: element.id)); + default: break; } diff --git a/lib/view/settings_page/import_export_page/folder_select_dialog.dart b/lib/view/settings_page/import_export_page/folder_select_dialog.dart index 284b1214a..39871bc9d 100644 --- a/lib/view/settings_page/import_export_page/folder_select_dialog.dart +++ b/lib/view/settings_page/import_export_page/folder_select_dialog.dart @@ -6,14 +6,22 @@ import 'package:miria/view/common/futable_list_builder.dart'; import 'package:miria/view/common/pushable_listview.dart'; import 'package:misskey_dart/misskey_dart.dart'; +class FolderResult { + const FolderResult(this.folder); + + final DriveFolder? folder; +} + class FolderSelectDialog extends ConsumerStatefulWidget { final Account account; - final String? fileShowTarget; + final List? fileShowTarget; + final String confirmationText; const FolderSelectDialog({ super.key, required this.account, required this.fileShowTarget, + required this.confirmationText, }); @override @@ -29,17 +37,18 @@ class FolderSelectDialogState extends ConsumerState { return AlertDialog( title: Column( children: [ - const Text("フォルダ選択"), + const Text("フォルダー選択"), Row( children: [ if (path.isNotEmpty) IconButton( - onPressed: () { - setState(() { - path.removeLast(); - }); - }, - icon: const Icon(Icons.arrow_back)), + onPressed: () { + setState(() { + path.removeLast(); + }); + }, + icon: const Icon(Icons.arrow_back), + ), Expanded(child: Text(path.map((e) => e.name).join("/"))), ], ) @@ -52,52 +61,65 @@ class FolderSelectDialogState extends ConsumerState { child: Column( children: [ PushableListView( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - initializeFuture: () async { - final misskey = ref.read(misskeyProvider(widget.account)); - final response = await misskey.drive.folders.folders( - DriveFoldersRequest( - folderId: path.isEmpty ? null : path.last.id)); - return response.toList(); - }, - nextFuture: (lastItem, _) async { - final misskey = ref.read(misskeyProvider(widget.account)); - final response = await misskey.drive.folders.folders( - DriveFoldersRequest( - untilId: lastItem.id, - folderId: path.isEmpty ? null : path.last.id)); - return response.toList(); - }, - listKey: path.map((e) => e.id).join("/"), - itemBuilder: (context, item) { - return ListTile( - leading: const Icon(Icons.folder), - title: Text(item.name ?? ""), - onTap: () { - setState(() { - path.add(item); - }); - }, - ); - }), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + initializeFuture: () async { + final misskey = ref.read(misskeyProvider(widget.account)); + final response = await misskey.drive.folders.folders( + DriveFoldersRequest( + folderId: path.isEmpty ? null : path.last.id, + ), + ); + return response.toList(); + }, + nextFuture: (lastItem, _) async { + final misskey = ref.read(misskeyProvider(widget.account)); + final response = await misskey.drive.folders.folders( + DriveFoldersRequest( + untilId: lastItem.id, + folderId: path.isEmpty ? null : path.last.id, + ), + ); + return response.toList(); + }, + listKey: path.map((e) => e.id).join("/"), + itemBuilder: (context, item) { + return ListTile( + leading: const Icon(Icons.folder), + title: Text(item.name ?? ""), + onTap: () { + setState(() { + path.add(item); + }); + }, + ); + }, + ), if (widget.fileShowTarget != null) FutureListView( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), future: () async { - final list = await ref - .read(misskeyProvider(widget.account)) - .drive - .files - .find(DriveFilesFindRequest( - folderId: path.lastOrNull?.id, - name: widget.fileShowTarget!)); + final list = []; + for (final element in widget.fileShowTarget!) { + list.addAll( + await ref + .read(misskeyProvider(widget.account)) + .drive + .files + .find( + DriveFilesFindRequest( + folderId: path.lastOrNull?.id, + name: element, + ), + ), + ); + } return list.toList(); }(), builder: (context, item) => Row( children: [ - Icon(Icons.description), + const Icon(Icons.description), Expanded(child: Text(item.name)), ], ), @@ -108,10 +130,11 @@ class FolderSelectDialogState extends ConsumerState { ), actions: [ ElevatedButton( - onPressed: () { - Navigator.of(context).pop(path.lastOrNull); - }, - child: const Text("このフォルダーに保存する")) + onPressed: () { + Navigator.of(context).pop(FolderResult(path.lastOrNull)); + }, + child: Text(widget.confirmationText), + ) ], ); } diff --git a/lib/view/settings_page/import_export_page/import_export_page.dart b/lib/view/settings_page/import_export_page/import_export_page.dart index d20114f08..c3b9e9755 100644 --- a/lib/view/settings_page/import_export_page/import_export_page.dart +++ b/lib/view/settings_page/import_export_page/import_export_page.dart @@ -25,95 +25,98 @@ class ImportExportPageState extends ConsumerState { body: Padding( padding: const EdgeInsets.only(left: 10, right: 10), child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "設定のインポート", - style: Theme.of(context).textTheme.titleLarge, - ), - const Text( - "設定ファイルをドライブから読み込みます。設定ファイルには保存されたときのすべてのアカウントの設定情報が記録されていますが、そのうちこの端末でログインしているアカウントの情報のみを読み込みます。"), - Row( - children: [ - Expanded( - child: DropdownButton( - isExpanded: true, - items: [ - for (final account - in ref.read(accountRepository).account) - DropdownMenuItem( - value: account, - child: Text("@${account.userId}@${account.host}"), - ), - ], - value: selectedImportAccount, - onChanged: (Account? value) { - setState(() { - selectedImportAccount = value; - }); - }, - ), + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "設定のインポート", + style: Theme.of(context).textTheme.titleLarge, + ), + const Text( + "設定ファイルをドライブから読み込みます。設定ファイルには保存されたときのすべてのアカウントの設定情報が記録されていますが、そのうちこの端末でログインしているアカウントの情報のみを読み込みます。", + ), + Row( + children: [ + Expanded( + child: DropdownButton( + isExpanded: true, + items: [ + for (final account in ref.read(accountRepository).account) + DropdownMenuItem( + value: account, + child: Text("@${account.userId}@${account.host}"), + ), + ], + value: selectedImportAccount, + onChanged: (Account? value) { + setState(() { + selectedImportAccount = value; + }); + }, ), - ElevatedButton( - onPressed: () async { - final account = selectedImportAccount; - if (account == null) { - await SimpleMessageDialog.show(context, "アカウントを選んでや"); - return; - } - await ref - .read(importExportRepository) - .import(context, account); - }, - child: const Text("設定ファイル選択")), - ], - ), - const Padding(padding: EdgeInsets.only(top: 30)), - Text( - "設定のエクスポート", - style: Theme.of(context).textTheme.titleLarge, - ), - const Text( - "設定ファイルをドライブに保存します。設定ファイルにはこの端末でログインしているすべてのアカウントの、ログイン情報以外の情報が記録されます。"), - const Text("設定ファイルは1回のエクスポートにつき1つのアカウントに対して保存します。"), - Row( - children: [ - Expanded( - child: DropdownButton( - isExpanded: true, - items: [ - for (final account - in ref.read(accountRepository).account) - DropdownMenuItem( - value: account, - child: Text("@${account.userId}@${account.host}"), - ), - ], - value: selectedExportAccount, - onChanged: (Account? value) { - setState(() { - selectedExportAccount = value; - }); - }, - ), + ), + ElevatedButton( + onPressed: () async { + final account = selectedImportAccount; + if (account == null) { + await SimpleMessageDialog.show(context, "アカウントを選んでや"); + return; + } + await ref + .read(importExportRepository) + .import(context, account); + }, + child: const Text("選択"), + ), + ], + ), + const Padding(padding: EdgeInsets.only(top: 30)), + Text( + "設定のエクスポート", + style: Theme.of(context).textTheme.titleLarge, + ), + const Text( + "設定ファイルをドライブに保存します。設定ファイルにはこの端末でログインしているすべてのアカウントの、ログイン情報以外の情報が記録されます。", + ), + const Text("設定ファイルは1回のエクスポートにつき1つのアカウントに対して保存します。"), + Row( + children: [ + Expanded( + child: DropdownButton( + isExpanded: true, + items: [ + for (final account in ref.read(accountRepository).account) + DropdownMenuItem( + value: account, + child: Text("@${account.userId}@${account.host}"), + ), + ], + value: selectedExportAccount, + onChanged: (Account? value) { + setState(() { + selectedExportAccount = value; + }); + }, ), - ElevatedButton( - onPressed: () { - final account = selectedExportAccount; - if (account == null) { - SimpleMessageDialog.show( - context, "設定ファイルを保存するアカウントを選んでや"); - return; - } - ref - .read(importExportRepository) - .export(context, account); - }, - child: const Text("保存")), - ], - ), - ]), + ), + ElevatedButton( + onPressed: () { + final account = selectedExportAccount; + if (account == null) { + SimpleMessageDialog.show( + context, + "設定ファイルを保存するアカウントを選んでや", + ); + return; + } + ref.read(importExportRepository).export(context, account); + }, + child: const Text("保存"), + ), + ], + ), + ], + ), ), ); } diff --git a/lib/view/settings_page/tab_settings_page/icon_select_dialog.dart b/lib/view/settings_page/tab_settings_page/icon_select_dialog.dart index 3903690a5..ac0c264b7 100644 --- a/lib/view/settings_page/tab_settings_page/icon_select_dialog.dart +++ b/lib/view/settings_page/tab_settings_page/icon_select_dialog.dart @@ -18,6 +18,8 @@ class IconSelectDialog extends StatelessWidget { Icons.rocket_outlined, Icons.rocket_launch, Icons.rocket_launch_outlined, + Icons.bookmark, + Icons.bookmark_outline, Icons.hub, Icons.hub_outlined, Icons.settings_input_antenna, diff --git a/lib/view/settings_page/tab_settings_page/role_select_dialog.dart b/lib/view/settings_page/tab_settings_page/role_select_dialog.dart new file mode 100644 index 000000000..5c51360d0 --- /dev/null +++ b/lib/view/settings_page/tab_settings_page/role_select_dialog.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:miria/model/account.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/view/common/account_scope.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +class RoleSelectDialog extends ConsumerStatefulWidget { + final Account account; + + const RoleSelectDialog({super.key, required this.account}); + + @override + ConsumerState createState() => + RoleSelectDialogState(); +} + +class RoleSelectDialogState extends ConsumerState { + final roles = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + Future(() async { + final rolesList = await ref + .read(misskeyProvider(widget.account)) + .roles + .list(); + roles + ..clear() + ..addAll(rolesList); + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return AccountScope( + account: widget.account, + child: AlertDialog( + title: const Text("ロール選択"), + content: SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + height: MediaQuery.of(context).size.height * 0.8, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "ロール", + style: Theme.of(context).textTheme.titleMedium, + ), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: roles.length, + itemBuilder: (context, index) { + return ListTile( + onTap: () { + Navigator.of(context).pop(roles[index]); + }, + title: Text(roles[index].name)); + }), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/view/settings_page/tab_settings_page/tab_settings_page.dart b/lib/view/settings_page/tab_settings_page/tab_settings_page.dart index c179a7ea4..ebf8cb18c 100644 --- a/lib/view/settings_page/tab_settings_page/tab_settings_page.dart +++ b/lib/view/settings_page/tab_settings_page/tab_settings_page.dart @@ -1,5 +1,6 @@ import 'package:auto_route/annotations.dart'; import 'package:flutter/material.dart'; +import 'package:miria/extensions/users_lists_show_response_extension.dart'; import 'package:miria/model/account.dart'; import 'package:miria/model/tab_icon.dart'; import 'package:miria/model/tab_setting.dart'; @@ -8,6 +9,7 @@ import 'package:miria/providers.dart'; import 'package:miria/view/common/account_scope.dart'; import 'package:miria/view/dialogs/simple_message_dialog.dart'; import 'package:miria/view/common/tab_icon_view.dart'; +import 'package:miria/view/settings_page/tab_settings_page/role_select_dialog.dart'; import 'package:miria/view/settings_page/tab_settings_page/antenna_select_dialog.dart'; import 'package:miria/view/settings_page/tab_settings_page/channel_select_dialog.dart'; import 'package:miria/view/settings_page/tab_settings_page/icon_select_dialog.dart'; @@ -29,6 +31,7 @@ class TabSettingsPage extends ConsumerStatefulWidget { class TabSettingsAddDialogState extends ConsumerState { late Account? selectedAccount = ref.read(accountRepository).account.first; TabType? selectedTabType = TabType.localTimeline; + RolesListResponse? selectedRole; CommunityChannel? selectedChannel; UsersList? selectedUserList; Antenna? selectedAntenna; @@ -47,6 +50,7 @@ class TabSettingsAddDialogState extends ConsumerState { ref.read(tabSettingsRepositoryProvider).tabSettings.toList()[tab]; selectedAccount = tabSetting.account; selectedTabType = tabSetting.tabType; + final roleId = tabSetting.roleId; final channelId = tabSetting.channelId; final listId = tabSetting.listId; final antennaId = tabSetting.antennaId; @@ -54,6 +58,15 @@ class TabSettingsAddDialogState extends ConsumerState { selectedIcon = tabSetting.icon; renoteDisplay = tabSetting.renoteDisplay; isSubscribe = tabSetting.isSubscribe; + if (roleId != null) { + Future(() async { + selectedRole = await ref + .read(misskeyProvider(tabSetting.account)) + .roles + .show(RolesShowRequest(roleId: roleId)); + setState(() {}); + }); + } if (channelId != null) { Future(() async { selectedChannel = await ref @@ -65,11 +78,12 @@ class TabSettingsAddDialogState extends ConsumerState { } if (listId != null) { Future(() async { - selectedUserList = await ref + final response = await ref .read(misskeyProvider(tabSetting.account)) .users .list .show(UsersListsShowRequest(listId: listId)); + selectedUserList = response.toUsersList(); setState(() {}); }); } @@ -160,6 +174,29 @@ class TabSettingsAddDialogState extends ConsumerState { value: selectedTabType, ), const Padding(padding: EdgeInsets.all(10)), + if (selectedTabType == TabType.roleTimeline) ...[ + const Text("ロールタイムライン"), + Row( + children: [ + Expanded(child: Text(selectedRole?.name ?? "")), + IconButton( + onPressed: () async { + final selected = selectedAccount; + if (selected == null) return; + + selectedRole = await showDialog( + context: context, + builder: (context) => + RoleSelectDialog(account: selected)); + setState(() { + nameController.text = + selectedRole?.name ?? nameController.text; + }); + }, + icon: const Icon(Icons.navigate_next)) + ], + ) + ], if (selectedTabType == TabType.channel) ...[ const Text("チャンネル"), Row( @@ -323,6 +360,7 @@ class TabSettingsAddDialogState extends ConsumerState { tabType: tabType, name: nameController.text, account: account, + roleId: selectedRole?.id, channelId: selectedChannel?.id, listId: selectedUserList?.id, antennaId: selectedAntenna?.id, diff --git a/lib/view/several_account_settings_page/hard_mute_page/hard_mute_page.dart b/lib/view/several_account_settings_page/hard_mute_page/hard_mute_page.dart index f46cd0be9..82c44f402 100644 --- a/lib/view/several_account_settings_page/hard_mute_page/hard_mute_page.dart +++ b/lib/view/several_account_settings_page/hard_mute_page/hard_mute_page.dart @@ -64,7 +64,7 @@ class HardMutePageState extends ConsumerState { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text("ハードミュート")), + appBar: AppBar(title: const Text("ワードミュート")), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(10), @@ -76,12 +76,6 @@ class HardMutePageState extends ConsumerState { complete: (context, data) { return Column( children: [ - const Card( - child: Padding( - padding: EdgeInsets.all(10), - child: Text( - "指定した条件のノートをタイムラインに追加しないようにします。追加されなかったノートは、条件を変更しても除外されたままになります。反映されるまでに時間がかかる場合があります。"), - )), const Padding( padding: EdgeInsets.only(top: 10), ), @@ -92,7 +86,7 @@ class HardMutePageState extends ConsumerState { autofocus: true, ), Text( - "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。\nキーワードをスラッシュで囲むと正規表現になります。", + "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。\nキーワードをスラッシュで囲むと正規表現になります。\nただし、Misskey Webと正規表現の仕様が異なるため、Miriaで動作する正規表現がMisskey Webで動作しなかったり、その逆が発生することがあります。", style: Theme.of(context).textTheme.bodySmall, ), ElevatedButton.icon( diff --git a/lib/view/several_account_settings_page/several_account_settings_page.dart b/lib/view/several_account_settings_page/several_account_settings_page.dart index 5b35c879a..7f561d116 100644 --- a/lib/view/several_account_settings_page/several_account_settings_page.dart +++ b/lib/view/several_account_settings_page/several_account_settings_page.dart @@ -33,7 +33,7 @@ class SeveralAccountSettingsPage extends StatelessWidget { onTap: () { context.pushRoute(HardMuteRoute(account: account)); }, - title: const Text("ハードミュート"), + title: const Text("ワードミュート"), ), ListTile( onTap: () { diff --git a/lib/view/themes/app_theme_scope.dart b/lib/view/themes/app_theme_scope.dart index f7af6c359..69fed19fc 100644 --- a/lib/view/themes/app_theme_scope.dart +++ b/lib/view/themes/app_theme_scope.dart @@ -106,7 +106,7 @@ class AppThemeScopeState extends ConsumerState { fontFamilyFallback: ["Segoe UI Emoji", "Noto Color Emoji", "Meiryo"]); } if (defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.linux) { + defaultTargetPlatform == TargetPlatform.linux) { return const TextStyle( fontFamily: "Noto Color Emoji", fontFamilyFallback: ["Noto Color Emoji", "Noto Sans JP"]); @@ -222,8 +222,19 @@ class AppThemeScopeState extends ConsumerState { suffixIconColor: theme.primary, isDense: true, ), - checkboxTheme: - CheckboxThemeData(fillColor: MaterialStatePropertyAll(theme.primary)), + checkboxTheme: CheckboxThemeData( + fillColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return theme.primary; + } + return null; + }, + ), + ), expansionTileTheme: ExpansionTileThemeData(iconColor: theme.primary), toggleButtonsTheme: ToggleButtonsThemeData( color: theme.primary, diff --git a/lib/view/time_line_page/time_line_page.dart b/lib/view/time_line_page/time_line_page.dart index a7701876b..abf2ddfed 100644 --- a/lib/view/time_line_page/time_line_page.dart +++ b/lib/view/time_line_page/time_line_page.dart @@ -250,6 +250,18 @@ class TimeLinePageState extends ConsumerState { }, icon: const Icon(Icons.info_outline), ) + else if (currentTabSetting.tabType == TabType.userList) + IconButton( + icon: const Icon(Icons.info_outline), + onPressed: () { + context.pushRoute( + UsersListDetailRoute( + account: currentTabSetting.account, + listId: currentTabSetting.listId!, + ), + ); + }, + ) else if ([ TabType.hybridTimeline, TabType.localTimeline, @@ -279,7 +291,10 @@ class TimeLinePageState extends ConsumerState { .timelineProvider(currentTabSetting), ) .reconnect(), - icon: const Icon(Icons.refresh), + icon: + socketTimeline != null && socketTimeline.isReconnecting + ? CircularProgressIndicator() + : const Icon(Icons.refresh), ) ], ), diff --git a/lib/view/user_page/user_control_dialog.dart b/lib/view/user_page/user_control_dialog.dart index 5898eb9be..6626ab330 100644 --- a/lib/view/user_page/user_control_dialog.dart +++ b/lib/view/user_page/user_control_dialog.dart @@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/extensions/users_show_response_extension.dart'; import 'package:miria/model/account.dart'; import 'package:miria/providers.dart'; -import 'package:miria/view/common/error_notification.dart'; +import 'package:miria/view/common/error_dialog_handler.dart'; import 'package:miria/view/common/futurable.dart'; import 'package:miria/view/dialogs/simple_confirm_dialog.dart'; import 'package:misskey_dart/misskey_dart.dart'; @@ -51,6 +51,20 @@ class UserControlDialogState extends ConsumerState { ); } + Future addToAntenna() async { + return showModalBottomSheet( + context: context, + builder: (context) => CommonFuture>( + future: ref.read(misskeyProvider(widget.account)).antennas.list(), + complete: (context, antennas) => AntennaControlDialog( + account: widget.account, + antennas: antennas.toList(), + acct: widget.response.acct, + ), + ), + ); + } + Future getExpire() async { return await showDialog( context: context, builder: (context) => const ExpireSelectDialog()); @@ -184,6 +198,10 @@ class UserControlDialogState extends ConsumerState { onTap: addToList, title: const Text("リストに追加"), ), + ListTile( + onTap: addToAntenna, + title: const Text("アンテナに追加"), + ), if (!widget.isMe) ...[ if (widget.response.isRenoteMuted ?? false) ListTile( @@ -220,26 +238,6 @@ class UserControlDialogState extends ConsumerState { } } -class ExpireSelectDialog extends StatefulWidget { - const ExpireSelectDialog({super.key}); - - @override - State createState() => ExpireSelectDialogState(); -} - -enum Expire { - indefinite(null, "無期限"), - minutes_10(Duration(minutes: 10), "10分間"), - hours_1(Duration(hours: 1), "1時間"), - day_1(Duration(days: 1), "1日"), - week_1(Duration(days: 7), "1週間"); - - final Duration? expires; - final String name; - - const Expire(this.expires, this.name); -} - class UserListControlDialog extends ConsumerStatefulWidget { final Account account; final List userLists; @@ -268,65 +266,165 @@ class _UserListControlDialogState extends ConsumerState { .toList(); } + Future pushTo(int index) async { + await ref.read(misskeyProvider(widget.account)).users.list.push( + UsersListsPushRequest( + listId: widget.userLists[index].id, + userId: widget.userId, + ), + ); + setState(() { + isUserInList[index] = true; + }); + } + + Future pullFrom(int index) async { + await ref.read(misskeyProvider(widget.account)).users.list.pull( + UsersListsPullRequest( + listId: widget.userLists[index].id, + userId: widget.userId, + ), + ); + setState(() { + isUserInList[index] = false; + }); + } + @override Widget build(BuildContext context) { return ListView.builder( itemCount: widget.userLists.length, itemBuilder: (context, i) { - final userList = widget.userLists[i]; - return CheckboxListTile( value: isUserInList[i], onChanged: (value) async { if (value == null) { return; } - try { - if (value) { - await ref - .read(misskeyProvider(widget.account)) - .users - .list - .push(UsersListsPushRequest( - listId: userList.id, - userId: widget.userId, - )); - } else { - await ref - .read(misskeyProvider(widget.account)) - .users - .list - .pull(UsersListsPullRequest( - listId: userList.id, - userId: widget.userId, - )); - } - } catch (e, s) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("エラー"), - content: ErrorNotification( - error: e, - stackTrace: s, - ), - ), - ); - return; + if (value) { + await pushTo(i).expectFailure(context); + } else { + await pullFrom(i).expectFailure(context); } - - setState(() { - isUserInList[i] = value; - }); }, title: Text(widget.userLists[i].name!), ); }, - shrinkWrap: true, ); } } +class AntennaControlDialog extends ConsumerStatefulWidget { + const AntennaControlDialog({ + super.key, + required this.account, + required this.antennas, + required this.acct, + }); + + final Account account; + final List antennas; + final String acct; + + @override + ConsumerState createState() => + _AntennaControlDialogState(); +} + +class _AntennaControlDialogState extends ConsumerState { + late final List userAntennas; + late List isUserInAntenna; + + @override + void initState() { + super.initState(); + userAntennas = widget.antennas + .where((antenna) => antenna.src == AntennaSource.users) + .toList(); + isUserInAntenna = userAntennas + .map((userAntenna) => userAntenna.users.contains(widget.acct)) + .toList(); + } + + Future updateUsers(Antenna antenna, List users) async { + await ref.read(misskeyProvider(widget.account)).antennas.update( + AntennasUpdateRequest( + antennaId: antenna.id, + name: antenna.name, + src: antenna.src, + keywords: antenna.keywords, + excludeKeywords: antenna.excludeKeywords, + users: users, + caseSensitive: antenna.caseSensitive, + withReplies: antenna.withReplies, + withFile: antenna.withFile, + notify: antenna.notify, + ), + ); + } + + Future pushTo(int index) async { + final antenna = userAntennas[index]; + final users = [...antenna.users, widget.acct]; + await updateUsers(antenna, users); + setState(() { + isUserInAntenna[index] = true; + }); + } + + Future pullFrom(int index) async { + final antenna = userAntennas[index]; + final users = antenna.users.where((user) => user != widget.acct).toList(); + await updateUsers(antenna, users); + setState(() { + isUserInAntenna[index] = false; + }); + } + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: userAntennas.length, + itemBuilder: (context, i) { + return CheckboxListTile( + value: isUserInAntenna[i], + onChanged: (value) async { + if (value == null) { + return; + } + if (value) { + await pushTo(i).expectFailure(context); + } else { + await pullFrom(i).expectFailure(context); + } + }, + title: Text(userAntennas[i].name), + ); + }, + ); + } +} + +class ExpireSelectDialog extends StatefulWidget { + const ExpireSelectDialog({super.key}); + + @override + State createState() => ExpireSelectDialogState(); +} + +enum Expire { + indefinite(null, "無期限"), + minutes_10(Duration(minutes: 10), "10分間"), + hours_1(Duration(hours: 1), "1時間"), + day_1(Duration(days: 1), "1日"), + week_1(Duration(days: 7), "1週間"); + + final Duration? expires; + final String name; + + const Expire(this.expires, this.name); +} + class ExpireSelectDialogState extends State { Expire? selectedExpire = Expire.indefinite; diff --git a/lib/view/user_page/user_notes.dart b/lib/view/user_page/user_notes.dart index a36efd44d..46314de2a 100644 --- a/lib/view/user_page/user_notes.dart +++ b/lib/view/user_page/user_notes.dart @@ -10,11 +10,15 @@ import 'package:misskey_dart/misskey_dart.dart'; class UserNotes extends ConsumerStatefulWidget { final String userId; - + final String? remoteUserId; final Account? actualAccount; - const UserNotes( - {super.key, required this.userId, required this.actualAccount}); + const UserNotes({ + super.key, + required this.userId, + this.remoteUserId, + this.actualAccount, + }) : assert((remoteUserId == null) == (actualAccount == null)); @override ConsumerState createState() => UserNotesState(); @@ -26,6 +30,7 @@ class UserNotesState extends ConsumerState { bool isFileOnly = false; bool withReply = false; bool renote = true; + bool highlight = false; DateTime? untilDate; @override @@ -43,105 +48,152 @@ class UserNotesState extends ConsumerState { children: [ Expanded( child: Center( - child: ToggleButtons( - isSelected: [ - withReply, - isFileOnly, - renote, - ], - onPressed: (value) { - setState(() { - switch (value) { - case 0: - withReply = !withReply; - case 1: - isFileOnly = !isFileOnly; - case 2: - renote = !renote; - } - }); - }, - children: const [ - Padding( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ToggleButtons( + isSelected: [withReply, isFileOnly, renote, highlight], + onPressed: (value) { + setState(() { + switch (value) { + case 0: + withReply = !withReply; + highlight = false; + case 1: + isFileOnly = !isFileOnly; + highlight = false; + case 2: + renote = !renote; + highlight = false; + case 3: + withReply = false; + isFileOnly = false; + renote = false; + highlight = true; + } + }); + }, + children: const [ + Padding( + padding: EdgeInsets.only(left: 5, right: 5), + child: Text("返信つき"), + ), + Padding( padding: EdgeInsets.only(left: 5, right: 5), - child: Text("返信つき")), - Padding( + child: Text("ファイルつき"), + ), + Padding( padding: EdgeInsets.only(left: 5, right: 5), - child: Text("ファイルつき")), - Padding( + child: Text("リノートも"), + ), + Padding( padding: EdgeInsets.only(left: 5, right: 5), - child: Text("リノートも")) - ], + child: Text("ハイライト"), + ) + ], + ), ), ), ), IconButton( - onPressed: () async { - final firstDate = widget.actualAccount == null - ? ref - .read(userInfoProvider(widget.userId)) - ?.response - ?.createdAt - : ref - .read(userInfoProvider(widget.userId)) - ?.remoteResponse - ?.createdAt; + onPressed: () async { + final userInfo = ref.read(userInfoProvider(widget.userId)); + final firstDate = widget.actualAccount == null + ? userInfo?.response?.createdAt + : userInfo?.remoteResponse?.createdAt; - final result = await showDatePicker( - context: context, - initialDate: untilDate ?? DateTime.now(), - helpText: "この日までを表示", - firstDate: firstDate ?? DateTime.now(), - lastDate: DateTime.now()); - if (result != null) { - untilDate = DateTime(result.year, result.month, - result.day, 23, 59, 59, 999); - } - setState(() {}); - }, - icon: const Icon(Icons.date_range)) + final result = await showDatePicker( + context: context, + initialDate: untilDate ?? DateTime.now(), + helpText: "この日までを表示", + firstDate: firstDate ?? DateTime.now(), + lastDate: DateTime.now(), + ); + if (result != null) { + untilDate = DateTime( + result.year, + result.month, + result.day, + 23, + 59, + 59, + 999, + ); + } + setState(() {}); + }, + icon: const Icon(Icons.date_range), + ), ], ), ), Expanded( child: PushableListView( - listKey: - Object.hashAll([isFileOnly, withReply, renote, untilDate]), - initializeFuture: () async { - final notes = await misskey.users.notes(UsersNotesRequest( - userId: widget.userId, - withFiles: isFileOnly, - includeReplies: withReply, - includeMyRenotes: renote, - untilDate: untilDate?.millisecondsSinceEpoch, - )); - if (!mounted) return []; - ref - .read(notesProvider(AccountScope.of(context))) - .registerAll(notes); - return notes.toList(); - }, - nextFuture: (lastElement, _) async { - final notes = await misskey.users.notes(UsersNotesRequest( - userId: widget.userId, - untilId: lastElement.id, - withFiles: isFileOnly, - includeReplies: withReply, - includeMyRenotes: renote, - untilDate: untilDate?.millisecondsSinceEpoch, - )); - if (!mounted) return []; - ref - .read(notesProvider(AccountScope.of(context))) - .registerAll(notes); - return notes.toList(); - }, - itemBuilder: (context, element) { - return MisskeyNote( - note: element, - loginAs: widget.actualAccount, + listKey: Object.hashAll( + [isFileOnly, withReply, renote, untilDate, highlight]), + additionalErrorInfo: highlight + ? (context, e) => const Text("ハイライトはMisskey 2023.10.0以降の機能です。") + : null, + initializeFuture: () async { + final Iterable notes; + if (highlight) { + notes = await misskey.users.featuredNotes( + UsersFeaturedNotesRequest( + userId: widget.remoteUserId ?? widget.userId), + ); + } else { + notes = await misskey.users.notes( + UsersNotesRequest( + userId: widget.remoteUserId ?? widget.userId, + withFiles: isFileOnly, + // 後方互換性のため + includeReplies: withReply, + withReplies: withReply, + includeMyRenotes: renote, + untilDate: untilDate?.millisecondsSinceEpoch, + ), ); - }), + } + if (!mounted) return []; + ref + .read(notesProvider(AccountScope.of(context))) + .registerAll(notes); + return notes.toList(); + }, + nextFuture: (lastElement, _) async { + final Iterable notes; + if (highlight) { + notes = await misskey.users.featuredNotes( + UsersFeaturedNotesRequest( + userId: widget.remoteUserId ?? widget.userId, + untilId: lastElement.id, + ), + ); + } else { + notes = await misskey.users.notes( + UsersNotesRequest( + userId: widget.remoteUserId ?? widget.userId, + untilId: lastElement.id, + withFiles: isFileOnly, + includeReplies: withReply, + withReplies: withReply, + includeMyRenotes: renote, + untilDate: untilDate?.millisecondsSinceEpoch, + ), + ); + } + if (!mounted) return []; + ref + .read(notesProvider(AccountScope.of(context))) + .registerAll(notes); + return notes.toList(); + }, + itemBuilder: (context, element) { + return MisskeyNote( + note: element, + loginAs: widget.actualAccount, + ); + }, + ), ), ], ); diff --git a/lib/view/user_page/user_page.dart b/lib/view/user_page/user_page.dart index 213fb158e..55ec82869 100644 --- a/lib/view/user_page/user_page.dart +++ b/lib/view/user_page/user_page.dart @@ -63,12 +63,14 @@ class UserPageState extends ConsumerState { bottom: TabBar( tabs: [ Tab( - text: - "アカウント情報${userInfo?.remoteResponse != null ? "(ローカル)" : ""}"), + text: + "アカウント情報${userInfo?.remoteResponse != null ? "(ローカル)" : ""}", + ), if (isRemoteUser) const Tab(text: "アカウント情報(リモート)"), Tab( - text: - "ノート${userInfo?.remoteResponse != null ? "(ローカル)" : ""}"), + text: + "ノート${userInfo?.remoteResponse != null ? "(ローカル)" : ""}", + ), if (isRemoteUser) const Tab(text: "ノート(リモート)"), const Tab(text: "クリップ"), if (isReactionAvailable) const Tab(text: "リアクション"), @@ -99,7 +101,6 @@ class UserPageState extends ConsumerState { padding: const EdgeInsets.only(left: 10, right: 10), child: UserNotes( userId: widget.userId, - actualAccount: null, ), ), if (isRemoteUser) @@ -108,35 +109,39 @@ class UserPageState extends ConsumerState { child: Padding( padding: const EdgeInsets.only(left: 10, right: 10), child: UserNotes( - userId: userInfo.remoteResponse!.id, + userId: widget.userId, + remoteUserId: userInfo.remoteResponse!.id, actualAccount: widget.account, ), ), ), Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: UserClips( - userId: widget.userId, - )), + padding: const EdgeInsets.only(left: 10, right: 10), + child: UserClips( + userId: widget.userId, + ), + ), if (isReactionAvailable) Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: UserReactions(userId: widget.userId)), + padding: const EdgeInsets.only(left: 10, right: 10), + child: UserReactions(userId: widget.userId), + ), if (isRemoteUser) AccountScope( account: Account.demoAccount(userInfo!.response!.host!), child: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: - UserPlays(userId: userInfo.remoteResponse!.id)), + padding: const EdgeInsets.only(left: 10, right: 10), + child: UserPlays(userId: userInfo.remoteResponse!.id), + ), ) else Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: UserPlays(userId: widget.userId)), + padding: const EdgeInsets.only(left: 10, right: 10), + child: UserPlays(userId: widget.userId), + ), ], ), - ) + ), ], ), ), @@ -178,10 +183,11 @@ class UserDetailTabState extends ConsumerState { .read(notesProvider(account)) .registerAll(response?.pinnedNotes ?? []); ref.read(userInfoProvider(widget.userId).notifier).state = UserInfo( - userId: widget.userId, - response: response, - remoteUserId: null, - remoteResponse: null); + userId: widget.userId, + response: response, + remoteUserId: null, + remoteResponse: null, + ); final remoteHost = response?.host; if (remoteHost != null) { @@ -189,7 +195,8 @@ class UserDetailTabState extends ConsumerState { .read(misskeyProvider(Account.demoAccount(remoteHost))) .users .showByName( - UsersShowByUserNameRequest(userName: response!.username)); + UsersShowByUserNameRequest(userName: response!.username), + ); await ref .read(emojiRepositoryProvider(Account.demoAccount(remoteHost))) @@ -199,10 +206,11 @@ class UserDetailTabState extends ConsumerState { .read(notesProvider(Account.demoAccount(remoteHost))) .registerAll(remoteResponse.pinnedNotes ?? []); ref.read(userInfoProvider(widget.userId).notifier).state = UserInfo( - userId: widget.userId, - response: response, - remoteUserId: remoteResponse.id, - remoteResponse: remoteResponse); + userId: widget.userId, + response: response, + remoteUserId: remoteResponse.id, + remoteResponse: remoteResponse, + ); } } catch (e, s) { setState(() { diff --git a/lib/view/user_page/user_reactions.dart b/lib/view/user_page/user_reactions.dart index d72a0f0ed..a4c020934 100644 --- a/lib/view/user_page/user_reactions.dart +++ b/lib/view/user_page/user_reactions.dart @@ -79,6 +79,7 @@ class UserReaction extends ConsumerWidget { child: CustomEmoji( emojiData: MisskeyEmojiData.fromEmojiName( emojiName: response.type, + emojiInfo: response.note.reactionEmojis, repository: ref.read( emojiRepositoryProvider(AccountScope.of(context)))), fontSizeRatio: 2, diff --git a/lib/view/users_list_page/users_list_detail_page.dart b/lib/view/users_list_page/users_list_detail_page.dart new file mode 100644 index 000000000..c0034225f --- /dev/null +++ b/lib/view/users_list_page/users_list_detail_page.dart @@ -0,0 +1,224 @@ +import 'package:auto_route/annotations.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/extensions/users_lists_show_response_extension.dart'; +import 'package:miria/model/account.dart'; +import 'package:miria/model/users_list_settings.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/view/common/account_scope.dart'; +import 'package:miria/view/common/error_detail.dart'; +import 'package:miria/view/common/error_dialog_handler.dart'; +import 'package:miria/view/dialogs/simple_confirm_dialog.dart'; +import 'package:miria/view/user_page/user_list_item.dart'; +import 'package:miria/view/user_select_dialog.dart'; +import 'package:miria/view/users_list_page/users_list_settings_dialog.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +final _usersListNotifierProvider = AutoDisposeAsyncNotifierProviderFamily< + _UsersListNotifier, UsersList, (Misskey, String)>(_UsersListNotifier.new); + +class _UsersListNotifier + extends AutoDisposeFamilyAsyncNotifier { + @override + Future build((Misskey, String) arg) async { + final response = await _misskey.users.list.show( + UsersListsShowRequest(listId: _listId), + ); + return response.toUsersList(); + } + + Misskey get _misskey => arg.$1; + + String get _listId => arg.$2; + + Future updateList(UsersListSettings settings) async { + await _misskey.users.list.update( + UsersListsUpdateRequest( + listId: _listId, + name: settings.name, + isPublic: settings.isPublic, + ), + ); + final list = state.valueOrNull; + if (list != null) { + state = AsyncValue.data( + list.copyWith( + name: settings.name, + isPublic: settings.isPublic, + ), + ); + } + } +} + +final _usersListUsersProvider = AutoDisposeAsyncNotifierProviderFamily< + _UsersListUsers, List, (Misskey, String)>( + _UsersListUsers.new, +); + +class _UsersListUsers + extends AutoDisposeFamilyAsyncNotifier, (Misskey, String)> { + @override + Future> build((Misskey, String) arg) async { + final list = await ref.watch(_usersListNotifierProvider(arg).future); + final response = await _misskey.users.showByIds( + UsersShowByIdsRequest( + userIds: list.userIds, + ), + ); + return response.map((e) => e.toUser()).toList(); + } + + Misskey get _misskey => arg.$1; + + String get _listId => arg.$2; + + Future push(User user) async { + await _misskey.users.list.push( + UsersListsPushRequest( + listId: _listId, + userId: user.id, + ), + ); + state = AsyncValue.data([...?state.valueOrNull, user]); + } + + Future pull(User user) async { + await _misskey.users.list.pull( + UsersListsPullRequest( + listId: _listId, + userId: user.id, + ), + ); + state = AsyncValue.data( + state.valueOrNull?.where((e) => e.id != user.id).toList() ?? [], + ); + } +} + +@RoutePage() +class UsersListDetailPage extends ConsumerWidget { + const UsersListDetailPage({ + super.key, + required this.account, + required this.listId, + }); + + final Account account; + final String listId; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final misskey = ref.watch(misskeyProvider(account)); + final arg = (misskey, listId); + final list = ref.watch(_usersListNotifierProvider(arg)); + final users = ref.watch(_usersListUsersProvider(arg)); + + return Scaffold( + appBar: list.maybeWhen( + data: (list) => AppBar( + title: Text(list.name ?? ""), + actions: [ + IconButton( + icon: const Icon(Icons.settings), + onPressed: () async { + final settings = await showDialog( + context: context, + builder: (context) => UsersListSettingsDialog( + title: const Text("編集"), + initialSettings: UsersListSettings.fromUsersList(list), + ), + ); + if (!context.mounted) return; + if (settings != null) { + ref + .read(_usersListNotifierProvider(arg).notifier) + .updateList(settings) + .expectFailure(context); + } + }, + ), + ], + ), + orElse: () => AppBar(), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: users.when( + data: (users) { + return AccountScope( + account: account, + child: Column( + children: [ + ListTile( + title: const Text("メンバー"), + subtitle: Text( + "${users.length}/${account.i.policies.userEachUserListsLimit} 人", + ), + trailing: ElevatedButton( + child: const Text("ユーザーを追加"), + onPressed: () async { + final user = await showDialog( + context: context, + builder: (context) => + UserSelectDialog(account: account), + ); + if (user == null) { + return; + } + if (!context.mounted) return; + await ref + .read(_usersListUsersProvider(arg).notifier) + .push(user) + .expectFailure(context); + }, + ), + ), + const Divider(), + Expanded( + child: ListView.builder( + itemCount: users.length, + itemBuilder: (context, index) { + final user = users[index]; + return Row( + children: [ + Expanded( + child: UserListItem(user: user), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () async { + final result = await SimpleConfirmDialog.show( + context: context, + message: "このユーザーをリストから外しますか?", + primary: "外す", + secondary: "やめる", + ); + if (!context.mounted) return; + if (result ?? false) { + await ref + .read( + _usersListUsersProvider(arg).notifier, + ) + .pull(user) + .expectFailure(context); + } + }, + ), + ], + ); + }, + ), + ), + ], + ), + ); + }, + error: (e, st) => + Center(child: ErrorDetail(error: e, stackTrace: st)), + loading: () => const Center(child: CircularProgressIndicator()), + ), + ), + ); + } +} diff --git a/lib/view/users_list_page/users_list_page.dart b/lib/view/users_list_page/users_list_page.dart index d3d7b34d5..0fde595b0 100644 --- a/lib/view/users_list_page/users_list_page.dart +++ b/lib/view/users_list_page/users_list_page.dart @@ -1,10 +1,55 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/model/account.dart'; +import 'package:miria/model/users_list_settings.dart'; import 'package:miria/providers.dart'; import 'package:miria/router/app_router.dart'; -import 'package:miria/view/common/futable_list_builder.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/view/common/error_detail.dart'; +import 'package:miria/view/common/error_dialog_handler.dart'; +import 'package:miria/view/dialogs/simple_confirm_dialog.dart'; +import 'package:miria/view/users_list_page/users_list_settings_dialog.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +final _usersListListNotifierProvider = AutoDisposeAsyncNotifierProviderFamily< + _UsersListListNotifier, + List, + Misskey>(_UsersListListNotifier.new); + +class _UsersListListNotifier + extends AutoDisposeFamilyAsyncNotifier, Misskey> { + @override + Future> build(Misskey arg) async { + final response = await _misskey.users.list.list(); + return response.toList(); + } + + Misskey get _misskey => arg; + + Future create(UsersListSettings settings) async { + final list = await _misskey.users.list.create( + UsersListsCreateRequest( + name: settings.name, + ), + ); + if (settings.isPublic) { + await _misskey.users.list.update( + UsersListsUpdateRequest( + listId: list.id, + isPublic: settings.isPublic, + ), + ); + } + state = AsyncValue.data([...?state.valueOrNull, list]); + } + + Future delete(String listId) async { + await _misskey.users.list.delete(UsersListsDeleteRequest(listId: listId)); + state = AsyncValue.data( + state.valueOrNull?.where((e) => e.id != listId).toList() ?? [], + ); + } +} @RoutePage() class UsersListPage extends ConsumerWidget { @@ -14,18 +59,78 @@ class UsersListPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final misskey = ref.watch(misskeyProvider(account)); + final list = ref.watch(_usersListListNotifierProvider(misskey)); + return Scaffold( - appBar: AppBar(title: const Text("リスト")), - body: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: FutureListView( - future: ref.read(misskeyProvider(account)).users.list.list(), - builder: (context, item) => ListTile( + appBar: AppBar( + title: const Text("リスト"), + actions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () async { + final settings = await showDialog( + context: context, + builder: (context) => const UsersListSettingsDialog( + title: Text("作成"), + ), + ); + if (!context.mounted) return; + if (settings != null) { + ref + .read(_usersListListNotifierProvider(misskey).notifier) + .create(settings) + .expectFailure(context); + } + }, + ), + ], + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: list.when( + data: (data) { + return ListView.builder( + itemCount: data.length, + itemBuilder: (context, index) { + final list = data[index]; + return ListTile( + title: Text(list.name ?? ""), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + final result = await SimpleConfirmDialog.show( + context: context, + message: "このリストを削除しますか?", + primary: "削除する", + secondary: "やめる", + ); + if (!context.mounted) return; + if (result ?? false) { + await ref + .read( + _usersListListNotifierProvider(misskey).notifier, + ) + .delete(list.id) + .expectFailure(context); + } + }, + ), onTap: () => context.pushRoute( - UsersListTimelineRoute( - account: account, listId: item.id), - ), - title: Text(item.name ?? "")), - ))); + UsersListTimelineRoute( + account: account, + list: list, + ), + ), + ); + }, + ); + }, + error: (e, st) => + Center(child: ErrorDetail(error: e, stackTrace: st)), + loading: () => const Center(child: CircularProgressIndicator()), + ), + ), + ); } } diff --git a/lib/view/users_list_page/users_list_settings_dialog.dart b/lib/view/users_list_page/users_list_settings_dialog.dart new file mode 100644 index 000000000..84bca9101 --- /dev/null +++ b/lib/view/users_list_page/users_list_settings_dialog.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/model/users_list_settings.dart'; + +final _formKeyProvider = Provider.autoDispose((ref) => GlobalKey()); + +final _initialSettingsProvider = Provider.autoDispose( + (ref) => throw UnimplementedError(), +); + +final _usersListSettingsNotifierProvider = + NotifierProvider.autoDispose<_UsersListSettingsNotifier, UsersListSettings>( + _UsersListSettingsNotifier.new, + dependencies: [_initialSettingsProvider], +); + +class _UsersListSettingsNotifier + extends AutoDisposeNotifier { + @override + UsersListSettings build() { + return ref.watch(_initialSettingsProvider); + } + + void updateName(String? name) { + if (name != null) { + state = state.copyWith(name: name); + } + } + + void updateIsPublic(bool? isPublic) { + if (isPublic != null) { + state = state.copyWith(isPublic: isPublic); + } + } +} + +class UsersListSettingsDialog extends StatelessWidget { + const UsersListSettingsDialog({ + super.key, + this.title, + this.initialSettings = const UsersListSettings(), + }); + + final Widget? title; + final UsersListSettings initialSettings; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: title, + content: ProviderScope( + overrides: [ + _initialSettingsProvider.overrideWithValue(initialSettings), + ], + child: const UsersListSettingsForm(), + ), + ); + } +} + +class UsersListSettingsForm extends ConsumerWidget { + const UsersListSettingsForm({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final formKey = ref.watch(_formKeyProvider); + final initialSettings = ref.watch(_initialSettingsProvider); + final settings = ref.watch(_usersListSettingsNotifierProvider); + + return Form( + key: formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + initialValue: initialSettings.name, + maxLength: 100, + decoration: const InputDecoration( + labelText: "リスト名", + contentPadding: EdgeInsets.fromLTRB(12, 24, 12, 16), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return "入力してください"; + } + return null; + }, + onSaved: ref + .read(_usersListSettingsNotifierProvider.notifier) + .updateName, + ), + CheckboxListTile( + title: const Text("パブリック"), + value: settings.isPublic, + onChanged: ref + .read(_usersListSettingsNotifierProvider.notifier) + .updateIsPublic, + ), + ElevatedButton( + child: const Text("決定"), + onPressed: () { + if (formKey.currentState!.validate()) { + formKey.currentState!.save(); + final settings = ref.read(_usersListSettingsNotifierProvider); + if (settings == initialSettings) { + Navigator.of(context).pop(); + } else { + Navigator.of(context).pop(settings); + } + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/view/users_list_page/users_list_timeline_page.dart b/lib/view/users_list_page/users_list_timeline_page.dart index 9f3f11255..a0eb3e1d2 100644 --- a/lib/view/users_list_page/users_list_timeline_page.dart +++ b/lib/view/users_list_page/users_list_timeline_page.dart @@ -1,27 +1,43 @@ -import 'package:auto_route/annotations.dart'; +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/model/account.dart'; +import 'package:miria/router/app_router.dart'; import 'package:miria/view/common/account_scope.dart'; import 'package:miria/view/users_list_page/users_list_timeline.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:misskey_dart/misskey_dart.dart'; @RoutePage() class UsersListTimelinePage extends ConsumerWidget { final Account account; - final String listId; + final UsersList list; - const UsersListTimelinePage(this.account, this.listId, {super.key}); + const UsersListTimelinePage(this.account, this.list, {super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( - appBar: AppBar(), + appBar: AppBar( + title: Text(list.name ?? ""), + actions: [ + IconButton( + icon: const Icon(Icons.info_outline), + onPressed: () => context.pushRoute( + UsersListDetailRoute( + account: account, + listId: list.id, + ), + ), + ), + ], + ), body: AccountScope( - account: account, - child: Padding( - padding: const EdgeInsets.only(left: 10, right: 10), - child: UsersListTimeline(listId: listId), - )), + account: account, + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: UsersListTimeline(listId: list.id), + ), + ), ); } } diff --git a/pubspec.lock b/pubspec.lock index d77b81535..90538f069 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -406,6 +406,38 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0-alpha.6" + flutter_image_compress: + dependency: "direct main" + description: + name: flutter_image_compress + sha256: "2725cce5c58fdeaf1db8f4203688228bb67e3523a66305ccaa6f99071beb6dc2" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + flutter_image_compress_common: + dependency: transitive + description: + name: flutter_image_compress_common + sha256: "8e7299afe109dc4b97fda34bf0f4967cc1fc10bc8050c374d449cab262d095b3" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + flutter_image_compress_platform_interface: + dependency: transitive + description: + name: flutter_image_compress_platform_interface + sha256: "3c7e86da7540b1adfa919b461885a41a018d4a26544d0fcbeaa769f6542e603d" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + flutter_image_compress_web: + dependency: transitive + description: + name: flutter_image_compress_web + sha256: e879189dc7f246dcf8f06c07ee849231341508bf51e8ed7d5dcbe778ddde0e81 + url: "https://pub.dev" + source: hosted + version: "0.1.3+1" flutter_launcher_icons: dependency: "direct dev" description: @@ -790,7 +822,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "284fdedd9a541cf26940cbbecf809ae717c2bbf4" + resolved-ref: a8e2cd0993b83bc793e5916854845be8144c870b url: "https://github.com/shiosyakeyakini-info/misskey_dart.git" source: git version: "1.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2e104b38e..50624f1f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: miria description: Miria is Misskey Client for Mobile App. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.3+63 +version: 1.0.7+73 environment: sdk: '>=3.0.0 <4.0.0' @@ -60,6 +60,7 @@ dependencies: matrix2d: ^1.0.4 twemoji_v2: ^0.5.3 flutter_secure_storage_linux: ^1.2.0 + flutter_image_compress: ^2.0.4 dependency_overrides: image_editor: diff --git a/test/repository/account_repository/open_mi_auth_test.dart b/test/repository/account_repository/open_mi_auth_test.dart index b43928607..cfde4f794 100644 --- a/test/repository/account_repository/open_mi_auth_test.dart +++ b/test/repository/account_repository/open_mi_auth_test.dart @@ -43,8 +43,14 @@ void main() { requestOptions: RequestOptions(), data: AuthTestData.calckeyNodeInfo)); when(dio.get(any)).thenAnswer((realInvocation) async => Response( requestOptions: RequestOptions(), data: AuthTestData.calckeyNodeInfo2)); - final provider = - ProviderContainer(overrides: [dioProvider.overrideWithValue(dio)]); + final mockMisskey = MockMisskey(); + when(mockMisskey.endpoints()).thenAnswer((_) async => []); + final provider = ProviderContainer( + overrides: [ + dioProvider.overrideWithValue(dio), + misskeyProvider.overrideWith((ref, arg) => mockMisskey), + ], + ); final accountRepository = AccountRepository(MockTabSettingsRepository(), MockAccountSettingsRepository(), provider.read); @@ -69,8 +75,14 @@ void main() { when(dio.get(any)).thenAnswer((realInvocation) async => Response( requestOptions: RequestOptions(), data: AuthTestData.oldVerMisskeyNodeInfo2)); - final provider = - ProviderContainer(overrides: [dioProvider.overrideWithValue(dio)]); + final mockMisskey = MockMisskey(); + when(mockMisskey.endpoints()).thenAnswer((_) async => []); + final provider = ProviderContainer( + overrides: [ + dioProvider.overrideWithValue(dio), + misskeyProvider.overrideWith((ref, arg) => mockMisskey), + ], + ); final accountRepository = AccountRepository(MockTabSettingsRepository(), MockAccountSettingsRepository(), provider.read); diff --git a/test/test_util/mock.dart b/test/test_util/mock.dart index 2fbfbbf6c..0d5a23601 100644 --- a/test/test_util/mock.dart +++ b/test/test_util/mock.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:dio/dio.dart'; import 'package:file_picker/file_picker.dart'; import 'package:miria/repository/account_settings_repository.dart'; @@ -18,16 +20,28 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; // API MockSpec(), - MockSpec(), - MockSpec(), - MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), // プラグインとか MockSpec(), + MockSpec(), MockSpec(as: #MockFilePickerPlatform) ]) // ignore: unused_import diff --git a/test/test_util/mock.mocks.dart b/test/test_util/mock.mocks.dart index eabbdaa9b..9d8a9bd99 100644 --- a/test/test_util/mock.mocks.dart +++ b/test/test_util/mock.mocks.dart @@ -3,37 +3,36 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i16; -import 'dart:io' as _i24; -import 'dart:typed_data' as _i25; -import 'dart:ui' as _i18; +import 'dart:async' as _i17; +import 'dart:io' as _i14; +import 'dart:typed_data' as _i24; +import 'dart:ui' as _i19; -import 'package:dio/dio.dart' as _i26; +import 'package:dio/dio.dart' as _i25; import 'package:dio/src/adapter.dart' as _i10; -import 'package:dio/src/cancel_token.dart' as _i27; +import 'package:dio/src/cancel_token.dart' as _i26; import 'package:dio/src/dio_mixin.dart' as _i12; import 'package:dio/src/options.dart' as _i9; import 'package:dio/src/response.dart' as _i13; import 'package:dio/src/transformer.dart' as _i11; -import 'package:file_picker/file_picker.dart' as _i29; -import 'package:miria/model/account.dart' as _i17; +import 'package:file_picker/file_picker.dart' as _i28; +import 'package:miria/model/account.dart' as _i18; import 'package:miria/model/account_settings.dart' as _i2; import 'package:miria/model/general_settings.dart' as _i3; -import 'package:miria/model/misskey_emoji_data.dart' as _i21; -import 'package:miria/model/tab_setting.dart' as _i15; -import 'package:miria/repository/account_settings_repository.dart' as _i19; -import 'package:miria/repository/emoji_repository.dart' as _i20; -import 'package:miria/repository/general_settings_repository.dart' as _i22; -import 'package:miria/repository/tab_settings_repository.dart' as _i14; +import 'package:miria/model/misskey_emoji_data.dart' as _i22; +import 'package:miria/model/tab_setting.dart' as _i16; +import 'package:miria/repository/account_settings_repository.dart' as _i20; +import 'package:miria/repository/emoji_repository.dart' as _i21; +import 'package:miria/repository/general_settings_repository.dart' as _i23; +import 'package:miria/repository/tab_settings_repository.dart' as _i15; import 'package:misskey_dart/misskey_dart.dart' as _i6; -import 'package:misskey_dart/src/data/base/flash.dart' as _i23; import 'package:misskey_dart/src/data/ping_response.dart' as _i8; import 'package:misskey_dart/src/data/stats_response.dart' as _i7; import 'package:misskey_dart/src/services/api_service.dart' as _i4; import 'package:misskey_dart/src/services/streaming_service.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'mock.dart' as _i28; +import 'mock.dart' as _i27; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -330,9 +329,8 @@ class _FakeSocketController_26 extends _i1.SmartFake ); } -class _FakeMisskeyNotesReactions_27 extends _i1.SmartFake - implements _i6.MisskeyNotesReactions { - _FakeMisskeyNotesReactions_27( +class _FakeAntenna_27 extends _i1.SmartFake implements _i6.Antenna { + _FakeAntenna_27( Object parent, Invocation parentInvocation, ) : super( @@ -341,9 +339,9 @@ class _FakeMisskeyNotesReactions_27 extends _i1.SmartFake ); } -class _FakeMisskeyNotesFavorites_28 extends _i1.SmartFake - implements _i6.MisskeyNotesFavorites { - _FakeMisskeyNotesFavorites_28( +class _FakeApShowResponse_28 extends _i1.SmartFake + implements _i6.ApShowResponse { + _FakeApShowResponse_28( Object parent, Invocation parentInvocation, ) : super( @@ -352,9 +350,9 @@ class _FakeMisskeyNotesFavorites_28 extends _i1.SmartFake ); } -class _FakeMisskeyNotesPolls_29 extends _i1.SmartFake - implements _i6.MisskeyNotesPolls { - _FakeMisskeyNotesPolls_29( +class _FakeCommunityChannel_29 extends _i1.SmartFake + implements _i6.CommunityChannel { + _FakeCommunityChannel_29( Object parent, Invocation parentInvocation, ) : super( @@ -363,9 +361,8 @@ class _FakeMisskeyNotesPolls_29 extends _i1.SmartFake ); } -class _FakeMisskeyNotesThreadMuting_30 extends _i1.SmartFake - implements _i6.MisskeyNotesThreadMuting { - _FakeMisskeyNotesThreadMuting_30( +class _FakeClip_30 extends _i1.SmartFake implements _i6.Clip { + _FakeClip_30( Object parent, Invocation parentInvocation, ) : super( @@ -374,8 +371,9 @@ class _FakeMisskeyNotesThreadMuting_30 extends _i1.SmartFake ); } -class _FakeNote_31 extends _i1.SmartFake implements _i6.Note { - _FakeNote_31( +class _FakeMisskeyDriveFiles_31 extends _i1.SmartFake + implements _i6.MisskeyDriveFiles { + _FakeMisskeyDriveFiles_31( Object parent, Invocation parentInvocation, ) : super( @@ -384,9 +382,9 @@ class _FakeNote_31 extends _i1.SmartFake implements _i6.Note { ); } -class _FakeNotesStateResponse_32 extends _i1.SmartFake - implements _i6.NotesStateResponse { - _FakeNotesStateResponse_32( +class _FakeMisskeyDriveFolders_32 extends _i1.SmartFake + implements _i6.MisskeyDriveFolders { + _FakeMisskeyDriveFolders_32( Object parent, Invocation parentInvocation, ) : super( @@ -395,9 +393,8 @@ class _FakeNotesStateResponse_32 extends _i1.SmartFake ); } -class _FakeMisskeyUsersLists_33 extends _i1.SmartFake - implements _i6.MisskeyUsersLists { - _FakeMisskeyUsersLists_33( +class _FakeDriveFile_33 extends _i1.SmartFake implements _i6.DriveFile { + _FakeDriveFile_33( Object parent, Invocation parentInvocation, ) : super( @@ -406,9 +403,9 @@ class _FakeMisskeyUsersLists_33 extends _i1.SmartFake ); } -class _FakeUsersShowResponse_34 extends _i1.SmartFake - implements _i6.UsersShowResponse { - _FakeUsersShowResponse_34( +class _FakeFederationShowInstanceResponse_34 extends _i1.SmartFake + implements _i6.FederationShowInstanceResponse { + _FakeFederationShowInstanceResponse_34( Object parent, Invocation parentInvocation, ) : super( @@ -417,9 +414,9 @@ class _FakeUsersShowResponse_34 extends _i1.SmartFake ); } -class _FakeCommunityChannel_35 extends _i1.SmartFake - implements _i6.CommunityChannel { - _FakeCommunityChannel_35( +class _FakeMisskeyFollowingRequests_35 extends _i1.SmartFake + implements _i6.MisskeyFollowingRequests { + _FakeMisskeyFollowingRequests_35( Object parent, Invocation parentInvocation, ) : super( @@ -428,9 +425,8 @@ class _FakeCommunityChannel_35 extends _i1.SmartFake ); } -class _FakeMisskeyDriveFiles_36 extends _i1.SmartFake - implements _i6.MisskeyDriveFiles { - _FakeMisskeyDriveFiles_36( +class _FakeUser_36 extends _i1.SmartFake implements _i6.User { + _FakeUser_36( Object parent, Invocation parentInvocation, ) : super( @@ -439,9 +435,105 @@ class _FakeMisskeyDriveFiles_36 extends _i1.SmartFake ); } -class _FakeMisskeyDriveFolders_37 extends _i1.SmartFake - implements _i6.MisskeyDriveFolders { - _FakeMisskeyDriveFolders_37( +class _FakeHashtag_37 extends _i1.SmartFake implements _i6.Hashtag { + _FakeHashtag_37( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIResponse_38 extends _i1.SmartFake implements _i6.IResponse { + _FakeIResponse_38( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeMisskeyNotesReactions_39 extends _i1.SmartFake + implements _i6.MisskeyNotesReactions { + _FakeMisskeyNotesReactions_39( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeMisskeyNotesFavorites_40 extends _i1.SmartFake + implements _i6.MisskeyNotesFavorites { + _FakeMisskeyNotesFavorites_40( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeMisskeyNotesPolls_41 extends _i1.SmartFake + implements _i6.MisskeyNotesPolls { + _FakeMisskeyNotesPolls_41( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeMisskeyNotesThreadMuting_42 extends _i1.SmartFake + implements _i6.MisskeyNotesThreadMuting { + _FakeMisskeyNotesThreadMuting_42( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeNote_43 extends _i1.SmartFake implements _i6.Note { + _FakeNote_43( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeNotesStateResponse_44 extends _i1.SmartFake + implements _i6.NotesStateResponse { + _FakeNotesStateResponse_44( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeRolesListResponse_45 extends _i1.SmartFake + implements _i6.RolesListResponse { + _FakeRolesListResponse_45( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeMisskeyUsersLists_46 extends _i1.SmartFake + implements _i6.MisskeyUsersLists { + _FakeMisskeyUsersLists_46( Object parent, Invocation parentInvocation, ) : super( @@ -450,8 +542,9 @@ class _FakeMisskeyDriveFolders_37 extends _i1.SmartFake ); } -class _FakeDriveFile_38 extends _i1.SmartFake implements _i6.DriveFile { - _FakeDriveFile_38( +class _FakeUsersShowResponse_47 extends _i1.SmartFake + implements _i6.UsersShowResponse { + _FakeUsersShowResponse_47( Object parent, Invocation parentInvocation, ) : super( @@ -460,8 +553,8 @@ class _FakeDriveFile_38 extends _i1.SmartFake implements _i6.DriveFile { ); } -class _FakeBaseOptions_39 extends _i1.SmartFake implements _i9.BaseOptions { - _FakeBaseOptions_39( +class _FakeBaseOptions_48 extends _i1.SmartFake implements _i9.BaseOptions { + _FakeBaseOptions_48( Object parent, Invocation parentInvocation, ) : super( @@ -470,9 +563,29 @@ class _FakeBaseOptions_39 extends _i1.SmartFake implements _i9.BaseOptions { ); } -class _FakeHttpClientAdapter_40 extends _i1.SmartFake +class _FakeHttpClientAdapter_49 extends _i1.SmartFake implements _i10.HttpClientAdapter { - _FakeHttpClientAdapter_40( + _FakeHttpClientAdapter_49( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeTransformer_50 extends _i1.SmartFake implements _i11.Transformer { + _FakeTransformer_50( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeInterceptors_51 extends _i1.SmartFake implements _i12.Interceptors { + _FakeInterceptors_51( Object parent, Invocation parentInvocation, ) : super( @@ -481,8 +594,8 @@ class _FakeHttpClientAdapter_40 extends _i1.SmartFake ); } -class _FakeTransformer_41 extends _i1.SmartFake implements _i11.Transformer { - _FakeTransformer_41( +class _FakeResponse_52 extends _i1.SmartFake implements _i13.Response { + _FakeResponse_52( Object parent, Invocation parentInvocation, ) : super( @@ -491,8 +604,8 @@ class _FakeTransformer_41 extends _i1.SmartFake implements _i11.Transformer { ); } -class _FakeInterceptors_42 extends _i1.SmartFake implements _i12.Interceptors { - _FakeInterceptors_42( +class _FakeDuration_53 extends _i1.SmartFake implements Duration { + _FakeDuration_53( Object parent, Invocation parentInvocation, ) : super( @@ -501,8 +614,9 @@ class _FakeInterceptors_42 extends _i1.SmartFake implements _i12.Interceptors { ); } -class _FakeResponse_43 extends _i1.SmartFake implements _i13.Response { - _FakeResponse_43( +class _FakeHttpClientRequest_54 extends _i1.SmartFake + implements _i14.HttpClientRequest { + _FakeHttpClientRequest_54( Object parent, Invocation parentInvocation, ) : super( @@ -515,13 +629,13 @@ class _FakeResponse_43 extends _i1.SmartFake implements _i13.Response { /// /// See the documentation for Mockito's code generation for more information. class MockTabSettingsRepository extends _i1.Mock - implements _i14.TabSettingsRepository { + implements _i15.TabSettingsRepository { @override - Iterable<_i15.TabSetting> get tabSettings => (super.noSuchMethod( + Iterable<_i16.TabSetting> get tabSettings => (super.noSuchMethod( Invocation.getter(#tabSettings), - returnValue: <_i15.TabSetting>[], - returnValueForMissingStub: <_i15.TabSetting>[], - ) as Iterable<_i15.TabSetting>); + returnValue: <_i16.TabSetting>[], + returnValueForMissingStub: <_i16.TabSetting>[], + ) as Iterable<_i16.TabSetting>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), @@ -529,17 +643,17 @@ class MockTabSettingsRepository extends _i1.Mock returnValueForMissingStub: false, ) as bool); @override - _i16.Future load() => (super.noSuchMethod( + _i17.Future load() => (super.noSuchMethod( Invocation.method( #load, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override void updateAccount( - _i17.Account? account, + _i18.Account? account, _i6.IResponse? response, ) => super.noSuchMethod( @@ -553,26 +667,26 @@ class MockTabSettingsRepository extends _i1.Mock returnValueForMissingStub: null, ); @override - _i16.Future save(List<_i15.TabSetting>? tabSettings) => + _i17.Future save(List<_i16.TabSetting>? tabSettings) => (super.noSuchMethod( Invocation.method( #save, [tabSettings], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future removeAccount(_i17.Account? account) => (super.noSuchMethod( + _i17.Future removeAccount(_i18.Account? account) => (super.noSuchMethod( Invocation.method( #removeAccount, [account], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i19.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -580,7 +694,7 @@ class MockTabSettingsRepository extends _i1.Mock returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i19.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -609,7 +723,7 @@ class MockTabSettingsRepository extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockAccountSettingsRepository extends _i1.Mock - implements _i19.AccountSettingsRepository { + implements _i20.AccountSettingsRepository { @override Iterable<_i2.AccountSettings> get accountSettings => (super.noSuchMethod( Invocation.getter(#accountSettings), @@ -623,34 +737,34 @@ class MockAccountSettingsRepository extends _i1.Mock returnValueForMissingStub: false, ) as bool); @override - _i16.Future load() => (super.noSuchMethod( + _i17.Future load() => (super.noSuchMethod( Invocation.method( #load, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future save(_i2.AccountSettings? settings) => (super.noSuchMethod( + _i17.Future save(_i2.AccountSettings? settings) => (super.noSuchMethod( Invocation.method( #save, [settings], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future removeAccount(_i17.Account? account) => (super.noSuchMethod( + _i17.Future removeAccount(_i18.Account? account) => (super.noSuchMethod( Invocation.method( #removeAccount, [account], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i2.AccountSettings fromAccount(_i17.Account? account) => (super.noSuchMethod( + _i2.AccountSettings fromAccount(_i18.Account? account) => (super.noSuchMethod( Invocation.method( #fromAccount, [account], @@ -671,7 +785,7 @@ class MockAccountSettingsRepository extends _i1.Mock ), ) as _i2.AccountSettings); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i19.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -679,7 +793,7 @@ class MockAccountSettingsRepository extends _i1.Mock returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i19.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -707,9 +821,9 @@ class MockAccountSettingsRepository extends _i1.Mock /// A class which mocks [EmojiRepository]. /// /// See the documentation for Mockito's code generation for more information. -class MockEmojiRepository extends _i1.Mock implements _i20.EmojiRepository { +class MockEmojiRepository extends _i1.Mock implements _i21.EmojiRepository { @override - set emoji(List<_i20.EmojiRepositoryData>? _emoji) => super.noSuchMethod( + set emoji(List<_i21.EmojiRepositoryData>? _emoji) => super.noSuchMethod( Invocation.setter( #emoji, _emoji, @@ -717,34 +831,34 @@ class MockEmojiRepository extends _i1.Mock implements _i20.EmojiRepository { returnValueForMissingStub: null, ); @override - _i16.Future loadFromSourceIfNeed() => (super.noSuchMethod( + _i17.Future loadFromSourceIfNeed() => (super.noSuchMethod( Invocation.method( #loadFromSourceIfNeed, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future loadFromSource() => (super.noSuchMethod( + _i17.Future loadFromSource() => (super.noSuchMethod( Invocation.method( #loadFromSource, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future loadFromLocalCache() => (super.noSuchMethod( + _i17.Future loadFromLocalCache() => (super.noSuchMethod( Invocation.method( #loadFromLocalCache, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future> searchEmojis( + _i17.Future> searchEmojis( String? name, { int? limit = 30, }) => @@ -754,30 +868,30 @@ class MockEmojiRepository extends _i1.Mock implements _i20.EmojiRepository { [name], {#limit: limit}, ), - returnValue: _i16.Future>.value( - <_i21.MisskeyEmojiData>[]), + returnValue: _i17.Future>.value( + <_i22.MisskeyEmojiData>[]), returnValueForMissingStub: - _i16.Future>.value( - <_i21.MisskeyEmojiData>[]), - ) as _i16.Future>); + _i17.Future>.value( + <_i22.MisskeyEmojiData>[]), + ) as _i17.Future>); @override - List<_i21.MisskeyEmojiData> defaultEmojis({int? limit}) => + List<_i22.MisskeyEmojiData> defaultEmojis({int? limit}) => (super.noSuchMethod( Invocation.method( #defaultEmojis, [], {#limit: limit}, ), - returnValue: <_i21.MisskeyEmojiData>[], - returnValueForMissingStub: <_i21.MisskeyEmojiData>[], - ) as List<_i21.MisskeyEmojiData>); + returnValue: <_i22.MisskeyEmojiData>[], + returnValueForMissingStub: <_i22.MisskeyEmojiData>[], + ) as List<_i22.MisskeyEmojiData>); } /// A class which mocks [GeneralSettingsRepository]. /// /// See the documentation for Mockito's code generation for more information. class MockGeneralSettingsRepository extends _i1.Mock - implements _i22.GeneralSettingsRepository { + implements _i23.GeneralSettingsRepository { @override _i3.GeneralSettings get settings => (super.noSuchMethod( Invocation.getter(#settings), @@ -797,26 +911,26 @@ class MockGeneralSettingsRepository extends _i1.Mock returnValueForMissingStub: false, ) as bool); @override - _i16.Future load() => (super.noSuchMethod( + _i17.Future load() => (super.noSuchMethod( Invocation.method( #load, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future update(_i3.GeneralSettings? settings) => + _i17.Future update(_i3.GeneralSettings? settings) => (super.noSuchMethod( Invocation.method( #update, [settings], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i19.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -824,7 +938,7 @@ class MockGeneralSettingsRepository extends _i1.Mock returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i19.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1201,36 +1315,36 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { returnValueForMissingStub: null, ); @override - _i16.Future> announcements( + _i17.Future> announcements( _i6.AnnouncementsRequest? request) => (super.noSuchMethod( Invocation.method( #announcements, [request], ), - returnValue: _i16.Future>.value( + returnValue: _i17.Future>.value( <_i6.AnnouncementsResponse>[]), returnValueForMissingStub: - _i16.Future>.value( + _i17.Future>.value( <_i6.AnnouncementsResponse>[]), - ) as _i16.Future>); + ) as _i17.Future>); @override - _i16.Future> endpoints() => (super.noSuchMethod( + _i17.Future> endpoints() => (super.noSuchMethod( Invocation.method( #endpoints, [], ), - returnValue: _i16.Future>.value([]), - returnValueForMissingStub: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i17.Future>.value([]), + returnValueForMissingStub: _i17.Future>.value([]), + ) as _i17.Future>); @override - _i16.Future<_i6.EmojisResponse> emojis() => (super.noSuchMethod( + _i17.Future<_i6.EmojisResponse> emojis() => (super.noSuchMethod( Invocation.method( #emojis, [], ), returnValue: - _i16.Future<_i6.EmojisResponse>.value(_FakeEmojisResponse_19( + _i17.Future<_i6.EmojisResponse>.value(_FakeEmojisResponse_19( this, Invocation.method( #emojis, @@ -1238,22 +1352,22 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { ), )), returnValueForMissingStub: - _i16.Future<_i6.EmojisResponse>.value(_FakeEmojisResponse_19( + _i17.Future<_i6.EmojisResponse>.value(_FakeEmojisResponse_19( this, Invocation.method( #emojis, [], ), )), - ) as _i16.Future<_i6.EmojisResponse>); + ) as _i17.Future<_i6.EmojisResponse>); @override - _i16.Future<_i6.EmojiResponse> emoji(_i6.EmojiRequest? request) => + _i17.Future<_i6.EmojiResponse> emoji(_i6.EmojiRequest? request) => (super.noSuchMethod( Invocation.method( #emoji, [request], ), - returnValue: _i16.Future<_i6.EmojiResponse>.value(_FakeEmojiResponse_20( + returnValue: _i17.Future<_i6.EmojiResponse>.value(_FakeEmojiResponse_20( this, Invocation.method( #emoji, @@ -1261,21 +1375,21 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { ), )), returnValueForMissingStub: - _i16.Future<_i6.EmojiResponse>.value(_FakeEmojiResponse_20( + _i17.Future<_i6.EmojiResponse>.value(_FakeEmojiResponse_20( this, Invocation.method( #emoji, [request], ), )), - ) as _i16.Future<_i6.EmojiResponse>); + ) as _i17.Future<_i6.EmojiResponse>); @override - _i16.Future<_i6.MetaResponse> meta() => (super.noSuchMethod( + _i17.Future<_i6.MetaResponse> meta() => (super.noSuchMethod( Invocation.method( #meta, [], ), - returnValue: _i16.Future<_i6.MetaResponse>.value(_FakeMetaResponse_21( + returnValue: _i17.Future<_i6.MetaResponse>.value(_FakeMetaResponse_21( this, Invocation.method( #meta, @@ -1283,21 +1397,21 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { ), )), returnValueForMissingStub: - _i16.Future<_i6.MetaResponse>.value(_FakeMetaResponse_21( + _i17.Future<_i6.MetaResponse>.value(_FakeMetaResponse_21( this, Invocation.method( #meta, [], ), )), - ) as _i16.Future<_i6.MetaResponse>); + ) as _i17.Future<_i6.MetaResponse>); @override - _i16.Future<_i7.StatsResponse> stats() => (super.noSuchMethod( + _i17.Future<_i7.StatsResponse> stats() => (super.noSuchMethod( Invocation.method( #stats, [], ), - returnValue: _i16.Future<_i7.StatsResponse>.value(_FakeStatsResponse_22( + returnValue: _i17.Future<_i7.StatsResponse>.value(_FakeStatsResponse_22( this, Invocation.method( #stats, @@ -1305,21 +1419,21 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { ), )), returnValueForMissingStub: - _i16.Future<_i7.StatsResponse>.value(_FakeStatsResponse_22( + _i17.Future<_i7.StatsResponse>.value(_FakeStatsResponse_22( this, Invocation.method( #stats, [], ), )), - ) as _i16.Future<_i7.StatsResponse>); + ) as _i17.Future<_i7.StatsResponse>); @override - _i16.Future<_i8.PingResponse> ping() => (super.noSuchMethod( + _i17.Future<_i8.PingResponse> ping() => (super.noSuchMethod( Invocation.method( #ping, [], ), - returnValue: _i16.Future<_i8.PingResponse>.value(_FakePingResponse_23( + returnValue: _i17.Future<_i8.PingResponse>.value(_FakePingResponse_23( this, Invocation.method( #ping, @@ -1327,21 +1441,21 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { ), )), returnValueForMissingStub: - _i16.Future<_i8.PingResponse>.value(_FakePingResponse_23( + _i17.Future<_i8.PingResponse>.value(_FakePingResponse_23( this, Invocation.method( #ping, [], ), )), - ) as _i16.Future<_i8.PingResponse>); + ) as _i17.Future<_i8.PingResponse>); @override - _i16.Future<_i6.ServerInfoResponse> serverInfo() => (super.noSuchMethod( + _i17.Future<_i6.ServerInfoResponse> serverInfo() => (super.noSuchMethod( Invocation.method( #serverInfo, [], ), - returnValue: _i16.Future<_i6.ServerInfoResponse>.value( + returnValue: _i17.Future<_i6.ServerInfoResponse>.value( _FakeServerInfoResponse_24( this, Invocation.method( @@ -1349,7 +1463,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { [], ), )), - returnValueForMissingStub: _i16.Future<_i6.ServerInfoResponse>.value( + returnValueForMissingStub: _i17.Future<_i6.ServerInfoResponse>.value( _FakeServerInfoResponse_24( this, Invocation.method( @@ -1357,15 +1471,15 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { [], ), )), - ) as _i16.Future<_i6.ServerInfoResponse>); + ) as _i17.Future<_i6.ServerInfoResponse>); @override - _i16.Future<_i6.GetOnlineUsersCountResponse> getOnlineUsersCount() => + _i17.Future<_i6.GetOnlineUsersCountResponse> getOnlineUsersCount() => (super.noSuchMethod( Invocation.method( #getOnlineUsersCount, [], ), - returnValue: _i16.Future<_i6.GetOnlineUsersCountResponse>.value( + returnValue: _i17.Future<_i6.GetOnlineUsersCountResponse>.value( _FakeGetOnlineUsersCountResponse_25( this, Invocation.method( @@ -1374,7 +1488,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { ), )), returnValueForMissingStub: - _i16.Future<_i6.GetOnlineUsersCountResponse>.value( + _i17.Future<_i6.GetOnlineUsersCountResponse>.value( _FakeGetOnlineUsersCountResponse_25( this, Invocation.method( @@ -1382,36 +1496,40 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { [], ), )), - ) as _i16.Future<_i6.GetOnlineUsersCountResponse>); + ) as _i17.Future<_i6.GetOnlineUsersCountResponse>); @override - _i16.Future> pinnedUsers() => (super.noSuchMethod( + _i17.Future> pinnedUsers() => (super.noSuchMethod( Invocation.method( #pinnedUsers, [], ), - returnValue: _i16.Future>.value(<_i6.User>[]), + returnValue: _i17.Future>.value(<_i6.User>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.User>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i6.User>[]), + ) as _i17.Future>); @override _i6.SocketController homeTimelineStream({ - _i16.FutureOr Function(_i6.Note)? onNoteReceived, - _i16.FutureOr Function( + _i17.FutureOr Function(_i6.Note)? onNoteReceived, + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onReacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onUnreacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, DateTime, )? onDeleted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineVoted, )? onVoted, + _i17.FutureOr Function( + String, + _i6.NoteEdited, + )? onUpdated, }) => (super.noSuchMethod( Invocation.method( @@ -1423,6 +1541,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), returnValue: _FakeSocketController_26( @@ -1436,6 +1555,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), @@ -1450,29 +1570,34 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), ) as _i6.SocketController); @override _i6.SocketController localTimelineStream({ - _i16.FutureOr Function(_i6.Note)? onNoteReceived, - _i16.FutureOr Function( + _i17.FutureOr Function(_i6.Note)? onNoteReceived, + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onReacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onUnreacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, DateTime, )? onDeleted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineVoted, )? onVoted, + _i17.FutureOr Function( + String, + _i6.NoteEdited, + )? onUpdated, }) => (super.noSuchMethod( Invocation.method( @@ -1484,6 +1609,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), returnValue: _FakeSocketController_26( @@ -1497,6 +1623,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), @@ -1511,29 +1638,34 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), ) as _i6.SocketController); @override _i6.SocketController globalTimelineStream({ - _i16.FutureOr Function(_i6.Note)? onNoteReceived, - _i16.FutureOr Function( + _i17.FutureOr Function(_i6.Note)? onNoteReceived, + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onReacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onUnreacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, DateTime, )? onDeleted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineVoted, )? onVoted, + _i17.FutureOr Function( + String, + _i6.NoteEdited, + )? onUpdated, }) => (super.noSuchMethod( Invocation.method( @@ -1545,6 +1677,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), returnValue: _FakeSocketController_26( @@ -1558,6 +1691,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), @@ -1572,29 +1706,34 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), ) as _i6.SocketController); @override _i6.SocketController hybridTimelineStream({ - _i16.FutureOr Function(_i6.Note)? onNoteReceived, - _i16.FutureOr Function( + _i17.FutureOr Function(_i6.Note)? onNoteReceived, + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onReacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onUnreacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, DateTime, )? onDeleted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineVoted, )? onVoted, + _i17.FutureOr Function( + String, + _i6.NoteEdited, + )? onUpdated, }) => (super.noSuchMethod( Invocation.method( @@ -1606,6 +1745,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), returnValue: _FakeSocketController_26( @@ -1619,6 +1759,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), @@ -1633,6 +1774,72 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, + }, + ), + ), + ) as _i6.SocketController); + @override + _i6.SocketController roleTimelineStream({ + required String? roleId, + _i17.FutureOr Function(_i6.Note)? onNoteReceived, + _i17.FutureOr Function( + String, + _i6.TimelineReacted, + )? onReacted, + _i17.FutureOr Function( + String, + _i6.TimelineReacted, + )? onUnreacted, + _i17.FutureOr Function( + String, + DateTime, + )? onDeleted, + _i17.FutureOr Function( + String, + _i6.TimelineVoted, + )? onVoted, + }) => + (super.noSuchMethod( + Invocation.method( + #roleTimelineStream, + [], + { + #roleId: roleId, + #onNoteReceived: onNoteReceived, + #onReacted: onReacted, + #onUnreacted: onUnreacted, + #onDeleted: onDeleted, + #onVoted: onVoted, + }, + ), + returnValue: _FakeSocketController_26( + this, + Invocation.method( + #roleTimelineStream, + [], + { + #roleId: roleId, + #onNoteReceived: onNoteReceived, + #onReacted: onReacted, + #onUnreacted: onUnreacted, + #onDeleted: onDeleted, + #onVoted: onVoted, + }, + ), + ), + returnValueForMissingStub: _FakeSocketController_26( + this, + Invocation.method( + #roleTimelineStream, + [], + { + #roleId: roleId, + #onNoteReceived: onNoteReceived, + #onReacted: onReacted, + #onUnreacted: onUnreacted, + #onDeleted: onDeleted, + #onVoted: onVoted, }, ), ), @@ -1640,23 +1847,27 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { @override _i6.SocketController channelStream({ required String? channelId, - _i16.FutureOr Function(_i6.Note)? onNoteReceived, - _i16.FutureOr Function( + _i17.FutureOr Function(_i6.Note)? onNoteReceived, + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onReacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onUnreacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, DateTime, )? onDeleted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineVoted, )? onVoted, + _i17.FutureOr Function( + String, + _i6.NoteEdited, + )? onUpdated, }) => (super.noSuchMethod( Invocation.method( @@ -1669,6 +1880,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), returnValue: _FakeSocketController_26( @@ -1683,6 +1895,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), @@ -1698,6 +1911,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), @@ -1705,19 +1919,23 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { @override _i6.SocketController userListStream({ required String? listId, - _i16.FutureOr Function(_i6.Note)? onNoteReceived, - _i16.FutureOr Function(_i6.User)? onUserAdded, - _i16.FutureOr Function(_i6.User)? onUserRemoved, - _i16.FutureOr Function( + _i17.FutureOr Function(_i6.Note)? onNoteReceived, + _i17.FutureOr Function(_i6.User)? onUserAdded, + _i17.FutureOr Function(_i6.User)? onUserRemoved, + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onReacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onUnreacted, - _i16.FutureOr Function(DateTime)? onDeleted, - _i16.FutureOr Function( + _i17.FutureOr Function( + String, + _i6.NoteEdited, + )? onUpdated, + _i17.FutureOr Function(DateTime)? onDeleted, + _i17.FutureOr Function( String, _i6.TimelineVoted, )? onVoted, @@ -1733,6 +1951,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUserRemoved: onUserRemoved, #onReacted: onReacted, #onUnreacted: onUnreacted, + #onUpdated: onUpdated, #onDeleted: onDeleted, #onVoted: onVoted, }, @@ -1749,6 +1968,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUserRemoved: onUserRemoved, #onReacted: onReacted, #onUnreacted: onUnreacted, + #onUpdated: onUpdated, #onDeleted: onDeleted, #onVoted: onVoted, }, @@ -1766,6 +1986,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUserRemoved: onUserRemoved, #onReacted: onReacted, #onUnreacted: onUnreacted, + #onUpdated: onUpdated, #onDeleted: onDeleted, #onVoted: onVoted, }, @@ -1775,23 +1996,27 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { @override _i6.SocketController antennaStream({ required String? antennaId, - _i16.FutureOr Function(_i6.Note)? onNoteReceived, - _i16.FutureOr Function( + _i17.FutureOr Function(_i6.Note)? onNoteReceived, + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onReacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineReacted, )? onUnreacted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, DateTime, )? onDeleted, - _i16.FutureOr Function( + _i17.FutureOr Function( String, _i6.TimelineVoted, )? onVoted, + _i17.FutureOr Function( + String, + _i6.NoteEdited, + )? onUpdated, }) => (super.noSuchMethod( Invocation.method( @@ -1804,6 +2029,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), returnValue: _FakeSocketController_26( @@ -1818,6 +2044,7 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), @@ -1833,14 +2060,15 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { #onUnreacted: onUnreacted, #onDeleted: onDeleted, #onVoted: onVoted, + #onUpdated: onUpdated, }, ), ), ) as _i6.SocketController); @override _i6.SocketController serverStatsLogStream( - _i16.FutureOr Function(List<_i6.StatsLogResponse>)? onLogReceived, - _i16.FutureOr Function(_i6.StatsLogResponse)? onEventReceived, + _i17.FutureOr Function(List<_i6.StatsLogResponse>)? onLogReceived, + _i17.FutureOr Function(_i6.StatsLogResponse)? onEventReceived, ) => (super.noSuchMethod( Invocation.method( @@ -1873,9 +2101,9 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { ) as _i6.SocketController); @override _i6.SocketController queueStatsLogStream( - _i16.FutureOr Function(List<_i6.QueueStatsLogResponse>)? + _i17.FutureOr Function(List<_i6.QueueStatsLogResponse>)? onLogReceived, - _i16.FutureOr Function(_i6.QueueStatsLogResponse)? onEventReceived, + _i17.FutureOr Function(_i6.QueueStatsLogResponse)? onEventReceived, ) => (super.noSuchMethod( Invocation.method( @@ -1908,28 +2136,28 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { ) as _i6.SocketController); @override _i6.SocketController mainStream({ - _i16.FutureOr Function(_i6.Emoji)? onEmojiAdded, - _i16.FutureOr Function(Iterable<_i6.Emoji>)? onEmojiUpdated, - _i16.FutureOr Function(Iterable<_i6.Emoji>)? onEmojiDeleted, - _i16.FutureOr Function(_i6.AnnouncementsResponse)? + _i17.FutureOr Function(_i6.Emoji)? onEmojiAdded, + _i17.FutureOr Function(Iterable<_i6.Emoji>)? onEmojiUpdated, + _i17.FutureOr Function(Iterable<_i6.Emoji>)? onEmojiDeleted, + _i17.FutureOr Function(_i6.AnnouncementsResponse)? onAnnouncementCreated, - _i16.FutureOr Function(_i6.INotificationsResponse)? onNotification, - _i16.FutureOr Function(_i6.Note)? onMention, - _i16.FutureOr Function(_i6.Note)? onReply, - _i16.FutureOr Function(_i6.Note)? onRenote, - _i16.FutureOr Function(_i6.User)? onFollow, - _i16.FutureOr Function(_i6.User)? onFollowed, - _i16.FutureOr Function(_i6.User)? onUnfollow, - _i16.FutureOr Function(_i6.User)? onMeUpdated, - _i16.FutureOr Function()? onReadAllNotifications, - _i16.FutureOr Function(_i6.INotificationsResponse)? + _i17.FutureOr Function(_i6.INotificationsResponse)? onNotification, + _i17.FutureOr Function(_i6.Note)? onMention, + _i17.FutureOr Function(_i6.Note)? onReply, + _i17.FutureOr Function(_i6.Note)? onRenote, + _i17.FutureOr Function(_i6.User)? onFollow, + _i17.FutureOr Function(_i6.User)? onFollowed, + _i17.FutureOr Function(_i6.User)? onUnfollow, + _i17.FutureOr Function(_i6.User)? onMeUpdated, + _i17.FutureOr Function()? onReadAllNotifications, + _i17.FutureOr Function(_i6.INotificationsResponse)? onUnreadNotification, - _i16.FutureOr Function(String)? onUnreadMention, - _i16.FutureOr Function()? onReadAllUnreadMentions, - _i16.FutureOr Function(String)? onUnreadSpecifiedNote, - _i16.FutureOr Function()? onReadAllUnreadSpecifiedNotes, - _i16.FutureOr Function(_i6.User)? onReceiveFollowRequest, - _i16.FutureOr Function()? onReadAllAnnouncements, + _i17.FutureOr Function(String)? onUnreadMention, + _i17.FutureOr Function()? onReadAllUnreadMentions, + _i17.FutureOr Function(String)? onUnreadSpecifiedNote, + _i17.FutureOr Function()? onReadAllUnreadSpecifiedNotes, + _i17.FutureOr Function(_i6.User)? onReceiveFollowRequest, + _i17.FutureOr Function()? onReadAllAnnouncements, }) => (super.noSuchMethod( Invocation.method( @@ -2018,750 +2246,515 @@ class MockMisskey extends _i1.Mock implements _i6.Misskey { ), ) as _i6.SocketController); @override - _i16.Future startStreaming() => (super.noSuchMethod( + _i17.Future startStreaming() => (super.noSuchMethod( Invocation.method( #startStreaming, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); } -/// A class which mocks [MisskeyNotes]. +/// A class which mocks [MisskeyAntenna]. /// /// See the documentation for Mockito's code generation for more information. -class MockMisskeyNotes extends _i1.Mock implements _i6.MisskeyNotes { - @override - _i6.MisskeyNotesReactions get reactions => (super.noSuchMethod( - Invocation.getter(#reactions), - returnValue: _FakeMisskeyNotesReactions_27( - this, - Invocation.getter(#reactions), - ), - returnValueForMissingStub: _FakeMisskeyNotesReactions_27( - this, - Invocation.getter(#reactions), - ), - ) as _i6.MisskeyNotesReactions); - @override - _i6.MisskeyNotesFavorites get favorites => (super.noSuchMethod( - Invocation.getter(#favorites), - returnValue: _FakeMisskeyNotesFavorites_28( - this, - Invocation.getter(#favorites), - ), - returnValueForMissingStub: _FakeMisskeyNotesFavorites_28( - this, - Invocation.getter(#favorites), - ), - ) as _i6.MisskeyNotesFavorites); - @override - _i6.MisskeyNotesPolls get polls => (super.noSuchMethod( - Invocation.getter(#polls), - returnValue: _FakeMisskeyNotesPolls_29( - this, - Invocation.getter(#polls), - ), - returnValueForMissingStub: _FakeMisskeyNotesPolls_29( - this, - Invocation.getter(#polls), - ), - ) as _i6.MisskeyNotesPolls); - @override - _i6.MisskeyNotesThreadMuting get threadMuting => (super.noSuchMethod( - Invocation.getter(#threadMuting), - returnValue: _FakeMisskeyNotesThreadMuting_30( - this, - Invocation.getter(#threadMuting), - ), - returnValueForMissingStub: _FakeMisskeyNotesThreadMuting_30( - this, - Invocation.getter(#threadMuting), - ), - ) as _i6.MisskeyNotesThreadMuting); +class MockMisskeyAntenna extends _i1.Mock implements _i6.MisskeyAntenna { @override - _i16.Future create(_i6.NotesCreateRequest? request) => + _i17.Future<_i6.Antenna> create(_i6.AntennasCreateRequest? request) => (super.noSuchMethod( Invocation.method( #create, [request], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future<_i6.Antenna>.value(_FakeAntenna_27( + this, + Invocation.method( + #create, + [request], + ), + )), + returnValueForMissingStub: + _i17.Future<_i6.Antenna>.value(_FakeAntenna_27( + this, + Invocation.method( + #create, + [request], + ), + )), + ) as _i17.Future<_i6.Antenna>); @override - _i16.Future delete(_i6.NotesDeleteRequest? request) => + _i17.Future delete(_i6.AntennasDeleteRequest? request) => (super.noSuchMethod( Invocation.method( #delete, [request], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + @override + _i17.Future> list() => (super.noSuchMethod( + Invocation.method( + #list, + [], + ), + returnValue: _i17.Future>.value(<_i6.Antenna>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Antenna>[]), + ) as _i17.Future>); @override - _i16.Future> notes(_i6.NotesRequest? request) => + _i17.Future> notes(_i6.AntennasNotesRequest? request) => (super.noSuchMethod( Invocation.method( #notes, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), + returnValue: _i17.Future>.value(<_i6.Note>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); @override - _i16.Future<_i6.Note> show(_i6.NotesShowRequest? request) => + _i17.Future<_i6.Antenna> show(_i6.AntennasShowRequest? request) => (super.noSuchMethod( Invocation.method( #show, [request], ), - returnValue: _i16.Future<_i6.Note>.value(_FakeNote_31( + returnValue: _i17.Future<_i6.Antenna>.value(_FakeAntenna_27( this, Invocation.method( #show, [request], ), )), - returnValueForMissingStub: _i16.Future<_i6.Note>.value(_FakeNote_31( + returnValueForMissingStub: + _i17.Future<_i6.Antenna>.value(_FakeAntenna_27( this, Invocation.method( #show, [request], ), )), - ) as _i16.Future<_i6.Note>); + ) as _i17.Future<_i6.Antenna>); @override - _i16.Future> homeTimeline( - _i6.NotesTimelineRequest? request) => + _i17.Future update(_i6.AntennasUpdateRequest? request) => (super.noSuchMethod( Invocation.method( - #homeTimeline, + #update, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); +} + +/// A class which mocks [MisskeyAp]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyAp extends _i1.Mock implements _i6.MisskeyAp { @override - _i16.Future> localTimeline( - _i6.NotesLocalTimelineRequest? request) => + _i17.Future<_i6.ApShowResponse> show(_i6.ApShowRequest? request) => (super.noSuchMethod( Invocation.method( - #localTimeline, + #show, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), + returnValue: + _i17.Future<_i6.ApShowResponse>.value(_FakeApShowResponse_28( + this, + Invocation.method( + #show, + [request], + ), + )), returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + _i17.Future<_i6.ApShowResponse>.value(_FakeApShowResponse_28( + this, + Invocation.method( + #show, + [request], + ), + )), + ) as _i17.Future<_i6.ApShowResponse>); +} + +/// A class which mocks [MisskeyBlocking]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyBlocking extends _i1.Mock implements _i6.MisskeyBlocking { @override - _i16.Future> hybridTimeline( - _i6.NotesHybridTimelineRequest? request) => + _i17.Future create(_i6.BlockCreateRequest? request) => (super.noSuchMethod( Invocation.method( - #hybridTimeline, + #create, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future> globalTimeline( - _i6.NotesGlobalTimelineRequest? request) => + _i17.Future delete(_i6.BlockDeleteRequest? request) => (super.noSuchMethod( Invocation.method( - #globalTimeline, + #delete, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); +} + +/// A class which mocks [MisskeyChannels]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyChannels extends _i1.Mock implements _i6.MisskeyChannels { @override - _i16.Future> userListTimeline( - _i6.UserListTimelineRequest? request) => + _i17.Future> timeline( + _i6.ChannelsTimelineRequest? request) => (super.noSuchMethod( Invocation.method( - #userListTimeline, + #timeline, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), + returnValue: _i17.Future>.value(<_i6.Note>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); @override - _i16.Future<_i6.NotesStateResponse> state(_i6.NotesStateRequest? request) => + _i17.Future<_i6.CommunityChannel> show(_i6.ChannelsShowRequest? request) => (super.noSuchMethod( Invocation.method( - #state, + #show, [request], ), - returnValue: _i16.Future<_i6.NotesStateResponse>.value( - _FakeNotesStateResponse_32( + returnValue: + _i17.Future<_i6.CommunityChannel>.value(_FakeCommunityChannel_29( this, Invocation.method( - #state, + #show, [request], ), )), - returnValueForMissingStub: _i16.Future<_i6.NotesStateResponse>.value( - _FakeNotesStateResponse_32( + returnValueForMissingStub: + _i17.Future<_i6.CommunityChannel>.value(_FakeCommunityChannel_29( this, Invocation.method( - #state, + #show, [request], ), )), - ) as _i16.Future<_i6.NotesStateResponse>); - @override - _i16.Future> search(_i6.NotesSearchRequest? request) => - (super.noSuchMethod( - Invocation.method( - #search, - [request], - ), - returnValue: _i16.Future>.value(<_i6.Note>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); - @override - _i16.Future> searchByTag( - _i6.NotesSearchByTagRequest? request) => - (super.noSuchMethod( - Invocation.method( - #searchByTag, - [request], - ), - returnValue: _i16.Future>.value(<_i6.Note>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); - @override - _i16.Future> renotes(_i6.NotesRenoteRequest? request) => - (super.noSuchMethod( - Invocation.method( - #renotes, - [request], - ), - returnValue: _i16.Future>.value(<_i6.Note>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + ) as _i17.Future<_i6.CommunityChannel>); @override - _i16.Future> replies(_i6.NotesRepliesRequest? request) => - (super.noSuchMethod( - Invocation.method( - #replies, - [request], - ), - returnValue: _i16.Future>.value(<_i6.Note>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); - @override - _i16.Future> children(_i6.NotesChildrenRequest? request) => + _i17.Future> followed( + _i6.ChannelsFollowedRequest? request) => (super.noSuchMethod( Invocation.method( - #children, + #followed, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), + returnValue: _i17.Future>.value( + <_i6.CommunityChannel>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + _i17.Future>.value( + <_i6.CommunityChannel>[]), + ) as _i17.Future>); @override - _i16.Future> conversation( - _i6.NotesConversationRequest? request) => + _i17.Future> myFavorite( + _i6.ChannelsMyFavoriteRequest? request) => (super.noSuchMethod( Invocation.method( - #conversation, + #myFavorite, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), + returnValue: _i17.Future>.value( + <_i6.CommunityChannel>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + _i17.Future>.value( + <_i6.CommunityChannel>[]), + ) as _i17.Future>); @override - _i16.Future> featured(_i6.NotesFeaturedRequest? request) => - (super.noSuchMethod( + _i17.Future> featured() => (super.noSuchMethod( Invocation.method( #featured, - [request], + [], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), + returnValue: _i17.Future>.value( + <_i6.CommunityChannel>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + _i17.Future>.value( + <_i6.CommunityChannel>[]), + ) as _i17.Future>); @override - _i16.Future> mentions(_i6.NotesMentionsRequest? request) => + _i17.Future> owned( + _i6.ChannelsOwnedRequest? request) => (super.noSuchMethod( Invocation.method( - #mentions, + #owned, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), + returnValue: _i17.Future>.value( + <_i6.CommunityChannel>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + _i17.Future>.value( + <_i6.CommunityChannel>[]), + ) as _i17.Future>); @override - _i16.Future> clips(_i6.NotesClipsRequest? request) => + _i17.Future> search( + _i6.ChannelsSearchRequest? request) => (super.noSuchMethod( Invocation.method( - #clips, + #search, [request], ), - returnValue: _i16.Future>.value(<_i6.Clip>[]), + returnValue: _i17.Future>.value( + <_i6.CommunityChannel>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.Clip>[]), - ) as _i16.Future>); - @override - _i16.Future unrenote(_i6.NotesUnrenoteRequest? request) => - (super.noSuchMethod( - Invocation.method( - #unrenote, - [request], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); -} - -/// A class which mocks [MisskeyNotesFavorites]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockMisskeyNotesFavorites extends _i1.Mock - implements _i6.MisskeyNotesFavorites { + _i17.Future>.value( + <_i6.CommunityChannel>[]), + ) as _i17.Future>); @override - _i16.Future create(_i6.NotesFavoritesCreateRequest? request) => + _i17.Future<_i6.CommunityChannel> create( + _i6.ChannelsCreateRequest? request) => (super.noSuchMethod( Invocation.method( #create, [request], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future delete(_i6.NotesFavoritesDeleteRequest? request) => - (super.noSuchMethod( - Invocation.method( - #delete, - [request], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); -} - -/// A class which mocks [MisskeyUsers]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockMisskeyUsers extends _i1.Mock implements _i6.MisskeyUsers { - @override - _i6.MisskeyUsersLists get list => (super.noSuchMethod( - Invocation.getter(#list), - returnValue: _FakeMisskeyUsersLists_33( - this, - Invocation.getter(#list), - ), - returnValueForMissingStub: _FakeMisskeyUsersLists_33( - this, - Invocation.getter(#list), - ), - ) as _i6.MisskeyUsersLists); - @override - _i16.Future<_i6.UsersShowResponse> show(_i6.UsersShowRequest? request) => - (super.noSuchMethod( - Invocation.method( - #show, - [request], - ), returnValue: - _i16.Future<_i6.UsersShowResponse>.value(_FakeUsersShowResponse_34( + _i17.Future<_i6.CommunityChannel>.value(_FakeCommunityChannel_29( this, Invocation.method( - #show, + #create, [request], ), )), returnValueForMissingStub: - _i16.Future<_i6.UsersShowResponse>.value(_FakeUsersShowResponse_34( + _i17.Future<_i6.CommunityChannel>.value(_FakeCommunityChannel_29( this, Invocation.method( - #show, + #create, [request], ), )), - ) as _i16.Future<_i6.UsersShowResponse>); + ) as _i17.Future<_i6.CommunityChannel>); @override - _i16.Future> showByIds( - _i6.UsersShowByIdsRequest? request) => + _i17.Future update(_i6.ChannelsUpdateRequest? request) => (super.noSuchMethod( Invocation.method( - #showByIds, + #update, [request], ), - returnValue: _i16.Future>.value( - <_i6.UsersShowResponse>[]), - returnValueForMissingStub: - _i16.Future>.value( - <_i6.UsersShowResponse>[]), - ) as _i16.Future>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future<_i6.UsersShowResponse> showByName( - _i6.UsersShowByUserNameRequest? request) => + _i17.Future favorite(_i6.ChannelsFavoriteRequest? request) => (super.noSuchMethod( Invocation.method( - #showByName, + #favorite, [request], ), - returnValue: - _i16.Future<_i6.UsersShowResponse>.value(_FakeUsersShowResponse_34( - this, - Invocation.method( - #showByName, - [request], - ), - )), - returnValueForMissingStub: - _i16.Future<_i6.UsersShowResponse>.value(_FakeUsersShowResponse_34( - this, - Invocation.method( - #showByName, - [request], - ), - )), - ) as _i16.Future<_i6.UsersShowResponse>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future> notes(_i6.UsersNotesRequest? request) => + _i17.Future unfavorite(_i6.ChannelsUnfavoriteRequest? request) => (super.noSuchMethod( Invocation.method( - #notes, + #unfavorite, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future> clips(_i6.UsersClipsRequest? request) => + _i17.Future follow(_i6.ChannelsFollowRequest? request) => (super.noSuchMethod( Invocation.method( - #clips, + #follow, [request], ), - returnValue: _i16.Future>.value(<_i6.Clip>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Clip>[]), - ) as _i16.Future>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future> followers( - _i6.UsersFollowersRequest? request) => + _i17.Future unfollow(_i6.ChannelsUnfollowRequest? request) => (super.noSuchMethod( Invocation.method( - #followers, + #unfollow, [request], ), - returnValue: - _i16.Future>.value(<_i6.Following>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Following>[]), - ) as _i16.Future>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); +} + +/// A class which mocks [MisskeyClips]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyClips extends _i1.Mock implements _i6.MisskeyClips { @override - _i16.Future> following( - _i6.UsersFollowingRequest? request) => - (super.noSuchMethod( + _i17.Future> list() => (super.noSuchMethod( Invocation.method( - #following, - [request], + #list, + [], ), - returnValue: - _i16.Future>.value(<_i6.Following>[]), + returnValue: _i17.Future>.value(<_i6.Clip>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.Following>[]), - ) as _i16.Future>); - @override - _i16.Future reportAbuse(_i6.UsersReportAbuseRequest? request) => - (super.noSuchMethod( - Invocation.method( - #reportAbuse, - [request], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + _i17.Future>.value(<_i6.Clip>[]), + ) as _i17.Future>); @override - _i16.Future> reactions( - _i6.UsersReactionsRequest? request) => - (super.noSuchMethod( + _i17.Future> myFavorites() => (super.noSuchMethod( Invocation.method( - #reactions, - [request], + #myFavorites, + [], ), - returnValue: _i16.Future>.value( - <_i6.UsersReactionsResponse>[]), + returnValue: _i17.Future>.value(<_i6.Clip>[]), returnValueForMissingStub: - _i16.Future>.value( - <_i6.UsersReactionsResponse>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i6.Clip>[]), + ) as _i17.Future>); @override - _i16.Future> search(_i6.UsersSearchRequest? request) => + _i17.Future> notes(_i6.ClipsNotesRequest? request) => (super.noSuchMethod( Invocation.method( - #search, + #notes, [request], ), - returnValue: _i16.Future>.value(<_i6.User>[]), + returnValue: _i17.Future>.value(<_i6.Note>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.User>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); @override - _i16.Future> recommendation( - _i6.UsersRecommendationRequest? request) => + _i17.Future addNote(_i6.ClipsAddNoteRequest? request) => (super.noSuchMethod( Invocation.method( - #recommendation, + #addNote, [request], ), - returnValue: _i16.Future>.value(<_i6.User>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.User>[]), - ) as _i16.Future>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future> users(_i6.UsersUsersRequest? request) => + _i17.Future removeNote(_i6.ClipsRemoveNoteRequest? request) => (super.noSuchMethod( Invocation.method( - #users, + #removeNote, [request], ), - returnValue: _i16.Future>.value(<_i6.User>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.User>[]), - ) as _i16.Future>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future updateMemo(_i6.UsersUpdateMemoRequest? request) => + _i17.Future<_i6.Clip> create(_i6.ClipsCreateRequest? request) => (super.noSuchMethod( Invocation.method( - #updateMemo, + #create, [request], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future<_i6.Clip>.value(_FakeClip_30( + this, + Invocation.method( + #create, + [request], + ), + )), + returnValueForMissingStub: _i17.Future<_i6.Clip>.value(_FakeClip_30( + this, + Invocation.method( + #create, + [request], + ), + )), + ) as _i17.Future<_i6.Clip>); @override - _i16.Future> flashs(_i6.UsersFlashsRequest? request) => + _i17.Future delete(_i6.ClipsDeleteRequest? request) => (super.noSuchMethod( Invocation.method( - #flashs, + #delete, [request], ), - returnValue: _i16.Future>.value(<_i23.Flash>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i23.Flash>[]), - ) as _i16.Future>); -} - -/// A class which mocks [MisskeyChannels]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockMisskeyChannels extends _i1.Mock implements _i6.MisskeyChannels { + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future> timeline( - _i6.ChannelsTimelineRequest? request) => + _i17.Future<_i6.Clip> update(_i6.ClipsUpdateRequest? request) => (super.noSuchMethod( Invocation.method( - #timeline, + #update, [request], ), - returnValue: _i16.Future>.value(<_i6.Note>[]), - returnValueForMissingStub: - _i16.Future>.value(<_i6.Note>[]), - ) as _i16.Future>); + returnValue: _i17.Future<_i6.Clip>.value(_FakeClip_30( + this, + Invocation.method( + #update, + [request], + ), + )), + returnValueForMissingStub: _i17.Future<_i6.Clip>.value(_FakeClip_30( + this, + Invocation.method( + #update, + [request], + ), + )), + ) as _i17.Future<_i6.Clip>); @override - _i16.Future<_i6.CommunityChannel> show(_i6.ChannelsShowRequest? request) => + _i17.Future<_i6.Clip> show(_i6.ClipsShowRequest? request) => (super.noSuchMethod( Invocation.method( #show, [request], ), - returnValue: - _i16.Future<_i6.CommunityChannel>.value(_FakeCommunityChannel_35( + returnValue: _i17.Future<_i6.Clip>.value(_FakeClip_30( this, Invocation.method( #show, [request], ), )), - returnValueForMissingStub: - _i16.Future<_i6.CommunityChannel>.value(_FakeCommunityChannel_35( + returnValueForMissingStub: _i17.Future<_i6.Clip>.value(_FakeClip_30( this, Invocation.method( #show, [request], ), )), - ) as _i16.Future<_i6.CommunityChannel>); + ) as _i17.Future<_i6.Clip>); @override - _i16.Future> followed( - _i6.ChannelsFollowedRequest? request) => + _i17.Future favorite(_i6.ClipsFavoriteRequest? request) => (super.noSuchMethod( Invocation.method( - #followed, + #favorite, [request], ), - returnValue: _i16.Future>.value( - <_i6.CommunityChannel>[]), - returnValueForMissingStub: - _i16.Future>.value( - <_i6.CommunityChannel>[]), - ) as _i16.Future>); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future> myFavorite( - _i6.ChannelsMyFavoriteRequest? request) => + _i17.Future unfavorite(_i6.ClipsUnfavoriteRequest? request) => (super.noSuchMethod( Invocation.method( - #myFavorite, + #unfavorite, [request], ), - returnValue: _i16.Future>.value( - <_i6.CommunityChannel>[]), - returnValueForMissingStub: - _i16.Future>.value( - <_i6.CommunityChannel>[]), - ) as _i16.Future>); - @override - _i16.Future> featured() => (super.noSuchMethod( - Invocation.method( - #featured, - [], - ), - returnValue: _i16.Future>.value( - <_i6.CommunityChannel>[]), - returnValueForMissingStub: - _i16.Future>.value( - <_i6.CommunityChannel>[]), - ) as _i16.Future>); - @override - _i16.Future> owned( - _i6.ChannelsOwnedRequest? request) => - (super.noSuchMethod( - Invocation.method( - #owned, - [request], - ), - returnValue: _i16.Future>.value( - <_i6.CommunityChannel>[]), - returnValueForMissingStub: - _i16.Future>.value( - <_i6.CommunityChannel>[]), - ) as _i16.Future>); - @override - _i16.Future> search( - _i6.ChannelsSearchRequest? request) => - (super.noSuchMethod( - Invocation.method( - #search, - [request], - ), - returnValue: _i16.Future>.value( - <_i6.CommunityChannel>[]), - returnValueForMissingStub: - _i16.Future>.value( - <_i6.CommunityChannel>[]), - ) as _i16.Future>); - @override - _i16.Future<_i6.CommunityChannel> create( - _i6.ChannelsCreateRequest? request) => - (super.noSuchMethod( - Invocation.method( - #create, - [request], - ), - returnValue: - _i16.Future<_i6.CommunityChannel>.value(_FakeCommunityChannel_35( - this, - Invocation.method( - #create, - [request], - ), - )), - returnValueForMissingStub: - _i16.Future<_i6.CommunityChannel>.value(_FakeCommunityChannel_35( - this, - Invocation.method( - #create, - [request], - ), - )), - ) as _i16.Future<_i6.CommunityChannel>); - @override - _i16.Future update(_i6.ChannelsUpdateRequest? request) => - (super.noSuchMethod( - Invocation.method( - #update, - [request], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future favorite(_i6.ChannelsFavoriteRequest? request) => - (super.noSuchMethod( - Invocation.method( - #favorite, - [request], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future unfavorite(_i6.ChannelsUnfavoriteRequest? request) => - (super.noSuchMethod( - Invocation.method( - #unfavorite, - [request], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future follow(_i6.ChannelsFollowRequest? request) => - (super.noSuchMethod( - Invocation.method( - #follow, - [request], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future unfollow(_i6.ChannelsUnfollowRequest? request) => - (super.noSuchMethod( - Invocation.method( - #unfollow, - [request], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); -} - -/// A class which mocks [MisskeyDrive]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockMisskeyDrive extends _i1.Mock implements _i6.MisskeyDrive { + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); +} + +/// A class which mocks [MisskeyDrive]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyDrive extends _i1.Mock implements _i6.MisskeyDrive { @override _i6.MisskeyDriveFiles get files => (super.noSuchMethod( Invocation.getter(#files), - returnValue: _FakeMisskeyDriveFiles_36( + returnValue: _FakeMisskeyDriveFiles_31( this, Invocation.getter(#files), ), - returnValueForMissingStub: _FakeMisskeyDriveFiles_36( + returnValueForMissingStub: _FakeMisskeyDriveFiles_31( this, Invocation.getter(#files), ), @@ -2769,11 +2762,11 @@ class MockMisskeyDrive extends _i1.Mock implements _i6.MisskeyDrive { @override _i6.MisskeyDriveFolders get folders => (super.noSuchMethod( Invocation.getter(#folders), - returnValue: _FakeMisskeyDriveFolders_37( + returnValue: _FakeMisskeyDriveFolders_32( this, Invocation.getter(#folders), ), - returnValueForMissingStub: _FakeMisskeyDriveFolders_37( + returnValueForMissingStub: _FakeMisskeyDriveFolders_32( this, Invocation.getter(#folders), ), @@ -2786,7 +2779,7 @@ class MockMisskeyDrive extends _i1.Mock implements _i6.MisskeyDrive { class MockMisskeyDriveFolders extends _i1.Mock implements _i6.MisskeyDriveFolders { @override - _i16.Future> folders( + _i17.Future> folders( _i6.DriveFoldersRequest? request) => (super.noSuchMethod( Invocation.method( @@ -2794,10 +2787,10 @@ class MockMisskeyDriveFolders extends _i1.Mock [request], ), returnValue: - _i16.Future>.value(<_i6.DriveFolder>[]), + _i17.Future>.value(<_i6.DriveFolder>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.DriveFolder>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i6.DriveFolder>[]), + ) as _i17.Future>); } /// A class which mocks [MisskeyDriveFiles]. @@ -2805,9 +2798,9 @@ class MockMisskeyDriveFolders extends _i1.Mock /// See the documentation for Mockito's code generation for more information. class MockMisskeyDriveFiles extends _i1.Mock implements _i6.MisskeyDriveFiles { @override - _i16.Future<_i6.DriveFile> create( + _i17.Future<_i6.DriveFile> create( _i6.DriveFilesCreateRequest? request, - _i24.File? fileContent, + _i14.File? fileContent, ) => (super.noSuchMethod( Invocation.method( @@ -2817,7 +2810,7 @@ class MockMisskeyDriveFiles extends _i1.Mock implements _i6.MisskeyDriveFiles { fileContent, ], ), - returnValue: _i16.Future<_i6.DriveFile>.value(_FakeDriveFile_38( + returnValue: _i17.Future<_i6.DriveFile>.value(_FakeDriveFile_33( this, Invocation.method( #create, @@ -2828,7 +2821,7 @@ class MockMisskeyDriveFiles extends _i1.Mock implements _i6.MisskeyDriveFiles { ), )), returnValueForMissingStub: - _i16.Future<_i6.DriveFile>.value(_FakeDriveFile_38( + _i17.Future<_i6.DriveFile>.value(_FakeDriveFile_33( this, Invocation.method( #create, @@ -2838,11 +2831,11 @@ class MockMisskeyDriveFiles extends _i1.Mock implements _i6.MisskeyDriveFiles { ], ), )), - ) as _i16.Future<_i6.DriveFile>); + ) as _i17.Future<_i6.DriveFile>); @override - _i16.Future<_i6.DriveFile> createAsBinary( + _i17.Future<_i6.DriveFile> createAsBinary( _i6.DriveFilesCreateRequest? request, - _i25.Uint8List? fileContent, + _i24.Uint8List? fileContent, ) => (super.noSuchMethod( Invocation.method( @@ -2852,7 +2845,7 @@ class MockMisskeyDriveFiles extends _i1.Mock implements _i6.MisskeyDriveFiles { fileContent, ], ), - returnValue: _i16.Future<_i6.DriveFile>.value(_FakeDriveFile_38( + returnValue: _i17.Future<_i6.DriveFile>.value(_FakeDriveFile_33( this, Invocation.method( #createAsBinary, @@ -2863,7 +2856,7 @@ class MockMisskeyDriveFiles extends _i1.Mock implements _i6.MisskeyDriveFiles { ), )), returnValueForMissingStub: - _i16.Future<_i6.DriveFile>.value(_FakeDriveFile_38( + _i17.Future<_i6.DriveFile>.value(_FakeDriveFile_33( this, Invocation.method( #createAsBinary, @@ -2873,15 +2866,15 @@ class MockMisskeyDriveFiles extends _i1.Mock implements _i6.MisskeyDriveFiles { ], ), )), - ) as _i16.Future<_i6.DriveFile>); + ) as _i17.Future<_i6.DriveFile>); @override - _i16.Future<_i6.DriveFile> update(_i6.DriveFilesUpdateRequest? request) => + _i17.Future<_i6.DriveFile> update(_i6.DriveFilesUpdateRequest? request) => (super.noSuchMethod( Invocation.method( #update, [request], ), - returnValue: _i16.Future<_i6.DriveFile>.value(_FakeDriveFile_38( + returnValue: _i17.Future<_i6.DriveFile>.value(_FakeDriveFile_33( this, Invocation.method( #update, @@ -2889,38 +2882,38 @@ class MockMisskeyDriveFiles extends _i1.Mock implements _i6.MisskeyDriveFiles { ), )), returnValueForMissingStub: - _i16.Future<_i6.DriveFile>.value(_FakeDriveFile_38( + _i17.Future<_i6.DriveFile>.value(_FakeDriveFile_33( this, Invocation.method( #update, [request], ), )), - ) as _i16.Future<_i6.DriveFile>); + ) as _i17.Future<_i6.DriveFile>); @override - _i16.Future delete(_i6.DriveFilesDeleteRequest? request) => + _i17.Future delete(_i6.DriveFilesDeleteRequest? request) => (super.noSuchMethod( Invocation.method( #delete, [request], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future> files(_i6.DriveFilesRequest? request) => + _i17.Future> files(_i6.DriveFilesRequest? request) => (super.noSuchMethod( Invocation.method( #files, [request], ), returnValue: - _i16.Future>.value(<_i6.DriveFile>[]), + _i17.Future>.value(<_i6.DriveFile>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.DriveFile>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i6.DriveFile>[]), + ) as _i17.Future>); @override - _i16.Future> find( + _i17.Future> find( _i6.DriveFilesFindRequest? request) => (super.noSuchMethod( Invocation.method( @@ -2928,973 +2921,2585 @@ class MockMisskeyDriveFiles extends _i1.Mock implements _i6.MisskeyDriveFiles { [request], ), returnValue: - _i16.Future>.value(<_i6.DriveFile>[]), + _i17.Future>.value(<_i6.DriveFile>[]), returnValueForMissingStub: - _i16.Future>.value(<_i6.DriveFile>[]), - ) as _i16.Future>); + _i17.Future>.value(<_i6.DriveFile>[]), + ) as _i17.Future>); } -/// A class which mocks [Dio]. +/// A class which mocks [MisskeyFederation]. /// /// See the documentation for Mockito's code generation for more information. -class MockDio extends _i1.Mock implements _i26.Dio { +class MockMisskeyFederation extends _i1.Mock implements _i6.MisskeyFederation { @override - _i9.BaseOptions get options => (super.noSuchMethod( - Invocation.getter(#options), - returnValue: _FakeBaseOptions_39( - this, - Invocation.getter(#options), - ), - returnValueForMissingStub: _FakeBaseOptions_39( - this, - Invocation.getter(#options), - ), - ) as _i9.BaseOptions); - @override - set options(_i9.BaseOptions? _options) => super.noSuchMethod( - Invocation.setter( - #options, - _options, + _i17.Future<_i6.FederationShowInstanceResponse> showInstance( + _i6.FederationShowInstanceRequest? request) => + (super.noSuchMethod( + Invocation.method( + #showInstance, + [request], ), - returnValueForMissingStub: null, - ); - @override - _i10.HttpClientAdapter get httpClientAdapter => (super.noSuchMethod( - Invocation.getter(#httpClientAdapter), - returnValue: _FakeHttpClientAdapter_40( + returnValue: _i17.Future<_i6.FederationShowInstanceResponse>.value( + _FakeFederationShowInstanceResponse_34( this, - Invocation.getter(#httpClientAdapter), - ), - returnValueForMissingStub: _FakeHttpClientAdapter_40( + Invocation.method( + #showInstance, + [request], + ), + )), + returnValueForMissingStub: + _i17.Future<_i6.FederationShowInstanceResponse>.value( + _FakeFederationShowInstanceResponse_34( this, - Invocation.getter(#httpClientAdapter), - ), - ) as _i10.HttpClientAdapter); + Invocation.method( + #showInstance, + [request], + ), + )), + ) as _i17.Future<_i6.FederationShowInstanceResponse>); @override - set httpClientAdapter(_i10.HttpClientAdapter? _httpClientAdapter) => - super.noSuchMethod( - Invocation.setter( - #httpClientAdapter, - _httpClientAdapter, + _i17.Future> users(_i6.FederationUsersRequest? request) => + (super.noSuchMethod( + Invocation.method( + #users, + [request], ), - returnValueForMissingStub: null, - ); + returnValue: _i17.Future>.value(<_i6.User>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.User>[]), + ) as _i17.Future>); +} + +/// A class which mocks [MisskeyFollowing]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyFollowing extends _i1.Mock implements _i6.MisskeyFollowing { @override - _i11.Transformer get transformer => (super.noSuchMethod( - Invocation.getter(#transformer), - returnValue: _FakeTransformer_41( + _i6.MisskeyFollowingRequests get requests => (super.noSuchMethod( + Invocation.getter(#requests), + returnValue: _FakeMisskeyFollowingRequests_35( this, - Invocation.getter(#transformer), + Invocation.getter(#requests), ), - returnValueForMissingStub: _FakeTransformer_41( + returnValueForMissingStub: _FakeMisskeyFollowingRequests_35( this, - Invocation.getter(#transformer), + Invocation.getter(#requests), ), - ) as _i11.Transformer); + ) as _i6.MisskeyFollowingRequests); @override - set transformer(_i11.Transformer? _transformer) => super.noSuchMethod( - Invocation.setter( - #transformer, - _transformer, + _i17.Future<_i6.User> create(_i6.FollowingCreateRequest? request) => + (super.noSuchMethod( + Invocation.method( + #create, + [request], ), - returnValueForMissingStub: null, - ); - @override - _i12.Interceptors get interceptors => (super.noSuchMethod( - Invocation.getter(#interceptors), - returnValue: _FakeInterceptors_42( + returnValue: _i17.Future<_i6.User>.value(_FakeUser_36( this, - Invocation.getter(#interceptors), - ), - returnValueForMissingStub: _FakeInterceptors_42( + Invocation.method( + #create, + [request], + ), + )), + returnValueForMissingStub: _i17.Future<_i6.User>.value(_FakeUser_36( this, - Invocation.getter(#interceptors), - ), - ) as _i12.Interceptors); - @override - void close({bool? force = false}) => super.noSuchMethod( - Invocation.method( - #close, - [], - {#force: force}, - ), - returnValueForMissingStub: null, - ); + Invocation.method( + #create, + [request], + ), + )), + ) as _i17.Future<_i6.User>); @override - _i16.Future<_i13.Response> get( - String? path, { - Object? data, - Map? queryParameters, - _i9.Options? options, - _i27.CancelToken? cancelToken, - _i9.ProgressCallback? onReceiveProgress, - }) => + _i17.Future<_i6.User> delete(_i6.FollowingDeleteRequest? request) => (super.noSuchMethod( Invocation.method( - #get, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - #onReceiveProgress: onReceiveProgress, - }, + #delete, + [request], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: _i17.Future<_i6.User>.value(_FakeUser_36( this, Invocation.method( - #get, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - #onReceiveProgress: onReceiveProgress, - }, + #delete, + [request], ), )), - returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValueForMissingStub: _i17.Future<_i6.User>.value(_FakeUser_36( this, Invocation.method( - #get, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - #onReceiveProgress: onReceiveProgress, - }, + #delete, + [request], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i6.User>); @override - _i16.Future<_i13.Response> getUri( - Uri? uri, { - Object? data, - _i9.Options? options, - _i27.CancelToken? cancelToken, - _i9.ProgressCallback? onReceiveProgress, - }) => + _i17.Future<_i6.User> invalidate(_i6.FollowingInvalidateRequest? request) => (super.noSuchMethod( Invocation.method( - #getUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - #onReceiveProgress: onReceiveProgress, - }, + #invalidate, + [request], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: _i17.Future<_i6.User>.value(_FakeUser_36( this, Invocation.method( - #getUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - #onReceiveProgress: onReceiveProgress, - }, + #invalidate, + [request], ), )), - returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValueForMissingStub: _i17.Future<_i6.User>.value(_FakeUser_36( this, Invocation.method( - #getUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - #onReceiveProgress: onReceiveProgress, - }, + #invalidate, + [request], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i6.User>); +} + +/// A class which mocks [MisskeyHashtags]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyHashtags extends _i1.Mock implements _i6.MisskeyHashtags { @override - _i16.Future<_i13.Response> post( - String? path, { - Object? data, - Map? queryParameters, - _i9.Options? options, - _i27.CancelToken? cancelToken, - _i9.ProgressCallback? onSendProgress, + _i17.Future> list(_i6.HashtagsListRequest? request) => + (super.noSuchMethod( + Invocation.method( + #list, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Hashtag>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Hashtag>[]), + ) as _i17.Future>); + @override + _i17.Future> search(_i6.HashtagsSearchRequest? request) => + (super.noSuchMethod( + Invocation.method( + #search, + [request], + ), + returnValue: _i17.Future>.value([]), + returnValueForMissingStub: + _i17.Future>.value([]), + ) as _i17.Future>); + @override + _i17.Future<_i6.Hashtag> show(_i6.HashtagsShowRequest? request) => + (super.noSuchMethod( + Invocation.method( + #show, + [request], + ), + returnValue: _i17.Future<_i6.Hashtag>.value(_FakeHashtag_37( + this, + Invocation.method( + #show, + [request], + ), + )), + returnValueForMissingStub: + _i17.Future<_i6.Hashtag>.value(_FakeHashtag_37( + this, + Invocation.method( + #show, + [request], + ), + )), + ) as _i17.Future<_i6.Hashtag>); + @override + _i17.Future> trend() => + (super.noSuchMethod( + Invocation.method( + #trend, + [], + ), + returnValue: _i17.Future>.value( + <_i6.HashtagsTrendResponse>[]), + returnValueForMissingStub: + _i17.Future>.value( + <_i6.HashtagsTrendResponse>[]), + ) as _i17.Future>); + @override + _i17.Future> users(_i6.HashtagsUsersRequest? request) => + (super.noSuchMethod( + Invocation.method( + #users, + [request], + ), + returnValue: _i17.Future>.value(<_i6.User>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.User>[]), + ) as _i17.Future>); +} + +/// A class which mocks [MisskeyI]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyI extends _i1.Mock implements _i6.MisskeyI { + @override + _i17.Future<_i6.IResponse> i() => (super.noSuchMethod( + Invocation.method( + #i, + [], + ), + returnValue: _i17.Future<_i6.IResponse>.value(_FakeIResponse_38( + this, + Invocation.method( + #i, + [], + ), + )), + returnValueForMissingStub: + _i17.Future<_i6.IResponse>.value(_FakeIResponse_38( + this, + Invocation.method( + #i, + [], + ), + )), + ) as _i17.Future<_i6.IResponse>); + @override + _i17.Future> notifications( + _i6.INotificationsRequest? request) => + (super.noSuchMethod( + Invocation.method( + #notifications, + [request], + ), + returnValue: _i17.Future>.value( + <_i6.INotificationsResponse>[]), + returnValueForMissingStub: + _i17.Future>.value( + <_i6.INotificationsResponse>[]), + ) as _i17.Future>); + @override + _i17.Future readAnnouncement(_i6.IReadAnnouncementRequest? request) => + (super.noSuchMethod( + Invocation.method( + #readAnnouncement, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + @override + _i17.Future> favorites( + _i6.IFavoritesRequest? request) => + (super.noSuchMethod( + Invocation.method( + #favorites, + [request], + ), + returnValue: _i17.Future>.value( + <_i6.IFavoritesResponse>[]), + returnValueForMissingStub: + _i17.Future>.value( + <_i6.IFavoritesResponse>[]), + ) as _i17.Future>); + @override + _i17.Future<_i6.IResponse> update(_i6.IUpdateRequest? request) => + (super.noSuchMethod( + Invocation.method( + #update, + [request], + ), + returnValue: _i17.Future<_i6.IResponse>.value(_FakeIResponse_38( + this, + Invocation.method( + #update, + [request], + ), + )), + returnValueForMissingStub: + _i17.Future<_i6.IResponse>.value(_FakeIResponse_38( + this, + Invocation.method( + #update, + [request], + ), + )), + ) as _i17.Future<_i6.IResponse>); +} + +/// A class which mocks [MisskeyNotes]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyNotes extends _i1.Mock implements _i6.MisskeyNotes { + @override + _i6.MisskeyNotesReactions get reactions => (super.noSuchMethod( + Invocation.getter(#reactions), + returnValue: _FakeMisskeyNotesReactions_39( + this, + Invocation.getter(#reactions), + ), + returnValueForMissingStub: _FakeMisskeyNotesReactions_39( + this, + Invocation.getter(#reactions), + ), + ) as _i6.MisskeyNotesReactions); + @override + _i6.MisskeyNotesFavorites get favorites => (super.noSuchMethod( + Invocation.getter(#favorites), + returnValue: _FakeMisskeyNotesFavorites_40( + this, + Invocation.getter(#favorites), + ), + returnValueForMissingStub: _FakeMisskeyNotesFavorites_40( + this, + Invocation.getter(#favorites), + ), + ) as _i6.MisskeyNotesFavorites); + @override + _i6.MisskeyNotesPolls get polls => (super.noSuchMethod( + Invocation.getter(#polls), + returnValue: _FakeMisskeyNotesPolls_41( + this, + Invocation.getter(#polls), + ), + returnValueForMissingStub: _FakeMisskeyNotesPolls_41( + this, + Invocation.getter(#polls), + ), + ) as _i6.MisskeyNotesPolls); + @override + _i6.MisskeyNotesThreadMuting get threadMuting => (super.noSuchMethod( + Invocation.getter(#threadMuting), + returnValue: _FakeMisskeyNotesThreadMuting_42( + this, + Invocation.getter(#threadMuting), + ), + returnValueForMissingStub: _FakeMisskeyNotesThreadMuting_42( + this, + Invocation.getter(#threadMuting), + ), + ) as _i6.MisskeyNotesThreadMuting); + @override + _i17.Future create(_i6.NotesCreateRequest? request) => + (super.noSuchMethod( + Invocation.method( + #create, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + @override + _i17.Future update(_i6.NotesUpdateRequest? request) => + (super.noSuchMethod( + Invocation.method( + #update, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + @override + _i17.Future delete(_i6.NotesDeleteRequest? request) => + (super.noSuchMethod( + Invocation.method( + #delete, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + @override + _i17.Future> notes(_i6.NotesRequest? request) => + (super.noSuchMethod( + Invocation.method( + #notes, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future<_i6.Note> show(_i6.NotesShowRequest? request) => + (super.noSuchMethod( + Invocation.method( + #show, + [request], + ), + returnValue: _i17.Future<_i6.Note>.value(_FakeNote_43( + this, + Invocation.method( + #show, + [request], + ), + )), + returnValueForMissingStub: _i17.Future<_i6.Note>.value(_FakeNote_43( + this, + Invocation.method( + #show, + [request], + ), + )), + ) as _i17.Future<_i6.Note>); + @override + _i17.Future> homeTimeline( + _i6.NotesTimelineRequest? request) => + (super.noSuchMethod( + Invocation.method( + #homeTimeline, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> localTimeline( + _i6.NotesLocalTimelineRequest? request) => + (super.noSuchMethod( + Invocation.method( + #localTimeline, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> hybridTimeline( + _i6.NotesHybridTimelineRequest? request) => + (super.noSuchMethod( + Invocation.method( + #hybridTimeline, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> globalTimeline( + _i6.NotesGlobalTimelineRequest? request) => + (super.noSuchMethod( + Invocation.method( + #globalTimeline, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> userListTimeline( + _i6.UserListTimelineRequest? request) => + (super.noSuchMethod( + Invocation.method( + #userListTimeline, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future<_i6.NotesStateResponse> state(_i6.NotesStateRequest? request) => + (super.noSuchMethod( + Invocation.method( + #state, + [request], + ), + returnValue: _i17.Future<_i6.NotesStateResponse>.value( + _FakeNotesStateResponse_44( + this, + Invocation.method( + #state, + [request], + ), + )), + returnValueForMissingStub: _i17.Future<_i6.NotesStateResponse>.value( + _FakeNotesStateResponse_44( + this, + Invocation.method( + #state, + [request], + ), + )), + ) as _i17.Future<_i6.NotesStateResponse>); + @override + _i17.Future> search(_i6.NotesSearchRequest? request) => + (super.noSuchMethod( + Invocation.method( + #search, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> searchByTag( + _i6.NotesSearchByTagRequest? request) => + (super.noSuchMethod( + Invocation.method( + #searchByTag, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> renotes(_i6.NotesRenoteRequest? request) => + (super.noSuchMethod( + Invocation.method( + #renotes, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> replies(_i6.NotesRepliesRequest? request) => + (super.noSuchMethod( + Invocation.method( + #replies, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> children(_i6.NotesChildrenRequest? request) => + (super.noSuchMethod( + Invocation.method( + #children, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> conversation( + _i6.NotesConversationRequest? request) => + (super.noSuchMethod( + Invocation.method( + #conversation, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> featured(_i6.NotesFeaturedRequest? request) => + (super.noSuchMethod( + Invocation.method( + #featured, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> mentions(_i6.NotesMentionsRequest? request) => + (super.noSuchMethod( + Invocation.method( + #mentions, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> clips(_i6.NotesClipsRequest? request) => + (super.noSuchMethod( + Invocation.method( + #clips, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Clip>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Clip>[]), + ) as _i17.Future>); + @override + _i17.Future unrenote(_i6.NotesUnrenoteRequest? request) => + (super.noSuchMethod( + Invocation.method( + #unrenote, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); +} + +/// A class which mocks [MisskeyNotesFavorites]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyNotesFavorites extends _i1.Mock + implements _i6.MisskeyNotesFavorites { + @override + _i17.Future create(_i6.NotesFavoritesCreateRequest? request) => + (super.noSuchMethod( + Invocation.method( + #create, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + @override + _i17.Future delete(_i6.NotesFavoritesDeleteRequest? request) => + (super.noSuchMethod( + Invocation.method( + #delete, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); +} + +/// A class which mocks [MisskeyNotesPolls]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyNotesPolls extends _i1.Mock implements _i6.MisskeyNotesPolls { + @override + _i17.Future vote(_i6.NotesPollsVoteRequest? request) => + (super.noSuchMethod( + Invocation.method( + #vote, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + @override + _i17.Future> recommendation( + _i6.NotesPollsRecommendationRequest? request) => + (super.noSuchMethod( + Invocation.method( + #recommendation, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); +} + +/// A class which mocks [MisskeyRenoteMute]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyRenoteMute extends _i1.Mock implements _i6.MisskeyRenoteMute { + @override + _i17.Future create(_i6.RenoteMuteCreateRequest? request) => + (super.noSuchMethod( + Invocation.method( + #create, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + @override + _i17.Future delete(_i6.RenoteMuteDeleteRequest? request) => + (super.noSuchMethod( + Invocation.method( + #delete, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); +} + +/// A class which mocks [MisskeyRoles]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyRoles extends _i1.Mock implements _i6.MisskeyRoles { + @override + _i17.Future> list() => (super.noSuchMethod( + Invocation.method( + #list, + [], + ), + returnValue: _i17.Future>.value( + <_i6.RolesListResponse>[]), + returnValueForMissingStub: + _i17.Future>.value( + <_i6.RolesListResponse>[]), + ) as _i17.Future>); + @override + _i17.Future> users( + _i6.RolesUsersRequest? request) => + (super.noSuchMethod( + Invocation.method( + #users, + [request], + ), + returnValue: _i17.Future>.value( + <_i6.RolesUsersResponse>[]), + returnValueForMissingStub: + _i17.Future>.value( + <_i6.RolesUsersResponse>[]), + ) as _i17.Future>); + @override + _i17.Future<_i6.RolesListResponse> show(_i6.RolesShowRequest? request) => + (super.noSuchMethod( + Invocation.method( + #show, + [request], + ), + returnValue: + _i17.Future<_i6.RolesListResponse>.value(_FakeRolesListResponse_45( + this, + Invocation.method( + #show, + [request], + ), + )), + returnValueForMissingStub: + _i17.Future<_i6.RolesListResponse>.value(_FakeRolesListResponse_45( + this, + Invocation.method( + #show, + [request], + ), + )), + ) as _i17.Future<_i6.RolesListResponse>); + @override + _i17.Future> notes(_i6.RolesNotesRequest? request) => + (super.noSuchMethod( + Invocation.method( + #notes, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); +} + +/// A class which mocks [MisskeyUsers]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMisskeyUsers extends _i1.Mock implements _i6.MisskeyUsers { + @override + _i6.MisskeyUsersLists get list => (super.noSuchMethod( + Invocation.getter(#list), + returnValue: _FakeMisskeyUsersLists_46( + this, + Invocation.getter(#list), + ), + returnValueForMissingStub: _FakeMisskeyUsersLists_46( + this, + Invocation.getter(#list), + ), + ) as _i6.MisskeyUsersLists); + @override + _i17.Future<_i6.UsersShowResponse> show(_i6.UsersShowRequest? request) => + (super.noSuchMethod( + Invocation.method( + #show, + [request], + ), + returnValue: + _i17.Future<_i6.UsersShowResponse>.value(_FakeUsersShowResponse_47( + this, + Invocation.method( + #show, + [request], + ), + )), + returnValueForMissingStub: + _i17.Future<_i6.UsersShowResponse>.value(_FakeUsersShowResponse_47( + this, + Invocation.method( + #show, + [request], + ), + )), + ) as _i17.Future<_i6.UsersShowResponse>); + @override + _i17.Future> showByIds( + _i6.UsersShowByIdsRequest? request) => + (super.noSuchMethod( + Invocation.method( + #showByIds, + [request], + ), + returnValue: _i17.Future>.value( + <_i6.UsersShowResponse>[]), + returnValueForMissingStub: + _i17.Future>.value( + <_i6.UsersShowResponse>[]), + ) as _i17.Future>); + @override + _i17.Future<_i6.UsersShowResponse> showByName( + _i6.UsersShowByUserNameRequest? request) => + (super.noSuchMethod( + Invocation.method( + #showByName, + [request], + ), + returnValue: + _i17.Future<_i6.UsersShowResponse>.value(_FakeUsersShowResponse_47( + this, + Invocation.method( + #showByName, + [request], + ), + )), + returnValueForMissingStub: + _i17.Future<_i6.UsersShowResponse>.value(_FakeUsersShowResponse_47( + this, + Invocation.method( + #showByName, + [request], + ), + )), + ) as _i17.Future<_i6.UsersShowResponse>); + @override + _i17.Future> notes(_i6.UsersNotesRequest? request) => + (super.noSuchMethod( + Invocation.method( + #notes, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); + @override + _i17.Future> clips(_i6.UsersClipsRequest? request) => + (super.noSuchMethod( + Invocation.method( + #clips, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Clip>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Clip>[]), + ) as _i17.Future>); + @override + _i17.Future> followers( + _i6.UsersFollowersRequest? request) => + (super.noSuchMethod( + Invocation.method( + #followers, + [request], + ), + returnValue: + _i17.Future>.value(<_i6.Following>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Following>[]), + ) as _i17.Future>); + @override + _i17.Future> following( + _i6.UsersFollowingRequest? request) => + (super.noSuchMethod( + Invocation.method( + #following, + [request], + ), + returnValue: + _i17.Future>.value(<_i6.Following>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Following>[]), + ) as _i17.Future>); + @override + _i17.Future reportAbuse(_i6.UsersReportAbuseRequest? request) => + (super.noSuchMethod( + Invocation.method( + #reportAbuse, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + @override + _i17.Future> reactions( + _i6.UsersReactionsRequest? request) => + (super.noSuchMethod( + Invocation.method( + #reactions, + [request], + ), + returnValue: _i17.Future>.value( + <_i6.UsersReactionsResponse>[]), + returnValueForMissingStub: + _i17.Future>.value( + <_i6.UsersReactionsResponse>[]), + ) as _i17.Future>); + @override + _i17.Future> search(_i6.UsersSearchRequest? request) => + (super.noSuchMethod( + Invocation.method( + #search, + [request], + ), + returnValue: _i17.Future>.value(<_i6.User>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.User>[]), + ) as _i17.Future>); + @override + _i17.Future> recommendation( + _i6.UsersRecommendationRequest? request) => + (super.noSuchMethod( + Invocation.method( + #recommendation, + [request], + ), + returnValue: _i17.Future>.value(<_i6.User>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.User>[]), + ) as _i17.Future>); + @override + _i17.Future> users(_i6.UsersUsersRequest? request) => + (super.noSuchMethod( + Invocation.method( + #users, + [request], + ), + returnValue: _i17.Future>.value(<_i6.User>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.User>[]), + ) as _i17.Future>); + @override + _i17.Future updateMemo(_i6.UsersUpdateMemoRequest? request) => + (super.noSuchMethod( + Invocation.method( + #updateMemo, + [request], + ), + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); + @override + _i17.Future> flashs(_i6.UsersFlashsRequest? request) => + (super.noSuchMethod( + Invocation.method( + #flashs, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Flash>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Flash>[]), + ) as _i17.Future>); + @override + _i17.Future> featuredNotes( + _i6.UsersFeaturedNotesRequest? request) => + (super.noSuchMethod( + Invocation.method( + #featuredNotes, + [request], + ), + returnValue: _i17.Future>.value(<_i6.Note>[]), + returnValueForMissingStub: + _i17.Future>.value(<_i6.Note>[]), + ) as _i17.Future>); +} + +/// A class which mocks [Dio]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDio extends _i1.Mock implements _i25.Dio { + @override + _i9.BaseOptions get options => (super.noSuchMethod( + Invocation.getter(#options), + returnValue: _FakeBaseOptions_48( + this, + Invocation.getter(#options), + ), + returnValueForMissingStub: _FakeBaseOptions_48( + this, + Invocation.getter(#options), + ), + ) as _i9.BaseOptions); + @override + set options(_i9.BaseOptions? _options) => super.noSuchMethod( + Invocation.setter( + #options, + _options, + ), + returnValueForMissingStub: null, + ); + @override + _i10.HttpClientAdapter get httpClientAdapter => (super.noSuchMethod( + Invocation.getter(#httpClientAdapter), + returnValue: _FakeHttpClientAdapter_49( + this, + Invocation.getter(#httpClientAdapter), + ), + returnValueForMissingStub: _FakeHttpClientAdapter_49( + this, + Invocation.getter(#httpClientAdapter), + ), + ) as _i10.HttpClientAdapter); + @override + set httpClientAdapter(_i10.HttpClientAdapter? _httpClientAdapter) => + super.noSuchMethod( + Invocation.setter( + #httpClientAdapter, + _httpClientAdapter, + ), + returnValueForMissingStub: null, + ); + @override + _i11.Transformer get transformer => (super.noSuchMethod( + Invocation.getter(#transformer), + returnValue: _FakeTransformer_50( + this, + Invocation.getter(#transformer), + ), + returnValueForMissingStub: _FakeTransformer_50( + this, + Invocation.getter(#transformer), + ), + ) as _i11.Transformer); + @override + set transformer(_i11.Transformer? _transformer) => super.noSuchMethod( + Invocation.setter( + #transformer, + _transformer, + ), + returnValueForMissingStub: null, + ); + @override + _i12.Interceptors get interceptors => (super.noSuchMethod( + Invocation.getter(#interceptors), + returnValue: _FakeInterceptors_51( + this, + Invocation.getter(#interceptors), + ), + returnValueForMissingStub: _FakeInterceptors_51( + this, + Invocation.getter(#interceptors), + ), + ) as _i12.Interceptors); + @override + void close({bool? force = false}) => super.noSuchMethod( + Invocation.method( + #close, + [], + {#force: force}, + ), + returnValueForMissingStub: null, + ); + @override + _i17.Future<_i13.Response> get( + String? path, { + Object? data, + Map? queryParameters, + _i9.Options? options, + _i26.CancelToken? cancelToken, + _i9.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #get, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #get, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> getUri( + Uri? uri, { + Object? data, + _i9.Options? options, + _i26.CancelToken? cancelToken, + _i9.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #getUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #getUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #getUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> post( + String? path, { + Object? data, + Map? queryParameters, + _i9.Options? options, + _i26.CancelToken? cancelToken, + _i9.ProgressCallback? onSendProgress, + _i9.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #post, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #post, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> postUri( + Uri? uri, { + Object? data, + _i9.Options? options, + _i26.CancelToken? cancelToken, + _i9.ProgressCallback? onSendProgress, + _i9.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #postUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #postUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #postUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> put( + String? path, { + Object? data, + Map? queryParameters, + _i9.Options? options, + _i26.CancelToken? cancelToken, + _i9.ProgressCallback? onSendProgress, + _i9.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #put, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #put, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #put, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> putUri( + Uri? uri, { + Object? data, + _i9.Options? options, + _i26.CancelToken? cancelToken, + _i9.ProgressCallback? onSendProgress, + _i9.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #putUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #putUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #putUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> head( + String? path, { + Object? data, + Map? queryParameters, + _i9.Options? options, + _i26.CancelToken? cancelToken, + }) => + (super.noSuchMethod( + Invocation.method( + #head, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #head, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #head, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> headUri( + Uri? uri, { + Object? data, + _i9.Options? options, + _i26.CancelToken? cancelToken, + }) => + (super.noSuchMethod( + Invocation.method( + #headUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #headUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #headUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> delete( + String? path, { + Object? data, + Map? queryParameters, + _i9.Options? options, + _i26.CancelToken? cancelToken, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #delete, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #delete, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> deleteUri( + Uri? uri, { + Object? data, + _i9.Options? options, + _i26.CancelToken? cancelToken, + }) => + (super.noSuchMethod( + Invocation.method( + #deleteUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #deleteUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #deleteUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> patch( + String? path, { + Object? data, + Map? queryParameters, + _i9.Options? options, + _i26.CancelToken? cancelToken, + _i9.ProgressCallback? onSendProgress, + _i9.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #patch, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #patch, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #patch, + [path], + { + #data: data, + #queryParameters: queryParameters, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> patchUri( + Uri? uri, { + Object? data, + _i9.Options? options, + _i26.CancelToken? cancelToken, + _i9.ProgressCallback? onSendProgress, + _i9.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #patchUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #patchUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #patchUri, + [uri], + { + #data: data, + #options: options, + #cancelToken: cancelToken, + #onSendProgress: onSendProgress, + #onReceiveProgress: onReceiveProgress, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> download( + String? urlPath, + dynamic savePath, { + _i9.ProgressCallback? onReceiveProgress, + Map? queryParameters, + _i26.CancelToken? cancelToken, + bool? deleteOnError = true, + String? lengthHeader = r'content-length', + Object? data, + _i9.Options? options, + }) => + (super.noSuchMethod( + Invocation.method( + #download, + [ + urlPath, + savePath, + ], + { + #onReceiveProgress: onReceiveProgress, + #queryParameters: queryParameters, + #cancelToken: cancelToken, + #deleteOnError: deleteOnError, + #lengthHeader: lengthHeader, + #data: data, + #options: options, + }, + ), + returnValue: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #download, + [ + urlPath, + savePath, + ], + { + #onReceiveProgress: onReceiveProgress, + #queryParameters: queryParameters, + #cancelToken: cancelToken, + #deleteOnError: deleteOnError, + #lengthHeader: lengthHeader, + #data: data, + #options: options, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #download, + [ + urlPath, + savePath, + ], + { + #onReceiveProgress: onReceiveProgress, + #queryParameters: queryParameters, + #cancelToken: cancelToken, + #deleteOnError: deleteOnError, + #lengthHeader: lengthHeader, + #data: data, + #options: options, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> downloadUri( + Uri? uri, + dynamic savePath, { _i9.ProgressCallback? onReceiveProgress, + _i26.CancelToken? cancelToken, + bool? deleteOnError = true, + String? lengthHeader = r'content-length', + Object? data, + _i9.Options? options, }) => (super.noSuchMethod( Invocation.method( - #post, + #downloadUri, + [ + uri, + savePath, + ], + { + #onReceiveProgress: onReceiveProgress, + #cancelToken: cancelToken, + #deleteOnError: deleteOnError, + #lengthHeader: lengthHeader, + #data: data, + #options: options, + }, + ), + returnValue: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #downloadUri, + [ + uri, + savePath, + ], + { + #onReceiveProgress: onReceiveProgress, + #cancelToken: cancelToken, + #deleteOnError: deleteOnError, + #lengthHeader: lengthHeader, + #data: data, + #options: options, + }, + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #downloadUri, + [ + uri, + savePath, + ], + { + #onReceiveProgress: onReceiveProgress, + #cancelToken: cancelToken, + #deleteOnError: deleteOnError, + #lengthHeader: lengthHeader, + #data: data, + #options: options, + }, + ), + )), + ) as _i17.Future<_i13.Response>); + @override + _i17.Future<_i13.Response> request( + String? path, { + Object? data, + Map? queryParameters, + _i26.CancelToken? cancelToken, + _i9.Options? options, + _i9.ProgressCallback? onSendProgress, + _i9.ProgressCallback? onReceiveProgress, + }) => + (super.noSuchMethod( + Invocation.method( + #request, [path], { #data: data, #queryParameters: queryParameters, - #options: options, #cancelToken: cancelToken, + #options: options, #onSendProgress: onSendProgress, #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( this, Invocation.method( - #post, + #request, [path], { #data: data, #queryParameters: queryParameters, - #options: options, #cancelToken: cancelToken, + #options: options, #onSendProgress: onSendProgress, #onReceiveProgress: onReceiveProgress, }, ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i13.Response>.value(_FakeResponse_52( this, Invocation.method( - #post, + #request, [path], { #data: data, #queryParameters: queryParameters, - #options: options, #cancelToken: cancelToken, + #options: options, #onSendProgress: onSendProgress, #onReceiveProgress: onReceiveProgress, }, ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i13.Response>); @override - _i16.Future<_i13.Response> postUri( + _i17.Future<_i13.Response> requestUri( Uri? uri, { Object? data, + _i26.CancelToken? cancelToken, _i9.Options? options, - _i27.CancelToken? cancelToken, _i9.ProgressCallback? onSendProgress, _i9.ProgressCallback? onReceiveProgress, }) => (super.noSuchMethod( Invocation.method( - #postUri, + #requestUri, [uri], { #data: data, - #options: options, #cancelToken: cancelToken, + #options: options, #onSendProgress: onSendProgress, #onReceiveProgress: onReceiveProgress, }, ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( this, Invocation.method( - #postUri, + #requestUri, [uri], { #data: data, - #options: options, #cancelToken: cancelToken, + #options: options, #onSendProgress: onSendProgress, #onReceiveProgress: onReceiveProgress, }, ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i13.Response>.value(_FakeResponse_52( this, Invocation.method( - #postUri, + #requestUri, [uri], { #data: data, - #options: options, #cancelToken: cancelToken, + #options: options, #onSendProgress: onSendProgress, #onReceiveProgress: onReceiveProgress, }, ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i13.Response>); @override - _i16.Future<_i13.Response> put( - String? path, { - Object? data, - Map? queryParameters, - _i9.Options? options, - _i27.CancelToken? cancelToken, - _i9.ProgressCallback? onSendProgress, - _i9.ProgressCallback? onReceiveProgress, - }) => + _i17.Future<_i13.Response> fetch(_i9.RequestOptions? requestOptions) => (super.noSuchMethod( Invocation.method( - #put, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #fetch, + [requestOptions], + ), + returnValue: _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #fetch, + [requestOptions], + ), + )), + returnValueForMissingStub: + _i17.Future<_i13.Response>.value(_FakeResponse_52( + this, + Invocation.method( + #fetch, + [requestOptions], + ), + )), + ) as _i17.Future<_i13.Response>); +} + +/// A class which mocks [HttpClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHttpClient extends _i1.Mock implements _i14.HttpClient { + @override + Duration get idleTimeout => (super.noSuchMethod( + Invocation.getter(#idleTimeout), + returnValue: _FakeDuration_53( + this, + Invocation.getter(#idleTimeout), + ), + returnValueForMissingStub: _FakeDuration_53( + this, + Invocation.getter(#idleTimeout), + ), + ) as Duration); + @override + set idleTimeout(Duration? _idleTimeout) => super.noSuchMethod( + Invocation.setter( + #idleTimeout, + _idleTimeout, + ), + returnValueForMissingStub: null, + ); + @override + set connectionTimeout(Duration? _connectionTimeout) => super.noSuchMethod( + Invocation.setter( + #connectionTimeout, + _connectionTimeout, + ), + returnValueForMissingStub: null, + ); + @override + set maxConnectionsPerHost(int? _maxConnectionsPerHost) => super.noSuchMethod( + Invocation.setter( + #maxConnectionsPerHost, + _maxConnectionsPerHost, + ), + returnValueForMissingStub: null, + ); + @override + bool get autoUncompress => (super.noSuchMethod( + Invocation.getter(#autoUncompress), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override + set autoUncompress(bool? _autoUncompress) => super.noSuchMethod( + Invocation.setter( + #autoUncompress, + _autoUncompress, + ), + returnValueForMissingStub: null, + ); + @override + set userAgent(String? _userAgent) => super.noSuchMethod( + Invocation.setter( + #userAgent, + _userAgent, + ), + returnValueForMissingStub: null, + ); + @override + set authenticate( + _i17.Future Function( + Uri, + String, + String?, + )? f) => + super.noSuchMethod( + Invocation.setter( + #authenticate, + f, + ), + returnValueForMissingStub: null, + ); + @override + set connectionFactory( + _i17.Future<_i14.ConnectionTask<_i14.Socket>> Function( + Uri, + String?, + int?, + )? f) => + super.noSuchMethod( + Invocation.setter( + #connectionFactory, + f, ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValueForMissingStub: null, + ); + @override + set findProxy(String Function(Uri)? f) => super.noSuchMethod( + Invocation.setter( + #findProxy, + f, + ), + returnValueForMissingStub: null, + ); + @override + set authenticateProxy( + _i17.Future Function( + String, + int, + String, + String?, + )? f) => + super.noSuchMethod( + Invocation.setter( + #authenticateProxy, + f, + ), + returnValueForMissingStub: null, + ); + @override + set badCertificateCallback( + bool Function( + _i14.X509Certificate, + String, + int, + )? callback) => + super.noSuchMethod( + Invocation.setter( + #badCertificateCallback, + callback, + ), + returnValueForMissingStub: null, + ); + @override + set keyLog(dynamic Function(String)? callback) => super.noSuchMethod( + Invocation.setter( + #keyLog, + callback, + ), + returnValueForMissingStub: null, + ); + @override + _i17.Future<_i14.HttpClientRequest> open( + String? method, + String? host, + int? port, + String? path, + ) => + (super.noSuchMethod( + Invocation.method( + #open, + [ + method, + host, + port, + path, + ], + ), + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #put, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #open, + [ + method, + host, + port, + path, + ], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #put, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #open, + [ + method, + host, + port, + path, + ], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> putUri( - Uri? uri, { - Object? data, - _i9.Options? options, - _i27.CancelToken? cancelToken, - _i9.ProgressCallback? onSendProgress, - _i9.ProgressCallback? onReceiveProgress, - }) => + _i17.Future<_i14.HttpClientRequest> openUrl( + String? method, + Uri? url, + ) => (super.noSuchMethod( Invocation.method( - #putUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #openUrl, + [ + method, + url, + ], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #putUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #openUrl, + [ + method, + url, + ], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #putUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #openUrl, + [ + method, + url, + ], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> head( - String? path, { - Object? data, - Map? queryParameters, - _i9.Options? options, - _i27.CancelToken? cancelToken, - }) => + _i17.Future<_i14.HttpClientRequest> get( + String? host, + int? port, + String? path, + ) => (super.noSuchMethod( Invocation.method( - #head, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - }, + #get, + [ + host, + port, + path, + ], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #head, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - }, + #get, + [ + host, + port, + path, + ], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #head, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - }, + #get, + [ + host, + port, + path, + ], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> headUri( - Uri? uri, { - Object? data, - _i9.Options? options, - _i27.CancelToken? cancelToken, - }) => - (super.noSuchMethod( + _i17.Future<_i14.HttpClientRequest> getUrl(Uri? url) => (super.noSuchMethod( Invocation.method( - #headUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - }, + #getUrl, + [url], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #headUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - }, + #getUrl, + [url], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #headUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - }, + #getUrl, + [url], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> delete( - String? path, { - Object? data, - Map? queryParameters, - _i9.Options? options, - _i27.CancelToken? cancelToken, - }) => + _i17.Future<_i14.HttpClientRequest> post( + String? host, + int? port, + String? path, + ) => (super.noSuchMethod( Invocation.method( - #delete, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - }, + #post, + [ + host, + port, + path, + ], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #delete, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - }, + #post, + [ + host, + port, + path, + ], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #delete, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - }, + #post, + [ + host, + port, + path, + ], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> deleteUri( - Uri? uri, { - Object? data, - _i9.Options? options, - _i27.CancelToken? cancelToken, - }) => - (super.noSuchMethod( + _i17.Future<_i14.HttpClientRequest> postUrl(Uri? url) => (super.noSuchMethod( Invocation.method( - #deleteUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - }, + #postUrl, + [url], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #deleteUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - }, + #postUrl, + [url], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #deleteUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - }, + #postUrl, + [url], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> patch( - String? path, { - Object? data, - Map? queryParameters, - _i9.Options? options, - _i27.CancelToken? cancelToken, - _i9.ProgressCallback? onSendProgress, - _i9.ProgressCallback? onReceiveProgress, - }) => + _i17.Future<_i14.HttpClientRequest> put( + String? host, + int? port, + String? path, + ) => (super.noSuchMethod( Invocation.method( - #patch, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #put, + [ + host, + port, + path, + ], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #patch, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #put, + [ + host, + port, + path, + ], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #patch, - [path], - { - #data: data, - #queryParameters: queryParameters, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #put, + [ + host, + port, + path, + ], ), - )), - ) as _i16.Future<_i13.Response>); - @override - _i16.Future<_i13.Response> patchUri( - Uri? uri, { - Object? data, - _i9.Options? options, - _i27.CancelToken? cancelToken, - _i9.ProgressCallback? onSendProgress, - _i9.ProgressCallback? onReceiveProgress, - }) => - (super.noSuchMethod( + )), + ) as _i17.Future<_i14.HttpClientRequest>); + @override + _i17.Future<_i14.HttpClientRequest> putUrl(Uri? url) => (super.noSuchMethod( Invocation.method( - #patchUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #putUrl, + [url], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #patchUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #putUrl, + [url], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #patchUri, - [uri], - { - #data: data, - #options: options, - #cancelToken: cancelToken, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #putUrl, + [url], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> download( - String? urlPath, - dynamic savePath, { - _i9.ProgressCallback? onReceiveProgress, - Map? queryParameters, - _i27.CancelToken? cancelToken, - bool? deleteOnError = true, - String? lengthHeader = r'content-length', - Object? data, - _i9.Options? options, - }) => + _i17.Future<_i14.HttpClientRequest> delete( + String? host, + int? port, + String? path, + ) => (super.noSuchMethod( Invocation.method( - #download, + #delete, [ - urlPath, - savePath, + host, + port, + path, ], - { - #onReceiveProgress: onReceiveProgress, - #queryParameters: queryParameters, - #cancelToken: cancelToken, - #deleteOnError: deleteOnError, - #lengthHeader: lengthHeader, - #data: data, - #options: options, - }, ), returnValue: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #download, + #delete, [ - urlPath, - savePath, + host, + port, + path, ], - { - #onReceiveProgress: onReceiveProgress, - #queryParameters: queryParameters, - #cancelToken: cancelToken, - #deleteOnError: deleteOnError, - #lengthHeader: lengthHeader, - #data: data, - #options: options, - }, ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #download, + #delete, [ - urlPath, - savePath, + host, + port, + path, ], - { - #onReceiveProgress: onReceiveProgress, - #queryParameters: queryParameters, - #cancelToken: cancelToken, - #deleteOnError: deleteOnError, - #lengthHeader: lengthHeader, - #data: data, - #options: options, - }, ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> downloadUri( - Uri? uri, - dynamic savePath, { - _i9.ProgressCallback? onReceiveProgress, - _i27.CancelToken? cancelToken, - bool? deleteOnError = true, - String? lengthHeader = r'content-length', - Object? data, - _i9.Options? options, - }) => + _i17.Future<_i14.HttpClientRequest> deleteUrl(Uri? url) => (super.noSuchMethod( Invocation.method( - #downloadUri, + #deleteUrl, + [url], + ), + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( + this, + Invocation.method( + #deleteUrl, + [url], + ), + )), + returnValueForMissingStub: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( + this, + Invocation.method( + #deleteUrl, + [url], + ), + )), + ) as _i17.Future<_i14.HttpClientRequest>); + @override + _i17.Future<_i14.HttpClientRequest> patch( + String? host, + int? port, + String? path, + ) => + (super.noSuchMethod( + Invocation.method( + #patch, [ - uri, - savePath, + host, + port, + path, ], - { - #onReceiveProgress: onReceiveProgress, - #cancelToken: cancelToken, - #deleteOnError: deleteOnError, - #lengthHeader: lengthHeader, - #data: data, - #options: options, - }, ), returnValue: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #downloadUri, + #patch, [ - uri, - savePath, + host, + port, + path, ], - { - #onReceiveProgress: onReceiveProgress, - #cancelToken: cancelToken, - #deleteOnError: deleteOnError, - #lengthHeader: lengthHeader, - #data: data, - #options: options, - }, ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #downloadUri, + #patch, [ - uri, - savePath, + host, + port, + path, ], - { - #onReceiveProgress: onReceiveProgress, - #cancelToken: cancelToken, - #deleteOnError: deleteOnError, - #lengthHeader: lengthHeader, - #data: data, - #options: options, - }, ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> request( - String? path, { - Object? data, - Map? queryParameters, - _i27.CancelToken? cancelToken, - _i9.Options? options, - _i9.ProgressCallback? onSendProgress, - _i9.ProgressCallback? onReceiveProgress, - }) => - (super.noSuchMethod( + _i17.Future<_i14.HttpClientRequest> patchUrl(Uri? url) => (super.noSuchMethod( Invocation.method( - #request, - [path], - { - #data: data, - #queryParameters: queryParameters, - #cancelToken: cancelToken, - #options: options, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #patchUrl, + [url], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #request, - [path], - { - #data: data, - #queryParameters: queryParameters, - #cancelToken: cancelToken, - #options: options, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #patchUrl, + [url], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #request, - [path], - { - #data: data, - #queryParameters: queryParameters, - #cancelToken: cancelToken, - #options: options, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #patchUrl, + [url], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> requestUri( - Uri? uri, { - Object? data, - _i27.CancelToken? cancelToken, - _i9.Options? options, - _i9.ProgressCallback? onSendProgress, - _i9.ProgressCallback? onReceiveProgress, - }) => + _i17.Future<_i14.HttpClientRequest> head( + String? host, + int? port, + String? path, + ) => (super.noSuchMethod( Invocation.method( - #requestUri, - [uri], - { - #data: data, - #cancelToken: cancelToken, - #options: options, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #head, + [ + host, + port, + path, + ], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #requestUri, - [uri], - { - #data: data, - #cancelToken: cancelToken, - #options: options, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #head, + [ + host, + port, + path, + ], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #requestUri, - [uri], - { - #data: data, - #cancelToken: cancelToken, - #options: options, - #onSendProgress: onSendProgress, - #onReceiveProgress: onReceiveProgress, - }, + #head, + [ + host, + port, + path, + ], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); @override - _i16.Future<_i13.Response> fetch(_i9.RequestOptions? requestOptions) => - (super.noSuchMethod( + _i17.Future<_i14.HttpClientRequest> headUrl(Uri? url) => (super.noSuchMethod( Invocation.method( - #fetch, - [requestOptions], + #headUrl, + [url], ), - returnValue: _i16.Future<_i13.Response>.value(_FakeResponse_43( + returnValue: + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #fetch, - [requestOptions], + #headUrl, + [url], ), )), returnValueForMissingStub: - _i16.Future<_i13.Response>.value(_FakeResponse_43( + _i17.Future<_i14.HttpClientRequest>.value(_FakeHttpClientRequest_54( this, Invocation.method( - #fetch, - [requestOptions], + #headUrl, + [url], ), )), - ) as _i16.Future<_i13.Response>); + ) as _i17.Future<_i14.HttpClientRequest>); + @override + void addCredentials( + Uri? url, + String? realm, + _i14.HttpClientCredentials? credentials, + ) => + super.noSuchMethod( + Invocation.method( + #addCredentials, + [ + url, + realm, + credentials, + ], + ), + returnValueForMissingStub: null, + ); + @override + void addProxyCredentials( + String? host, + int? port, + String? realm, + _i14.HttpClientCredentials? credentials, + ) => + super.noSuchMethod( + Invocation.method( + #addProxyCredentials, + [ + host, + port, + realm, + credentials, + ], + ), + returnValueForMissingStub: null, + ); + @override + void close({bool? force = false}) => super.noSuchMethod( + Invocation.method( + #close, + [], + {#force: force}, + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [FakeFilePickerPlatform]. /// /// See the documentation for Mockito's code generation for more information. class MockFilePickerPlatform extends _i1.Mock - implements _i28.FakeFilePickerPlatform { + implements _i27.FakeFilePickerPlatform { @override - _i16.Future<_i29.FilePickerResult?> pickFiles({ + _i17.Future<_i28.FilePickerResult?> pickFiles({ String? dialogTitle, String? initialDirectory, - _i29.FileType? type = _i29.FileType.any, + _i28.FileType? type = _i28.FileType.any, List? allowedExtensions, - dynamic Function(_i29.FilePickerStatus)? onFileLoading, + dynamic Function(_i28.FilePickerStatus)? onFileLoading, bool? allowCompression = true, bool? allowMultiple = false, bool? withData = false, @@ -3918,20 +5523,20 @@ class MockFilePickerPlatform extends _i1.Mock #lockParentWindow: lockParentWindow, }, ), - returnValue: _i16.Future<_i29.FilePickerResult?>.value(), - returnValueForMissingStub: _i16.Future<_i29.FilePickerResult?>.value(), - ) as _i16.Future<_i29.FilePickerResult?>); + returnValue: _i17.Future<_i28.FilePickerResult?>.value(), + returnValueForMissingStub: _i17.Future<_i28.FilePickerResult?>.value(), + ) as _i17.Future<_i28.FilePickerResult?>); @override - _i16.Future clearTemporaryFiles() => (super.noSuchMethod( + _i17.Future clearTemporaryFiles() => (super.noSuchMethod( Invocation.method( #clearTemporaryFiles, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future getDirectoryPath({ + _i17.Future getDirectoryPath({ String? dialogTitle, bool? lockParentWindow = false, String? initialDirectory, @@ -3946,15 +5551,15 @@ class MockFilePickerPlatform extends _i1.Mock #initialDirectory: initialDirectory, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); @override - _i16.Future saveFile({ + _i17.Future saveFile({ String? dialogTitle, String? fileName, String? initialDirectory, - _i29.FileType? type = _i29.FileType.any, + _i28.FileType? type = _i28.FileType.any, List? allowedExtensions, bool? lockParentWindow = false, }) => @@ -3971,7 +5576,7 @@ class MockFilePickerPlatform extends _i1.Mock #lockParentWindow: lockParentWindow, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i17.Future.value(), + returnValueForMissingStub: _i17.Future.value(), + ) as _i17.Future); } diff --git a/test/test_util/test_datas.dart b/test/test_util/test_datas.dart index d9f91fb84..6b823c22e 100644 --- a/test/test_util/test_datas.dart +++ b/test/test_util/test_datas.dart @@ -379,8 +379,6 @@ class TestData { } ''')); - static String note1ExpectText = "気づいたら、健康保険証っぽいプラズマ化したつまようじの賞味期限が切れてました…"; - static String note1ExpectId = "9g3rcngj3e"; static Note note2 = Note.fromJson(JSON5.parse(r''' { @@ -420,10 +418,6 @@ class TestData { } ''')); - /// 自身のノート(藍ちゃん)2 - static String note2ExpectText = - "みにゃさん、数取りゲームしましょう!\n0~100の中で最も大きい数字を取った人が勝ちです。他の人と被ったらだめですよ~\n制限時間は10分です。数字はこの投稿にリプライで送ってくださいね!"; - /// 自身でないノート1 static Note note3AsAnotherUser = Note.fromJson(JSON5.parse(r''' { @@ -650,8 +644,6 @@ class TestData { } ''')); - static String note3ExpectUserName = "@oishiibot"; - // ドライブ(フォルダ) static DriveFolder folder1 = DriveFolder.fromJson(JSON5.parse(r''' { @@ -1398,8 +1390,8 @@ class TestData { hasUnreadNote: false, } ''')); - static String channel1ExpectId = "9axtmmcxuy"; - static String channel1ExpectName = "ブルーアーカイ部 総合"; + + static String expectChannel1DescriptionContaining = "ありがとうブルーアーカイブ"; static CommunityChannel channel2 = CommunityChannel.fromJson(JSON5.parse(r''' { @@ -1422,8 +1414,270 @@ class TestData { hasUnreadNote: false, } ''')); - static String channel2ExpectId = "9b3chwrm7f"; - static String channel2ExpectName = "Misskeyアークナイツ部"; + + // アンテナ + static Antenna antenna = Antenna.fromJson(JSON5.parse(r''' +{ + id: '9f7kcbzcoe', + createdAt: '2023-05-26T03:24:02.856Z', + name: 'ポテイモン', + keywords: [ + [ + 'ポテイモン', + ], + [ + '芋神', + ], + [ + 'misscat', + ], + ], + excludeKeywords: [ + [ + '応天門', + ], + ], + src: 'all', + userListId: null, + users: [ + '', + ], + caseSensitive: false, + notify: false, + withReplies: false, + withFile: false, + isActive: true, + hasUnreadNote: false, + } +''')); + + static Clip clip = Clip.fromJson(JSON5.parse(r''' +{ + id: '9crm7l2n4k', + createdAt: '2023-03-25T14:12:37.103Z', + lastClippedAt: '2023-06-27T10:08:56.762Z', + userId: '9byjlos32z', + user: { + id: '7rkr3b1c1c', + name: '藍', + username: 'ai', + host: null, + avatarUrl: 'https://proxy.misskeyusercontent.com/avatar.webp?url=https%3A%2F%2Fs3.arkjp.net%2Fmisskey%2Fwebpublic-ecc1008f-3e2e-4206-ae7e-5093221f08be.png&avatar=1', + avatarBlurhash: null, + isBot: true, + isCat: true, + emojis: {}, + onlineStatus: 'online', + badgeRoles: [], + }, + name: 'ぴょんぷっぷー', + description: null, + isPublic: true, + favoritedCount: 0, + isFavorited: false, + } + +''')); + + static RolesListResponse role = RolesListResponse.fromJson(JSON5.parse(''' +{ + id: '9diazxez3m', + createdAt: '2023-04-13T06:28:30.827Z', + updatedAt: '2023-06-25T09:28:16.529Z', + name: 'Super New User', + description: 'アカウント作成してから1日以内のユーザー', + color: '#a4850a', + iconUrl: 'https://s3.arkjp.net/misskey/358069f4-e033-4366-891b-fcc4fc4d0c70.png', + target: 'conditional', + condFormula: { + id: '83328ff6-8bdc-4516-9dee-751222897481', + type: 'and', + values: [ + { + id: 'deac20b7-2688-4d7e-a264-5d0f0345f003', + type: 'isLocal', + }, + { + id: '9959c843-b273-4edf-8632-41b103af8b88', + sec: 86400, + type: 'createdLessThan', + }, + ], + }, + isPublic: true, + isAdministrator: false, + isModerator: false, + isExplorable: true, + asBadge: true, + canEditMembersByModerator: false, + displayOrder: 0, + policies: { + pinLimit: { + value: 3, + priority: 0, + useDefault: true, + }, + canInvite: { + value: false, + priority: 0, + useDefault: true, + }, + clipLimit: { + value: 10, + priority: 0, + useDefault: true, + }, + canHideAds: { + value: false, + priority: 0, + useDefault: true, + }, + antennaLimit: { + value: 5, + priority: 0, + useDefault: true, + }, + gtlAvailable: { + value: true, + priority: 0, + useDefault: true, + }, + ltlAvailable: { + value: true, + priority: 0, + useDefault: true, + }, + webhookLimit: { + value: 3, + priority: 0, + useDefault: true, + }, + canPublicNote: { + value: true, + priority: 0, + useDefault: true, + }, + userListLimit: { + value: 5, + priority: 0, + useDefault: true, + }, + wordMuteLimit: { + value: 200, + priority: 0, + useDefault: true, + }, + alwaysMarkNsfw: { + value: false, + priority: 0, + useDefault: true, + }, + canSearchNotes: { + value: false, + priority: 0, + useDefault: true, + }, + driveCapacityMb: { + value: 10240, + priority: 0, + useDefault: true, + }, + rateLimitFactor: { + value: 2, + priority: 0, + useDefault: true, + }, + noteEachClipsLimit: { + value: 50, + priority: 0, + useDefault: true, + }, + canManageCustomEmojis: { + value: false, + priority: 0, + useDefault: true, + }, + userEachUserListsLimit: { + value: 20, + priority: 0, + useDefault: true, + }, + canCreateContent: { + useDefault: true, + priority: 0, + value: true, + }, + canUpdateContent: { + useDefault: true, + priority: 0, + value: true, + }, + canDeleteContent: { + useDefault: true, + priority: 0, + value: true, + }, + inviteLimit: { + useDefault: true, + priority: 0, + value: 0, + }, + inviteLimitCycle: { + useDefault: true, + priority: 0, + value: 10080, + }, + inviteExpirationTime: { + useDefault: true, + priority: 0, + value: 0, + }, + }, + usersCount: 0, + } +''')); + + static HashtagsTrendResponse hashtagTrends = + HashtagsTrendResponse.fromJson(JSON5.parse(r''' +{ + tag: 'ろぐぼチャレンジ', + chart: [ + 3, + 2, + 2, + 1, + 3, + 4, + 2, + 4, + 2, + 4, + 0, + 0, + 3, + 2, + 3, + 6, + 5, + 4, + 12, + 8, + ], + usersCount: 15, + } +''')); + + static Hashtag hashtag = Hashtag.fromJson(JSON5.parse(r''' +{ + tag: 'アークナイツ', + mentionedUsersCount: 531, + mentionedLocalUsersCount: 3, + mentionedRemoteUsersCount: 528, + attachedUsersCount: 67, + attachedLocalUsersCount: 2, + attachedRemoteUsersCount: 65, + } +''')); // Dio static DioError response404 = DioError( diff --git a/test/view/antenna_list_page/antenna_list_page_test.dart b/test/view/antenna_list_page/antenna_list_page_test.dart new file mode 100644 index 000000000..88740ab9f --- /dev/null +++ b/test/view/antenna_list_page/antenna_list_page_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/router/app_router.dart'; +import 'package:mockito/mockito.dart'; + +import '../../test_util/default_root_widget.dart'; +import '../../test_util/mock.mocks.dart'; +import '../../test_util/test_datas.dart'; + +void main() { + group("アンテナ一覧", () { + testWidgets("アンテナ一覧が表示されること", (tester) async { + final antennas = MockMisskeyAntenna(); + final misskey = MockMisskey(); + when(misskey.antennas).thenReturn(antennas); + when(antennas.list()).thenAnswer((_) async => [TestData.antenna]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: AntennaRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + + expect(find.text(TestData.antenna.name), findsOneWidget); + }); + }); +} diff --git a/test/view/antenna_notes_page/antenna_notes_page_test.dart b/test/view/antenna_notes_page/antenna_notes_page_test.dart new file mode 100644 index 000000000..18dcd9354 --- /dev/null +++ b/test/view/antenna_notes_page/antenna_notes_page_test.dart @@ -0,0 +1,33 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/router/app_router.dart'; +import 'package:misskey_dart/misskey_dart.dart'; +import 'package:mockito/mockito.dart'; + +import '../../test_util/default_root_widget.dart'; +import '../../test_util/mock.mocks.dart'; +import '../../test_util/test_datas.dart'; + +void main() { + group("アンテナノート一覧", () { + testWidgets("アンテナのノート一覧が表示されること", (tester) async { + final antennas = MockMisskeyAntenna(); + final misskey = MockMisskey(); + when(misskey.antennas).thenReturn(antennas); + when(antennas.notes(any)).thenAnswer((_) async => [TestData.note1]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: AntennaNotesRoute( + account: TestData.account, antenna: TestData.antenna), + ))); + await tester.pumpAndSettle(); + + expect(find.text(TestData.note1.text!), findsOneWidget); + verify(antennas.notes(argThat( + equals(AntennasNotesRequest(antennaId: TestData.antenna.id))))); + }); + }); +} diff --git a/test/view/channel_detail_page/channel_detail_page_test.dart b/test/view/channel_detail_page/channel_detail_page_test.dart new file mode 100644 index 000000000..ffb49d825 --- /dev/null +++ b/test/view/channel_detail_page/channel_detail_page_test.dart @@ -0,0 +1,172 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/router/app_router.dart'; +import 'package:misskey_dart/misskey_dart.dart'; +import 'package:mockito/mockito.dart'; + +import '../../test_util/default_root_widget.dart'; +import '../../test_util/mock.mocks.dart'; +import '../../test_util/test_datas.dart'; + +void main() { + group("チャンネル詳細", () { + testWidgets("チャンネルの詳細情報が表示されること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.show(any)) + .thenAnswer((_) async => TestData.channel1.copyWith(bannerUrl: null)); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelDetailRoute( + account: TestData.account, channelId: TestData.channel1.id), + ))); + await tester.pumpAndSettle(); + + expect( + find.textContaining(TestData.expectChannel1DescriptionContaining, + findRichText: true), + findsOneWidget); + }); + + testWidgets("チャンネルがセンシティブの場合、センシティブである旨が表示されること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.show(any)).thenAnswer((_) async => + TestData.channel1.copyWith(bannerUrl: null, isSensitive: true)); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelDetailRoute( + account: TestData.account, channelId: TestData.channel1.id), + ))); + await tester.pumpAndSettle(); + + expect(find.textContaining("センシティブ"), findsOneWidget); + }); + + testWidgets("チャンネルをお気に入りに設定していない場合、お気に入りにすることができること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.show(any)).thenAnswer((_) async => + TestData.channel1.copyWith(bannerUrl: null, isFavorited: false)); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelDetailRoute( + account: TestData.account, channelId: TestData.channel1.id), + ))); + await tester.pumpAndSettle(); + + await tester.tap(find.text("お気に入りにいれる")); + await tester.pumpAndSettle(); + expect(find.text("お気に入り中"), findsOneWidget); + + verify(channel.favorite(argThat( + equals(ChannelsFavoriteRequest(channelId: TestData.channel1.id))))); + }); + + testWidgets("チャンネルをお気に入りに設定している場合、お気に入りを解除できること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.show(any)).thenAnswer((_) async => + TestData.channel1.copyWith(bannerUrl: null, isFavorited: true)); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelDetailRoute( + account: TestData.account, channelId: TestData.channel1.id), + ))); + await tester.pumpAndSettle(); + + await tester.tap(find.text("お気に入り中")); + await tester.pumpAndSettle(); + expect(find.text("お気に入りにいれる"), findsOneWidget); + + verify(channel.unfavorite(argThat( + equals(ChannelsUnfavoriteRequest(channelId: TestData.channel1.id))))); + }); + + testWidgets("チャンネルをフォローしていない場合、フォローすることができること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.show(any)).thenAnswer((_) async => + TestData.channel1.copyWith(bannerUrl: null, isFollowing: false)); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelDetailRoute( + account: TestData.account, channelId: TestData.channel1.id), + ))); + await tester.pumpAndSettle(); + + await tester.tap(find.text("フォローする")); + await tester.pumpAndSettle(); + expect(find.text("フォローしています"), findsOneWidget); + + verify(channel.follow(argThat( + equals(ChannelsFollowRequest(channelId: TestData.channel1.id))))); + }); + + testWidgets("チャンネルをフォローしている場合、フォロー解除をすることができること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.show(any)).thenAnswer((_) async => + TestData.channel1.copyWith(bannerUrl: null, isFollowing: true)); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelDetailRoute( + account: TestData.account, channelId: TestData.channel1.id), + ))); + await tester.pumpAndSettle(); + + await tester.tap(find.text("フォローしています")); + await tester.pumpAndSettle(); + expect(find.text("フォローする"), findsOneWidget); + + verify(channel.unfollow(argThat( + equals(ChannelsUnfollowRequest(channelId: TestData.channel1.id))))); + }); + }); + + group("チャンネル内ノート", () { + testWidgets("チャンネル内のノートが表示されること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.show(any)).thenAnswer((_) async => + TestData.channel1.copyWith(bannerUrl: null, isFollowing: false)); + when(channel.timeline(any)) + .thenAnswer((realInvocation) async => [TestData.note1]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelDetailRoute( + account: TestData.account, channelId: TestData.channel1.id), + ))); + await tester.pumpAndSettle(); + + await tester.tap(find.text("タイムライン")); + await tester.pumpAndSettle(); + + expect(find.text(TestData.note1.text!), findsOneWidget); + verify(channel.timeline(argThat(predicate( + (e) => e.channelId == TestData.channel1.id)))); + }); + }); +} diff --git a/test/view/channel_page/channel_page_test.dart b/test/view/channel_page/channel_page_test.dart new file mode 100644 index 000000000..ef8df45ce --- /dev/null +++ b/test/view/channel_page/channel_page_test.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/router/app_router.dart'; +import 'package:misskey_dart/misskey_dart.dart'; +import 'package:mockito/mockito.dart'; + +import '../../test_util/default_root_widget.dart'; +import '../../test_util/mock.mocks.dart'; +import '../../test_util/test_datas.dart'; + +void main() { + group("チャンネル", () { + group("チャンネル検索", () { + testWidgets("チャンネル検索ができること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.search(any)).thenAnswer( + (_) async => [TestData.channel1.copyWith(bannerUrl: null)]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelsRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + + await tester.tap(find.text("検索")); + await tester.pumpAndSettle(); + + await tester.enterText(find.byType(TextField), "ゲーム開発部"); + await tester.testTextInput.receiveAction(TextInputAction.done); + await tester.pumpAndSettle(); + + expect(find.text(TestData.channel1.name), findsOneWidget); + verify(channel.search( + argThat(equals(const ChannelsSearchRequest(query: "ゲーム開発部"))))); + }); + }); + + group("トレンド", () { + testWidgets("トレンドが表示されること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.featured()).thenAnswer( + (_) async => [TestData.channel1.copyWith(bannerUrl: null)]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelsRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + + await tester.tap(find.text("トレンド")); + await tester.pumpAndSettle(); + + expect(find.text(TestData.channel1.name), findsOneWidget); + }); + }); + + group("お気に入り", () { + testWidgets("お気に入り中のチャンネルが表示されること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.myFavorite(any)).thenAnswer( + (_) async => [TestData.channel1.copyWith(bannerUrl: null)]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelsRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + + await tester.tap(find.text("お気に入り")); + await tester.pumpAndSettle(); + + expect(find.text(TestData.channel1.name), findsOneWidget); + verify(channel + .myFavorite(argThat(equals(const ChannelsMyFavoriteRequest())))); + }); + }); + + group("フォロー中", () { + testWidgets("フォロー中のチャンネルが表示されること", (tester) async { + final channel = MockMisskeyChannels(); + final misskey = MockMisskey(); + when(misskey.channels).thenReturn(channel); + when(channel.followed(any)).thenAnswer( + (_) async => [TestData.channel1.copyWith(bannerUrl: null)]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ChannelsRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + + await tester.tap(find.text("フォロー中")); + await tester.pumpAndSettle(); + + expect(find.text(TestData.channel1.name), findsOneWidget); + verify( + channel.followed(argThat(equals(const ChannelsFollowedRequest())))); + }); + }); + }); +} diff --git a/test/view/clip_detail_page/clip_detail_page_test.dart b/test/view/clip_detail_page/clip_detail_page_test.dart new file mode 100644 index 000000000..6d71ddd78 --- /dev/null +++ b/test/view/clip_detail_page/clip_detail_page_test.dart @@ -0,0 +1,30 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/router/app_router.dart'; +import 'package:mockito/mockito.dart'; + +import '../../test_util/default_root_widget.dart'; +import '../../test_util/mock.mocks.dart'; +import '../../test_util/test_datas.dart'; + +void main() { + group("クリップのノート一覧", () { + testWidgets("クリップ済みノートが表示されること", (tester) async { + final clip = MockMisskeyClips(); + final misskey = MockMisskey(); + when(misskey.clips).thenReturn(clip); + when(clip.notes(any)).thenAnswer((_) async => [TestData.note1]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ClipDetailRoute( + id: TestData.clip.id, account: TestData.account), + ))); + await tester.pumpAndSettle(); + + expect(find.text(TestData.note1.text!), findsOneWidget); + }); + }); +} diff --git a/test/view/clip_list_page/clip_list_page_test.dart b/test/view/clip_list_page/clip_list_page_test.dart new file mode 100644 index 000000000..32729c355 --- /dev/null +++ b/test/view/clip_list_page/clip_list_page_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/router/app_router.dart'; +import 'package:mockito/mockito.dart'; + +import '../../test_util/default_root_widget.dart'; +import '../../test_util/mock.mocks.dart'; +import '../../test_util/test_datas.dart'; + +void main() { + group("クリップ一覧", () { + testWidgets("クリップ一覧が表示されること", (tester) async { + final clip = MockMisskeyClips(); + final misskey = MockMisskey(); + when(misskey.clips).thenReturn(clip); + when(clip.list()).thenAnswer((_) async => [TestData.clip]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ClipListRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + + expect(find.text(TestData.clip.name!), findsOneWidget); + }); + }); +} diff --git a/test/view/explore_page/explore_page_test.dart b/test/view/explore_page/explore_page_test.dart new file mode 100644 index 000000000..0c29f579a --- /dev/null +++ b/test/view/explore_page/explore_page_test.dart @@ -0,0 +1,230 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/router/app_router.dart'; +import 'package:misskey_dart/misskey_dart.dart'; +import 'package:mockito/mockito.dart'; + +import '../../test_util/default_root_widget.dart'; +import '../../test_util/mock.mocks.dart'; +import '../../test_util/test_datas.dart'; + +void main() { + group("みつける", () { + group("ハイライト", () { + testWidgets("ハイライトのノートを表示できること", (tester) async { + final notes = MockMisskeyNotes(); + final misskey = MockMisskey(); + when(misskey.notes).thenReturn(notes); + when(notes.featured(any)).thenAnswer((_) async => [TestData.note1]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ExploreRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + + expect(find.text(TestData.note1.text!), findsOneWidget); + verify(notes.featured(argThat(equals(const NotesFeaturedRequest())))); + }); + + testWidgets("アンケートのノートを表示できること", (tester) async { + final polls = MockMisskeyNotesPolls(); + final notes = MockMisskeyNotes(); + final misskey = MockMisskey(); + when(misskey.notes).thenReturn(notes); + when(notes.polls).thenReturn(polls); + when(polls.recommendation(any)) + .thenAnswer((_) async => [TestData.note1]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ExploreRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + await tester.tap(find.text("アンケート")); + await tester.pumpAndSettle(); + + expect(find.text(TestData.note1.text!), findsOneWidget); + verify(polls.recommendation( + argThat(equals(const NotesPollsRecommendationRequest())))); + }); + }); + + group("ユーザー", () { + testWidgets("ピン留めされたユーザーを表示できること", (tester) async { + final misskey = MockMisskey(); + final notes = MockMisskeyNotes(); + when(misskey.notes).thenReturn(notes); + when(misskey.pinnedUsers()) + .thenAnswer((_) async => [TestData.detailedUser1]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ExploreRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + await tester.tap(find.text("ユーザー")); + await tester.pumpAndSettle(); + + expect(find.text(TestData.detailedUser1.name!), findsOneWidget); + }); + + testWidgets("ローカルのユーザーを表示できること", (tester) async { + final misskey = MockMisskey(); + final notes = MockMisskeyNotes(); + final users = MockMisskeyUsers(); + when(misskey.notes).thenReturn(notes); + when(misskey.users).thenReturn(users); + when(users.users(any)) + .thenAnswer((_) async => [TestData.detailedUser1]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ExploreRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + await tester.tap(find.text("ユーザー")); + await tester.pumpAndSettle(); + await tester.tap(find.text("ローカル")); + await tester.pumpAndSettle(); + + expect(find.text(TestData.detailedUser1.name!), findsOneWidget); + verify(users.users(argThat(equals(const UsersUsersRequest( + state: UsersState.alive, + origin: Origin.local, + sort: UsersSortType.followerDescendant))))); + }); + + testWidgets("リモートのユーザーを表示できること", (tester) async { + final misskey = MockMisskey(); + final notes = MockMisskeyNotes(); + final users = MockMisskeyUsers(); + when(misskey.notes).thenReturn(notes); + when(misskey.users).thenReturn(users); + when(users.users(any)) + .thenAnswer((_) async => [TestData.detailedUser1]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ExploreRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + await tester.tap(find.text("ユーザー")); + await tester.pumpAndSettle(); + await tester.tap(find.text("リモート")); + await tester.pumpAndSettle(); + + expect(find.text(TestData.detailedUser1.name!), findsOneWidget); + verify(users.users(argThat(equals(const UsersUsersRequest( + state: UsersState.alive, + origin: Origin.remote, + sort: UsersSortType.followerDescendant))))); + }); + }); + + group("ロール", () { + testWidgets("公開ロールを表示できること", (tester) async { + final misskey = MockMisskey(); + final roles = MockMisskeyRoles(); + when(misskey.roles).thenReturn(roles); + when(misskey.notes).thenReturn(MockMisskeyNotes()); + when(roles.list()) + .thenAnswer((_) async => [TestData.role.copyWith(usersCount: 495)]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ExploreRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + await tester.tap(find.text("ロール")); + await tester.pumpAndSettle(); + + expect(find.textContaining(TestData.role.name, findRichText: true), + findsOneWidget); + }); + }); + + group("ハッシュタグ", () { + testWidgets("トレンドのハッシュタグを表示できること", (tester) async { + final misskey = MockMisskey(); + final hashtags = MockMisskeyHashtags(); + when(misskey.hashtags).thenReturn(hashtags); + when(misskey.notes).thenReturn(MockMisskeyNotes()); + when(hashtags.trend()) + .thenAnswer((_) async => [TestData.hashtagTrends]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ExploreRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + await tester.tap(find.text("ハッシュタグ")); + await tester.pumpAndSettle(); + + expect(find.textContaining(TestData.hashtagTrends.tag), findsOneWidget); + }); + + testWidgets("ローカルのハッシュタグを表示できること", (tester) async { + final misskey = MockMisskey(); + final hashtags = MockMisskeyHashtags(); + when(misskey.hashtags).thenReturn(hashtags); + when(misskey.notes).thenReturn(MockMisskeyNotes()); + when(hashtags.list(any)).thenAnswer((_) async => [TestData.hashtag]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ExploreRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + await tester.tap(find.text("ハッシュタグ")); + await tester.pumpAndSettle(); + await tester.tap(find.text("ローカル")); + await tester.pumpAndSettle(); + + expect(find.textContaining(TestData.hashtag.tag), findsOneWidget); + + verify(hashtags.list(argThat(predicate((request) => + request.attachedToLocalUserOnly == true && + request.sort == + HashtagsListSortType.attachedLocalUsersDescendant)))); + }); + + testWidgets("リモートのハッシュタグを表示できること", (tester) async { + final misskey = MockMisskey(); + final hashtags = MockMisskeyHashtags(); + when(misskey.hashtags).thenReturn(hashtags); + when(misskey.notes).thenReturn(MockMisskeyNotes()); + when(hashtags.list(any)).thenAnswer((_) async => [TestData.hashtag]); + + await tester.pumpWidget(ProviderScope( + overrides: [misskeyProvider.overrideWith((_, __) => misskey)], + child: DefaultRootWidget( + initialRoute: ExploreRoute(account: TestData.account), + ))); + await tester.pumpAndSettle(); + await tester.tap(find.text("ハッシュタグ")); + await tester.pumpAndSettle(); + await tester.tap(find.text("リモート")); + await tester.pumpAndSettle(); + + expect(find.textContaining(TestData.hashtag.tag), findsOneWidget); + + verify(hashtags.list(argThat(predicate((request) => + request.attachedToRemoteUserOnly == true && + request.sort == + HashtagsListSortType.attachedRemoteUsersDescendant)))); + }); + }); + + group("よそのサーバー", () {}); + }); +} diff --git a/test/view/note_create_page/note_create_page_test.dart b/test/view/note_create_page/note_create_page_test.dart index 96dd70de3..3bb268d8b 100644 --- a/test/view/note_create_page/note_create_page_test.dart +++ b/test/view/note_create_page/note_create_page_test.dart @@ -48,7 +48,7 @@ void main() { ))); await tester.pumpAndSettle(); - expect(find.text(TestData.channel1ExpectName), findsOneWidget); + expect(find.text(TestData.channel1.name), findsOneWidget); await tester.enterText( find.byType(TextField).hitTestable(), ":ai_yay:"); @@ -56,7 +56,7 @@ void main() { await tester.pumpAndSettle(); verify(mockNote.create(argThat(equals(predicate( - (arg) => arg.channelId == TestData.channel1ExpectId))))); + (arg) => arg.channelId == TestData.channel1.id))))); }); testWidgets("削除されたノートを直す場合で、そのノートがチャンネルのノートの場合、チャンネルのノートになること", @@ -72,7 +72,7 @@ void main() { child: DefaultRootWidget( initialRoute: NoteCreateRoute( initialAccount: TestData.account, - deletedNote: TestData.note1.copyWith( + note: TestData.note1.copyWith( channelId: TestData.channel1.id, channel: NoteChannelInfo( id: TestData.channel1.id, @@ -80,7 +80,7 @@ void main() { ))); await tester.pumpAndSettle(); - expect(find.text(TestData.channel1ExpectName), findsOneWidget); + expect(find.text(TestData.channel1.name), findsOneWidget); await tester.enterText( find.byType(TextField).hitTestable(), ":ai_yay:"); @@ -88,7 +88,7 @@ void main() { await tester.pumpAndSettle(); verify(mockNote.create(argThat(equals(predicate( - (arg) => arg.channelId == TestData.channel1ExpectId))))); + (arg) => arg.channelId == TestData.channel1.id))))); }); testWidgets("チャンネルのノートにリプライをする場合、そのノートもチャンネルのノートになること", (tester) async { @@ -111,7 +111,7 @@ void main() { ))); await tester.pumpAndSettle(); - expect(find.text(TestData.channel1ExpectName), findsOneWidget); + expect(find.text(TestData.channel1.name), findsOneWidget); await tester.enterText( find.byType(TextField).hitTestable(), ":ai_yay:"); @@ -119,7 +119,7 @@ void main() { await tester.pumpAndSettle(); verify(mockNote.create(argThat(equals(predicate( - (arg) => arg.channelId == TestData.channel1ExpectId))))); + (arg) => arg.channelId == TestData.channel1.id))))); }); testWidgets("チャンネルのノートに引用リノートをする場合、引数のチャンネル先が選択されること", (tester) async { @@ -143,7 +143,7 @@ void main() { ))); await tester.pumpAndSettle(); - expect(find.text(TestData.channel2ExpectName), findsOneWidget); + expect(find.text(TestData.channel2.name), findsOneWidget); await tester.enterText( find.byType(TextField).hitTestable(), ":ai_yay:"); @@ -151,7 +151,7 @@ void main() { await tester.pumpAndSettle(); verify(mockNote.create(argThat(equals(predicate( - (arg) => arg.channelId == TestData.channel2ExpectId))))); + (arg) => arg.channelId == TestData.channel2.id))))); }); }); @@ -198,7 +198,7 @@ void main() { child: DefaultRootWidget( initialRoute: NoteCreateRoute( initialAccount: TestData.account, - deletedNote: TestData.note1 + note: TestData.note1 .copyWith(visibility: NoteVisibility.specified)), ))); await tester.pumpAndSettle(); @@ -365,7 +365,7 @@ void main() { child: DefaultRootWidget( initialRoute: NoteCreateRoute( initialAccount: TestData.account, - deletedNote: TestData.note1.copyWith(localOnly: true)), + note: TestData.note1.copyWith(localOnly: true)), ))); await tester.pumpAndSettle(); await tester.tap(find.byIcon(Icons.send)); @@ -506,7 +506,7 @@ void main() { child: DefaultRootWidget( initialRoute: NoteCreateRoute( initialAccount: TestData.account, - deletedNote: TestData.note1.copyWith( + note: TestData.note1.copyWith( reactionAcceptance: ReactionAcceptance.likeOnly)), ))); await tester.pumpAndSettle(); @@ -602,7 +602,7 @@ void main() { child: DefaultRootWidget( initialRoute: NoteCreateRoute( initialAccount: TestData.account, - deletedNote: TestData.note1.copyWith(cw: "えっちなやつ")), + note: TestData.note1.copyWith(cw: "えっちなやつ")), ))); await tester.pumpAndSettle(); @@ -681,8 +681,7 @@ void main() { ], child: DefaultRootWidget( initialRoute: NoteCreateRoute( - initialAccount: TestData.account, - deletedNote: TestData.note1), + initialAccount: TestData.account, note: TestData.note1), ))); await tester.pumpAndSettle(); @@ -752,7 +751,7 @@ void main() { child: DefaultRootWidget( initialRoute: NoteCreateRoute( initialAccount: TestData.account, - deletedNote: TestData.note1.copyWith( + note: TestData.note1.copyWith( files: [TestData.drive1], fileIds: [TestData.drive1.id])), ))); await tester.pumpAndSettle(); @@ -803,11 +802,15 @@ void main() { await tester.pumpAndSettle(); verify(mockDriveFiles.createAsBinary( - argThat(equals(const DriveFilesCreateRequest( - name: "test.png", - force: true, - comment: "", - isSensitive: false))), + argThat( + equals( + const DriveFilesCreateRequest( + name: "test.png", + force: true, + isSensitive: false, + ), + ), + ), argThat(equals(predicate((value) => const DeepCollectionEquality().equals(value, binaryImage)))))); verify(mockNote.create(argThat(equals(predicate( @@ -857,11 +860,15 @@ void main() { await tester.pumpAndSettle(); verify(mockDriveFiles.createAsBinary( - argThat(equals(const DriveFilesCreateRequest( - name: "test.txt", - force: true, - comment: "", - isSensitive: false))), + argThat( + equals( + const DriveFilesCreateRequest( + name: "test.txt", + force: true, + isSensitive: false, + ), + ), + ), argThat(equals(predicate((value) => const DeepCollectionEquality().equals(value, binaryData)))))); verify(mockNote.create(argThat(equals(predicate( @@ -887,7 +894,8 @@ void main() { expect( find.descendant( of: find.byType(ReplyToArea), - matching: find.text(TestData.note3ExpectUserName, + matching: find.textContaining( + TestData.note3AsAnotherUser.user.username, findRichText: true)), findsOneWidget); }); @@ -943,7 +951,7 @@ void main() { child: DefaultRootWidget( initialRoute: NoteCreateRoute( initialAccount: TestData.account, - deletedNote: TestData.note1.copyWith(mentions: ["@ai"]), + note: TestData.note1.copyWith(mentions: ["@ai"]), )))); await tester.pumpAndSettle(); expect(find.text("返信先:"), findsOneWidget); @@ -1015,7 +1023,7 @@ void main() { child: DefaultRootWidget( initialRoute: NoteCreateRoute( initialAccount: TestData.account, - deletedNote: TestData.note4AsVote.copyWith( + note: TestData.note4AsVote.copyWith( poll: TestData.note4AsVote.poll?.copyWith(multiple: false)), )))); await tester.pumpAndSettle(); @@ -1045,7 +1053,7 @@ void main() { child: DefaultRootWidget( initialRoute: NoteCreateRoute( initialAccount: TestData.account, - deletedNote: TestData.note4AsVote.copyWith( + note: TestData.note4AsVote.copyWith( poll: TestData.note4AsVote.poll?.copyWith(multiple: true)), )))); await tester.pumpAndSettle(); @@ -1147,11 +1155,15 @@ void main() { await tester.pumpAndSettle(); verify(mockDriveFiles.createAsBinary( - argThat(equals(const DriveFilesCreateRequest( - name: "test.txt", - force: true, - comment: "", - isSensitive: false))), + argThat( + equals( + const DriveFilesCreateRequest( + name: "test.txt", + force: true, + isSensitive: false, + ), + ), + ), argThat(equals(predicate((value) => const DeepCollectionEquality().equals(value, binaryData)))))); verify(mockNote.create(argThat(equals(predicate( @@ -1168,8 +1180,8 @@ void main() { TestData.unicodeEmojiRepositoryData1, TestData.customEmojiRepositoryData1 ]); - when(emojiRepository.searchEmojis(any)).thenAnswer( - (_) async => [TestData.unicodeEmoji1, TestData.customEmoji1]); + when(emojiRepository.defaultEmojis()).thenAnswer( + (_) => [TestData.unicodeEmoji1, TestData.customEmoji1]); final generalSettingsRepository = MockGeneralSettingsRepository(); when(generalSettingsRepository.settings) .thenReturn(const GeneralSettings(emojiType: EmojiType.system)); @@ -1213,8 +1225,8 @@ void main() { TestData.unicodeEmojiRepositoryData1, TestData.customEmojiRepositoryData1 ]); - when(emojiRepository.searchEmojis(any)).thenAnswer( - (_) async => [TestData.unicodeEmoji1, TestData.customEmoji1]); + when(emojiRepository.defaultEmojis()).thenAnswer( + (_) => [TestData.unicodeEmoji1, TestData.customEmoji1]); final generalSettingsRepository = MockGeneralSettingsRepository(); when(generalSettingsRepository.settings) @@ -1283,6 +1295,71 @@ void main() { .text, ":${TestData.customEmoji1.baseName}:"); }); + + testWidgets("MFMの関数名の入力補完が可能なこと", (tester) async { + await tester.pumpWidget( + ProviderScope( + child: DefaultRootWidget( + initialRoute: NoteCreateRoute(initialAccount: TestData.account), + ), + ), + ); + await tester.pumpAndSettle(); + await tester.tap(find.text(r"$[")); + await tester.pumpAndSettle(); + expect(find.text("tada"), findsOneWidget); + + await tester.enterText(find.byType(TextField).hitTestable(), r"$[r"); + await tester.pumpAndSettle(); + await tester.tap(find.text("rainbow")); + await tester.pumpAndSettle(); + expect( + tester + .textEditingController(find.byType(TextField).hitTestable()) + .text, + r"$[rainbow ", + ); + }); + + testWidgets("ハッシュタグの入力補完が可能なこと", (tester) async { + final mockMisskey = MockMisskey(); + final mockHashtags = MockMisskeyHashtags(); + when(mockMisskey.hashtags).thenReturn(mockHashtags); + when(mockHashtags.trend()).thenAnswer( + (_) async => [ + const HashtagsTrendResponse(tag: "abc", chart: [], usersCount: 0), + ], + ); + when(mockHashtags.search(any)).thenAnswer( + (_) async => ["def"], + ); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + misskeyProvider.overrideWith((ref, arg) => mockMisskey) + ], + child: DefaultRootWidget( + initialRoute: NoteCreateRoute(initialAccount: TestData.account), + ), + ), + ); + await tester.pumpAndSettle(); + await tester.enterText(find.byType(TextField).hitTestable(), "#"); + await tester.pumpAndSettle(); + expect(find.text("abc"), findsOneWidget); + + await tester.enterText(find.byType(TextField).hitTestable(), "#d"); + await tester.pumpAndSettle(); + await tester.tap(find.text("def")); + await tester.pumpAndSettle(); + expect( + tester + .textEditingController(find.byType(TextField).hitTestable()) + .text, + "#def ", + ); + }); }); group("プレビュー", () { @@ -1336,7 +1413,7 @@ void main() { find.descendant( of: find.byType(MfmPreview), matching: find.textContaining( - TestData.note3ExpectUserName.tight, + TestData.note3AsAnotherUser.user.username.tight, findRichText: true)), findsOneWidget); }); @@ -2065,6 +2142,8 @@ void main() { await tester.tap(find.text(TestData.drive1.name), warnIfMissed: false); await tester.pumpAndSettle(); + await tester.tap(find.byIcon(Icons.check)); + await tester.pumpAndSettle(); await tester.enterText( find.byType(TextField).hitTestable(), ":ai_yay:"); @@ -2143,11 +2222,15 @@ void main() { .equals([TestData.drive1.id], arg.fileIds)))))); verify(mockDriveFiles.createAsBinary( - argThat(equals(const DriveFilesCreateRequest( - name: "test.png", - force: true, - comment: "", - isSensitive: false))), + argThat( + equals( + const DriveFilesCreateRequest( + name: "test.png", + force: true, + isSensitive: false, + ), + ), + ), argThat(equals(predicate((value) => const DeepCollectionEquality().equals(value, binaryImage)))))); }); diff --git a/test/view/search_page/search_page_test.dart b/test/view/search_page/search_page_test.dart index 5e0260c51..4ed6fd98f 100644 --- a/test/view/search_page/search_page_test.dart +++ b/test/view/search_page/search_page_test.dart @@ -35,16 +35,16 @@ void main() { verify(mockNote.search( argThat(equals(const NotesSearchRequest(query: "Misskey"))))) .called(1); - expect(find.text(TestData.note1ExpectText), findsOneWidget); + expect(find.text(TestData.note1.text!), findsOneWidget); when(mockNote.search(any)).thenAnswer((_) async => [TestData.note2]); await tester.tap(find.byIcon(Icons.keyboard_arrow_down).at(1)); await tester.pumpAndSettle(); verify(mockNote.search(argThat(equals(NotesSearchRequest( - query: "Misskey", untilId: TestData.note1ExpectId))))) + query: "Misskey", untilId: TestData.note1.id))))) .called(1); - expect(find.text(TestData.note2ExpectText), findsOneWidget); + expect(find.text(TestData.note2.text!), findsOneWidget); }); testWidgets("ユーザー指定ができること", (tester) async { @@ -82,7 +82,7 @@ void main() { findsOneWidget); // ノートが表示されていること - expect(find.text(TestData.note1ExpectText), findsOneWidget); + expect(find.text(TestData.note1.text!), findsOneWidget); verify(mockNote.search(argThat(equals( NotesSearchRequest(query: "", userId: TestData.user1ExpectId))))) .called(1); @@ -114,20 +114,20 @@ void main() { await tester.tap(find.byIcon(Icons.keyboard_arrow_right).at(1)); await tester.pumpAndSettle(); - await tester.tap(find.text(TestData.channel1ExpectName)); + await tester.tap(find.text(TestData.channel1.name)); await tester.pumpAndSettle(); // 指定したユーザーが表示されていること expect( find.descendant( of: find.byType(Card), - matching: find.text(TestData.channel1ExpectName)), + matching: find.text(TestData.channel1.name)), findsOneWidget); // ノートが表示されていること - expect(find.text(TestData.note1ExpectText), findsOneWidget); - verify(mockNote.search(argThat(equals(NotesSearchRequest( - query: "", channelId: TestData.channel1ExpectId))))) + expect(find.text(TestData.note1.text!), findsOneWidget); + verify(mockNote.search(argThat(equals( + NotesSearchRequest(query: "", channelId: TestData.channel1.id))))) .called(1); }); @@ -152,16 +152,16 @@ void main() { verify(mockNote.searchByTag(argThat( equals(const NotesSearchByTagRequest(tag: "藍ちゃん大食いチャレンジ"))))) .called(1); - expect(find.text(TestData.note1ExpectText), findsOneWidget); + expect(find.text(TestData.note1.text!), findsOneWidget); when(mockNote.searchByTag(any)).thenAnswer((_) async => [TestData.note2]); await tester.tap(find.byIcon(Icons.keyboard_arrow_down).at(1)); await tester.pumpAndSettle(); verify(mockNote.searchByTag(argThat(equals(NotesSearchByTagRequest( - tag: "藍ちゃん大食いチャレンジ", untilId: TestData.note1ExpectId))))) + tag: "藍ちゃん大食いチャレンジ", untilId: TestData.note1.id))))) .called(1); - expect(find.text(TestData.note2ExpectText), findsOneWidget); + expect(find.text(TestData.note2.text!), findsOneWidget); }); }); @@ -302,7 +302,7 @@ void main() { )); await tester.pumpAndSettle(); - expect(find.text(TestData.note1ExpectText), findsOneWidget); + expect(find.text(TestData.note1.text!), findsOneWidget); verify(mockNote.search( argThat(equals(const NotesSearchRequest(query: "Misskey"))))) .called(1);