diff --git a/lib/providers.dart b/lib/providers.dart index b64f407c1..2526f7251 100644 --- a/lib/providers.dart +++ b/lib/providers.dart @@ -28,6 +28,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:miria/state_notifier/common/download_file_notifier.dart'; import 'package:miria/state_notifier/common/misskey_server_list_notifier.dart'; import 'package:miria/state_notifier/drive_page/breadcrumbs_notifier.dart'; +import 'package:miria/state_notifier/drive_page/drive_file_notes_page/drive_files_attached_notes_notifier.dart'; import 'package:miria/state_notifier/drive_page/drive_files_notifier.dart'; import 'package:miria/state_notifier/drive_page/drive_folders_notifier.dart'; import 'package:miria/state_notifier/note_create_page/note_create_state_notifier.dart'; @@ -286,3 +287,8 @@ final breadcrumbsNotifierProvider = NotifierProvider.autoDispose>( BreadcrumbsNotifier.new, ); + +final driveFilesAttachedNotesProvider = NotifierProvider.autoDispose.family< + DriveFilesAttachedNotesNotifier, + PaginationState, + (Misskey, String)>(DriveFilesAttachedNotesNotifier.new); diff --git a/lib/state_notifier/drive_page/drive_file_notes_page/drive_files_attached_notes_notifier.dart b/lib/state_notifier/drive_page/drive_file_notes_page/drive_files_attached_notes_notifier.dart new file mode 100644 index 000000000..ae4f21065 --- /dev/null +++ b/lib/state_notifier/drive_page/drive_file_notes_page/drive_files_attached_notes_notifier.dart @@ -0,0 +1,45 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/model/pagination_state.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +class DriveFilesAttachedNotesNotifier extends AutoDisposeFamilyNotifier< + PaginationState, (Misskey, String)> { + @override + PaginationState build((Misskey, String) arg) { + Future(loadMore); + return const PaginationState(); + } + + Misskey get _misskey => arg.$1; + + String get _fileId => arg.$2; + + Future loadMore() async { + if (state.isLoading || state.isLastLoaded) { + return; + } + state = state.copyWith(isLoading: true); + final untilId = state.lastOrNull?.id; + try { + final response = await _misskey.drive.files.attachedNotes( + DriveFilesAttachedNotesRequest( + fileId: _fileId, + untilId: untilId, + ), + ); + // Misskey 2023.10.0 より前はpaginationがなかったため + if (response.any((e) => e.id == state.firstOrNull?.id)) { + state = state.copyWith(isLastLoaded: true); + } else { + state = state.copyWith( + items: [...state, ...response], + isLastLoaded: response.isEmpty, + ); + } + } finally { + state = state.copyWith(isLoading: false); + } + } +} diff --git a/lib/view/drive_page/drive_file_grid_item.dart b/lib/view/drive_page/drive_file_grid_item.dart index bc0964dca..d2253f9b4 100644 --- a/lib/view/drive_page/drive_file_grid_item.dart +++ b/lib/view/drive_page/drive_file_grid_item.dart @@ -27,6 +27,13 @@ class DriveFileGridItem extends ConsumerWidget { file: file, ), ), + onLongPress: () => showModalBottomSheet( + context: context, + builder: (context) => DriveFileModalSheet( + account: account, + file: file, + ), + ), child: Column( children: [ Expanded( diff --git a/lib/view/drive_page/drive_file_page/drive_file_notes.dart b/lib/view/drive_page/drive_file_page/drive_file_notes.dart new file mode 100644 index 000000000..78f8e746d --- /dev/null +++ b/lib/view/drive_page/drive_file_page/drive_file_notes.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:miria/model/account.dart'; +import 'package:miria/model/general_settings.dart'; +import 'package:miria/providers.dart'; +import 'package:miria/view/common/account_scope.dart'; +import 'package:miria/view/common/error_dialog_handler.dart'; +import 'package:miria/view/common/misskey_notes/misskey_note.dart'; +import 'package:miria/view/common/pagination_bottom_item.dart'; +import 'package:misskey_dart/misskey_dart.dart'; + +class DriveFileNotes extends ConsumerWidget { + const DriveFileNotes({ + super.key, + required this.account, + required this.file, + }); + + final Account account; + final DriveFile file; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final misskey = ref.watch(misskeyProvider(account)); + final notes = + ref.watch(driveFilesAttachedNotesProvider((misskey, file.id))); + final loadAutomatically = ref.watch( + generalSettingsRepositoryProvider.select( + (repository) => + repository.settings.automaticPush == AutomaticPush.automatic, + ), + ); + + return RefreshIndicator( + onRefresh: () async => + ref.invalidate(driveFilesAttachedNotesProvider((misskey, file.id))), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: ListView.builder( + itemCount: notes.length + 1, + itemBuilder: (context, index) { + if (index < notes.length) { + return AccountScope( + account: account, + child: MisskeyNote(note: notes[index]), + ); + } + if (loadAutomatically && !notes.isLoading && !notes.isLastLoaded) { + Future(() { + ref + .read( + driveFilesAttachedNotesProvider((misskey, file.id)) + .notifier, + ) + .loadMore() + .expectFailure(context); + }); + } + return Center( + child: Padding( + padding: const EdgeInsets.all(10), + child: PaginationBottomItem( + paginationState: notes, + noItemLabel: const Text("添付されているノートがありません"), + child: IconButton( + onPressed: () {}, + icon: const Icon(Icons.keyboard_arrow_down), + ), + ), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/view/drive_page/drive_file_page/drive_file_page.dart b/lib/view/drive_page/drive_file_page/drive_file_page.dart index ea136e8b8..fe482230c 100644 --- a/lib/view/drive_page/drive_file_page/drive_file_page.dart +++ b/lib/view/drive_page/drive_file_page/drive_file_page.dart @@ -6,6 +6,7 @@ import 'package:miria/model/account.dart'; import 'package:miria/providers.dart'; import 'package:miria/view/drive_page/drive_file_modal_sheet.dart'; import 'package:miria/view/drive_page/drive_file_page/drive_file_details.dart'; +import 'package:miria/view/drive_page/drive_file_page/drive_file_notes.dart'; import 'package:misskey_dart/misskey_dart.dart'; @RoutePage() @@ -29,7 +30,7 @@ class DriveFilePage extends ConsumerWidget { ) ?? this.file; return DefaultTabController( - length: 1, + length: 2, child: Scaffold( appBar: AppBar( title: const Text("ファイルの詳細"), @@ -57,12 +58,14 @@ class DriveFilePage extends ConsumerWidget { bottom: const TabBar( tabs: [ Tab(icon: Icon(Icons.info), text: "情報"), + Tab(icon: Icon(Icons.edit), text: "添付されているノート"), ], ), ), body: TabBarView( children: [ DriveFileDetails(account: account, file: file), + DriveFileNotes(account: account, file: file), ], ), ),