♻️ Rebuilt the context menu
This commit is contained in:
parent
a7d4975e18
commit
1d52b8b5ed
@ -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),
|
||||||
),
|
),
|
||||||
|
@ -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',
|
||||||
|
@ -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),
|
||||||
|
@ -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
|
||||||
|
84
lib/widgets/context_menu.dart
Normal file
84
lib/widgets/context_menu.dart
Normal 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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user