♻️ Rebuilt the context menu

This commit is contained in:
LittleSheep 2025-04-26 18:35:17 +08:00
parent a7d4975e18
commit 1d52b8b5ed
6 changed files with 164 additions and 59 deletions

View File

@ -48,7 +48,12 @@ class ExploreScreen extends ConsumerWidget {
onFetchData: controller.fetchMore, onFetchData: controller.fetchMore,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final post = controller.posts[index]; final post = controller.posts[index];
return PostItem(item: post); return PostItem(
item: post,
onRefresh: (_) {
ref.invalidate(postListProvider);
},
);
}, },
separatorBuilder: (_, __) => const Divider(height: 1), separatorBuilder: (_, __) => const Divider(height: 1),
), ),

View File

@ -131,7 +131,7 @@ class PostComposeScreen extends HookConsumerWidget {
attachmentProgress.value = {...attachmentProgress.value, index: 0}; attachmentProgress.value = {...attachmentProgress.value, index: 0};
final cloudFile = final cloudFile =
await putMediaToCloud( await putMediaToCloud(
fileData: attachment, fileData: attachment.data,
atk: atk, atk: atk,
baseUrl: baseUrl, baseUrl: baseUrl,
filename: attachment.data.name ?? 'Post media', filename: attachment.data.name ?? 'Post media',

View File

@ -38,7 +38,7 @@ class PostDetailScreen extends HookConsumerWidget {
children: [ children: [
Column( Column(
children: [ children: [
PostItem(item: post!), PostItem(item: post!, isOpenable: false),
const Divider(height: 1), const Divider(height: 1),
Expanded(child: PostRepliesList(postId: id)), Expanded(child: PostRepliesList(postId: id)),
Gap(MediaQuery.of(context).padding.bottom), Gap(MediaQuery.of(context).padding.bottom),

View File

@ -3,11 +3,14 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:media_kit/media_kit.dart'; import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart'; import 'package:media_kit_video/media_kit_video.dart';
class UniversalVideo extends StatefulWidget { class UniversalVideo extends ConsumerStatefulWidget {
final String uri; final String uri;
final double aspectRatio; final double aspectRatio;
const UniversalVideo({ const UniversalVideo({
@ -17,10 +20,10 @@ class UniversalVideo extends StatefulWidget {
}); });
@override @override
State<UniversalVideo> createState() => _UniversalVideoState(); ConsumerState<UniversalVideo> createState() => _UniversalVideoState();
} }
class _UniversalVideoState extends State<UniversalVideo> { class _UniversalVideoState extends ConsumerState<UniversalVideo> {
Player? _player; Player? _player;
VideoController? _videoController; VideoController? _videoController;
@ -35,9 +38,18 @@ class _UniversalVideoState extends State<UniversalVideo> {
final inCacheInfo = await DefaultCacheManager().getFileFromCache(url); final inCacheInfo = await DefaultCacheManager().getFileFromCache(url);
if (inCacheInfo == null) { if (inCacheInfo == null) {
log('[MediaPlayer] Miss cache: $url'); log('[MediaPlayer] Miss cache: $url');
final baseUrl = ref.watch(serverUrlProvider);
final atk = await getFreshAtk(
ref.watch(tokenPairProvider),
baseUrl,
onRefreshed: (atk, rtk) {
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
ref.invalidate(tokenPairProvider);
},
);
final fileStream = DefaultCacheManager().getFileStream( final fileStream = DefaultCacheManager().getFileStream(
url, url,
// headers: {'Authorization': 'Bearer ${await ua.atk}'}, headers: {'Authorization': 'Bearer $atk'},
withProgress: true, withProgress: true,
); );
await for (var fileInfo in fileStream) { await for (var fileInfo in fileStream) {
@ -55,7 +67,7 @@ class _UniversalVideoState extends State<UniversalVideo> {
return; return;
} }
_player!.open(Media(uri)); _player!.open(Media(uri), play: false);
} }
@override @override

View File

@ -0,0 +1,84 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
typedef ContextMenuBuilder =
Widget Function(BuildContext context, Offset offset);
class ContextMenuRegion extends HookWidget {
final Offset? mobileAnchor;
final Widget child;
final ContextMenuBuilder contextMenuBuilder;
const ContextMenuRegion({
super.key,
required this.child,
required this.contextMenuBuilder,
this.mobileAnchor,
});
@override
Widget build(BuildContext context) {
final contextMenuController = useMemoized(() => ContextMenuController());
final mobileOffset = useState<Offset?>(null);
bool canBeTouchScreen = switch (defaultTargetPlatform) {
TargetPlatform.android || TargetPlatform.iOS => true,
_ => false,
};
void showMenu(Offset position) {
contextMenuController.show(
context: context,
contextMenuBuilder: (BuildContext context) {
return contextMenuBuilder(context, position);
},
);
}
void hideMenu() {
contextMenuController.remove();
}
void onSecondaryTapUp(TapUpDetails details) {
showMenu(details.globalPosition);
}
void onTap() {
if (!contextMenuController.isShown) {
return;
}
hideMenu();
}
void onLongPressStart(LongPressStartDetails details) {
mobileOffset.value = details.globalPosition;
}
void onLongPress() {
assert(mobileOffset.value != null);
showMenu(mobileAnchor ?? mobileOffset.value!);
mobileOffset.value = null;
}
useEffect(() {
return () {
hideMenu();
};
}, []);
return TapRegion(
behavior: HitTestBehavior.opaque,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onSecondaryTapUp: onSecondaryTapUp,
onTap: onTap,
onLongPress: canBeTouchScreen ? onLongPress : null,
onLongPressStart: canBeTouchScreen ? onLongPressStart : null,
child: child,
),
onTapOutside: (_) {
hideMenu();
},
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
@ -6,18 +7,20 @@ import 'package:island/route.gr.dart';
import 'package:island/widgets/content/cloud_file_collection.dart'; import 'package:island/widgets/content/cloud_file_collection.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/markdown.dart'; import 'package:island/widgets/content/markdown.dart';
import 'package:lucide_icons/lucide_icons.dart'; import 'package:island/widgets/context_menu.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
class PostItem extends StatelessWidget { class PostItem extends StatelessWidget {
final SnPost item; final SnPost item;
final EdgeInsets? padding; final EdgeInsets? padding;
final bool isOpenable; final bool isOpenable;
final Function? onRefresh;
const PostItem({ const PostItem({
super.key, super.key,
required this.item, required this.item,
this.padding, this.padding,
this.isOpenable = true, this.isOpenable = true,
this.onRefresh,
}); });
@override @override
@ -25,21 +28,25 @@ class PostItem extends StatelessWidget {
final renderingPadding = final renderingPadding =
padding ?? EdgeInsets.symmetric(horizontal: 12, vertical: 16); padding ?? EdgeInsets.symmetric(horizontal: 12, vertical: 16);
return CupertinoContextMenu.builder( return ContextMenuRegion(
actions: [ contextMenuBuilder: (_, offset) {
CupertinoContextMenuAction( return AdaptiveTextSelectionToolbar.buttonItems(
trailingIcon: LucideIcons.edit, anchors: TextSelectionToolbarAnchors(primaryAnchor: offset),
buttonItems: <ContextMenuButtonItem>[
ContextMenuButtonItem(
onPressed: () { onPressed: () {
context.router.push(PostEditRoute(id: item.id)); ContextMenuController.removeAny();
context.router.push(PostEditRoute(id: item.id)).then((value) {
if (value != null) {
onRefresh?.call();
}
});
}, },
child: Text('Edit'), label: 'edit'.tr(),
), ),
], ],
builder: (context, animation) { );
return Material( },
color: Theme.of(context).colorScheme.surface,
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Padding( child: Padding(
padding: renderingPadding, padding: renderingPadding,
child: Column( child: Column(
@ -75,9 +82,6 @@ class PostItem extends StatelessWidget {
], ],
), ),
), ),
),
);
},
); );
} }
} }