增加换肤功能
This commit is contained in:
39
QXLive/HomePage(声播)/View/礼物/QXEffectSvgaView.h
Executable file
39
QXLive/HomePage(声播)/View/礼物/QXEffectSvgaView.h
Executable file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// SREffectSvgaView.h
|
||||
// SoundRiver
|
||||
//
|
||||
// Created by 段智博 on 2020/10/26.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface QXEffectSvgaView : UIView
|
||||
/// isAutoPlay加载完成是否自动播放 【默认为YES】
|
||||
- (instancetype)initWithFrame:(CGRect)frame isAutoPlay:(BOOL)isAutoPlay;
|
||||
|
||||
@property (nonatomic,copy) void(^didFinishedDisplay)(QXEffectSvgaView *svgaView);
|
||||
@property (nonatomic,copy) void(^didStartAnimation)(void);
|
||||
|
||||
/// 加载资源
|
||||
- (void)loadSVGAPlayerWith:(NSString *)loadPath;
|
||||
/// 加载资源
|
||||
- (void)loadSVGAPlayerWith:(NSString *)loadPath inBundle:(BOOL)inBundle;
|
||||
/// 加载资源
|
||||
- (void)loadSVGAPlayerWith:(NSString *)loadPath inBundle:(BOOL)inBundle loop:(int)loop;
|
||||
|
||||
|
||||
/// 开始动画【不回调didStartAnimation开始block】
|
||||
- (void)startEffectSvgaPlay;
|
||||
/// 暂停动画
|
||||
- (void)pauseEffectSvgaPlay;
|
||||
/// 停止动画
|
||||
- (void)stopEffectSvgaPlay;
|
||||
|
||||
/// 销毁
|
||||
- (void)destroySvga;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
182
QXLive/HomePage(声播)/View/礼物/QXEffectSvgaView.m
Executable file
182
QXLive/HomePage(声播)/View/礼物/QXEffectSvgaView.m
Executable file
@@ -0,0 +1,182 @@
|
||||
//
|
||||
// QXEffectSvgaView.m
|
||||
// SoundRiver
|
||||
//
|
||||
// Created by 段智博 on 2020/10/26.
|
||||
//
|
||||
|
||||
#import "QXEffectSvgaView.h"
|
||||
#import <SVGA.h>
|
||||
|
||||
@interface QXEffectSvgaView ()<SVGAPlayerDelegate>
|
||||
//播放动画
|
||||
@property (nonatomic,strong) SVGAPlayer *player;
|
||||
@property (nonatomic,strong) SVGAParser *parser;
|
||||
|
||||
@property (nonatomic,assign) BOOL isAutoPlay;
|
||||
|
||||
@end
|
||||
|
||||
@implementation QXEffectSvgaView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame isAutoPlay:(BOOL)isAutoPlay {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initalizeData];
|
||||
self.isAutoPlay = isAutoPlay;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initalizeData];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initalizeData {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.userInteractionEnabled = NO;
|
||||
self.isAutoPlay = YES;
|
||||
[self initPlayer];
|
||||
}
|
||||
|
||||
- (void)initPlayer {
|
||||
[self addSubview:self.player];
|
||||
[self.player mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)loadSVGAPlayerWith:(NSString *)loadPath {
|
||||
[self loadSVGAPlayerWith:loadPath inBundle:NO];
|
||||
}
|
||||
|
||||
- (void)loadSVGAPlayerWith:(NSString *)loadPath inBundle:(BOOL)inBundle {
|
||||
[self loadSVGAPlayerWith:loadPath inBundle:inBundle loop:1];
|
||||
}
|
||||
|
||||
- (void)loadSVGAPlayerWith:(NSString *)loadPath inBundle:(BOOL)inBundle loop:(int)loop {
|
||||
if (![loadPath isExist]) {
|
||||
return;
|
||||
}
|
||||
if (!_player) {
|
||||
[self initPlayer];
|
||||
}
|
||||
[self stopEffectSvgaPlay];
|
||||
|
||||
self.player.loops = loop;
|
||||
|
||||
__weak typeof(self)weakSelf = self;
|
||||
if ([loadPath hasPrefix:@"https:"] || [loadPath hasPrefix:@"http:"]) {
|
||||
/// URL
|
||||
[self.parser parseWithURL:[NSURL URLWithString:loadPath] completionBlock:^(SVGAVideoEntity * _Nullable videoItem) {
|
||||
if (videoItem != nil) {
|
||||
weakSelf.player.videoItem = videoItem;
|
||||
if (weakSelf.isAutoPlay) {
|
||||
[weakSelf.player startAnimation];
|
||||
if (weakSelf.didStartAnimation) {
|
||||
weakSelf.didStartAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
} failureBlock:^(NSError * _Nullable error) {
|
||||
if (weakSelf.didFinishedDisplay) {
|
||||
weakSelf.didFinishedDisplay(weakSelf);
|
||||
}
|
||||
}];
|
||||
}else if (inBundle) {
|
||||
[self.parser parseWithNamed:loadPath inBundle:[NSBundle mainBundle] completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) {
|
||||
if (videoItem != nil) {
|
||||
weakSelf.player.videoItem = videoItem;
|
||||
if (weakSelf.isAutoPlay) {
|
||||
[weakSelf.player startAnimation];
|
||||
if (weakSelf.didStartAnimation) {
|
||||
weakSelf.didStartAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
} failureBlock:^(NSError * _Nonnull error) {
|
||||
if (weakSelf.didFinishedDisplay) {
|
||||
weakSelf.didFinishedDisplay(weakSelf);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
NSData *data = [NSData dataWithContentsOfFile:loadPath];
|
||||
if (!data || data.length < 4) {
|
||||
if (weakSelf.didFinishedDisplay) {
|
||||
weakSelf.didFinishedDisplay(weakSelf);
|
||||
}
|
||||
return;
|
||||
}
|
||||
[self.parser parseWithData:data cacheKey:loadPath completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) {
|
||||
if (videoItem != nil) {
|
||||
weakSelf.player.videoItem = videoItem;
|
||||
if (weakSelf.isAutoPlay) {
|
||||
[weakSelf.player startAnimation];
|
||||
if (weakSelf.didStartAnimation) {
|
||||
weakSelf.didStartAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
} failureBlock:^(NSError * _Nonnull error) {
|
||||
if (weakSelf.didFinishedDisplay) {
|
||||
weakSelf.didFinishedDisplay(weakSelf);
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Public
|
||||
- (void)startEffectSvgaPlay {
|
||||
if (self.player.videoItem) {
|
||||
[self.player startAnimation];
|
||||
}
|
||||
}
|
||||
- (void)pauseEffectSvgaPlay {
|
||||
[self.player pauseAnimation];
|
||||
}
|
||||
- (void)stopEffectSvgaPlay {
|
||||
[self.player stopAnimation];
|
||||
}
|
||||
|
||||
- (void)destroySvga {
|
||||
[self.player stopAnimation];
|
||||
[self.player removeFromSuperview];
|
||||
_player = nil;
|
||||
_parser = nil;
|
||||
}
|
||||
|
||||
#pragma mark - SVGAPlayerDelegate
|
||||
- (void)svgaPlayerDidFinishedAnimation:(SVGAPlayer *)player {
|
||||
if (self.didFinishedDisplay) {
|
||||
self.didFinishedDisplay(self);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
// 动画
|
||||
- (SVGAPlayer *)player {
|
||||
if (!_player) {
|
||||
_player = [[SVGAPlayer alloc] initWithFrame:CGRectZero];
|
||||
_player.backgroundColor = [UIColor clearColor];
|
||||
_player.contentMode = UIViewContentModeScaleAspectFill;
|
||||
// _player.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_player.clipsToBounds = YES;
|
||||
_player.delegate = self;
|
||||
_player.clearsAfterStop = YES;
|
||||
}
|
||||
return _player;
|
||||
}
|
||||
|
||||
- (SVGAParser *)parser {
|
||||
if (!_parser) {
|
||||
_parser = [[SVGAParser alloc] init];
|
||||
}
|
||||
return _parser;
|
||||
}
|
||||
|
||||
@end
|
||||
46
QXLive/HomePage(声播)/View/礼物/QXGiftPlayerManager.h
Normal file
46
QXLive/HomePage(声播)/View/礼物/QXGiftPlayerManager.h
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// QXGiftPlayer.h
|
||||
// QXLive
|
||||
//
|
||||
// Created by 启星 on 2025/5/8.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "UIView+VAP.h"
|
||||
#import "QXGiftModel.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@class QXEffectContentView;
|
||||
@interface QXGiftPlayerManager : NSObject
|
||||
+ (instancetype)shareManager;
|
||||
- (UIView *)defaultBgEffectView;
|
||||
/// 礼物、坐骑【全屏位置】特效
|
||||
- (QXEffectContentView *)defaultFullEffectView;
|
||||
/// 坐骑【公屏信息流位置】特效
|
||||
- (QXEffectContentView *)defaultChatEffectView;
|
||||
|
||||
/// 礼物、坐骑【全屏位置】特效 加载
|
||||
- (void)displayFullEffectView:(QXGiftModel *)gift;
|
||||
|
||||
/// 坐骑【公屏信息流位置】特效 加载
|
||||
- (void)displayChatEffectView:(QXGiftModel *)gift;
|
||||
|
||||
/// 关闭打开动效
|
||||
- (void)openOrCloseEffectViewWith:(BOOL)isShow;
|
||||
|
||||
/// 销毁
|
||||
- (void)destroyEffectSvga;
|
||||
|
||||
-(void)stopPlay;
|
||||
@end
|
||||
|
||||
|
||||
@interface QXEffectContentView : UIView
|
||||
@property (nonatomic,strong) VAPView *playerMp4View;
|
||||
@property (nonatomic, strong) dispatch_queue_t queue;
|
||||
- (void)displayEffectView:(QXGiftModel *)gift;
|
||||
- (void)openOrCloseEffectViewWith:(BOOL)isShow;
|
||||
@property (nonatomic,assign) BOOL isShow;
|
||||
- (void)destroyEffectView;
|
||||
-(void)stopPlay;
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
||||
279
QXLive/HomePage(声播)/View/礼物/QXGiftPlayerManager.m
Normal file
279
QXLive/HomePage(声播)/View/礼物/QXGiftPlayerManager.m
Normal file
@@ -0,0 +1,279 @@
|
||||
//
|
||||
// QXGiftPlayer.m
|
||||
// QXLive
|
||||
//
|
||||
// Created by 启星 on 2025/5/8.
|
||||
//
|
||||
|
||||
#import "QXGiftPlayerManager.h"
|
||||
#import "QXEffectSvgaView.h"
|
||||
#import "QXFileManager.h"
|
||||
@interface QXGiftPlayerManager()
|
||||
@property (nonatomic,strong) UIView *bgEffectView;
|
||||
// 礼物、坐骑【全屏位置】特效
|
||||
@property (nonatomic,strong) QXEffectContentView *fullEffectView;
|
||||
|
||||
// 坐骑【公屏信息流位置】特效
|
||||
@property (nonatomic,strong) QXEffectContentView *chatEffectView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation QXGiftPlayerManager
|
||||
+ (instancetype)shareManager{
|
||||
static QXGiftPlayerManager *manager;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[QXGiftPlayerManager alloc] init];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (UIView *)defaultBgEffectView {
|
||||
return self.bgEffectView;
|
||||
}
|
||||
|
||||
/// 礼物、坐骑【全屏位置】特效
|
||||
- (QXEffectContentView *)defaultFullEffectView {
|
||||
return self.fullEffectView;
|
||||
}
|
||||
|
||||
/// 坐骑【公屏信息流位置】特效
|
||||
- (QXEffectContentView *)defaultChatEffectView {
|
||||
return self.chatEffectView;
|
||||
}
|
||||
|
||||
- (void)displayFullEffectView:(QXGiftModel *)gift {
|
||||
[self.fullEffectView displayEffectView:gift];
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void)displayChatEffectView:(QXGiftModel *)gift {
|
||||
[self.chatEffectView displayEffectView:gift];
|
||||
}
|
||||
/// 关闭打开动效
|
||||
- (void)openOrCloseEffectViewWith:(BOOL)isShow {
|
||||
[self.fullEffectView openOrCloseEffectViewWith:isShow];
|
||||
[self.chatEffectView openOrCloseEffectViewWith:isShow];
|
||||
}
|
||||
|
||||
|
||||
/// 销毁
|
||||
- (void)destroyEffectSvga {
|
||||
[self.fullEffectView destroyEffectView];
|
||||
[self.chatEffectView destroyEffectView];
|
||||
|
||||
[self.fullEffectView removeFromSuperview];
|
||||
[self.chatEffectView removeFromSuperview];
|
||||
[self.bgEffectView removeFromSuperview];
|
||||
|
||||
_fullEffectView = nil;
|
||||
_chatEffectView = nil;
|
||||
_bgEffectView = nil;
|
||||
}
|
||||
-(void)stopPlay{
|
||||
[self.fullEffectView stopPlay];
|
||||
[self.chatEffectView stopPlay];
|
||||
}
|
||||
- (UIView *)bgEffectView {
|
||||
if (!_bgEffectView) {
|
||||
_bgEffectView = [[UIView alloc] init];
|
||||
_bgEffectView.userInteractionEnabled = NO;
|
||||
|
||||
[_bgEffectView addSubview:self.fullEffectView];
|
||||
[_bgEffectView addSubview:self.chatEffectView];
|
||||
}
|
||||
return _bgEffectView;
|
||||
}
|
||||
- (QXEffectContentView *)fullEffectView {
|
||||
if (!_fullEffectView) {
|
||||
_fullEffectView = [[QXEffectContentView alloc] initWithFrame:CGRectZero];
|
||||
_fullEffectView.queue = dispatch_queue_create("qx_room_full_svga_message.com", NULL);
|
||||
}
|
||||
return _fullEffectView;
|
||||
}
|
||||
|
||||
|
||||
- (QXEffectContentView *)chatEffectView {
|
||||
if (!_chatEffectView) {
|
||||
_chatEffectView = [[QXEffectContentView alloc] initWithFrame:CGRectZero];
|
||||
[_chatEffectView.playerMp4View mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
// make.centerX.centerY.equalTo(_chatEffectView);
|
||||
// make.width.mas_equalTo(ScaleWidth(375));
|
||||
// make.height.mas_equalTo(ScaleWidth(375));
|
||||
make.edges.equalTo(_chatEffectView);
|
||||
}];
|
||||
_chatEffectView.queue = dispatch_queue_create("qx_room_chat_svga_message.com", NULL);
|
||||
}
|
||||
return _chatEffectView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface QXEffectContentView ()<HWDMP4PlayDelegate>
|
||||
@property (nonatomic,strong) QXEffectSvgaView *svagView;
|
||||
@property (nonatomic,strong) QXGiftModel *playModel;
|
||||
|
||||
@property (nonatomic, assign) BOOL isLoadEffect; ///< 是否正在加载
|
||||
@property (nonatomic, strong) NSRecursiveLock *lock;
|
||||
@property (nonatomic, strong) NSMutableArray *giftArray;
|
||||
@end
|
||||
|
||||
@implementation QXEffectContentView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.userInteractionEnabled = NO;
|
||||
self.isLoadEffect = NO;
|
||||
[self addSubview:self.effectView];
|
||||
[self addSubview:self.playerMp4View];
|
||||
|
||||
[self.effectView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
[self.playerMp4View mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (void)displayEffectView:(QXGiftModel *)gift {
|
||||
dispatch_async(self.queue, ^{
|
||||
if (![gift.play_image isExist]) {
|
||||
return;
|
||||
}
|
||||
NSString *pathExtension = [gift.play_image pathExtension].lowercaseString;
|
||||
if (!([pathExtension isEqualToString:@"svga"] || [pathExtension isEqualToString:@"mp4"])) {
|
||||
return;
|
||||
}
|
||||
[self.lock lock];
|
||||
[self.giftArray addObject:gift];
|
||||
[self.lock unlock];
|
||||
if (self.isLoadEffect == NO) {
|
||||
self.isLoadEffect = YES;
|
||||
[self loadStartSVGAPlayer];
|
||||
}
|
||||
});
|
||||
}
|
||||
- (void)openOrCloseEffectViewWith:(BOOL)isShow {
|
||||
_isShow = isShow;
|
||||
[self removeSvgaQueueData];
|
||||
|
||||
[self.effectView stopEffectSvgaPlay];
|
||||
// [self.playerView stop];
|
||||
[self.playerMp4View stopHWDMP4];
|
||||
// [self.alphaVideoView stop];
|
||||
self.playerMp4View.hidden = YES;
|
||||
[self setHidden:!isShow];
|
||||
}
|
||||
-(void)stopPlay{
|
||||
[self removeSvgaQueueData];
|
||||
|
||||
[self.effectView stopEffectSvgaPlay];
|
||||
// [self.playerView stop];
|
||||
[self.playerMp4View stopHWDMP4];
|
||||
// [self.alphaVideoView stop];
|
||||
self.playerMp4View.hidden = YES;
|
||||
}
|
||||
-(void)viewDidFinishPlayMP4:(NSInteger)totalFrameCount view:(VAPView *)container{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.playerMp4View.hidden = YES;
|
||||
});
|
||||
[self loadStartSVGAPlayer];
|
||||
}
|
||||
- (void)loadStartSVGAPlayer {
|
||||
if (!_isShow) {
|
||||
return;
|
||||
}
|
||||
QXGiftModel *giftModel = nil;
|
||||
[self.lock lock];
|
||||
if (self.giftArray.count > 0) {
|
||||
giftModel = self.giftArray.firstObject;
|
||||
[self.giftArray removeObjectAtIndex:0];
|
||||
self.isLoadEffect = YES;
|
||||
}else {
|
||||
self.isLoadEffect = NO;
|
||||
}
|
||||
[self.lock unlock];
|
||||
if (self.isLoadEffect && [giftModel.play_image isExist]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([giftModel.play_image hasSuffix:@"mp4"]) {
|
||||
__weak typeof(self)weakSelf = self;
|
||||
[[QXRequset shareInstance] downloadVideoPlayerWithUrl:giftModel.play_image completion:^(BOOL result, NSString * _Nonnull fileName) {
|
||||
NSString *videoPath = [QXFileManager getGiftVideoPath:fileName];
|
||||
weakSelf.playerMp4View.hidden = NO;
|
||||
[weakSelf.playerMp4View playHWDMP4:videoPath delegate:self];
|
||||
}];
|
||||
}else if ([giftModel.play_image hasSuffix:@"svg"] || [giftModel.play_image hasSuffix:@"svga"]) {
|
||||
__weak typeof(self)weakSelf = self;
|
||||
[[QXRequset shareInstance] downloadVideoPlayerWithUrl:giftModel.play_image completion:^(BOOL result, NSString * _Nonnull fileName) {
|
||||
NSString *filePath = [QXFileManager getGiftVideoPath:fileName];
|
||||
[weakSelf.effectView loadSVGAPlayerWith:filePath];
|
||||
}];
|
||||
}else {
|
||||
[self.lock lock];
|
||||
self.isLoadEffect = NO;
|
||||
[self.lock unlock];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadEndSvgaPlayer {
|
||||
dispatch_async(self.queue, ^{
|
||||
[self loadStartSVGAPlayer];
|
||||
});
|
||||
}
|
||||
- (void)destroyEffectView {
|
||||
[self removeSvgaQueueData];
|
||||
|
||||
[self.svagView destroySvga];
|
||||
// [self.playerView destroyPlayer];
|
||||
[self.playerMp4View stopHWDMP4];
|
||||
|
||||
[self.effectView removeFromSuperview];
|
||||
[self.playerMp4View removeFromSuperview];
|
||||
|
||||
_svagView = nil;
|
||||
// _playerView = nil;
|
||||
_playModel = nil;
|
||||
_playerMp4View = nil;
|
||||
// _alphaVideoView = nil;
|
||||
}
|
||||
- (void)removeSvgaQueueData {
|
||||
[self.lock lock];
|
||||
[self.giftArray removeAllObjects];
|
||||
self.isLoadEffect = NO;
|
||||
[self.lock unlock];
|
||||
}
|
||||
-(NSMutableArray *)giftArray{
|
||||
if (!_giftArray) {
|
||||
_giftArray = [NSMutableArray array];
|
||||
}
|
||||
return _giftArray;
|
||||
}
|
||||
|
||||
- (QXEffectSvgaView *)effectView {
|
||||
if (!_svagView) {
|
||||
_svagView = [[QXEffectSvgaView alloc] initWithFrame:CGRectZero isAutoPlay:YES];
|
||||
__weak typeof(self)weakSelf = self;
|
||||
_svagView.didFinishedDisplay = ^(QXEffectSvgaView * _Nonnull svgaView) {
|
||||
[svgaView destroySvga];
|
||||
[weakSelf loadEndSvgaPlayer];
|
||||
};
|
||||
}
|
||||
return _svagView;
|
||||
}
|
||||
-(VAPView *)playerMp4View{
|
||||
if (!_playerMp4View) {
|
||||
_playerMp4View = [[VAPView alloc] initWithFrame:CGRectZero];
|
||||
_playerMp4View.hwd_enterBackgroundOP = HWDMP4EBOperationTypePauseAndResume;
|
||||
_playerMp4View.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_playerMp4View.userInteractionEnabled = NO;
|
||||
_playerMp4View.backgroundColor = [UIColor clearColor];
|
||||
// _playerMp4View.hwd_Delegate = self;
|
||||
}
|
||||
return _playerMp4View;
|
||||
}
|
||||
@end
|
||||
Reference in New Issue
Block a user