Skip to content

Commit

Permalink
6.2.0 (#1014)
Browse files Browse the repository at this point in the history
Features:
- Group creation
- Group management
- Displaying qr-codes when generating invitations, thanks lissine
- Added privacy setting to ignore incoming messages from unknown sources

Fixes:
- Improved background handling
- Multiple iPad related fixes
- Fixed backscrolling for selfchats (notes to self)
- Fixed muc status code handling
- Fixed dark mode for some alerts
- Improved accessibility of several of our new UIs (thank you very much
@ryanlintott)
- Improve first login
- Improve multi account devices
- Fix crash on macOS 14.4
- Many crash & bug fixes
  • Loading branch information
FriedrichAltheide committed Mar 8, 2024
2 parents eb38277 + 1a68005 commit d074852
Show file tree
Hide file tree
Showing 69 changed files with 2,355 additions and 1,312 deletions.
4 changes: 2 additions & 2 deletions Monal/Classes/AVCallUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ struct AVCallUI: View {
dismissButton: .default(Text("OK"))
)
}
.richAlert(isPresented:$showSecurityHelpAlert, title:Text("Call security help").foregroundColor(.black)) {
.richAlert(isPresented:$showSecurityHelpAlert, title:Text("Call security help")) {
VStack(alignment: .leading) {
HStack {
Image(systemName: "xmark.shield.fill")
Expand Down Expand Up @@ -606,7 +606,7 @@ struct AVCallUI: View {
}.font(Font.body.weight(showSecurityHelpAlert == .trusted ? .heavy : .medium))
Text("This means your call is encrypted and the remote party was verified using OMEMO encryption.\nYou manually verified the used OMEMO keys and no Man-In-The-Middle can take place.")
Spacer().frame(height: 20)
}.foregroundColor(.black)
}
}
.onAppear {
//force portrait mode and lock ui there
Expand Down
3 changes: 2 additions & 1 deletion Monal/Classes/ActiveChatsViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ NS_ASSUME_NONNULL_BEGIN

@property (nonatomic, strong) UITableView* chatListTable;
@property (nonatomic, weak) IBOutlet UIBarButtonItem* settingsButton;
@property (weak, nonatomic) IBOutlet UIBarButtonItem* spinnerButton;
@property (nonatomic, weak) IBOutlet UIBarButtonItem* composeButton;
@property (nonatomic, strong) chatViewController* currentChatViewController;
@property (nonatomic, strong) UIActivityIndicatorView* spinner;

-(void) showCallContactNotFoundAlert:(NSString*) jid;
-(void) callContact:(MLContact*) contact;
-(void) callContact:(MLContact*) contact withUIKitSender:(_Nullable id) sender;
-(void) callContact:(MLContact*) contact withCallType:(MLCallType) callType;
-(void) presentAccountPickerForContacts:(NSArray<MLContact*>*) contacts andCallType:(MLCallType) callType;
-(void) presentCall:(MLCall*) call;
Expand Down
58 changes: 33 additions & 25 deletions Monal/Classes/ActiveChatsViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,17 @@ -(id) initWithNibName:(NSString*) nibNameOrNil bundle:(NSBundle*) nibBundleOrNil

-(void) configureComposeButton
{
UIImage* composeImage = [[UIImage systemImageNamed:@"person.2.fill"] imageWithTintColor:UIColor.monalGreen];
UITapGestureRecognizer* composeTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showContacts:)];
self.composeButton.customView = [HelperTools
buttonWithNotificationBadgeForImage:composeImage
hasNotification:[[DataLayer sharedInstance] allContactRequests].count > 0
withTapHandler:composeTapRecoginzer];
[self.composeButton.customView setIsAccessibilityElement:YES];
[self.composeButton.customView setAccessibilityLabel:@"Open contacts list"];
UIImage* composeImage = [[UIImage systemImageNamed:@"person.2.fill"] imageWithTintColor:UIColor.monalGreen];
if([[DataLayer sharedInstance] allContactRequests].count > 0)
{
self.composeButton.image = [HelperTools imageWithNotificationBadgeForImage:composeImage];
}
else
{
self.composeButton.image = composeImage;
}
[self.composeButton setAccessibilityLabel:@"Open contacts list"];
[self.composeButton setAccessibilityHint:NSLocalizedString(@"Open contact list", @"")];
}

-(void) viewDidLoad
Expand Down Expand Up @@ -120,9 +123,7 @@ -(void) viewDidLoad
self.settingsButton.image = [UIImage systemImageNamed:@"gearshape.fill"];
[self configureComposeButton];

UIBarButtonItem* spinnerButton = [[UIBarButtonItem alloc] initWithCustomView:self.spinner];
[spinnerButton setIsAccessibilityElement:NO];
[self.navigationItem setRightBarButtonItems:@[self.composeButton, spinnerButton] animated:NO];
self.spinnerButton.customView = self.spinner;

self.chatListTable.emptyDataSetSource = self;
self.chatListTable.emptyDataSetDelegate = self;
Expand Down Expand Up @@ -269,24 +270,23 @@ -(void) handleContactRemoved:(NSNotification*) notification
{
MLContact* removedContact = [notification.userInfo objectForKey:@"contact"];
if(removedContact == nil)
{
unreachable();
}

//update red dot
dispatch_async(dispatch_get_main_queue(), ^{
DDLogInfo(@"Contact removed, refreshing active chats...");

//update red dot
[self configureComposeButton];
});

// ignore all removals that aren't in foreground
if([removedContact isEqualToContact:[MLNotificationManager sharedInstance].currentContact] == NO)
return;
dispatch_async(dispatch_get_main_queue(), ^{
DDLogInfo(@"Contact removed, closing chat view...");

// remove contact from activechats table
[self refreshDisplay];
// open placeholder
[self presentChatWithContact:nil];

// open placeholder if the removed contact was "in foreground"
if([removedContact isEqualToContact:[MLNotificationManager sharedInstance].currentContact])
{
DDLogInfo(@"Contact removed, closing chat view...");
[self presentChatWithContact:nil];
}
});
}

Expand Down Expand Up @@ -509,7 +509,7 @@ -(void) callContact:(MLContact*) contact withCallType:(MLCallType) callType
[self presentCall:[appDelegate.voipProcessor initiateCallWithType:callType toContact:contact]];
}

-(void) callContact:(MLContact*) contact
-(void) callContact:(MLContact*) contact withUIKitSender:(_Nullable id) sender
{
MonalAppDelegate* appDelegate = (MonalAppDelegate *)[[UIApplication sharedApplication] delegate];
MLCall* activeCall = [appDelegate.voipProcessor getActiveCallWithContact:contact];
Expand All @@ -530,7 +530,15 @@ -(void) callContact:(MLContact*) contact
[self dismissViewControllerAnimated:YES completion:nil];
}]];
UIPopoverPresentationController* popPresenter = [alert popoverPresentationController];
popPresenter.sourceView = self.view;
if(sender != nil)
{
if(@available(iOS 16.0, macCatalyst 16.0, *))
popPresenter.sourceItem = sender;
else
popPresenter.barButtonItem = sender;
}
else
popPresenter.sourceView = self.view;
[self presentViewController:alert animated:YES completion:nil];
}
}
Expand Down
33 changes: 19 additions & 14 deletions Monal/Classes/AddContactMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ struct AddContactMenu: View {
@State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close"))
@State private var invitationResult: [String:AnyObject]? = nil

@ObservedObject private var overlay = LoadingOverlayState()
@StateObject private var overlay = LoadingOverlayState()

@State private var showQRCodeScanner = false
@State private var success = false
@State private var newContact : MLContact?


@State private var isEditingJid = false

private let dismissWithNewContact: (MLContact) -> ()
private let preauthToken: String?

Expand Down Expand Up @@ -181,12 +183,13 @@ struct AddContactMenu: View {
}
.pickerStyle(.menu)
}
TextField(NSLocalizedString("Contact or Group/Channel Jid", comment: "placeholder when adding jid"), text: $toAdd)

TextField(NSLocalizedString("Contact or Group/Channel Jid", comment: "placeholder when adding jid"), text: $toAdd, onEditingChanged: { isEditingJid = $0 })
//ios15: .textInputAutocapitalization(.never)
.autocapitalization(.none)
.autocorrectionDisabled()
.keyboardType(.emailAddress)
.addClearButton(text:$toAdd)
.addClearButton(isEditing: isEditingJid, text:$toAdd)
.disabled(scannedFingerprints != nil)
.foregroundColor(scannedFingerprints != nil ? .secondary : .primary)
.onChange(of: toAdd) { _ in
Expand Down Expand Up @@ -245,14 +248,12 @@ struct AddContactMenu: View {
}
.richAlert(isPresented: $invitationResult, title:Text("Invitation for \(splitJid["host"]!) created")) { data in
VStack {
Text("Direct your buddy to this webpage for instructions on how to setup an xmpp client. You will then automatically be added to their contact list.")
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading)
Link(data["landing"] as! String, destination:URL(string:data["landing"] as! String)!)
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 10)
.padding(.bottom, 10)
Image(uiImage: createQrCode(value: data["landing"] as! String))
.interpolation(.none)
.resizable()
.scaledToFit()
.aspectRatio(1, contentMode: .fit)

if let expires = data["expires"] as? Date {
HStack {
if #available(iOS 15, *) {
Expand All @@ -271,8 +272,12 @@ struct AddContactMenu: View {
UIPasteboard.general.setValue(data["landing"] as! String, forPasteboardType:UTType.utf8PlainText.identifier as String)
invitationResult = nil
}) {
Text("Copy link to clipboard")
.frame(maxWidth: .infinity)
if #available(iOS 16, *) {
ShareLink("Share invitation link", item: URL(string: data["landing"] as! String)!)
} else {
Text("Copy invitation link to clipboard")
.frame(maxWidth: .infinity)
}
}
Button(action: {
invitationResult = nil
Expand Down
61 changes: 61 additions & 0 deletions Monal/Classes/ChannelMemberList.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// ChannelMemberList.swift
// Monal
//
// Created by Friedrich Altheide on 17.02.24.
// Copyright © 2024 monal-im.org. All rights reserved.
//

import SwiftUI
import monalxmpp
import OrderedCollections

struct ChannelMemberList: View {
@State private var channelMembers: OrderedDictionary<String, String>
@StateObject var channel: ObservableKVOWrapper<MLContact>
private let account: xmpp

init(mucContact: ObservableKVOWrapper<MLContact>) {
self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: mucContact.accountId)! as xmpp
_channel = StateObject(wrappedValue: mucContact)

let jidList = Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: mucContact.contactJid, forAccountId: mucContact.accountId))
var nickSet : OrderedDictionary<String, String> = OrderedDictionary()
for jidDict in jidList {
if let nick = jidDict["room_nick"] as? String {
nickSet.updateValue((jidDict["affiliation"] as? String) ?? "none", forKey:nick)
}
}
_channelMembers = State(wrappedValue: nickSet)
}

var body: some View {
List {
Section(header: Text(self.channel.obj.contactDisplayName)) {
ForEach(self.channelMembers.sorted(by: <), id: \.self.key) {
member in
ZStack(alignment: .topLeading) {
HStack(alignment: .center) {
Text(member.key)
Spacer()
if member.value == "owner" {
Text(NSLocalizedString("Owner", comment: ""))
} else if member.value == "admin" {
Text(NSLocalizedString("Admin", comment: ""))
} else {
Text(NSLocalizedString("Member", comment: ""))
}
}
}
}
}
}
.navigationBarTitle("Channel Members", displayMode: .inline)
}
}

struct ChannelMemberList_Previews: PreviewProvider {
static var previews: some View {
ChannelMemberList(mucContact:ObservableKVOWrapper<MLContact>(MLContact.makeDummyContact(3)));
}
}
Loading

0 comments on commit d074852

Please sign in to comment.