Compare commits

...

5 Commits

Author SHA1 Message Date
Levitating Pineapple b6b7d8353c fix chatView 2024-10-16 14:37:45 +03:00
Levitating Pineapple 113f0cce6a clear laRequest on dismiss 2024-10-16 12:49:19 +03:00
Levitating Pineapple ffd0de57d5 minor 2024-10-16 12:33:24 +03:00
Levitating Pineapple b24d4980b6 prevent feedback; fix toolbars 2024-10-16 12:28:27 +03:00
Levitating Pineapple 141f3c8fe5 ios: fix screen protect for sheets 2024-10-16 11:55:54 +03:00
6 changed files with 140 additions and 117 deletions
+1 -2
View File
@@ -27,7 +27,6 @@ struct ContentView: View {
@AppStorage(DEFAULT_SHOW_LA_NOTICE) private var prefShowLANotice = false
@AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
@AppStorage(DEFAULT_NOTIFICATION_ALERT_SHOWN) private var notificationAlertShown = false
@State private var showWhatsNew = false
@State private var showChooseLAMode = false
@@ -250,7 +249,7 @@ struct ContentView: View {
private func mainView() -> some View {
ZStack(alignment: .top) {
ChatListView(activeUserPickerSheet: $chatListUserPickerSheet).privacySensitive(protectScreen)
ChatListView(activeUserPickerSheet: $chatListUserPickerSheet)
.onAppear {
requestNtfAuthorization()
// Local Authentication notice is to be shown on next start after onboarding is complete
+111 -105
View File
@@ -48,6 +48,7 @@ struct ChatView: View {
@State private var allowToDeleteSelectedMessagesForAll: Bool = false
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
var body: some View {
if #available(iOS 16.0, *) {
@@ -116,6 +117,7 @@ struct ChatView: View {
.background(theme.colors.background)
.navigationBarTitleDisplayMode(.inline)
.environmentObject(theme)
.privacySensitive(protectScreen)
.confirmationDialog(selectedChatItems?.count == 1 ? "Delete message?" : "Delete \((selectedChatItems?.count ?? 0)) messages?", isPresented: $showDeleteSelectedMessages, titleVisibility: .visible) {
Button("Delete for me", role: .destructive) {
if let selected = selectedChatItems {
@@ -211,126 +213,130 @@ struct ChatView: View {
}
.toolbar {
ToolbarItem(placement: .principal) {
if selectedChatItems != nil {
SelectedItemsTopToolbar(selectedChatItems: $selectedChatItems)
} else if case let .direct(contact) = cInfo {
Button {
Task {
showChatInfoSheet = true
Group {
if selectedChatItems != nil {
SelectedItemsTopToolbar(selectedChatItems: $selectedChatItems)
} else if case let .direct(contact) = cInfo {
Button {
Task {
showChatInfoSheet = true
}
} label: {
ChatInfoToolbar(chat: chat)
}
} label: {
.appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
ChatInfoView(
chat: chat,
contact: contact,
localAlias: chat.chatInfo.localAlias,
onSearch: { focusSearch() }
)
}
} else if case let .group(groupInfo) = cInfo {
Button {
Task { await chatModel.loadGroupMembers(groupInfo) { showChatInfoSheet = true } }
} label: {
ChatInfoToolbar(chat: chat)
.tint(theme.colors.primary)
}
.appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
GroupChatInfoView(
chat: chat,
groupInfo: Binding(
get: { groupInfo },
set: { gInfo in
chat.chatInfo = .group(groupInfo: gInfo)
chat.created = Date.now
}
),
onSearch: { focusSearch() }
)
}
} else if case .local = cInfo {
ChatInfoToolbar(chat: chat)
}
.appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
ChatInfoView(
chat: chat,
contact: contact,
localAlias: chat.chatInfo.localAlias,
onSearch: { focusSearch() }
)
}
} else if case let .group(groupInfo) = cInfo {
Button {
Task { await chatModel.loadGroupMembers(groupInfo) { showChatInfoSheet = true } }
} label: {
ChatInfoToolbar(chat: chat)
.tint(theme.colors.primary)
}
.appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
GroupChatInfoView(
chat: chat,
groupInfo: Binding(
get: { groupInfo },
set: { gInfo in
chat.chatInfo = .group(groupInfo: gInfo)
chat.created = Date.now
}
),
onSearch: { focusSearch() }
)
}
} else if case .local = cInfo {
ChatInfoToolbar(chat: chat)
}
}.privacySensitive(protectScreen)
}
ToolbarItem(placement: .navigationBarTrailing) {
let isLoading = im.isLoading && im.showLoadingProgress
if selectedChatItems != nil {
Button {
withAnimation {
selectedChatItems = nil
}
} label: {
Text("Cancel")
}
} else {
switch cInfo {
case let .direct(contact):
HStack {
let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser
if callsPrefEnabled {
if chatModel.activeCall == nil {
callButton(contact, .audio, imageName: "phone")
.disabled(!contact.ready || !contact.active)
} else if let call = chatModel.activeCall, call.contact.id == cInfo.id {
endCallButton(call)
}
Group {
let isLoading = im.isLoading && im.showLoadingProgress
if selectedChatItems != nil {
Button {
withAnimation {
selectedChatItems = nil
}
Menu {
if !isLoading {
if callsPrefEnabled && chatModel.activeCall == nil {
Button {
CallController.shared.startCall(contact, .video)
} label: {
Label("Video call", systemImage: "video")
}
.disabled(!contact.ready || !contact.active)
} label: {
Text("Cancel")
}
} else {
switch cInfo {
case let .direct(contact):
HStack {
let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser
if callsPrefEnabled {
if chatModel.activeCall == nil {
callButton(contact, .audio, imageName: "phone")
.disabled(!contact.ready || !contact.active)
} else if let call = chatModel.activeCall, call.contact.id == cInfo.id {
endCallButton(call)
}
searchButton()
ToggleNtfsButton(chat: chat)
.disabled(!contact.ready || !contact.active)
}
} label: {
Image(systemName: "ellipsis")
.tint(isLoading ? Color.clear : nil)
.overlay { if isLoading { ProgressView() } }
}
}
case let .group(groupInfo):
HStack {
if groupInfo.canAddMembers {
if (chat.chatInfo.incognito) {
groupLinkButton()
.appSheet(isPresented: $showGroupLinkSheet) {
GroupLinkView(
groupId: groupInfo.groupId,
groupLink: $groupLink,
groupLinkMemberRole: $groupLinkMemberRole,
showTitle: true,
creatingGroup: false
)
Menu {
if !isLoading {
if callsPrefEnabled && chatModel.activeCall == nil {
Button {
CallController.shared.startCall(contact, .video)
} label: {
Label("Video call", systemImage: "video")
}
.disabled(!contact.ready || !contact.active)
}
} else {
addMembersButton()
searchButton()
ToggleNtfsButton(chat: chat)
.disabled(!contact.ready || !contact.active)
}
} label: {
Image(systemName: "ellipsis")
.tint(isLoading ? Color.clear : nil)
.overlay { if isLoading { ProgressView() } }
}
}
Menu {
if !isLoading {
searchButton()
ToggleNtfsButton(chat: chat)
case let .group(groupInfo):
HStack {
if groupInfo.canAddMembers {
if (chat.chatInfo.incognito) {
groupLinkButton()
.appSheet(isPresented: $showGroupLinkSheet) {
GroupLinkView(
groupId: groupInfo.groupId,
groupLink: $groupLink,
groupLinkMemberRole: $groupLinkMemberRole,
showTitle: true,
creatingGroup: false
)
}
} else {
addMembersButton()
}
}
Menu {
if !isLoading {
searchButton()
ToggleNtfsButton(chat: chat)
}
} label: {
Image(systemName: "ellipsis")
.tint(isLoading ? Color.clear : nil)
.overlay { if isLoading { ProgressView() } }
}
} label: {
Image(systemName: "ellipsis")
.tint(isLoading ? Color.clear : nil)
.overlay { if isLoading { ProgressView() } }
}
case .local:
searchButton()
default:
EmptyView()
}
case .local:
searchButton()
default:
EmptyView()
}
}
}.privacySensitive(protectScreen)
}
}
}
@@ -95,6 +95,7 @@ struct ChatListView: View {
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
@AppStorage(DEFAULT_ONE_HAND_UI_CARD_SHOWN) private var oneHandUICardShown = false
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
var body: some View {
if #available(iOS 16.0, *) {
@@ -119,11 +120,14 @@ struct ChatListView: View {
.modifier(
Sheet(isPresented: $userPickerShown) {
UserPicker(userPickerShown: $userPickerShown, activeSheet: $activeUserPickerSheet)
.privacySensitive(protectScreen)
}
)
.sheet(item: $activeUserPickerSheet) {
UserPickerSheetView(sheet: $0)
}
.appSheet(
item: $activeUserPickerSheet,
onDismiss: { chatModel.laRequest = nil },
content: { UserPickerSheetView(sheet: $0) }
)
.onChange(of: activeUserPickerSheet) {
if $0 != nil {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
@@ -140,6 +144,7 @@ struct ChatListView: View {
.background(theme.colors.background)
.navigationBarTitleDisplayMode(.inline)
.navigationBarHidden(searchMode || oneHandUI)
.privacySensitive(protectScreen)
}
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
.onDisappear() { activeUserPickerSheet = nil }
@@ -213,6 +218,7 @@ struct ChatListView: View {
Spacer()
trailingToolbarItem.padding(.bottom, padding)
}
.privacySensitive(protectScreen)
.contentShape(Rectangle())
.onTapGesture { scrollToSearchBar = true }
}
@@ -241,17 +247,20 @@ struct ChatListView: View {
unreadBadge(size: 12)
}
}
.privacySensitive(protectScreen)
.onTapGesture {
userPickerShown = true
}
}
@ViewBuilder var trailingToolbarItem: some View {
switch chatModel.chatRunning {
case .some(true): NewChatMenuButton()
case .some(false): chatStoppedIcon()
case .none: EmptyView()
}
Group {
switch chatModel.chatRunning {
case .some(true): NewChatMenuButton()
case .some(false): chatStoppedIcon()
case .none: EmptyView()
}
}.privacySensitive(protectScreen)
}
@ViewBuilder private var chatList: some View {
@@ -408,6 +417,7 @@ struct SubsStatusIndicator: View {
@State private var showServersSummary = false
@AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
var body: some View {
Button {
@@ -422,6 +432,7 @@ struct SubsStatusIndicator: View {
}
}
.disabled(ChatModel.shared.chatRunning != true)
.privacySensitive(protectScreen)
.onAppear {
startTask()
}
+3 -1
View File
@@ -11,6 +11,8 @@ import SwiftUI
class AppSheetState: ObservableObject {
static let shared = AppSheetState()
@Published var scenePhaseActive: Bool = false
// Scehe phase is also be inactive while faceID is requested
@Published var biometricAuth: Bool = false
}
private struct PrivacySensitive: ViewModifier {
@@ -22,7 +24,7 @@ private struct PrivacySensitive: ViewModifier {
if !protectScreen {
content
} else {
content.privacySensitive(!appSheetState.scenePhaseActive).redacted(reason: .privacy)
content.privacySensitive(!appSheetState.scenePhaseActive && !appSheetState.biometricAuth).redacted(reason: .privacy)
}
}
}
@@ -63,9 +63,11 @@ func systemAuthenticate(_ reason: String, _ completed: @escaping (LAResult) -> V
var authAvailabilityError: NSError?
if laContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: &authAvailabilityError) {
logger.debug("DEBUGGING: systemAuthenticate: canEvaluatePolicy callback")
AppSheetState.shared.biometricAuth = true
laContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, authError in
logger.debug("DEBUGGING: systemAuthenticate evaluatePolicy callback")
DispatchQueue.main.async {
AppSheetState.shared.biometricAuth = false
if success {
completed(LAResult.success)
} else {
@@ -59,7 +59,10 @@ struct UserProfilesView: View {
userProfilesView()
} else {
Button(action: runAuth) { Label("Unlock", systemImage: "lock") }
.onAppear(perform: runAuth)
.onAppear {
// `scenePhase` check Prevents a feedback loop
if AppSheetState.shared.scenePhaseActive { runAuth() }
}
}
}