mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-24 18:42:47 +02:00
Merge pull request #6039 from vector-im/aringenbach/3526_user_pills
Add mention pills to timeline & composer
This commit is contained in:
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
@import UIKit;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UITextView(MatrixKit)
|
||||
|
||||
/**
|
||||
Determine if there is a link near a location point in UITextView bounds.
|
||||
|
||||
@param point The point inside the UITextView bounds
|
||||
@return YES to indicate that a link has been detected near the location point.
|
||||
*/
|
||||
- (BOOL)isThereALinkNearPoint:(CGPoint)point;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "UITextView+MatrixKit.h"
|
||||
|
||||
@implementation UITextView(MatrixKit)
|
||||
|
||||
- (BOOL)isThereALinkNearPoint:(CGPoint)point
|
||||
{
|
||||
if (!CGRectContainsPoint(self.bounds, point))
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
UITextPosition *textPosition = [self closestPositionToPoint:point];
|
||||
|
||||
if (!textPosition)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
UITextRange *textRange = [self.tokenizer rangeEnclosingPosition:textPosition
|
||||
withGranularity:UITextGranularityCharacter
|
||||
inDirection:UITextLayoutDirectionLeft];
|
||||
|
||||
if (!textRange)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSInteger startIndex = [self offsetFromPosition:self.beginningOfDocument toPosition:textRange.start];
|
||||
|
||||
if (startIndex < 0)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [self.attributedText attribute:NSLinkAttributeName atIndex:startIndex effectiveRange:NULL] != nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UITextView {
|
||||
private enum Constants {
|
||||
/// Distance threshold at which linkified text attachment can still be considered as "near" location.
|
||||
static let attachmentLinkHorizontalDistanceThreshold: CGFloat = 16.0
|
||||
}
|
||||
|
||||
/// Determine if there is a link near a location point in UITextView bounds.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - point: The point inside the UITextView bounds
|
||||
/// - Returns: true to indicate that a link has been detected near the location point.
|
||||
@objc func isThereALinkNearLocation(_ point: CGPoint) -> Bool {
|
||||
return urlForLinkAtLocation(point) != nil
|
||||
}
|
||||
|
||||
/// Detect link near a location point in UITextView bounds.
|
||||
///
|
||||
/// - Parameter point: The point inside the UITextView bounds
|
||||
/// - Returns: link detected at given location
|
||||
@objc func urlForLinkAtLocation(_ point: CGPoint) -> URL? {
|
||||
guard bounds.contains(point),
|
||||
let textPosition = closestPosition(to: point)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The value of `NSLinkAttributeName` attribute could be an URL or a String object.
|
||||
func attributeToLink(_ attribute: Any) -> URL? {
|
||||
if let link = attribute as? URL {
|
||||
return link
|
||||
} else if let stringURL = attribute as? String {
|
||||
return URL(string: stringURL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on cursor position on a character containing both an attachment
|
||||
// and a link (e.g. a mention pill), a positive result can be retrieved either
|
||||
// from textStylingAtPosition or tokenizer's rangeEnclosingPosition.
|
||||
if let attributes = textStyling(at: textPosition, in: .forward),
|
||||
let linkAttribute = attributes[.link] {
|
||||
// Using textStyling shouldn't provide false positives.
|
||||
return attributeToLink(linkAttribute)
|
||||
} else if let textRange = tokenizer.rangeEnclosingPosition(textPosition,
|
||||
with: .character,
|
||||
inDirection: .layout(.left)) {
|
||||
let startIndex = offset(from: beginningOfDocument, to: textRange.start)
|
||||
if let linkAttribute = attributedText.attribute(.link, at: startIndex, effectiveRange: nil) {
|
||||
// Fix false positives from tokenizer's rangeEnclosingPosition.
|
||||
// These occur if given point is located on the same line as a
|
||||
// trailing linkified text attachment. Detected link is
|
||||
// rejected if actual distance from attachment trailing to point
|
||||
// is greater than linkHorizontalDistanceThreshold.
|
||||
let glyphIndex = layoutManager.glyphIndexForCharacter(at: startIndex)
|
||||
let attachmentWidth = layoutManager.attachmentSize(forGlyphAt: glyphIndex).width
|
||||
// Width is -1 when there is no attachment.
|
||||
if attachmentWidth > 0 {
|
||||
let glyphStartX = layoutManager.location(forGlyphAt: glyphIndex).x
|
||||
let start = glyphStartX - Constants.attachmentLinkHorizontalDistanceThreshold
|
||||
let end = glyphStartX + attachmentWidth + Constants.attachmentLinkHorizontalDistanceThreshold
|
||||
let range = (start...end)
|
||||
|
||||
return range.contains(point.x) ? attributeToLink(linkAttribute) : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -170,6 +170,11 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey;
|
||||
*/
|
||||
@property (nonatomic) NSString *partialTextMessage;
|
||||
|
||||
/**
|
||||
The current attributed text message partially typed in text input (use nil to reset it).
|
||||
*/
|
||||
@property (nonatomic) NSAttributedString *partialAttributedTextMessage;
|
||||
|
||||
/**
|
||||
The current thread id for the data source. If provided, data source displays the specified thread, otherwise the whole room messages.
|
||||
*/
|
||||
@@ -471,6 +476,14 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey;
|
||||
success:(void (^)(NSString *))success
|
||||
failure:(void (^)(NSError *))failure;
|
||||
|
||||
/**
|
||||
Updates an event with replacement event.
|
||||
@note the original event is defined in the `MXEventContentRelatesTo` object.
|
||||
|
||||
@param replaceEvent the new event to display
|
||||
*/
|
||||
- (void)updateEventWithReplaceEvent:(MXEvent*)replaceEvent;
|
||||
|
||||
/**
|
||||
Indicates if replying to the provided event is supported.
|
||||
Only event of type 'MXEventTypeRoomMessage' are supported for the moment, and for certain msgtype.
|
||||
@@ -735,6 +748,25 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey;
|
||||
roomState:(MXRoomState*)roomState
|
||||
direction:(MXTimelineDirection)direction;
|
||||
|
||||
/**
|
||||
Queue an event in order to process its display later.
|
||||
|
||||
@param event the event to process.
|
||||
@param roomState the state of the room when the event fired.
|
||||
@param direction the order of the events in the arrays
|
||||
*/
|
||||
- (void)queueEventForProcessing:(MXEvent*)event
|
||||
withRoomState:(MXRoomState*)roomState
|
||||
direction:(MXTimelineDirection)direction;
|
||||
|
||||
/**
|
||||
Start processing pending events.
|
||||
|
||||
@param onComplete a block called (on the main thread) when the processing has been done. Can be nil.
|
||||
Note this block returns the number of added cells in first and last positions.
|
||||
*/
|
||||
- (void)processQueuedEvents:(void (^)(NSUInteger addedHistoryCellNb, NSUInteger addedLiveCellNb))onComplete;
|
||||
|
||||
#pragma mark - Bubble collapsing
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
|
||||
#import "MXKAppSettings.h"
|
||||
|
||||
#import "MXKSendReplyEventStringLocalizer.h"
|
||||
#import "MXKSlashCommands.h"
|
||||
|
||||
#import "GeneratedInterface-Swift.h"
|
||||
@@ -991,6 +990,16 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
|
||||
_room.partialTextMessage = partialTextMessage;
|
||||
}
|
||||
|
||||
- (NSAttributedString *)partialAttributedTextMessage
|
||||
{
|
||||
return _room.partialAttributedTextMessage;
|
||||
}
|
||||
|
||||
- (void)setPartialAttributedTextMessage:(NSAttributedString *)partialAttributedTextMessage
|
||||
{
|
||||
_room.partialAttributedTextMessage = partialAttributedTextMessage;
|
||||
}
|
||||
|
||||
- (void)refreshEventListeners:(NSArray *)liveEventTypesFilterForMessages
|
||||
{
|
||||
// Remove the existing listeners
|
||||
@@ -2982,13 +2991,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
|
||||
return processingQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
Queue an event in order to process its display later.
|
||||
|
||||
@param event the event to process.
|
||||
@param roomState the state of the room when the event fired.
|
||||
@param direction the order of the events in the arrays
|
||||
*/
|
||||
- (void)queueEventForProcessing:(MXEvent*)event withRoomState:(MXRoomState*)roomState direction:(MXTimelineDirection)direction
|
||||
{
|
||||
if (event.isLocalEvent)
|
||||
@@ -3166,12 +3168,6 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
|
||||
return isHighlighted;
|
||||
}
|
||||
|
||||
/**
|
||||
Start processing pending events.
|
||||
|
||||
@param onComplete a block called (on the main thread) when the processing has been done. Can be nil.
|
||||
Note this block returns the number of added cells in first and last positions.
|
||||
*/
|
||||
- (void)processQueuedEvents:(void (^)(NSUInteger addedHistoryCellNb, NSUInteger addedLiveCellNb))onComplete
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MatrixSDK/MXSendReplyEventStringLocalizerProtocol.h>
|
||||
|
||||
/**
|
||||
A `MXKSendReplyEventStringLocalizer` instance represents string localizations used when send reply event to a message in a room.
|
||||
*/
|
||||
@interface MXKSendReplyEventStringLocalizer : NSObject<MXSendReplyEventStringLocalizerProtocol>
|
||||
|
||||
@end
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#import "MXKSendReplyEventStringLocalizer.h"
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
@implementation MXKSendReplyEventStringLocalizer
|
||||
|
||||
- (NSString *)senderSentAnImage
|
||||
{
|
||||
return [VectorL10n messageReplyToSenderSentAnImage];
|
||||
}
|
||||
|
||||
- (NSString *)senderSentAVideo
|
||||
{
|
||||
return [VectorL10n messageReplyToSenderSentAVideo];
|
||||
}
|
||||
|
||||
- (NSString *)senderSentAnAudioFile
|
||||
{
|
||||
return [VectorL10n messageReplyToSenderSentAnAudioFile];
|
||||
}
|
||||
|
||||
- (NSString *)senderSentAVoiceMessage
|
||||
{
|
||||
return [VectorL10n messageReplyToSenderSentAVoiceMessage];
|
||||
}
|
||||
|
||||
- (NSString *)senderSentAFile
|
||||
{
|
||||
return [VectorL10n messageReplyToSenderSentAFile];
|
||||
}
|
||||
|
||||
- (NSString *)senderSentTheirLocation
|
||||
{
|
||||
return [VectorL10n messageReplyToSenderSentTheirLocation];
|
||||
}
|
||||
|
||||
- (NSString *)messageToReplyToPrefix
|
||||
{
|
||||
return [VectorL10n messageReplyToMessageToReplyToPrefix];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class MXKSendReplyEventStringLocalizer: NSObject, MXSendReplyEventStringLocalizerProtocol {
|
||||
func senderSentAnImage() -> String {
|
||||
return VectorL10n.messageReplyToSenderSentAnImage
|
||||
}
|
||||
|
||||
func senderSentAVideo() -> String {
|
||||
return VectorL10n.messageReplyToSenderSentAVideo
|
||||
}
|
||||
|
||||
func senderSentAnAudioFile() -> String {
|
||||
return VectorL10n.messageReplyToSenderSentAnAudioFile
|
||||
}
|
||||
|
||||
func senderSentAVoiceMessage() -> String {
|
||||
return VectorL10n.messageReplyToSenderSentAVoiceMessage
|
||||
}
|
||||
|
||||
func senderSentAFile() -> String {
|
||||
return VectorL10n.messageReplyToSenderSentAFile
|
||||
}
|
||||
|
||||
func senderSentTheirLocation() -> String {
|
||||
return VectorL10n.messageReplyToSenderSentTheirLocation
|
||||
}
|
||||
|
||||
func messageToReplyToPrefix() -> String {
|
||||
return VectorL10n.messageReplyToMessageToReplyToPrefix
|
||||
}
|
||||
}
|
||||
@@ -228,6 +228,18 @@ typedef enum : NSUInteger {
|
||||
*/
|
||||
- (NSAttributedString*)renderHTMLString:(NSString*)htmlString forEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState;
|
||||
|
||||
/**
|
||||
Render a random html string into an attributed string with the font and the text color
|
||||
that correspond to the passed event.
|
||||
|
||||
@param htmlString the HTLM string to render.
|
||||
@param event the event associated to the string.
|
||||
@param roomState the room state right before the event.
|
||||
@param isEditMode wether string will be used for edition in the composer
|
||||
@return an attributed string.
|
||||
*/
|
||||
- (NSAttributedString*)renderHTMLString:(NSString*)htmlString forEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState isEditMode:(BOOL)isEditMode;
|
||||
|
||||
/**
|
||||
Same as [self renderString:forEvent:] but add a prefix.
|
||||
The prefix will be rendered with 'prefixTextFont' and 'prefixTextColor'.
|
||||
|
||||
@@ -1750,11 +1750,17 @@ static NSString *const kHTMLATagRegexPattern = @"<a href=\"(.*?)\">([^<]*)</a>";
|
||||
}
|
||||
|
||||
- (NSAttributedString*)renderHTMLString:(NSString*)htmlString forEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState
|
||||
{
|
||||
return [self renderHTMLString:htmlString forEvent:event withRoomState:roomState isEditMode:NO];
|
||||
}
|
||||
|
||||
- (NSAttributedString*)renderHTMLString:(NSString*)htmlString forEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState isEditMode:(BOOL)isEditMode
|
||||
{
|
||||
NSString *html = htmlString;
|
||||
|
||||
// Special treatment for "In reply to" message
|
||||
if (event.isReplyEvent || (!RiotSettings.shared.enableThreads && event.isInThread))
|
||||
// Note: `isEditMode` fixes an issue where editing a reply would display an "In reply to" span instead of a mention.
|
||||
if (!isEditMode && (event.isReplyEvent || (!RiotSettings.shared.enableThreads && event.isInThread)))
|
||||
{
|
||||
html = [self renderReplyTo:html withRoomState:roomState];
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
#import "MXKMessageTextView.h"
|
||||
#import "UITextView+MatrixKit.h"
|
||||
#import "GeneratedInterface-Swift.h"
|
||||
|
||||
@interface MXKMessageTextView()
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [self isThereALinkNearPoint:point];
|
||||
return [self isThereALinkNearLocation:point];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -269,6 +269,11 @@ typedef enum : NSUInteger
|
||||
*/
|
||||
- (IBAction)onTouchUpInside:(UIButton*)button;
|
||||
|
||||
/**
|
||||
Send message currently displayed inside toolbar's ` UITextView`.
|
||||
*/
|
||||
- (void)sendCurrentMessage;
|
||||
|
||||
/**
|
||||
Handle image attachment
|
||||
Save the image in user's photos library when 'isPhotoLibraryAsset' flag is NO and auto saving is enabled.
|
||||
|
||||
@@ -277,22 +277,27 @@
|
||||
}
|
||||
else if (button == self.rightInputToolbarButton && self.textMessage.length)
|
||||
{
|
||||
// This forces an autocorrect event to happen when "Send" is pressed, which is necessary to accept a pending correction on send
|
||||
self.textMessage = [NSString stringWithFormat:@"%@ ", self.textMessage];
|
||||
self.textMessage = [self.textMessage substringToIndex:[self.textMessage length]-1];
|
||||
[self sendCurrentMessage];
|
||||
}
|
||||
}
|
||||
|
||||
NSString *message = self.textMessage;
|
||||
|
||||
// Reset message, disable view animation during the update to prevent placeholder distorsion.
|
||||
[UIView setAnimationsEnabled:NO];
|
||||
self.textMessage = nil;
|
||||
[UIView setAnimationsEnabled:YES];
|
||||
|
||||
// Send button has been pressed
|
||||
if (message.length && [self.delegate respondsToSelector:@selector(roomInputToolbarView:sendTextMessage:)])
|
||||
{
|
||||
[self.delegate roomInputToolbarView:self sendTextMessage:message];
|
||||
}
|
||||
- (void)sendCurrentMessage
|
||||
{
|
||||
// This forces an autocorrect event to happen when "Send" is pressed, which is necessary to accept a pending correction on send
|
||||
self.textMessage = [NSString stringWithFormat:@"%@ ", self.textMessage];
|
||||
self.textMessage = [self.textMessage substringToIndex:[self.textMessage length]-1];
|
||||
|
||||
NSString *message = self.textMessage;
|
||||
|
||||
// Reset message, disable view animation during the update to prevent placeholder distorsion.
|
||||
[UIView setAnimationsEnabled:NO];
|
||||
self.textMessage = nil;
|
||||
[UIView setAnimationsEnabled:YES];
|
||||
|
||||
// Send button has been pressed
|
||||
if (message.length && [self.delegate respondsToSelector:@selector(roomInputToolbarView:sendTextMessage:)])
|
||||
{
|
||||
[self.delegate roomInputToolbarView:self sendTextMessage:message];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user