Various Fixes

This commit is contained in:
Ilya Laktyushin 2021-07-26 16:39:26 +03:00
parent df7a68ea73
commit 472dbd21b1
10 changed files with 208 additions and 80 deletions

View File

@ -1126,6 +1126,26 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(true)
self.chatListDisplayNode.containerNode.didBeginSelectingChats = { [weak self] in
guard let strongSelf = self else {
return
}
if !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing {
var isEditing = false
strongSelf.chatListDisplayNode.containerNode.updateState { state in
isEditing = state.editing
return state
}
if !isEditing {
strongSelf.editPressed()
}
strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing = true
if let layout = strongSelf.validLayout {
strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut))
}
}
}
guard case .root = self.groupId else {
return
}
@ -1265,27 +1285,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return true
})
}
self.chatListDisplayNode.containerNode.didBeginSelectingChats = { [weak self] in
guard let strongSelf = self else {
return
}
if !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing {
var isEditing = false
strongSelf.chatListDisplayNode.containerNode.updateState { state in
isEditing = state.editing
return state
}
if !isEditing {
strongSelf.editPressed()
}
strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing = true
if let layout = strongSelf.validLayout {
strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut))
}
}
}
if !self.processedFeaturedFilters {
let initializedFeatured = self.context.account.postbox.preferencesView(keys: [
PreferencesKeys.chatListFiltersFeaturedState

View File

@ -1997,6 +1997,13 @@ public final class ChatListNode: ListView {
}
private func handlePanSelection(location: CGPoint) {
var location = location
if location.y < self.insets.top {
location.y = self.insets.top + 5.0
} else if location.y > self.frame.height - self.insets.bottom {
location.y = self.frame.height - self.insets.bottom - 5.0
}
if let state = self.selectionPanState {
if let peer = self.peerAtPoint(location) {
if peer.id == state.initialPeerId {

View File

@ -27,7 +27,7 @@
- (void)setHidden:(bool)hidden animated:(bool)animated;
- (instancetype)initWithFrame:(CGRect)frame hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera;
- (instancetype)initWithFrame:(CGRect)frame hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera minZoomLevel:(CGFloat)minZoomLevel maxZoomLevel:(CGFloat)maxZoomLevel;
@end
@ -38,4 +38,6 @@
- (void)setHidden:(bool)hidden animated:(bool)animated;
- (instancetype)initWithFrame:(CGRect)frame hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera;
@end

View File

@ -82,6 +82,8 @@
bool _displayedTooltip;
TGMenuContainerView *_tooltipContainerView;
NSTimer *_tooltipTimer;
bool _dismissingWheel;
}
@end
@ -261,21 +263,31 @@
_toastView.userInteractionEnabled = false;
[self addSubview:_toastView];
_zoomModeView = [[TGCameraZoomModeView alloc] initWithFrame:CGRectMake(floor((frame.size.width - 129.0) / 2.0), frame.size.height - _bottomPanelHeight - _bottomPanelOffset - 18 - 43, 129, 43) hasUltrawideCamera:hasUltrawideCamera hasTelephotoCamera:hasTelephotoCamera];
_zoomModeView = [[TGCameraZoomModeView alloc] initWithFrame:CGRectMake(floor((frame.size.width - 129.0) / 2.0), frame.size.height - _bottomPanelHeight - _bottomPanelOffset - 18 - 43, 129, 43) hasUltrawideCamera:hasUltrawideCamera hasTelephotoCamera:hasTelephotoCamera minZoomLevel:hasUltrawideCamera ? 0.5 : 1.0 maxZoomLevel:8.0];
_zoomModeView.zoomChanged = ^(CGFloat zoomLevel, bool done, bool animated) {
__strong TGCameraMainPhoneView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (!done) {
[strongSelf->_zoomWheelView setZoomLevel:zoomLevel];
[strongSelf->_zoomModeView setHidden:true animated:true];
[strongSelf->_zoomWheelView setHidden:false animated:true];
} else {
if (done) {
[strongSelf->_zoomWheelView setZoomLevel:zoomLevel];
[strongSelf->_zoomModeView setZoomLevel:zoomLevel animated:false];
[strongSelf->_zoomModeView setHidden:false animated:true];
[strongSelf->_zoomWheelView setHidden:true animated:true];
if (!strongSelf->_zoomWheelView.isHidden) {
strongSelf->_dismissingWheel = true;
TGDispatchAfter(0.6, dispatch_get_main_queue(), ^{
if (strongSelf->_dismissingWheel) {
[strongSelf->_zoomModeView setHidden:false animated:true];
// [strongSelf->_zoomWheelView setHidden:true animated:true];
}
});
}
} else {
strongSelf->_dismissingWheel = false;
[strongSelf->_zoomWheelView setZoomLevel:zoomLevel];
[strongSelf->_zoomModeView setHidden:true animated:true];
// [strongSelf->_zoomWheelView setHidden:false animated:true];
}
if (strongSelf.zoomChanged != nil)
@ -284,7 +296,7 @@
[_zoomModeView setZoomLevel:1.0];
[self addSubview:_zoomModeView];
_zoomWheelView = [[TGCameraZoomWheelView alloc] initWithFrame:CGRectMake(0.0, frame.size.height - _bottomPanelHeight - _bottomPanelOffset - 132, frame.size.width, 132)];
_zoomWheelView = [[TGCameraZoomWheelView alloc] initWithFrame:CGRectMake(0.0, frame.size.height - _bottomPanelHeight - _bottomPanelOffset - 132, frame.size.width, 132) hasUltrawideCamera:hasUltrawideCamera hasTelephotoCamera:hasTelephotoCamera];
[_zoomWheelView setHidden:true animated:false];
[_zoomWheelView setZoomLevel:1.0];
_zoomWheelView.userInteractionEnabled = false;

View File

@ -227,6 +227,9 @@
@interface TGCameraZoomModeView ()
{
CGFloat _minZoomLevel;
CGFloat _maxZoomLevel;
UIView *_backgroundView;
bool _hasUltrawideCamera;
@ -240,13 +243,15 @@
@implementation TGCameraZoomModeView
- (instancetype)initWithFrame:(CGRect)frame hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera
- (instancetype)initWithFrame:(CGRect)frame hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera minZoomLevel:(CGFloat)minZoomLevel maxZoomLevel:(CGFloat)maxZoomLevel
{
self = [super initWithFrame:frame];
if (self != nil)
{
_hasUltrawideCamera = hasUltrawideCamera;
_hasTelephotoCamera = hasTelephotoCamera;
_minZoomLevel = minZoomLevel;
_maxZoomLevel = maxZoomLevel;
_backgroundView = [[UIView alloc] initWithFrame:self.bounds];
_backgroundView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.15];
@ -270,34 +275,39 @@
[self addSubview:_rightItem];
}
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
// [self addGestureRecognizer:gestureRecognizer];
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
[self addGestureRecognizer:panGestureRecognizer];
UILongPressGestureRecognizer *pressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(pressGesture:)];
[self addGestureRecognizer:pressGestureRecognizer];
}
return self;
}
- (void)pressGesture:(UILongPressGestureRecognizer *)gestureRecognizer {
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
self.zoomChanged(_zoomLevel, false, false);
break;
case UIGestureRecognizerStateEnded:
self.zoomChanged(_zoomLevel, true, false);
break;
case UIGestureRecognizerStateCancelled:
self.zoomChanged(_zoomLevel, true, false);
break;
default:
break;
}
}
- (void)panGesture:(UIPanGestureRecognizer *)gestureRecognizer {
CGPoint translation = [gestureRecognizer translationInView:self];
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
self.zoomChanged(_zoomLevel, false, false);
break;
case UIGestureRecognizerStateChanged:
_zoomLevel = MAX(0.5, MIN(10.0, _zoomLevel - translation.x / 100.0));
self.zoomChanged(_zoomLevel, false, false);
break;
case UIGestureRecognizerStateEnded:
self.zoomChanged(_zoomLevel, true, false);
break;
case UIGestureRecognizerStateCancelled:
self.zoomChanged(_zoomLevel, true, false);
break;
default:
break;
}
@ -425,21 +435,85 @@
@interface TGCameraZoomWheelView ()
{
bool _hasUltrawideCamera;
bool _hasTelephotoCamera;
UIImageView *_backgroundView;
}
@end
@implementation TGCameraZoomWheelView
- (instancetype)initWithFrame:(CGRect)frame
- (void)_drawLineInContext:(CGContextRef)context side:(CGFloat)side atAngle:(CGFloat)angle lineLength:(CGFloat)lineLength lineWidth:(CGFloat)lineWidth opaque:(bool)opaque {
CGContextSaveGState(context);
CGContextTranslateCTM(context, side / 2.0, side / 2.0);
CGContextRotateCTM(context, angle);
CGContextTranslateCTM(context, -side / 2.0, -side / 2.0);
CGContextSetLineWidth(context, lineWidth);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:1.0 alpha:opaque ? 1.0 : 0.5].CGColor);
CGContextMoveToPoint(context, side / 2.0, 4.0);
CGContextAddLineToPoint(context, side / 2.0, 4.0 + lineLength);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}
- (instancetype)initWithFrame:(CGRect)frame hasUltrawideCamera:(bool)hasUltrawideCamera hasTelephotoCamera:(bool)hasTelephotoCamera
{
self = [super initWithFrame:frame];
if (self != nil)
{
self.clipsToBounds = true;
_hasUltrawideCamera = true;// hasUltrawideCamera;
_hasTelephotoCamera = true;//hasTelephotoCamera;
_backgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(-28.0, 0.0, 446.0, 446.0)];
_backgroundView.alpha = 0.75;
self.clipsToBounds = true;
CGFloat side = floor(frame.size.width * 1.1435);
CGFloat length = 17.0;
CGFloat smallWidth = MAX(0.5, 1.0 - TGScreenPixel);
CGFloat mediumWidth = 1.0;
CGFloat bigWidth = 1.0 + TGScreenPixel;
CGFloat smallAngle = M_PI * 0.12;
CGFloat finalAngle = 1.08;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:0.0 alpha:0.75].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0, 0, side, side));
[self _drawLineInContext:context side:side atAngle:0.0 lineLength:length lineWidth:bigWidth opaque:true];
if (_hasUltrawideCamera) {
for (NSInteger i = 0; i < 4; i++) {
CGFloat angle = (smallAngle / 5.0) * (i + 1) + (0.007 * (i - 1));
[self _drawLineInContext:context side:side atAngle:-angle lineLength:length lineWidth:smallWidth opaque:false];
}
[self _drawLineInContext:context side:side atAngle:-smallAngle lineLength:length lineWidth:bigWidth opaque:true];
}
if (_hasTelephotoCamera) {
[self _drawLineInContext:context side:side atAngle:smallAngle lineLength:length lineWidth:bigWidth opaque:true];
for (NSInteger i = 0; i < 4; i++) {
CGFloat angle = (smallAngle / 5.0) * (i + 1) + (0.01 * (i - 1));
[self _drawLineInContext:context side:side atAngle:angle lineLength:length lineWidth:smallWidth opaque:false];
}
[self _drawLineInContext:context side:side atAngle:finalAngle lineLength:length lineWidth:bigWidth opaque:true];
} else {
[self _drawLineInContext:context side:side atAngle:smallAngle lineLength:length lineWidth:mediumWidth opaque:true];
[self _drawLineInContext:context side:side atAngle:finalAngle lineLength:length lineWidth:bigWidth opaque:true];
}
UIImage *image = [UIGraphicsGetImageFromCurrentImageContext() stretchableImageWithLeftCapWidth:25 topCapHeight:25];
UIGraphicsEndImageContext();
_backgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(TGScreenPixelFloor((frame.size.width - side) / 2.0), 0.0, side, side)];
_backgroundView.image = image;
[self addSubview:_backgroundView];
}

View File

@ -11,8 +11,8 @@ const CGFloat TGPhotoPaintColorWeightGestureRange = 320.0f;
const CGFloat TGPhotoPaintVerticalThreshold = 5.0f;
const CGFloat TGPhotoPaintPreviewOffset = -70.0f;
const CGFloat TGPhotoPaintPreviewScale = 2.0f;
const CGFloat TGPhotoPaintDefaultBrushWeight = 0.22f;
const CGFloat TGPhotoPaintDefaultColorLocation = 1.0f;
const CGFloat TGPhotoPaintDefaultBrushWeight = 0.08f;
const CGFloat TGPhotoPaintDefaultColorLocation = 0.0f;
@interface TGPhotoPaintColorPickerKnobCircleView : UIView
{
@ -97,7 +97,7 @@ const CGFloat TGPhotoPaintDefaultColorLocation = 1.0f;
[self addGestureRecognizer:_tapGestureRecognizer];
_location = [self restoreLastColorLocation];
_weight = 0.08f;
_weight = TGPhotoPaintDefaultBrushWeight;
}
return self;
}
@ -107,7 +107,7 @@ const CGFloat TGPhotoPaintDefaultColorLocation = 1.0f;
NSNumber *lastColor = [[NSUserDefaults standardUserDefaults] objectForKey:@"TG_paintLastColorLocation_v0"];
if (lastColor != nil)
return [lastColor floatValue];
return TGPhotoPaintDefaultColorLocation;
}

View File

@ -2218,6 +2218,13 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
private func handlePanSelection(location: CGPoint) {
var location = location
if location.y < self.insets.top {
location.y = self.insets.top + 5.0
} else if location.y > self.frame.height - self.insets.bottom {
location.y = self.frame.height - self.insets.bottom - 5.0
}
if let state = self.selectionPanState {
if let messages = self.messagesAtPoint(location), let message = messages.first {
if message.id == state.initialMessageId {

View File

@ -70,6 +70,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
}
}
private var wasPlaying = false
required init() {
self.contextSourceNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
@ -97,7 +99,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
return false
}
if strongSelf.appliedCurrentlyPlaying && !strongSelf.interactiveVideoNode.isPlaying {
return false
return strongSelf.interactiveVideoNode.frame.insetBy(dx: 0.15 * strongSelf.interactiveVideoNode.frame.width, dy: 0.15 * strongSelf.interactiveVideoNode.frame.height).contains(location)
}
if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) {
if case .action = action {
@ -126,10 +128,24 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
case let .openContextMenu(tapMessage, selectAll, subFrame):
strongSelf.recognizer?.cancel()
item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, strongSelf, subFrame, gesture)
if strongSelf.appliedCurrentlyPlaying && strongSelf.interactiveVideoNode.isPlaying {
strongSelf.wasPlaying = true
strongSelf.interactiveVideoNode.pause()
}
}
}
}
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] extracted, _ in
guard let strongSelf = self, let _ = strongSelf.item else {
return
}
if !extracted && strongSelf.wasPlaying {
strongSelf.wasPlaying = false
strongSelf.interactiveVideoNode.play()
}
}
self.containerNode.addSubnode(self.contextSourceNode)
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
self.addSubnode(self.containerNode)

View File

@ -777,9 +777,9 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if !self.bounds.contains(point) {
return nil
}
if let playbackNode = self.playbackStatusNode, !self.isPlaying, !playbackNode.frame.insetBy(dx: 0.15 * playbackNode.frame.width, dy: 0.15 * playbackNode.frame.height).contains(point) {
if let playbackNode = self.playbackStatusNode, !self.isPlaying, !playbackNode.frame.insetBy(dx: 0.2 * playbackNode.frame.width, dy: 0.2 * playbackNode.frame.height).contains(point) {
let distanceFromCenter = point.distanceTo(playbackNode.position)
if distanceFromCenter < 0.15 * playbackNode.frame.width {
if distanceFromCenter < 0.2 * playbackNode.frame.width {
return self.view
} else {
return playbackNode.view

View File

@ -11,12 +11,14 @@ private final class InstantVideoRadialStatusNodeParameters: NSObject {
let progress: CGFloat
let dimProgress: CGFloat
let playProgress: CGFloat
let hasSeek: Bool
init(color: UIColor, progress: CGFloat, dimProgress: CGFloat, playProgress: CGFloat) {
init(color: UIColor, progress: CGFloat, dimProgress: CGFloat, playProgress: CGFloat, hasSeek: Bool) {
self.color = color
self.progress = progress
self.dimProgress = dimProgress
self.playProgress = playProgress
self.hasSeek = hasSeek
}
}
@ -141,7 +143,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele
let center = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
let location = gestureRecognizer.location(in: self.view)
let distanceFromCenter = location.distanceTo(center)
if distanceFromCenter < self.bounds.width * 0.15 {
if distanceFromCenter < self.bounds.width * 0.2 {
return false
}
return true
@ -210,7 +212,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele
}
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
return InstantVideoRadialStatusNodeParameters(color: self.color, progress: self.effectiveProgress, dimProgress: self.effectiveDimProgress, playProgress: self.effectivePlayProgress)
return InstantVideoRadialStatusNodeParameters(color: self.color, progress: self.effectiveProgress, dimProgress: self.effectiveDimProgress, playProgress: self.effectivePlayProgress, hasSeek: self.hasSeek)
}
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
@ -239,20 +241,30 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele
progress = min(1.0, progress)
var lineWidth: CGFloat = 4.0
lineWidth += 1.0 * parameters.dimProgress
if parameters.hasSeek {
lineWidth += 1.0 * parameters.dimProgress
}
var pathDiameter = bounds.size.width - lineWidth - 8.0
pathDiameter -= (18.0 * 2.0) * parameters.dimProgress
if !parameters.dimProgress.isZero {
context.setStrokeColor(parameters.color.withAlphaComponent(0.2 * parameters.dimProgress).cgColor)
context.setLineWidth(lineWidth)
context.strokeEllipse(in: CGRect(x: (bounds.size.width - pathDiameter) / 2.0 , y: (bounds.size.height - pathDiameter) / 2.0, width: pathDiameter, height: pathDiameter))
if parameters.hasSeek {
pathDiameter -= (18.0 * 2.0) * parameters.dimProgress
}
if !parameters.dimProgress.isZero {
if parameters.hasSeek {
context.setStrokeColor(parameters.color.withAlphaComponent(0.2 * parameters.dimProgress).cgColor)
context.setLineWidth(lineWidth)
context.strokeEllipse(in: CGRect(x: (bounds.size.width - pathDiameter) / 2.0 , y: (bounds.size.height - pathDiameter) / 2.0, width: pathDiameter, height: pathDiameter))
}
if !parameters.playProgress.isZero {
context.saveGState()
context.translateBy(x: bounds.width / 2.0, y: bounds.height / 2.0)
context.scaleBy(x: 1.0 + 1.4 * parameters.playProgress, y: 1.0 + 1.4 * parameters.playProgress)
if parameters.hasSeek {
context.scaleBy(x: 1.0 + 1.4 * parameters.playProgress, y: 1.0 + 1.4 * parameters.playProgress)
} else {
context.scaleBy(x: 1.0 + 0.7 * parameters.playProgress, y: 1.0 + 0.7 * parameters.playProgress)
}
context.translateBy(x: -bounds.width / 2.0, y: -bounds.height / 2.0)
let iconSize = CGSize(width: 15.0, height: 18.0)
@ -272,12 +284,14 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele
path.lineCapStyle = .round
path.stroke()
let handleSide = 16.0 * min(1.0, (parameters.dimProgress * 2.0))
let handleSize = CGSize(width: handleSide, height: handleSide)
let handlePosition = CGPoint(x: 0.5 * pathDiameter * cos(endAngle), y: 0.5 * pathDiameter * sin(endAngle)).offsetBy(dx: bounds.size.width / 2.0, dy: bounds.size.height / 2.0)
let handleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(handlePosition.x - handleSize.width / 2.0), y: floorToScreenPixels(handlePosition.y - handleSize.height / 2.0)), size: handleSize)
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: handleFrame)
if parameters.hasSeek {
let handleSide = 16.0 * min(1.0, (parameters.dimProgress * 2.0))
let handleSize = CGSize(width: handleSide, height: handleSide)
let handlePosition = CGPoint(x: 0.5 * pathDiameter * cos(endAngle), y: 0.5 * pathDiameter * sin(endAngle)).offsetBy(dx: bounds.size.width / 2.0, dy: bounds.size.height / 2.0)
let handleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(handlePosition.x - handleSize.width / 2.0), y: floorToScreenPixels(handlePosition.y - handleSize.height / 2.0)), size: handleSize)
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: handleFrame)
}
}
}
@ -296,11 +310,7 @@ final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDele
if self.seeking {
dimmed = true
}
if !self.hasSeek {
dimmed = false
}
if dimmed != self.dimmed {
self.dimmed = dimmed