Merge pull request #4 from talksik/live-voice-call-api-integration
Live voice call api integration
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
89091997277DDE030025F4BB /* ProfilePicturesOverlappedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89091996277DDE030025F4BB /* ProfilePicturesOverlappedView.swift */; };
|
89091997277DDE030025F4BB /* ProfilePicturesOverlappedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89091996277DDE030025F4BB /* ProfilePicturesOverlappedView.swift */; };
|
||||||
89091999277EFDCC0025F4BB /* leaveSound.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 89091998277EFDCC0025F4BB /* leaveSound.mp3 */; };
|
89091999277EFDCC0025F4BB /* leaveSound.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 89091998277EFDCC0025F4BB /* leaveSound.mp3 */; };
|
||||||
8909199C277EFDF30025F4BB /* joinSound.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 8909199B277EFDF30025F4BB /* joinSound.mp3 */; };
|
8909199C277EFDF30025F4BB /* joinSound.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 8909199B277EFDF30025F4BB /* joinSound.mp3 */; };
|
||||||
|
8909199E278155920025F4BB /* UserStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8909199D278155920025F4BB /* UserStatusView.swift */; };
|
||||||
890C245D277BF628009B4A30 /* AgoraRtcKit in Frameworks */ = {isa = PBXBuildFile; productRef = 890C245C277BF628009B4A30 /* AgoraRtcKit */; };
|
890C245D277BF628009B4A30 /* AgoraRtcKit in Frameworks */ = {isa = PBXBuildFile; productRef = 890C245C277BF628009B4A30 /* AgoraRtcKit */; };
|
||||||
890C2460277BF896009B4A30 /* AgoraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890C245F277BF896009B4A30 /* AgoraViewController.swift */; };
|
890C2460277BF896009B4A30 /* AgoraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890C245F277BF896009B4A30 /* AgoraViewController.swift */; };
|
||||||
890C2462277BF935009B4A30 /* CallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890C2461277BF935009B4A30 /* CallView.swift */; };
|
890C2462277BF935009B4A30 /* CallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890C2461277BF935009B4A30 /* CallView.swift */; };
|
||||||
@@ -102,6 +103,7 @@
|
|||||||
89091996277DDE030025F4BB /* ProfilePicturesOverlappedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePicturesOverlappedView.swift; sourceTree = "<group>"; };
|
89091996277DDE030025F4BB /* ProfilePicturesOverlappedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePicturesOverlappedView.swift; sourceTree = "<group>"; };
|
||||||
89091998277EFDCC0025F4BB /* leaveSound.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = leaveSound.mp3; sourceTree = "<group>"; };
|
89091998277EFDCC0025F4BB /* leaveSound.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = leaveSound.mp3; sourceTree = "<group>"; };
|
||||||
8909199B277EFDF30025F4BB /* joinSound.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = joinSound.mp3; sourceTree = "<group>"; };
|
8909199B277EFDF30025F4BB /* joinSound.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = joinSound.mp3; sourceTree = "<group>"; };
|
||||||
|
8909199D278155920025F4BB /* UserStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStatusView.swift; sourceTree = "<group>"; };
|
||||||
890C245F277BF896009B4A30 /* AgoraViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgoraViewController.swift; sourceTree = "<group>"; };
|
890C245F277BF896009B4A30 /* AgoraViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgoraViewController.swift; sourceTree = "<group>"; };
|
||||||
890C2461277BF935009B4A30 /* CallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallView.swift; sourceTree = "<group>"; };
|
890C2461277BF935009B4A30 /* CallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallView.swift; sourceTree = "<group>"; };
|
||||||
890C2463277BFA97009B4A30 /* AgroRep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgroRep.swift; sourceTree = "<group>"; };
|
890C2463277BFA97009B4A30 /* AgroRep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgroRep.swift; sourceTree = "<group>"; };
|
||||||
@@ -316,6 +318,7 @@
|
|||||||
891A15FE27656F9200B26659 /* HeaderView.swift */,
|
891A15FE27656F9200B26659 /* HeaderView.swift */,
|
||||||
89288ECA27675B9F00E962C4 /* OnboardingTemplateView.swift */,
|
89288ECA27675B9F00E962C4 /* OnboardingTemplateView.swift */,
|
||||||
89BCABEC276D2B05009E9B10 /* WavesGlassBackgroundView.swift */,
|
89BCABEC276D2B05009E9B10 /* WavesGlassBackgroundView.swift */,
|
||||||
|
8909199D278155920025F4BB /* UserStatusView.swift */,
|
||||||
);
|
);
|
||||||
path = Templates;
|
path = Templates;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -656,6 +659,7 @@
|
|||||||
8904DEC8277055E90071E6CA /* OnboardingOneView.swift in Sources */,
|
8904DEC8277055E90071E6CA /* OnboardingOneView.swift in Sources */,
|
||||||
896CE7022770892F00DB474E /* CircleGridView.swift in Sources */,
|
896CE7022770892F00DB474E /* CircleGridView.swift in Sources */,
|
||||||
89091995277DD9F80025F4BB /* ConvoFooterView.swift in Sources */,
|
89091995277DD9F80025F4BB /* ConvoFooterView.swift in Sources */,
|
||||||
|
8909199E278155920025F4BB /* UserStatusView.swift in Sources */,
|
||||||
89BDCDEA27697F9C00577829 /* UserMenu.swift in Sources */,
|
89BDCDEA27697F9C00577829 /* UserMenu.swift in Sources */,
|
||||||
89BCABE7276B3D64009E9B10 /* InnerCircleViewModel.swift in Sources */,
|
89BCABE7276B3D64009E9B10 /* InnerCircleViewModel.swift in Sources */,
|
||||||
89A107E5277476CC00B971FD /* PushNotificationService.swift in Sources */,
|
89A107E5277476CC00B971FD /* PushNotificationService.swift in Sources */,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ struct Convo: Identifiable, Codable {
|
|||||||
|
|
||||||
@ServerTimestamp var startedTimestamp: Date?
|
@ServerTimestamp var startedTimestamp: Date?
|
||||||
var endedTimestamp: Date?
|
var endedTimestamp: Date?
|
||||||
var receiverEndedTimestamp: Date?
|
var secondToLastUserEndedTimestamp: Date?
|
||||||
}
|
}
|
||||||
|
|
||||||
//struct DetailConvo: Convo {
|
//struct DetailConvo: Convo {
|
||||||
|
|||||||
@@ -224,9 +224,11 @@ extension AuthSessionStore {
|
|||||||
// TODO: THIS WON'T UPDATE VIEW since user is a reference type...can manually publish...research and learn more
|
// TODO: THIS WON'T UPDATE VIEW since user is a reference type...can manually publish...research and learn more
|
||||||
self.firestoreService.getUserRealtime(userId: currUserId!) {[weak self] realtimeUpdatedUser in
|
self.firestoreService.getUserRealtime(userId: currUserId!) {[weak self] realtimeUpdatedUser in
|
||||||
if realtimeUpdatedUser != nil {
|
if realtimeUpdatedUser != nil {
|
||||||
print("up to date user: \(realtimeUpdatedUser)")
|
print("up to date user")
|
||||||
self?.user = realtimeUpdatedUser
|
self?.user = realtimeUpdatedUser
|
||||||
self?.objectWillChange.send()
|
self?.objectWillChange.send()
|
||||||
|
|
||||||
|
self?.relevantUsersDict[currUserId!] = realtimeUpdatedUser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,89 +9,95 @@ import SwiftUI
|
|||||||
|
|
||||||
struct ConvoFooterView: View {
|
struct ConvoFooterView: View {
|
||||||
@EnvironmentObject var convoVM: ConvoViewModel
|
@EnvironmentObject var convoVM: ConvoViewModel
|
||||||
|
@EnvironmentObject var authSessionStore: AuthSessionStore
|
||||||
|
|
||||||
@State private var convoRelativeTime = "started 20 minutes ago"
|
@State private var convoRelativeTime = "started 20 minutes ago"
|
||||||
|
@State private var selectedConvo: Convo? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
ZStack(alignment:.bottomTrailing) {
|
ZStack(alignment:.bottomTrailing) {
|
||||||
VStack(alignment:.center) {
|
VStack(alignment:.center) {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
ProfilePicturesOverlappedView(indvAvatarWidth: CGFloat(35))
|
if self.selectedConvo != nil {
|
||||||
.padding(.leading, 20)
|
let filteredUsers = self.authSessionStore.relevantUsersDict.filter {
|
||||||
.padding(.trailing, 10)
|
return self.selectedConvo!.users.contains($0.key)
|
||||||
|
}
|
||||||
|
let convoUsers: [User] = Array(filteredUsers.values)
|
||||||
// meta data of convo
|
|
||||||
VStack (alignment: .leading) {
|
|
||||||
Text("kev, sarth, and 5 others")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(NirvanaColor.light)
|
|
||||||
|
|
||||||
switch self.convoVM.connectionState {
|
ProfilePicturesOverlappedView(indvAvatarWidth: CGFloat(35), users: convoUsers)
|
||||||
case .connecting:
|
.padding(.leading, 20)
|
||||||
Label("connecting", systemImage: "chart.bar")
|
.padding(.trailing, 10)
|
||||||
.foregroundColor(Color.orange)
|
|
||||||
.font(.caption)
|
// meta data of convo
|
||||||
case .connected:
|
VStack (alignment: .leading) {
|
||||||
Label("connected", systemImage: "chart.bar.fill")
|
ConvoUsernames(users: convoUsers)
|
||||||
.foregroundColor(Color.green)
|
|
||||||
.font(.caption)
|
switch self.convoVM.connectionState {
|
||||||
case .reconnecting:
|
case .connecting:
|
||||||
Label("reconnecting", systemImage: "chart.bar")
|
Label("connecting", systemImage: "chart.bar")
|
||||||
.foregroundColor(Color.orange)
|
.foregroundColor(Color.orange)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
case .failed:
|
case .connected:
|
||||||
Label("failed", systemImage: "chart.bar")
|
Label("connected", systemImage: "chart.bar.fill")
|
||||||
.foregroundColor(Color.red)
|
.foregroundColor(Color.green)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
case .disconnected:
|
case .reconnecting:
|
||||||
Label("disconnected", systemImage: "chart.bar")
|
Label("reconnecting", systemImage: "chart.bar")
|
||||||
.foregroundColor(Color.red)
|
.foregroundColor(Color.orange)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
default:
|
case .failed:
|
||||||
Label("disconnected", systemImage: "chart.bar")
|
Label("failed", systemImage: "chart.bar")
|
||||||
.foregroundColor(Color.red)
|
.foregroundColor(Color.red)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
|
case .disconnected:
|
||||||
|
Label("disconnected", systemImage: "chart.bar")
|
||||||
|
.foregroundColor(Color.red)
|
||||||
|
.font(.caption)
|
||||||
|
default:
|
||||||
|
Label("disconnected", systemImage: "chart.bar")
|
||||||
|
.foregroundColor(Color.red)
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(self.convoRelativeTime)
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(self.convoRelativeTime)
|
Spacer()
|
||||||
.font(.caption2)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
// disconnect button
|
|
||||||
Button {
|
|
||||||
print("showing more info about the chat")
|
|
||||||
|
|
||||||
// show a sheet of the members in the call and add them if I want
|
// disconnect button
|
||||||
|
Button {
|
||||||
|
print("showing more info about the chat")
|
||||||
|
|
||||||
|
// show a sheet of the members in the call and add them if I want
|
||||||
|
|
||||||
|
} label: {
|
||||||
|
Label("attendees", systemImage: "person.2.circle.fill")
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.foregroundColor(NirvanaColor.teal)
|
||||||
|
.font(.title2)
|
||||||
|
.padding(.trailing, 5)
|
||||||
|
}
|
||||||
|
|
||||||
} label: {
|
// disconnect button
|
||||||
Label("attendees", systemImage: "person.2.circle.fill")
|
Button {
|
||||||
.labelStyle(.iconOnly)
|
print("disconnecting user now ")
|
||||||
.foregroundColor(NirvanaColor.teal)
|
|
||||||
.font(.title2)
|
self.convoVM.leaveConvo()
|
||||||
.padding(.trailing, 5)
|
|
||||||
|
// show success or failure toast
|
||||||
|
} label: {
|
||||||
|
Label("Call", systemImage: "powerplug.fill")
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.foregroundColor(Color.orange)
|
||||||
|
.font(.title2)
|
||||||
|
.padding(.trailing, 5)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// disconnect button
|
|
||||||
Button {
|
|
||||||
print("disconnecting user now ")
|
|
||||||
|
|
||||||
self.convoVM.leaveConvo()
|
|
||||||
|
|
||||||
// show success or failure toast
|
|
||||||
} label: {
|
|
||||||
Label("Call", systemImage: "powerplug.fill")
|
|
||||||
.labelStyle(.iconOnly)
|
|
||||||
.foregroundColor(Color.orange)
|
|
||||||
.font(.title2)
|
|
||||||
.padding(.trailing, 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: 60) // 60 is the height of the footer control big circle
|
.frame(maxWidth: .infinity, maxHeight: 60) // 60 is the height of the footer control big circle
|
||||||
.background(Color.white.opacity(0.5))
|
.background(Color.white.opacity(0.5))
|
||||||
@@ -105,17 +111,25 @@ struct ConvoFooterView: View {
|
|||||||
x:0,
|
x:0,
|
||||||
y:self.convoVM.selectedConvoId == nil ? 150 : 0
|
y:self.convoVM.selectedConvoId == nil ? 150 : 0
|
||||||
)
|
)
|
||||||
.onChange(of: self.convoVM.selectedConvoId) {newConvo in
|
.onReceive(self.convoVM.$relevantConvos) {newConvos in
|
||||||
// update meta data based on which convo was selected
|
// update meta data based on which convo was selected
|
||||||
|
|
||||||
|
if self.convoVM.selectedConvoId == nil {
|
||||||
|
print("nothing to show as there is no convo selected")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// reset the selected options to start fresh
|
// reset the selected options to start fresh
|
||||||
// self.convoRelativeTime = ""
|
self.convoRelativeTime = ""
|
||||||
|
|
||||||
|
self.selectedConvo = newConvos.first {convo in
|
||||||
|
convo.id == self.convoVM.selectedConvoId
|
||||||
|
}
|
||||||
|
|
||||||
// get relative start time of convo
|
// get relative start time of convo
|
||||||
// let formatter = RelativeDateTimeFormatter()
|
let formatter = RelativeDateTimeFormatter()
|
||||||
// formatter.unitsStyle = .full
|
formatter.unitsStyle = .full
|
||||||
//
|
self.convoRelativeTime = formatter.localizedString(for: self.selectedConvo?.startedTimestamp ?? Date(), relativeTo: Date())
|
||||||
// let relativeDate = formatter.localizedString(for: ?.sentTimestamp ?? Date(), relativeTo: Date())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,3 +139,14 @@ struct ConvoFooterView_Previews: PreviewProvider {
|
|||||||
ConvoFooterView()
|
ConvoFooterView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ConvoUsernames: View {
|
||||||
|
var users: [User]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let userNames = self.users.map{$0.nickname ?? ""}.joined(separator: ",")
|
||||||
|
Text(userNames)
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(NirvanaColor.light)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Firebase
|
|||||||
|
|
||||||
class ConvoViewModel: NSObject, ObservableObject {
|
class ConvoViewModel: NSObject, ObservableObject {
|
||||||
@Published var selectedConvoId:String? = nil
|
@Published var selectedConvoId:String? = nil
|
||||||
@Published var connectionState: AgoraConnectionStateType? = nil
|
@Published var connectionState: AgoraConnectionStateType?
|
||||||
|
|
||||||
let firestoreService = FirestoreService()
|
let firestoreService = FirestoreService()
|
||||||
private var db = Firestore.firestore()
|
private var db = Firestore.firestore()
|
||||||
@@ -19,7 +19,7 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
|
|
||||||
let agoraService = AgoraService()
|
let agoraService = AgoraService()
|
||||||
// TODO: put the value in environment variables
|
// TODO: put the value in environment variables
|
||||||
lazy var agoraKit: AgoraRtcEngineKit? = AgoraRtcEngineKit.sharedEngine(withAppId: "c8dfd65deb5c4741bd564085627139d0", delegate: self)
|
var agoraKit: AgoraRtcEngineKit?
|
||||||
|
|
||||||
private var allConvos: [Convo] = []
|
private var allConvos: [Convo] = []
|
||||||
@Published var relevantConvos: [Convo] = []
|
@Published var relevantConvos: [Convo] = []
|
||||||
@@ -28,15 +28,22 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
return self.selectedConvoId != nil
|
return self.selectedConvoId != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
static var relevancyAcceptance = 0.6
|
static var relevancyAcceptance = 0.5
|
||||||
|
|
||||||
|
func initializeAgoraEngine() {
|
||||||
|
self.agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: "c8dfd65deb5c4741bd564085627139d0", delegate: self)
|
||||||
|
}
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
initializeAgoraEngine()
|
||||||
|
|
||||||
// start process of data collection
|
// start process of data collection
|
||||||
let userId = AuthSessionStore.getCurrentUserId()
|
let userId = AuthSessionStore.getCurrentUserId()
|
||||||
if userId == nil {
|
if userId == nil {
|
||||||
print("no authenticated user")
|
print("no authenticated user")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all active userfriends
|
// get all active userfriends
|
||||||
@@ -88,13 +95,19 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
|
|
||||||
// if convo receiver is me and I'm not in a call or this call already, then join in
|
// if convo receiver is me and I'm not in a call or this call already, then join in
|
||||||
if convo!.receiverUserId == userId && !(self?.isInCall())!
|
if convo!.receiverUserId == userId && !(self?.isInCall())!
|
||||||
&& convo!.receiverEndedTimestamp == nil {
|
&& convo!.secondToLastUserEndedTimestamp == nil {
|
||||||
|
print("I am the direct receiver...joining convo")
|
||||||
self?.joinConvo(convo: convo!)
|
self?.joinConvo(convo: convo!)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if I am in a convo in which the last/loner has left, I need to leave the convo
|
// if I am in a convo in which the last/loner has left, I need to leave the convo
|
||||||
if convo!.receiverEndedTimestamp != nil && (self?.isInCall())! && convo!.users.contains(userId!) {
|
else if convo!.secondToLastUserEndedTimestamp != nil && (self?.isInCall())! && convo!.users.contains(userId!) {
|
||||||
|
print("I am the last one now... ending the convo")
|
||||||
self?.leaveConvo()
|
self?.leaveConvo()
|
||||||
|
} //also if I am added after a receiver was added and I am not already in a convo, then join the call
|
||||||
|
else if convo!.users.contains(userId!) && !(self?.isInCall())! {
|
||||||
|
// should be free or online and not in another call unless it's a race condition
|
||||||
|
print("I was added onto the call because I was free...joining automatically")
|
||||||
|
self?.joinConvo(convo: convo!)
|
||||||
}
|
}
|
||||||
|
|
||||||
self?.allConvos.append(convo!)
|
self?.allConvos.append(convo!)
|
||||||
@@ -111,10 +124,10 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let relevancyScore = Double(relevancyCount / convo.users.count)
|
let relevancyScore = Double(relevancyCount) / Double(convo.users.count)
|
||||||
print("relevancy score: \(relevancyScore)")
|
print("relevancy score: \(relevancyScore)")
|
||||||
|
|
||||||
if relevancyScore > Self.relevancyAcceptance {
|
if relevancyScore > Self.relevancyAcceptance && convo.users.count > 1 { //offchance that it's just the one person in the convo right now, don't want to see it until there are at least 2 people
|
||||||
print("this convo counts as relevant!!!")
|
print("this convo counts as relevant!!!")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -126,15 +139,12 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
// deinit the firestore listener
|
|
||||||
self.convosListener?.remove()
|
|
||||||
|
|
||||||
// deinit the agora engine
|
|
||||||
print("deiniting and cleaning up convo view model/agora services")
|
print("deiniting and cleaning up convo view model/agora services")
|
||||||
|
|
||||||
AgoraRtcEngineKit.destroy()
|
self.leaveConvo(destroyInstance: true)
|
||||||
|
|
||||||
self.leaveConvo()
|
// deinit the firestore listener
|
||||||
|
self.convosListener?.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -175,7 +185,8 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinConvo(convo: Convo) {
|
/* internal control to join a convo */
|
||||||
|
private func joinConvo(convo: Convo) {
|
||||||
// ensure that this user leaves all other channels
|
// ensure that this user leaves all other channels
|
||||||
if self.isInCall() {
|
if self.isInCall() {
|
||||||
self.leaveConvo()
|
self.leaveConvo()
|
||||||
@@ -186,25 +197,19 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
|
|
||||||
self.agoraKit?.setDefaultAudioRouteToSpeakerphone(true)
|
self.agoraKit?.setDefaultAudioRouteToSpeakerphone(true)
|
||||||
// finally join channel
|
// finally join channel
|
||||||
self.agoraKit?.delegate = self
|
|
||||||
self.agoraKit?.joinChannel(byToken: convo.agoraToken, channelId: convo.id!, info: nil, uid: 0, joinSuccess: nil)
|
self.agoraKit?.joinChannel(byToken: convo.agoraToken, channelId: convo.id!, info: nil, uid: 0, joinSuccess: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Allow user to join an active convo
|
Allow user to join an active convo
|
||||||
*/
|
*/
|
||||||
func joinConvo() {
|
func joinConvo(convoId: String) {
|
||||||
// ensure the convo is active and includes 2 people in it currently...shouldn't be shown if not anyway
|
// ensure the convo is active and includes 2 people in it currently...shouldn't be shown if not anyway
|
||||||
|
|
||||||
// ensure that this user leaves all other channels
|
// ensure that this user leaves all other channels
|
||||||
if self.isInCall() {
|
|
||||||
print("can't join another call, already in one")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let convo = self.relevantConvos.first {convo in
|
let convo = self.relevantConvos.first {convo in
|
||||||
convo.id == self.selectedConvoId
|
convo.id == convoId
|
||||||
}
|
}
|
||||||
|
|
||||||
if convo == nil {
|
if convo == nil {
|
||||||
@@ -215,20 +220,44 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
|
|
||||||
// TODO: if the convo has more than 10 people, stop user from joining...that's too expensive...
|
// TODO: if the convo has more than 10 people, stop user from joining...that's too expensive...
|
||||||
|
|
||||||
|
self.selectedConvoId = convoId
|
||||||
let convoAgoraToken:String = convo!.agoraToken
|
let convoAgoraToken:String = convo!.agoraToken
|
||||||
let channelName:String = convo!.id!
|
let channelName:String = convo!.id!
|
||||||
|
|
||||||
|
print("joining convo: \(channelName)")
|
||||||
|
|
||||||
self.agoraKit?.setDefaultAudioRouteToSpeakerphone(true)
|
self.agoraKit?.setDefaultAudioRouteToSpeakerphone(true)
|
||||||
// finally join channel
|
// finally join channel
|
||||||
self.agoraKit?.delegate = self
|
|
||||||
self.agoraKit?.joinChannel(byToken: convoAgoraToken, channelId: channelName, info: nil, uid: 0, joinSuccess: nil)
|
self.agoraKit?.joinChannel(byToken: convoAgoraToken, channelId: channelName, info: nil, uid: 0, joinSuccess: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// anyone can leave at any time
|
func addThirdPartyToConvo(friendId: String) {
|
||||||
func leaveConvo() {
|
// recheck if this other friend that I am adding is online/free
|
||||||
self.agoraKit?.delegate = self
|
|
||||||
agoraKit?.leaveChannel(nil)
|
|
||||||
|
|
||||||
|
if !self.isInCall() {
|
||||||
|
print("not in a call, can't invite/force someone else...must have a selected convo")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the currconvo details
|
||||||
|
var convo = self.relevantConvos.first {convo in
|
||||||
|
convo.id == self.selectedConvoId
|
||||||
|
}
|
||||||
|
if convo == nil {
|
||||||
|
print("convo not available")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// update convo users array with this other friend
|
||||||
|
convo!.users.append(friendId)
|
||||||
|
|
||||||
|
self.firestoreService.updateConvo(convo: convo!) {[weak self] res in
|
||||||
|
print("updated with new friend added in the convo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// anyone can leave at any time
|
||||||
|
func leaveConvo(destroyInstance: Bool = false) {
|
||||||
let userId = AuthSessionStore.getCurrentUserId()
|
let userId = AuthSessionStore.getCurrentUserId()
|
||||||
|
|
||||||
if userId == nil {
|
if userId == nil {
|
||||||
@@ -241,6 +270,8 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agoraKit?.leaveChannel(nil)
|
||||||
|
|
||||||
self.firestoreService.updateUserStatus(userId: userId!, userStatus: .online) {[weak self] res in
|
self.firestoreService.updateUserStatus(userId: userId!, userStatus: .online) {[weak self] res in
|
||||||
print(res)
|
print(res)
|
||||||
|
|
||||||
@@ -269,7 +300,7 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
updatedConvo!.endedTimestamp = Date()
|
updatedConvo!.endedTimestamp = Date()
|
||||||
} // if I am second to last, the last guy is a loner
|
} // if I am second to last, the last guy is a loner
|
||||||
else if updatedConvo!.users.count == 2 && updatedConvo!.users.contains(userId!) {
|
else if updatedConvo!.users.count == 2 && updatedConvo!.users.contains(userId!) {
|
||||||
updatedConvo!.receiverEndedTimestamp = Date()
|
updatedConvo!.secondToLastUserEndedTimestamp = Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
let updatedUsers: [String] = updatedConvo!.users.filter{ arrUserId in
|
let updatedUsers: [String] = updatedConvo!.users.filter{ arrUserId in
|
||||||
@@ -282,13 +313,19 @@ class ConvoViewModel: NSObject, ObservableObject {
|
|||||||
self?.selectedConvoId = nil
|
self?.selectedConvoId = nil
|
||||||
|
|
||||||
print("left convo officially in our db as well")
|
print("left convo officially in our db as well")
|
||||||
|
|
||||||
|
|
||||||
|
// deinit the agora engine
|
||||||
|
if destroyInstance {
|
||||||
|
self?.destroyInstance()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func destroyInstance() {
|
func destroyInstance() {
|
||||||
AgoraRtcEngineKit.destroy()
|
AgoraRtcEngineKit.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ConvoViewModel {
|
extension ConvoViewModel {
|
||||||
@@ -377,7 +414,10 @@ extension ConvoViewModel: AgoraRtcEngineDelegate {
|
|||||||
case .success:
|
case .success:
|
||||||
// update convo in database users array to let them know I am in now
|
// update convo in database users array to let them know I am in now
|
||||||
|
|
||||||
updatedConvo!.users.append(userId!)
|
// only add if I am not already in the users arr...if I was a third party, I should be in there already
|
||||||
|
if !updatedConvo!.users.contains(userId!) {
|
||||||
|
updatedConvo!.users.append(userId!)
|
||||||
|
}
|
||||||
updatedConvo!.state = .active
|
updatedConvo!.state = .active
|
||||||
|
|
||||||
self?.firestoreService.updateConvo(convo: updatedConvo!) {[weak self] res in
|
self?.firestoreService.updateConvo(convo: updatedConvo!) {[weak self] res in
|
||||||
|
|||||||
@@ -7,67 +7,70 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ConvosView: View {
|
/**
|
||||||
@EnvironmentObject var vm: ConvoViewModel
|
deprecated...used to insert into circlegridview but can isolate later
|
||||||
@State var animate = false
|
*/
|
||||||
|
//struct ConvosView: View {
|
||||||
var body: some View {
|
// @EnvironmentObject var vm: ConvoViewModel
|
||||||
ScrollView([.horizontal]) {
|
// @State var animate = false
|
||||||
HStack {
|
|
||||||
ForEach(0..<self.vm.relevantConvos.count, id: \.self) {index in
|
|
||||||
let currConvo = self.vm.relevantConvos[index]
|
|
||||||
ZStack(alignment: .topTrailing) {
|
|
||||||
Circle()
|
|
||||||
.foregroundColor(self.vm.selectedConvoId == currConvo.id ? Color.green : NirvanaColor.dimTeal.opacity(0.2))
|
|
||||||
|
|
||||||
ZStack {
|
|
||||||
Color.clear
|
|
||||||
ProfilePicturesOverlappedView()
|
|
||||||
.shadow(radius: 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text("+2")
|
|
||||||
.padding(5)
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(Color.white)
|
|
||||||
.background(NirvanaColor.dimTeal)
|
|
||||||
.cornerRadius(100)
|
|
||||||
|
|
||||||
// ZStack(alignment: .bottom) {
|
|
||||||
// Color.clear
|
|
||||||
//
|
//
|
||||||
// Text("kev, sarth...")
|
// var body: some View {
|
||||||
// .foregroundColor(NirvanaColor.teal)
|
// ScrollView([.horizontal]) {
|
||||||
// .font(.caption)
|
// HStack {
|
||||||
|
// ForEach(0..<self.vm.relevantConvos.count, id: \.self) {index in
|
||||||
|
// let currConvo = self.vm.relevantConvos[index]
|
||||||
|
// ZStack(alignment: .topTrailing) {
|
||||||
|
// Circle()
|
||||||
|
// .foregroundColor(self.vm.selectedConvoId == currConvo.id ? Color.green : NirvanaColor.dimTeal.opacity(0.2))
|
||||||
|
//
|
||||||
|
// ZStack {
|
||||||
|
// Color.clear
|
||||||
|
// ProfilePicturesOverlappedView()
|
||||||
|
// .shadow(radius: 10)
|
||||||
// }
|
// }
|
||||||
}
|
//
|
||||||
.frame(width: 100, height: 100)
|
// Text("+2")
|
||||||
.scaleEffect(self.animate ? 1 : 0.85)
|
// .padding(5)
|
||||||
.animation(
|
// .font(.caption)
|
||||||
Animation.easeInOut(duration: self.vm.selectedConvoId == currConvo.id ? 2 : 4
|
// .foregroundColor(Color.white)
|
||||||
).repeatForever(autoreverses: true),
|
// .background(NirvanaColor.dimTeal)
|
||||||
value: self.animate
|
// .cornerRadius(100)
|
||||||
)
|
//
|
||||||
.onTapGesture {
|
//// ZStack(alignment: .bottom) {
|
||||||
// if not in a call already
|
//// Color.clear
|
||||||
if !self.vm.isInCall() {
|
////
|
||||||
self.vm.selectedConvoId = self.vm.relevantConvos[index].id!
|
//// Text("kev, sarth...")
|
||||||
self.vm.joinConvo()
|
//// .foregroundColor(NirvanaColor.teal)
|
||||||
|
//// .font(.caption)
|
||||||
return
|
//// }
|
||||||
}
|
// }
|
||||||
|
// .frame(width: 100, height: 100)
|
||||||
self.vm.leaveConvo()
|
// .scaleEffect(self.animate ? 1 : 0.85)
|
||||||
}
|
// .animation(
|
||||||
}
|
// Animation.easeInOut(duration: self.vm.selectedConvoId == currConvo.id ? 2 : 4
|
||||||
}
|
// ).repeatForever(autoreverses: true),
|
||||||
.onAppear {
|
// value: self.animate
|
||||||
self.animate = true
|
// )
|
||||||
}
|
// .onTapGesture {
|
||||||
}
|
// // if not in a call already
|
||||||
.padding()
|
// if !self.vm.isInCall() {
|
||||||
}
|
// self.vm.selectedConvoId = self.vm.relevantConvos[index].id!
|
||||||
}
|
// self.vm.joinConvo(convoId: currConvo.id!)
|
||||||
|
//
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// self.vm.leaveConvo()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .onAppear {
|
||||||
|
// self.animate = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .padding()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
//struct ConvosView_Previews: PreviewProvider {
|
//struct ConvosView_Previews: PreviewProvider {
|
||||||
// static var previews: some View {
|
// static var previews: some View {
|
||||||
|
|||||||
@@ -7,31 +7,33 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
/**
|
||||||
|
@return a view that overlaps the user avatars
|
||||||
|
*/
|
||||||
struct ProfilePicturesOverlappedView: View {
|
struct ProfilePicturesOverlappedView: View {
|
||||||
let numberAttendees:Int = 5
|
|
||||||
let offset:Int = 15
|
let offset:Int = 15
|
||||||
|
|
||||||
var indvAvatarWidth: CGFloat = 50
|
var indvAvatarWidth: CGFloat = 50
|
||||||
|
|
||||||
|
var users: [User]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
ForEach(0..<numberAttendees, id: \.self) {attendeeIndex in
|
ForEach(0..<users.count, id: \.self) {userIndex in
|
||||||
if attendeeIndex < 3 {
|
Image(users[userIndex].avatar ?? Avatars.avatarSystemNames[1])
|
||||||
Image(Avatars.avatarSystemNames[attendeeIndex+1])
|
.resizable()
|
||||||
.resizable()
|
.scaledToFit()
|
||||||
.scaledToFit()
|
.background(NirvanaColor.dimTeal)
|
||||||
.background(NirvanaColor.dimTeal)
|
.clipShape(Circle())
|
||||||
.clipShape(Circle())
|
.offset(x: CGFloat((-15 * userIndex)) + 15)
|
||||||
.offset(x: CGFloat((-15 * attendeeIndex)) + 15)
|
.frame(width: indvAvatarWidth)
|
||||||
.frame(width: indvAvatarWidth)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProfilePicturesOverlappedView_Previews: PreviewProvider {
|
//struct ProfilePicturesOverlappedView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
// static var previews: some View {
|
||||||
ProfilePicturesOverlappedView()
|
// ProfilePicturesOverlappedView()
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ struct CircleGridView: View {
|
|||||||
// magic variables for grid
|
// magic variables for grid
|
||||||
// TODO: make the top left person or one horizontal person be in the center of screen...makes the top honeycomb pop
|
// TODO: make the top left person or one horizontal person be in the center of screen...makes the top honeycomb pop
|
||||||
@State var numberOfItems: Int = 12
|
@State var numberOfItems: Int = 12
|
||||||
private static let size: CGFloat = UIScreen.main.bounds.height*0.15 // scaling with screen size
|
private static let size: CGFloat = 130 // TODO: scaling with screen size? nahhh no need
|
||||||
private static let spacingBetweenColumns: CGFloat = 0
|
private static let spacingBetweenColumns: CGFloat = 0
|
||||||
private static let spacingBetweenRows: CGFloat = 0
|
private static let spacingBetweenRows: CGFloat = 0
|
||||||
private static let totalColumns: Int = 3 // scaling the circles and calculating column count
|
private static let totalColumns: Int = 3 // scaling the circles and calculating column count
|
||||||
@@ -54,6 +54,8 @@ struct CircleGridView: View {
|
|||||||
|
|
||||||
@State var animateLiveConvos = false
|
@State var animateLiveConvos = false
|
||||||
|
|
||||||
|
let maxNumAvatarsToShowInConvo = 3
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
// main communication hub
|
// main communication hub
|
||||||
// TODO: client side, sort the honeycomb from top left to bottom right
|
// TODO: client side, sort the honeycomb from top left to bottom right
|
||||||
@@ -74,6 +76,10 @@ struct CircleGridView: View {
|
|||||||
// live convos
|
// live convos
|
||||||
ForEach(0..<convos.count, id: \.self) {value in
|
ForEach(0..<convos.count, id: \.self) {value in
|
||||||
let currConvo = self.convoVM.relevantConvos[value]
|
let currConvo = self.convoVM.relevantConvos[value]
|
||||||
|
let filteredUsers = self.authSessionStore.relevantUsersDict.filter {
|
||||||
|
return currConvo.users.contains($0.key)
|
||||||
|
}
|
||||||
|
let convoUsers: [User] = Array(filteredUsers.values)
|
||||||
|
|
||||||
GeometryReader {gridProxy in
|
GeometryReader {gridProxy in
|
||||||
let scale = getScale(proxy: gridProxy, itemNumber: value, userId: nil, convoId: currConvo.id)
|
let scale = getScale(proxy: gridProxy, itemNumber: value, userId: nil, convoId: currConvo.id)
|
||||||
@@ -81,29 +87,26 @@ struct CircleGridView: View {
|
|||||||
ZStack(alignment: .topTrailing) {
|
ZStack(alignment: .topTrailing) {
|
||||||
Circle()
|
Circle()
|
||||||
.foregroundColor(self.getBubbleTint(convoId: currConvo.id))
|
.foregroundColor(self.getBubbleTint(convoId: currConvo.id))
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.clear
|
Color.clear
|
||||||
ProfilePicturesOverlappedView()
|
ProfilePicturesOverlappedView(users: Array(convoUsers.prefix(maxNumAvatarsToShowInConvo)))
|
||||||
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 20)
|
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("+2")
|
// top right number or people in convo
|
||||||
|
Circle()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.foregroundColor(Color.green)
|
||||||
|
.font(.title2)
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.font(.caption)
|
.overlay(
|
||||||
.foregroundColor(Color.white)
|
Text("\(convoUsers.count)")
|
||||||
.background(NirvanaColor.dimTeal)
|
.foregroundColor(Color.white)
|
||||||
.cornerRadius(100)
|
.font(.caption2)
|
||||||
|
)
|
||||||
// ZStack(alignment: .bottom) {
|
|
||||||
// Color.clear
|
|
||||||
//
|
|
||||||
// Text("kev, sarth...")
|
|
||||||
// .foregroundColor(NirvanaColor.teal)
|
|
||||||
// .font(.caption)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
.scaleEffect(self.animateLiveConvos ? scale + 0.2 : scale)
|
.scaleEffect(scale)
|
||||||
.padding(scale * 5)
|
.padding(scale * 5)
|
||||||
} // geometry reader
|
} // geometry reader
|
||||||
.offset(
|
.offset(
|
||||||
@@ -115,21 +118,26 @@ struct CircleGridView: View {
|
|||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
// if not in a call already
|
// if not in a call already
|
||||||
if !self.convoVM.isInCall() {
|
if !self.convoVM.isInCall() {
|
||||||
self.convoVM.selectedConvoId = self.convoVM.relevantConvos[value].id!
|
print("joining convo now")
|
||||||
self.convoVM.joinConvo()
|
|
||||||
|
|
||||||
// if had selected an individual, don't want them in here anymore
|
if let convoId = currConvo.id {
|
||||||
self.selectedFriendIndex = nil
|
self.convoVM.selectedConvoId = convoId
|
||||||
|
self.convoVM.joinConvo(convoId: convoId)
|
||||||
|
|
||||||
|
// if had selected an individual, don't want them selected here anymore
|
||||||
|
self.selectedFriendIndex = nil
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print("no convo id to join")
|
||||||
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animation(
|
// .animation(
|
||||||
Animation.easeInOut(duration: self.convoVM.selectedConvoId == currConvo.id ? 2 : 4
|
// Animation.easeInOut(duration: 4).repeatForever(autoreverses: true),
|
||||||
).repeatForever(autoreverses: true),
|
// value: self.animateLiveConvos
|
||||||
value: self.animateLiveConvos
|
// )
|
||||||
)
|
.animation(Animation.spring())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// active friends
|
// active friends
|
||||||
@@ -141,25 +149,16 @@ struct CircleGridView: View {
|
|||||||
let scale = getScale(proxy: gridProxy, itemNumber: adjustedValue, userId: friendId)
|
let scale = getScale(proxy: gridProxy, itemNumber: adjustedValue, userId: friendId)
|
||||||
|
|
||||||
ZStack(alignment: .topTrailing) {
|
ZStack(alignment: .topTrailing) {
|
||||||
|
// bubble color
|
||||||
Circle()
|
Circle()
|
||||||
.foregroundColor(self.getBubbleTint(friendDbId: friendId)) // different color for a selected user
|
.foregroundColor(self.getBubbleTint(friendDbId: friendId))
|
||||||
.blur(radius: 8)
|
.blur(radius: 8)
|
||||||
.cornerRadius(100)
|
.cornerRadius(100)
|
||||||
|
|
||||||
// this friend is online
|
// user status
|
||||||
if self.authSessionStore.relevantUsersDict[friendId]?.userStatus == .online {
|
UserStatusView(status: self.authSessionStore.relevantUsersDict[friendId]?.userStatus, size: 20, padding: 5)
|
||||||
Circle()
|
|
||||||
.frame(width: 20, height: 20)
|
// inside avatar
|
||||||
.foregroundColor(Color.green)
|
|
||||||
.font(.title2)
|
|
||||||
.padding(5)
|
|
||||||
}
|
|
||||||
else if self.haveNewMessageFromFriend(friendDbId: friendId) { // check if the last message in the conversation between me and my friend was me talking or him
|
|
||||||
Image(systemName: "arrow.down.left.circle.fill")
|
|
||||||
.foregroundColor(Color.orange)
|
|
||||||
.font(.title2)
|
|
||||||
}
|
|
||||||
//
|
|
||||||
Image(self.authSessionStore.relevantUsersDict[friendId]?.avatar ?? Avatars.avatarSystemNames[0])
|
Image(self.authSessionStore.relevantUsersDict[friendId]?.avatar ?? Avatars.avatarSystemNames[0])
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
@@ -183,6 +182,12 @@ struct CircleGridView: View {
|
|||||||
.gesture(
|
.gesture(
|
||||||
LongPressGesture(minimumDuration: longPressMinDuration)
|
LongPressGesture(minimumDuration: longPressMinDuration)
|
||||||
.onEnded {_ in // on activation of long press
|
.onEnded {_ in // on activation of long press
|
||||||
|
// if in a call, I cannot record or select someone...strict rules on user if in a call
|
||||||
|
if self.convoVM.isInCall() {
|
||||||
|
print("can't select another friend because I am in a call...leave call first")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// stop any player still playing of a message
|
// stop any player still playing of a message
|
||||||
self.queuePlayer.removeAllItems()
|
self.queuePlayer.removeAllItems()
|
||||||
|
|
||||||
@@ -331,7 +336,7 @@ struct CircleGridView: View {
|
|||||||
scrollReaderValue.scrollTo(self.selectedFriendIndex)
|
scrollReaderValue.scrollTo(self.selectedFriendIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.animateLiveConvos = true
|
self.animateLiveConvos = true
|
||||||
}
|
}
|
||||||
} // scrollview reader
|
} // scrollview reader
|
||||||
}
|
}
|
||||||
@@ -457,16 +462,35 @@ extension CircleGridView {
|
|||||||
extension CircleGridView {
|
extension CircleGridView {
|
||||||
// listening to messages
|
// listening to messages
|
||||||
private func handleTap(gridItemIndex: Int, friendId: String) {
|
private func handleTap(gridItemIndex: Int, friendId: String) {
|
||||||
print("tap gesture activated")
|
print("tap gesture activated")
|
||||||
|
|
||||||
// if friend and I are online, start call immediately
|
// if friend and I are online, and I am not in a convo, start convo immediately with them
|
||||||
if self.authSessionStore.relevantUsersDict[friendId]?.userStatus == .online
|
if self.authSessionStore.relevantUsersDict[friendId]?.userStatus == .online
|
||||||
&& self.authSessionStore.user?.userStatus == .online {
|
&& self.authSessionStore.user?.userStatus == .online && !self.convoVM.isInCall() {
|
||||||
|
print("starting a direct convo...getting it going")
|
||||||
|
|
||||||
|
self.selectedFriendIndex = nil // deselect and clear up ui for the call
|
||||||
|
|
||||||
self.convoVM.startConvo(friendId: friendId)
|
self.convoVM.startConvo(friendId: friendId)
|
||||||
|
|
||||||
|
return
|
||||||
|
} // if I am in a convo and I am adding someone else online, then make them join my convo
|
||||||
|
else if self.convoVM.isInCall() && self.authSessionStore.relevantUsersDict[friendId]?.userStatus == .online {
|
||||||
|
print("chaining the convo with more people...forcing someone else in")
|
||||||
|
|
||||||
|
self.selectedFriendIndex = nil // deselect and clear up ui for the call
|
||||||
|
|
||||||
|
self.convoVM.addThirdPartyToConvo(friendId: friendId)
|
||||||
|
|
||||||
|
return
|
||||||
|
} // if I am in a convo, don't allow listening to a message or expanding details of a friend in my circle
|
||||||
|
else if self.convoVM.isInCall() {
|
||||||
|
// TODO: show toast
|
||||||
|
print("can't select this friend as you are in a convo")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// clearing the player to make room for this friend's convo or to deselect this user
|
// clearing the player to make room for this friend's convo or to deselect this user
|
||||||
self.queuePlayer.removeAllItems()
|
self.queuePlayer.removeAllItems()
|
||||||
|
|
||||||
|
|||||||
@@ -103,22 +103,7 @@ struct CircleNavigationView: View {
|
|||||||
.shadow(radius: 10)
|
.shadow(radius: 10)
|
||||||
.overlay(alignment: .topTrailing) {
|
.overlay(alignment: .topTrailing) {
|
||||||
// user status
|
// user status
|
||||||
switch self.authSessionStore.user?.userStatus {
|
UserStatusView(status: self.authSessionStore.user?.userStatus, size: 10)
|
||||||
case .online:
|
|
||||||
Circle()
|
|
||||||
.frame(width: 10, height: 10)
|
|
||||||
.foregroundColor(Color.green)
|
|
||||||
case .offline:
|
|
||||||
Circle()
|
|
||||||
.frame(width: 10, height: 10)
|
|
||||||
.foregroundColor(Color.red)
|
|
||||||
case .inConvo:
|
|
||||||
Circle()
|
|
||||||
.frame(width: 10, height: 10)
|
|
||||||
.foregroundColor(Color.orange)
|
|
||||||
default:
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// UserStatusView.swift
|
||||||
|
// nirvana-ios
|
||||||
|
//
|
||||||
|
// Created by Arjun Patel on 1/1/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct UserStatusView: View {
|
||||||
|
var status: UserStatus?
|
||||||
|
var size: CGFloat = 10
|
||||||
|
var padding: CGFloat = 0
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
// user status
|
||||||
|
switch self.status {
|
||||||
|
case .online:
|
||||||
|
Circle()
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.foregroundColor(Color.green)
|
||||||
|
.padding(padding)
|
||||||
|
case .offline:
|
||||||
|
Circle()
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.foregroundColor(Color.red)
|
||||||
|
.padding(padding)
|
||||||
|
case .inConvo:
|
||||||
|
Circle()
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.foregroundColor(Color.orange)
|
||||||
|
.padding(padding)
|
||||||
|
default:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//struct UserStatusView_Previews: PreviewProvider {
|
||||||
|
// static var previews: some View {
|
||||||
|
// UserStatusView()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
Reference in New Issue
Block a user