mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-17 02:58:07 +00:00
add bottom sheet to add a friend to the favorites list
This commit is contained in:
parent
219ee2d438
commit
5dc38b6c91
15 changed files with 568 additions and 141 deletions
|
|
@ -56,6 +56,9 @@
|
|||
D7DA67642ACCB31700E95002 /* ProfileModeFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DA67632ACCB31700E95002 /* ProfileModeFragment.swift */; };
|
||||
D7E6D0492AE933AD00A57AAF /* FavoriteContactsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D0482AE933AD00A57AAF /* FavoriteContactsListFragment.swift */; };
|
||||
D7E6D04B2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D04A2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift */; };
|
||||
D7E6D04D2AEBD77600A57AAF /* CustomBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D04C2AEBD77600A57AAF /* CustomBottomSheet.swift */; };
|
||||
D7E6D0512AEBDBD500A57AAF /* ContactsListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D0502AEBDBD500A57AAF /* ContactsListBottomSheet.swift */; };
|
||||
D7E6D0552AEBFCCE00A57AAF /* ContactsInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */; };
|
||||
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */; };
|
||||
D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
|
@ -114,6 +117,9 @@
|
|||
D7DA67632ACCB31700E95002 /* ProfileModeFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModeFragment.swift; sourceTree = "<group>"; };
|
||||
D7E6D0482AE933AD00A57AAF /* FavoriteContactsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteContactsListFragment.swift; sourceTree = "<group>"; };
|
||||
D7E6D04A2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteContactsListViewModel.swift; sourceTree = "<group>"; };
|
||||
D7E6D04C2AEBD77600A57AAF /* CustomBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBottomSheet.swift; sourceTree = "<group>"; };
|
||||
D7E6D0502AEBDBD500A57AAF /* ContactsListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsListBottomSheet.swift; sourceTree = "<group>"; };
|
||||
D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsInnerFragment.swift; sourceTree = "<group>"; };
|
||||
D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsFragment.swift; sourceTree = "<group>"; };
|
||||
D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterFragment.swift; sourceTree = "<group>"; };
|
||||
FB718F405DAF7B9993AEB878 /* Pods-Linphone.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Linphone.debug.xcconfig"; path = "Target Support Files/Pods-Linphone/Pods-Linphone.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
|
|
@ -288,6 +294,7 @@
|
|||
D750D3382AD3E6EE00EC99C5 /* PopupLoadingView.swift */,
|
||||
D706BA812ADD72D100278F45 /* DeviceRotationViewModifier.swift */,
|
||||
D72250682ADFBF2D008FB426 /* SideMenu.swift */,
|
||||
D7E6D04C2AEBD77600A57AAF /* CustomBottomSheet.swift */,
|
||||
);
|
||||
path = Fragments;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -316,6 +323,8 @@
|
|||
D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */,
|
||||
D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */,
|
||||
D7E6D0482AE933AD00A57AAF /* FavoriteContactsListFragment.swift */,
|
||||
D7E6D0502AEBDBD500A57AAF /* ContactsListBottomSheet.swift */,
|
||||
D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */,
|
||||
);
|
||||
path = Fragments;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -539,18 +548,21 @@
|
|||
D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */,
|
||||
D78290BB2ADD40B2004AA85C /* ContactViewModel.swift in Sources */,
|
||||
D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */,
|
||||
D7E6D04D2AEBD77600A57AAF /* CustomBottomSheet.swift in Sources */,
|
||||
D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */,
|
||||
D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */,
|
||||
D71FCA7F2AE1397200D2E43E /* ContactsListViewModel.swift in Sources */,
|
||||
D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */,
|
||||
D719ABB72ABC67BF00B41C10 /* LinphoneApp.swift in Sources */,
|
||||
D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */,
|
||||
D7E6D0512AEBDBD500A57AAF /* ContactsListBottomSheet.swift in Sources */,
|
||||
D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */,
|
||||
D7A03FC62ACC458A0081A588 /* SplashScreen.swift in Sources */,
|
||||
D7A03FC02ACC2E390081A588 /* HistoryView.swift in Sources */,
|
||||
D748BF2E2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift in Sources */,
|
||||
D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */,
|
||||
D74C9CF82ACACECE0021626A /* WelcomePage1Fragment.swift in Sources */,
|
||||
D7E6D0552AEBFCCE00A57AAF /* ContactsInnerFragment.swift in Sources */,
|
||||
D72343362AD037AF009AA24E /* ToastView.swift in Sources */,
|
||||
D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */,
|
||||
D72343322ACEFF58009AA24E /* QRScannerController.swift in Sources */,
|
||||
|
|
|
|||
21
Linphone/Assets.xcassets/heart.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/heart.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "heart.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/heart.imageset/heart.svg
vendored
Normal file
1
Linphone/Assets.xcassets/heart.imageset/heart.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M178,32c-20.65,0-38.73,8.88-50,23.89C116.73,40.88,98.65,32,78,32A62.07,62.07,0,0,0,16,94c0,70,103.79,126.66,108.21,129a8,8,0,0,0,7.58,0C136.21,220.66,240,164,240,94A62.07,62.07,0,0,0,178,32ZM128,206.8C109.74,196.16,32,147.69,32,94A46.06,46.06,0,0,1,78,48c19.45,0,35.78,10.36,42.6,27a8,8,0,0,0,14.8,0c6.82-16.67,23.15-27,42.6-27a46.06,46.06,0,0,1,46,46C224,147.61,146.24,196.15,128,206.8Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 511 B |
21
Linphone/Assets.xcassets/share-network.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/share-network.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "share-network.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/share-network.imageset/share-network.svg
vendored
Normal file
1
Linphone/Assets.xcassets/share-network.imageset/share-network.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M176,160a39.89,39.89,0,0,0-28.62,12.09l-46.1-29.63a39.8,39.8,0,0,0,0-28.92l46.1-29.63a40,40,0,1,0-8.66-13.45l-46.1,29.63a40,40,0,1,0,0,55.82l46.1,29.63A40,40,0,1,0,176,160Zm0-128a24,24,0,1,1-24,24A24,24,0,0,1,176,32ZM64,152a24,24,0,1,1,24-24A24,24,0,0,1,64,152Zm112,72a24,24,0,1,1,24-24A24,24,0,0,1,176,224Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 431 B |
21
Linphone/Assets.xcassets/trash-simple.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/trash-simple.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "trash-simple.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/trash-simple.imageset/trash-simple.svg
vendored
Normal file
1
Linphone/Assets.xcassets/trash-simple.imageset/trash-simple.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216,48H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM192,208H64V64H192ZM80,24a8,8,0,0,1,8-8h80a8,8,0,0,1,0,16H88A8,8,0,0,1,80,24Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 293 B |
|
|
@ -92,6 +92,9 @@
|
|||
},
|
||||
"Accept all" : {
|
||||
|
||||
},
|
||||
"Add to favourites" : {
|
||||
|
||||
},
|
||||
"All contacts" : {
|
||||
|
||||
|
|
@ -142,6 +145,9 @@
|
|||
},
|
||||
"Default mode" : {
|
||||
|
||||
},
|
||||
"Delete" : {
|
||||
|
||||
},
|
||||
"Demande d’autorisations" : {
|
||||
|
||||
|
|
@ -249,6 +255,9 @@
|
|||
},
|
||||
"Register" : {
|
||||
|
||||
},
|
||||
"Remove to favourites" : {
|
||||
|
||||
},
|
||||
"Scan QR code" : {
|
||||
|
||||
|
|
@ -261,6 +270,9 @@
|
|||
},
|
||||
"See Linphone contact" : {
|
||||
|
||||
},
|
||||
"Share" : {
|
||||
|
||||
},
|
||||
"sip.linphone.org" : {
|
||||
|
||||
|
|
|
|||
|
|
@ -21,57 +21,22 @@ import SwiftUI
|
|||
|
||||
struct ContactsFragment: View {
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
||||
@State var isFavoriteOpen: Bool = true
|
||||
@State private var showingSheet = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if !magicSearch.lastSearch.filter({ $0.friend?.starred == true }).isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("Favourites")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(isFavoriteOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
if #available(iOS 16.0, *) {
|
||||
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
.sheet(isPresented: $showingSheet) {
|
||||
ContactsListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
.presentationDetents([.fraction(0.2)])
|
||||
}
|
||||
.padding(.top, 30)
|
||||
.padding(.horizontal, 16)
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
isFavoriteOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if isFavoriteOpen {
|
||||
FavoriteContactsListFragment(contactViewModel: contactViewModel, favoriteContactsListViewModel: FavoriteContactsListViewModel())
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("All contacts")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel())
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
.onRotate { newOrientation in
|
||||
orientation = newOrientation
|
||||
} else {
|
||||
ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
.halfSheet(showSheet: $showingSheet) {
|
||||
ContactsListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
|
||||
} onDismiss: {}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContactsInnerFragment: View {
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
@State private var isFavoriteOpen = true
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if !magicSearch.lastSearch.filter({ $0.friend?.starred == true }).isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("Favourites")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(isFavoriteOpen ? "caret-up" : "caret-down")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c600)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
}
|
||||
.padding(.top, 30)
|
||||
.padding(.horizontal, 16)
|
||||
.background(.white)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
isFavoriteOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
if isFavoriteOpen {
|
||||
FavoriteContactsListFragment(contactViewModel: contactViewModel, favoriteContactsListViewModel: FavoriteContactsListViewModel(), showingSheet: $showingSheet)
|
||||
.zIndex(-1)
|
||||
.transition(.move(edge: .top))
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Text("All contacts")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel(), showingSheet: $showingSheet)
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContactsInnerFragment(contactViewModel: ContactViewModel(), showingSheet: .constant(false))
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
|
||||
struct ContactsListBottomSheet: View {
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
|
||||
@State private var orientation = UIDevice.current.orientation
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if orientation == .landscapeLeft
|
||||
|| orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Close") {
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.trailing)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Button {
|
||||
if contactViewModel.selectedFriend != nil {
|
||||
contactViewModel.selectedFriend!.starred.toggle()
|
||||
}
|
||||
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("heart")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text(contactViewModel.selectedFriend != nil && contactViewModel.selectedFriend!.starred == true
|
||||
? "Remove to favourites"
|
||||
: "Add to favourites")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("share-network")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text("Share")
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
VStack {
|
||||
Divider()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Button {
|
||||
if contactViewModel.selectedFriend != nil {
|
||||
contactViewModel.selectedFriend!.remove()
|
||||
}
|
||||
self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
showingSheet.toggle()
|
||||
} else {
|
||||
showingSheet.toggle()
|
||||
dismiss()
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("trash-simple")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
Text("Delete")
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.default_text_style(styleSize: 16)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 30)
|
||||
.background(Color.gray100)
|
||||
|
||||
}
|
||||
.onRotate { newOrientation in
|
||||
orientation = newOrientation
|
||||
}
|
||||
.background(Color.gray100)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
|
|
@ -21,95 +21,109 @@ import SwiftUI
|
|||
import linphonesw
|
||||
|
||||
struct ContactsListFragment: View {
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var contactsListViewModel: ContactsListViewModel
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
ForEach(0..<magicSearch.lastSearch.count, id: \.self) { index in
|
||||
Button {
|
||||
withAnimation {
|
||||
contactViewModel.contactTitle = (magicSearch.lastSearch[index].friend?.name)!
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
if index == 0 || magicSearch.lastSearch[index].friend?.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current).first !=
|
||||
magicSearch.lastSearch[index-1].friend?.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current).first {
|
||||
Text(String((magicSearch.lastSearch[index].friend?.name!.uppercased().folding(options: .diacriticInsensitive, locale: .current).first)!))
|
||||
.contact_text_style_500(styleSize: 20)
|
||||
.frame(width: 18)
|
||||
.padding(.leading, -5)
|
||||
.padding(.trailing, 10)
|
||||
} else {
|
||||
Text("")
|
||||
.contact_text_style_500(styleSize: 20)
|
||||
.frame(width: 18)
|
||||
.padding(.leading, -5)
|
||||
.padding(.trailing, 10)
|
||||
}
|
||||
|
||||
if magicSearch.lastSearch[index].friend!.photo != nil && !magicSearch.lastSearch[index].friend!.photo!.isEmpty {
|
||||
AsyncImage(url: URL(string: magicSearch.lastSearch[index].friend!.photo!)) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
.frame(width: 45, height: 45)
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
case .failure:
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
Text((magicSearch.lastSearch[index].friend?.name)!)
|
||||
|
||||
@ObservedObject var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var contactsListViewModel: ContactsListViewModel
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
ForEach(0..<magicSearch.lastSearch.count, id: \.self) { index in
|
||||
Button {
|
||||
} label: {
|
||||
HStack {
|
||||
if index == 0 || magicSearch.lastSearch[index].friend?.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current).first !=
|
||||
magicSearch.lastSearch[index-1].friend?.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current).first {
|
||||
Text(String((magicSearch.lastSearch[index].friend?.name!.uppercased().folding(options: .diacriticInsensitive, locale: .current).first)!))
|
||||
.contact_text_style_500(styleSize: 20)
|
||||
.frame(width: 18)
|
||||
.padding(.leading, -5)
|
||||
.padding(.trailing, 10)
|
||||
} else {
|
||||
Text("")
|
||||
.contact_text_style_500(styleSize: 20)
|
||||
.frame(width: 18)
|
||||
.padding(.leading, -5)
|
||||
.padding(.trailing, 10)
|
||||
}
|
||||
|
||||
if magicSearch.lastSearch[index].friend!.photo != nil && !magicSearch.lastSearch[index].friend!.photo!.isEmpty {
|
||||
AsyncImage(url: URL(string: magicSearch.lastSearch[index].friend!.photo!)) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
.frame(width: 45, height: 45)
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
case .failure:
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
Text((magicSearch.lastSearch[index].friend?.name)!)
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame( maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.overlay(
|
||||
VStack {
|
||||
if magicSearch.lastSearch.isEmpty {
|
||||
Spacer()
|
||||
Image("illus-belledonne1")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipped()
|
||||
.padding(.all)
|
||||
Text("No contacts for the moment...")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.all)
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame( maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.selectedFriend = magicSearch.lastSearch[index].friend
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
contactViewModel.contactTitle = (magicSearch.lastSearch[index].friend?.name)!
|
||||
}
|
||||
}
|
||||
)
|
||||
.buttonStyle(.borderless)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.overlay(
|
||||
VStack {
|
||||
if magicSearch.lastSearch.isEmpty {
|
||||
Spacer()
|
||||
Image("illus-belledonne1")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipped()
|
||||
.padding(.all)
|
||||
Text("No contacts for the moment...")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.all)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel())
|
||||
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import linphonesw
|
||||
|
||||
struct FavoriteContactsListFragment: View {
|
||||
|
||||
|
|
@ -26,18 +27,18 @@ struct FavoriteContactsListFragment: View {
|
|||
@ObservedObject var contactViewModel: ContactViewModel
|
||||
@ObservedObject var favoriteContactsListViewModel: FavoriteContactsListViewModel
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
ScrollView(.horizontal) {
|
||||
HStack {
|
||||
ForEach(0..<magicSearch.lastSearch.count, id: \.self) { index in
|
||||
ForEach(0..<magicSearch.lastSearch.filter({ $0.friend?.starred == true }).count, id: \.self) { index in
|
||||
Button {
|
||||
withAnimation {
|
||||
contactViewModel.contactTitle = (magicSearch.lastSearch[index].friend?.name)!
|
||||
}
|
||||
} label: {
|
||||
VStack {
|
||||
if magicSearch.lastSearch[index].friend!.photo != nil && !magicSearch.lastSearch[index].friend!.photo!.isEmpty {
|
||||
AsyncImage(url: URL(string: magicSearch.lastSearch[index].friend!.photo!)) { image in
|
||||
if magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo != nil
|
||||
&& !magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo!.isEmpty {
|
||||
AsyncImage(url: URL(string: magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo!)) { image in
|
||||
switch image {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
|
|
@ -62,11 +63,26 @@ struct FavoriteContactsListFragment: View {
|
|||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
Text((magicSearch.lastSearch[index].friend?.name)!)
|
||||
Text((magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend?.name)!)
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame( maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
.simultaneousGesture(
|
||||
LongPressGesture()
|
||||
.onEnded { _ in
|
||||
contactViewModel.selectedFriend = magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend
|
||||
showingSheet.toggle()
|
||||
}
|
||||
)
|
||||
.highPriorityGesture(
|
||||
TapGesture()
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
contactViewModel.contactTitle = (magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend?.name)!
|
||||
}
|
||||
}
|
||||
)
|
||||
.frame(minWidth: 70, maxWidth: 70)
|
||||
}
|
||||
}
|
||||
|
|
@ -76,5 +92,5 @@ struct FavoriteContactsListFragment: View {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
FavoriteContactsListFragment(contactViewModel: ContactViewModel(), favoriteContactsListViewModel: FavoriteContactsListViewModel())
|
||||
FavoriteContactsListFragment(contactViewModel: ContactViewModel(), favoriteContactsListViewModel: FavoriteContactsListViewModel(), showingSheet: .constant(false))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import linphonesw
|
|||
class ContactViewModel: ObservableObject {
|
||||
|
||||
@Published var contactTitle: String = ""
|
||||
|
||||
var selectedFriend: Friend?
|
||||
|
||||
private var magicSearch = MagicSearchSingleton.shared
|
||||
|
||||
|
|
|
|||
107
Linphone/UI/Main/Fragments/CustomBottomSheet.swift
Normal file
107
Linphone/UI/Main/Fragments/CustomBottomSheet.swift
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
//binding show bariable...
|
||||
func halfSheet<Content: View>(
|
||||
showSheet: Binding<Bool>,
|
||||
@ViewBuilder content: @escaping () -> Content,
|
||||
onDismiss: @escaping () -> Void
|
||||
) -> some View {
|
||||
return self
|
||||
.background(
|
||||
HalfSheetHelper(sheetView: content(), showSheet: showSheet, onDismiss: onDismiss)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// UIKit integration
|
||||
struct HalfSheetHelper<Content: View>: UIViewControllerRepresentable {
|
||||
|
||||
var sheetView: Content
|
||||
let controller: UIViewController = UIViewController()
|
||||
@Binding var showSheet: Bool
|
||||
var onDismiss: () -> Void
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(parent: self)
|
||||
}
|
||||
|
||||
func makeUIViewController(context: Context) -> UIViewController {
|
||||
controller.view.backgroundColor = .clear
|
||||
return controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
|
||||
if showSheet {
|
||||
let sheetController = CustomHostingController(rootView: sheetView)
|
||||
sheetController.presentationController?.delegate = context.coordinator
|
||||
uiViewController.present(sheetController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
//on dismiss...
|
||||
final class Coordinator: NSObject, UISheetPresentationControllerDelegate {
|
||||
|
||||
var parent: HalfSheetHelper
|
||||
|
||||
init(parent: HalfSheetHelper) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
|
||||
parent.showSheet = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom UIHostingController for halfSheet...
|
||||
final class CustomHostingController<Content: View>: UIHostingController<Content> {
|
||||
override func viewDidLoad() {
|
||||
view.backgroundColor = .clear
|
||||
if let presentationController = presentationController as? UISheetPresentationController {
|
||||
presentationController.detents = [
|
||||
.medium()
|
||||
]
|
||||
|
||||
//MARK: - sheet grabber visbility
|
||||
presentationController.prefersGrabberVisible = false // i wanted to design my own grabber hehehe
|
||||
|
||||
// this allows you to scroll even during medium detent
|
||||
presentationController.prefersScrollingExpandsWhenScrolledToEdge = false
|
||||
|
||||
//MARK: - sheet corner radius
|
||||
presentationController.preferredCornerRadius = 30
|
||||
|
||||
// for more sheet customisation check out this great article https://sarunw.com/posts/bottom-sheet-in-ios-15-with-uisheetpresentationcontroller/#scrolling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct LazyView<Content: View>: View {
|
||||
private let build: () -> Content
|
||||
public init(_ build: @autoclosure @escaping () -> Content) {
|
||||
self.build = build
|
||||
}
|
||||
public var body: Content {
|
||||
build()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue