add bottom sheet to add a friend to the favorites list

This commit is contained in:
benoit.martins 2023-10-27 17:04:11 +02:00
parent 219ee2d438
commit 5dc38b6c91
15 changed files with 568 additions and 141 deletions

View file

@ -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 */,

View 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
}
}

View 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

View 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
}
}

View 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

View 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
}
}

View 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

View file

@ -92,6 +92,9 @@
},
"Accept all" : {
},
"Add to favourites" : {
},
"All contacts" : {
@ -142,6 +145,9 @@
},
"Default mode" : {
},
"Delete" : {
},
"Demande dautorisations" : {
@ -249,6 +255,9 @@
},
"Register" : {
},
"Remove to favourites" : {
},
"Scan QR code" : {
@ -261,6 +270,9 @@
},
"See Linphone contact" : {
},
"Share" : {
},
"sip.linphone.org" : {

View file

@ -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: {}
}
}

View file

@ -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))
}

View file

@ -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)
}
}

View file

@ -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))
}

View file

@ -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))
}

View file

@ -22,6 +22,8 @@ import linphonesw
class ContactViewModel: ObservableObject {
@Published var contactTitle: String = ""
var selectedFriend: Friend?
private var magicSearch = MagicSearchSingleton.shared

View 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()
}
}