Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
ChatGPT
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
关振斌
ChatGPT
Commits
02675894
Commit
02675894
authored
Apr 11, 2023
by
skeyboy
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
弹出菜单修改
parent
0f4ec721
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
468 additions
and
4 deletions
+468
-4
lib/package/chat_dash/dash_chat_2.dart
lib/package/chat_dash/dash_chat_2.dart
+2
-1
lib/package/chat_dash/src/widgets/message_row/default_message_text.dart
...at_dash/src/widgets/message_row/default_message_text.dart
+3
-3
lib/package/chat_dash/src/widgets/message_row/pop_menu.dart
lib/package/chat_dash/src/widgets/message_row/pop_menu.dart
+463
-0
No files found.
lib/package/chat_dash/dash_chat_2.dart
View file @
02675894
...
...
@@ -2,7 +2,7 @@ library dash_chat_2;
import
'dart:io'
;
import
'dart:math'
;
import
'package:custom_pop_up_menu/custom_pop_up_menu.dart'
;
//
import 'package:custom_pop_up_menu/custom_pop_up_menu.dart';
import
'package:clipboard/clipboard.dart'
;
import
'package:flutter_easyloading/flutter_easyloading.dart'
;
import
'package:flutter_screenutil/flutter_screenutil.dart'
;
...
...
@@ -30,6 +30,7 @@ import 'package:video_player/video_player.dart' as vp;
import
'../../pages/home/controller.dart'
;
import
'../markdown/src/style_sheet.dart'
;
import
'src/widgets/image_provider/image_provider.dart'
;
import
'src/widgets/message_row/pop_menu.dart'
;
export
'package:flutter_parsed_text/flutter_parsed_text.dart'
;
...
...
lib/package/chat_dash/src/widgets/message_row/default_message_text.dart
View file @
02675894
...
...
@@ -312,14 +312,14 @@ class _DefaultMessageTextState extends State<DefaultMessageText> {
return
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
5
),
child:
Container
(
width:
220
,
width:
menuItems
.
menuMaxWidth
,
// padding: EdgeInsets.symmetric(horizontal: 5, vertical: 5),
color:
const
Color
(
0xFF4C4C4C
),
child:
GridView
.
count
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
10
,
vertical:
10
),
crossAxisCount:
5
,
crossAxisCount:
menuItems
.
rowMaxItemCount
,
crossAxisSpacing:
0
,
mainAxisSpacing:
10
,
mainAxisSpacing:
5
,
shrinkWrap:
true
,
physics:
NeverScrollableScrollPhysics
(),
children:
menuItems
...
...
lib/package/chat_dash/src/widgets/message_row/pop_menu.dart
0 → 100644
View file @
02675894
import
'dart:io'
;
import
'dart:math'
as
math
;
import
'dart:math'
;
import
'package:flutter/material.dart'
;
enum
PressType
{
longPress
,
singleClick
,
}
enum
PreferredPosition
{
top
,
bottom
,
}
extension
CustomPopupItemConfig
on
List
{
double
get
menuMaxWidth
=>
min
(
length
,
5
)
*
PopupMenuItemConfig
.
itemWidth
;
int
get
rowMaxItemCount
=>
min
(
length
,
5
);
}
class
PopupMenuItemConfig
{
static
final
double
itemWidth
=
44.0
;
}
class
CustomPopupMenuController
extends
ChangeNotifier
{
bool
menuIsShowing
=
false
;
Offset
localPosition
=
Offset
.
zero
;
void
showMenu
()
{
menuIsShowing
=
true
;
notifyListeners
();
}
void
hideMenu
()
{
menuIsShowing
=
false
;
notifyListeners
();
}
void
toggleMenu
()
{
menuIsShowing
=
!
menuIsShowing
;
notifyListeners
();
}
}
Rect
_menuRect
=
Rect
.
zero
;
class
CustomPopupMenu
extends
StatefulWidget
{
CustomPopupMenu
({
required
this
.
child
,
required
this
.
menuBuilder
,
required
this
.
pressType
,
this
.
controller
,
this
.
arrowColor
=
const
Color
(
0xFF4C4C4C
),
this
.
showArrow
=
true
,
this
.
barrierColor
=
Colors
.
black12
,
this
.
arrowSize
=
10.0
,
this
.
horizontalMargin
=
10.0
,
this
.
verticalMargin
=
10.0
,
this
.
position
,
this
.
menuOnChange
,
this
.
enablePassEvent
=
true
,
});
final
Widget
child
;
final
PressType
pressType
;
final
bool
showArrow
;
final
Color
arrowColor
;
final
Color
barrierColor
;
final
double
horizontalMargin
;
final
double
verticalMargin
;
final
double
arrowSize
;
final
CustomPopupMenuController
?
controller
;
final
Widget
Function
()
menuBuilder
;
final
PreferredPosition
?
position
;
final
void
Function
(
bool
)?
menuOnChange
;
/// Pass tap event to the widgets below the mask.
/// It only works when [barrierColor] is transparent.
final
bool
enablePassEvent
;
@override
_CustomPopupMenuState
createState
()
=>
_CustomPopupMenuState
();
}
class
_CustomPopupMenuState
extends
State
<
CustomPopupMenu
>
{
RenderBox
?
_childBox
;
RenderBox
?
_parentBox
;
OverlayEntry
?
_overlayEntry
;
Offset
?
eventLocation
=
Offset
.
zero
;
CustomPopupMenuController
?
_controller
;
bool
_canResponse
=
true
;
_showMenu
()
{
Widget
arrow
=
ClipPath
(
child:
Container
(
width:
widget
.
arrowSize
,
height:
widget
.
arrowSize
,
color:
widget
.
arrowColor
,
),
clipper:
_ArrowClipper
(),
);
print
(
"父容器
${_parentBox!.size}
${_parentBox!.localToGlobal(Offset.zero)}
${_parentBox!.globalToLocal(Offset.zero)}
"
);
print
(
"子容器
${_childBox!.size}
${_childBox!.localToGlobal(Offset.zero)}
${_childBox!.globalToLocal(Offset.zero)}
"
);
_overlayEntry
=
OverlayEntry
(
builder:
(
context
)
{
Widget
menu
=
Center
(
child:
Container
(
constraints:
BoxConstraints
(
maxWidth:
_parentBox
!.
size
.
width
-
2
*
widget
.
horizontalMargin
,
minWidth:
0
,
),
child:
CustomMultiChildLayout
(
delegate:
_MenuLayoutDelegate
(
tapedPoint:
_controller
?.
localPosition
??
Offset
.
zero
,
anchorSize:
_childBox
!.
size
,
anchorOffset:
_childBox
!.
localToGlobal
(
Offset
(-
widget
.
horizontalMargin
,
0
),
),
verticalMargin:
widget
.
verticalMargin
,
position:
widget
.
position
,
),
children:
<
Widget
>[
if
(
widget
.
showArrow
)
LayoutId
(
id:
_MenuLayoutId
.
arrow
,
child:
arrow
,
),
if
(
widget
.
showArrow
)
LayoutId
(
id:
_MenuLayoutId
.
downArrow
,
child:
Transform
.
rotate
(
angle:
math
.
pi
,
child:
arrow
,
),
),
LayoutId
(
id:
_MenuLayoutId
.
content
,
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
Material
(
child:
widget
.
menuBuilder
(),
color:
Colors
.
transparent
,
),
],
),
),
],
),
),
);
return
Listener
(
behavior:
widget
.
enablePassEvent
?
HitTestBehavior
.
translucent
:
HitTestBehavior
.
opaque
,
onPointerDown:
(
PointerDownEvent
event
)
{
Offset
offset
=
event
.
localPosition
;
// If tap position in menu
if
(
_menuRect
.
contains
(
Offset
(
offset
.
dx
-
widget
.
horizontalMargin
,
offset
.
dy
)))
{
return
;
}
_controller
?.
localPosition
=
event
.
localPosition
;
_controller
?.
hideMenu
();
// When [enablePassEvent] works and we tap the [child] to [hideMenu],
// but the passed event would trigger [showMenu] again.
// So, we use time threshold to solve this bug.
_canResponse
=
false
;
Future
.
delayed
(
Duration
(
milliseconds:
300
))
.
then
((
_
)
=>
_canResponse
=
true
);
},
child:
widget
.
barrierColor
==
Colors
.
transparent
?
menu
:
Container
(
color:
widget
.
barrierColor
,
child:
menu
,
),
);
},
);
if
(
_overlayEntry
!=
null
)
{
Overlay
.
of
(
context
)!.
insert
(
_overlayEntry
!);
}
}
_hideMenu
()
{
if
(
_overlayEntry
!=
null
)
{
_overlayEntry
?.
remove
();
_overlayEntry
=
null
;
}
}
_updateView
()
{
bool
menuIsShowing
=
_controller
?.
menuIsShowing
??
false
;
widget
.
menuOnChange
?.
call
(
menuIsShowing
);
if
(
menuIsShowing
)
{
_showMenu
();
}
else
{
_hideMenu
();
}
}
@override
void
initState
()
{
super
.
initState
();
_controller
=
widget
.
controller
;
if
(
_controller
==
null
)
_controller
=
CustomPopupMenuController
();
_controller
?.
addListener
(
_updateView
);
WidgetsBinding
.
instance
.
addPostFrameCallback
((
call
)
{
if
(
mounted
)
{
_childBox
=
context
.
findRenderObject
()
as
RenderBox
?;
_parentBox
=
Overlay
.
of
(
context
)?.
context
.
findRenderObject
()
as
RenderBox
?;
}
});
}
@override
void
dispose
()
{
_hideMenu
();
_controller
?.
removeListener
(
_updateView
);
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
var
child
=
Material
(
child:
InkWell
(
hoverColor:
Colors
.
transparent
,
focusColor:
Colors
.
transparent
,
splashColor:
Colors
.
transparent
,
highlightColor:
Colors
.
transparent
,
child:
widget
.
child
,
onTapDown:
(
detail
)
{
_controller
?.
localPosition
=
detail
.
localPosition
;
},
onTapUp:
(
detail
)
{
_controller
?.
localPosition
=
detail
.
localPosition
;
},
onTap:
()
{
if
(
widget
.
pressType
==
PressType
.
singleClick
&&
_canResponse
)
{
_controller
?.
showMenu
();
}
},
onLongPress:
()
{
if
(
widget
.
pressType
==
PressType
.
longPress
&&
_canResponse
)
{
_controller
?.
showMenu
();
}
},
),
color:
Colors
.
transparent
,
);
if
(
Platform
.
isIOS
)
{
return
child
;
}
else
{
return
WillPopScope
(
onWillPop:
()
{
_hideMenu
();
return
Future
.
value
(
true
);
},
child:
child
,
);
}
}
}
enum
_MenuLayoutId
{
arrow
,
downArrow
,
content
,
}
enum
_MenuPosition
{
bottomLeft
,
bottomCenter
,
bottomRight
,
topLeft
,
topCenter
,
topRight
,
}
class
_MenuLayoutDelegate
extends
MultiChildLayoutDelegate
{
_MenuLayoutDelegate
({
required
this
.
anchorSize
,
required
this
.
anchorOffset
,
required
this
.
verticalMargin
,
required
this
.
tapedPoint
,
this
.
position
,
});
final
Offset
tapedPoint
;
final
Size
anchorSize
;
final
Offset
anchorOffset
;
final
double
verticalMargin
;
final
PreferredPosition
?
position
;
@override
void
performLayout
(
Size
size
)
{
Size
contentSize
=
Size
.
zero
;
Size
arrowSize
=
Size
.
zero
;
Offset
contentOffset
=
Offset
(
0
,
0
);
Offset
arrowOffset
=
Offset
(
0
,
0
);
double
anchorCenterX
=
anchorOffset
.
dx
+
anchorSize
.
width
/
2
;
double
anchorTopY
=
anchorOffset
.
dy
;
double
anchorBottomY
=
anchorTopY
+
anchorSize
.
height
;
_MenuPosition
menuPosition
=
_MenuPosition
.
bottomCenter
;
if
(
hasChild
(
_MenuLayoutId
.
content
))
{
contentSize
=
layoutChild
(
_MenuLayoutId
.
content
,
BoxConstraints
.
loose
(
size
),
);
}
if
(
hasChild
(
_MenuLayoutId
.
arrow
))
{
arrowSize
=
layoutChild
(
_MenuLayoutId
.
arrow
,
BoxConstraints
.
loose
(
size
),
);
}
if
(
hasChild
(
_MenuLayoutId
.
downArrow
))
{
layoutChild
(
_MenuLayoutId
.
downArrow
,
BoxConstraints
.
loose
(
size
),
);
}
print
(
"文本内容大小
$size
$contentSize
$anchorOffset
点击位置
$tapedPoint
"
);
bool
isTop
=
false
;
if
(
position
==
null
)
{
// auto calculate position
isTop
=
anchorBottomY
>
size
.
height
/
2
;
}
else
{
isTop
=
position
==
PreferredPosition
.
top
;
}
if
(
anchorCenterX
-
contentSize
.
width
/
2
<
0
)
{
menuPosition
=
isTop
?
_MenuPosition
.
topLeft
:
_MenuPosition
.
bottomLeft
;
}
else
if
(
anchorCenterX
+
contentSize
.
width
/
2
>
size
.
width
)
{
menuPosition
=
isTop
?
_MenuPosition
.
topRight
:
_MenuPosition
.
bottomRight
;
}
else
{
menuPosition
=
isTop
?
_MenuPosition
.
topCenter
:
_MenuPosition
.
bottomCenter
;
}
switch
(
menuPosition
)
{
case
_MenuPosition
.
bottomCenter
:
arrowOffset
=
Offset
(
anchorCenterX
-
arrowSize
.
width
/
2
,
anchorBottomY
+
verticalMargin
,
);
contentOffset
=
Offset
(
anchorCenterX
-
contentSize
.
width
/
2
,
anchorBottomY
+
verticalMargin
+
arrowSize
.
height
,
);
break
;
case
_MenuPosition
.
bottomLeft
:
arrowOffset
=
Offset
(
anchorCenterX
-
arrowSize
.
width
/
2
,
anchorBottomY
+
verticalMargin
);
contentOffset
=
Offset
(
0
,
anchorBottomY
+
verticalMargin
+
arrowSize
.
height
,
);
break
;
case
_MenuPosition
.
bottomRight
:
arrowOffset
=
Offset
(
anchorCenterX
-
arrowSize
.
width
/
2
,
anchorBottomY
+
verticalMargin
);
contentOffset
=
Offset
(
size
.
width
-
contentSize
.
width
,
anchorBottomY
+
verticalMargin
+
arrowSize
.
height
,
);
break
;
case
_MenuPosition
.
topCenter
:
arrowOffset
=
Offset
(
anchorCenterX
-
arrowSize
.
width
/
2
,
math
.
max
(
anchorTopY
-
verticalMargin
-
arrowSize
.
height
,
contentSize
.
height
*
2
),
);
contentOffset
=
Offset
(
anchorCenterX
-
contentSize
.
width
/
2
,
math
.
max
(
anchorTopY
-
verticalMargin
-
arrowSize
.
height
-
contentSize
.
height
,
contentSize
.
height
),
);
break
;
case
_MenuPosition
.
topLeft
:
arrowOffset
=
Offset
(
anchorCenterX
-
arrowSize
.
width
/
2
,
anchorTopY
-
verticalMargin
-
arrowSize
.
height
,
);
contentOffset
=
Offset
(
0
,
anchorTopY
-
verticalMargin
-
arrowSize
.
height
-
contentSize
.
height
,
);
break
;
case
_MenuPosition
.
topRight
:
arrowOffset
=
Offset
(
anchorCenterX
-
arrowSize
.
width
/
2
,
anchorTopY
-
verticalMargin
-
arrowSize
.
height
,
);
contentOffset
=
Offset
(
size
.
width
-
contentSize
.
width
,
anchorTopY
-
verticalMargin
-
arrowSize
.
height
-
contentSize
.
height
,
);
break
;
}
if
(
hasChild
(
_MenuLayoutId
.
content
))
{
positionChild
(
_MenuLayoutId
.
content
,
contentOffset
);
}
_menuRect
=
Rect
.
fromLTWH
(
contentOffset
.
dx
,
contentOffset
.
dy
,
contentSize
.
width
,
contentSize
.
height
,
);
bool
isBottom
=
false
;
if
(
_MenuPosition
.
values
.
indexOf
(
menuPosition
)
<
3
)
{
// bottom
isBottom
=
true
;
}
if
(
hasChild
(
_MenuLayoutId
.
arrow
))
{
positionChild
(
_MenuLayoutId
.
arrow
,
isBottom
?
Offset
(
arrowOffset
.
dx
,
arrowOffset
.
dy
+
0.1
)
:
Offset
(-
100
,
0
),
);
}
if
(
hasChild
(
_MenuLayoutId
.
downArrow
))
{
positionChild
(
_MenuLayoutId
.
downArrow
,
!
isBottom
?
Offset
(
arrowOffset
.
dx
,
arrowOffset
.
dy
-
0.1
)
:
Offset
(-
100
,
0
),
);
}
}
@override
bool
shouldRelayout
(
MultiChildLayoutDelegate
oldDelegate
)
=>
false
;
}
class
_ArrowClipper
extends
CustomClipper
<
Path
>
{
@override
Path
getClip
(
Size
size
)
{
Path
path
=
Path
();
path
.
moveTo
(
0
,
size
.
height
);
path
.
lineTo
(
size
.
width
/
2
,
size
.
height
/
2
);
path
.
lineTo
(
size
.
width
,
size
.
height
);
return
path
;
}
@override
bool
shouldReclip
(
CustomClipper
<
Path
>
oldClipper
)
{
return
true
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment