Condividi tramite


Integrare chiamate e chat usando la libreria dell'interfaccia utente

Questo articolo illustra come integrare le funzionalità di chiamata e chat nell'app Android o iOS usando la libreria dell'interfaccia utente di Servizi di comunicazione di Azure.

Prerequisiti

Ottenere un esempio completo

È possibile ottenere un progetto di esempio completo da GitHub.

Animazione per mostrare l'esperienza in Android usando chiamate e chat nella stessa app.

Configurare il progetto

In Android Studio, creare un nuovo progetto:

  1. Scegliere Nuovo>progetto dal menu File.

  2. In Nuovo progetto, selezionare il modello di progetto Attività vuota.

    Screenshot che mostra la finestra di dialogo Nuovo progetto in Android Studio con l'opzione Attività vuota selezionata.

  3. Selezionare Avanti.

  4. In Attività vuota, assegnare al progetto il nome UILibraryQuickStart. Per il linguaggio selezionare Java o Kotlin. Per l'SDK minimo, scegliere API 26: Android 8.0 (Oreo) o versione successiva.

  5. Selezionare Fine.

    Screenshot che mostra le nuove opzioni del progetto e il pulsante Fine.

Installare i pacchetti

Per installare i pacchetti dell'applicazione necessari, completare le sezioni seguenti.

Aggiungere una dipendenza

Nel file UILibraryQuickStart/app/build.gradle a livello di app aggiungere la dipendenza seguente:

dependencies {
    ...
    implementation("com.azure.android:azure-communication-ui-calling:+")
    implementation("com.azure.android:azure-communication-ui-chat:+")
    ...
}

Aggiungere un'esclusione META-INF alla sezione UILibraryQuickStart/app/build.gradleandroid:

packaging {
    resources.excludes.add("META-INF/*")
}

Aggiungere archivi Maven

Per integrare la libreria sono necessari due repository Maven:

  • Repository mavenCentral

  • Repository di pacchetti di Azure

    repositories {
        ...
        mavenCentral()
        maven {
            url = URI("https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1")
        }
        ...
    }
    

Connettersi alla riunione di Teams con chiamate e chat

CallComposite Usare per connettersi alla chiamata. Dopo che un utente è stato ammesso alla chiamata, CallComposite notifica l'utente modificando lo stato su connected. L'utente può quindi essere connesso al thread di chat.

Quando l'utente seleziona il pulsante Chat , viene aggiunto un pulsante personalizzato a CallComposite. CallComposite è ridotta a icona e la chat viene visualizzata in Teams.

Aggiungere un pulsante e una visualizzazione contenitore di chat per Activity_main.xml

Nel file di layout app/src/main/res/layout/activity_main.xml, aggiungere il codice seguente per creare un pulsante per l'avvio del composito:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <LinearLayout
        android:id="@+id/buttonContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        >
        <Button
            android:id="@+id/startCallButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Start Call"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="4dp"
            />
    </LinearLayout>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/chatContainer"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@+id/buttonContainer"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        >
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

Scaricare un'icona chat

  1. Scaricare un'icona dal repository GitHub.
  2. Salvare l'icona in UILibraryQuickStart/app/src/main/res/drawable.
  3. Aprire l'icona e passare android:fillColor a @color/white.

Inizializzare il composito

Per inizializzare la chiamata composita, passare a MainActivity e aggiornare le impostazioni di connessione:

  • Sostituire TEAM_MEETING_LINK con il collegamento alla riunione di Teams.
  • Sostituire ACS_ENDPOINT con l'endpoint della risorsa Servizi di comunicazione di Azure.
  • Sostituire DISPLAY_NAME con il proprio nome.
  • Sostituire USER_ID con l'ID utente Servizi di comunicazione di Azure.
  • Sostituire USER_ACCESS_TOKEN con il token.

Ottenere un thread di chat riunione di Teams per un utente Servizi di comunicazione di Azure

È possibile recuperare i dettagli delle riunioni di Teams usando le API Graph, come descritto nella documentazione di Graph. L'SDK per chiamate Servizi di comunicazione di Azure accetta un collegamento completo alla riunione di Teams o un ID riunione. Vengono restituiti come parte della onlineMeeting risorsa, accessibile nella proprietà joinWebUrl .

Con le API Graph è anche possibile ottenere il threadID valore. La risposta ha un chatInfo oggetto che contiene il threadID valore .

package com.example.uilibraryquickstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.ViewGroup
import android.widget.Button
import androidx.constraintlayout.widget.ConstraintLayout
import com.azure.android.communication.common.CommunicationTokenCredential
import com.azure.android.communication.common.CommunicationTokenRefreshOptions
import com.azure.android.communication.common.CommunicationUserIdentifier
import com.azure.android.communication.ui.calling.CallComposite
import com.azure.android.communication.ui.calling.CallCompositeBuilder
import com.azure.android.communication.ui.calling.models.CallCompositeCallScreenHeaderViewData
import com.azure.android.communication.ui.calling.models.CallCompositeCallScreenOptions
import com.azure.android.communication.ui.calling.models.CallCompositeCallStateCode
import com.azure.android.communication.ui.calling.models.CallCompositeCustomButtonViewData
import com.azure.android.communication.ui.calling.models.CallCompositeLocalOptions
import com.azure.android.communication.ui.calling.models.CallCompositeMultitaskingOptions
import com.azure.android.communication.ui.calling.models.CallCompositeTeamsMeetingLinkLocator
import com.azure.android.communication.ui.chat.ChatAdapter
import com.azure.android.communication.ui.chat.ChatAdapterBuilder
import com.azure.android.communication.ui.chat.presentation.ChatThreadView
import java.util.UUID

class MainActivity : AppCompatActivity() {
    companion object {
        private var callComposite: CallComposite? = null
        private var chatAdapter: ChatAdapter? = null
    }

    private val displayName = "USER_NAME"
    private val endpoint = "ACS_ENDPOINT"
    private val teamsMeetingLink = "TEAM_MEETING_LINK"
    private val threadId = "CHAT_THREAD_ID"
    private val communicationUserId = "USER_ID"
    private val userToken = "USER_ACCESS_TOKEN"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.startCallButton).setOnClickListener {
            startCallComposite()
        }
    }

    private fun startCallComposite() {
        val communicationTokenRefreshOptions = CommunicationTokenRefreshOptions({ userToken }, true)
        val communicationTokenCredential = CommunicationTokenCredential(communicationTokenRefreshOptions)
        val locator = CallCompositeTeamsMeetingLinkLocator(teamsMeetingLink)

        val localOptions = CallCompositeLocalOptions()
            .setCallScreenOptions(
                CallCompositeCallScreenOptions().setHeaderViewData(
                    CallCompositeCallScreenHeaderViewData().setCustomButtons(
                        listOf(
                            CallCompositeCustomButtonViewData(
                                UUID.randomUUID().toString(),
                                R.drawable.ic_fluent_chat_24_regular,
                                "Open Chat",
                            ) {
                                callComposite?.sendToBackground()
                                showChatUI()
                            }
                        )
                    )
                ))
        val callComposite = CallCompositeBuilder()
            .applicationContext(this.applicationContext)
            .credential(communicationTokenCredential)
            .displayName(displayName)
            .multitasking(CallCompositeMultitaskingOptions(true, true))
            .build()

        callComposite.addOnCallStateChangedEventHandler { callState ->
            // When a user is admitted to the Teams meeting, the call state becomes connected.
            // Only users admitted to the meeting can connect to the meeting's chat thread.
            if (callState.code == CallCompositeCallStateCode.CONNECTED) {
                connectChat()
            }
        }

        callComposite.launch(this, locator, localOptions)
        MainActivity.callComposite = callComposite
    }

    private fun connectChat() {
        if (chatAdapter != null)
            return

        val communicationTokenRefreshOptions =
            CommunicationTokenRefreshOptions( { userToken }, true)
        val communicationTokenCredential =
            CommunicationTokenCredential(communicationTokenRefreshOptions)

        val chatAdapter = ChatAdapterBuilder()
            .endpoint(endpoint)
            .credential(communicationTokenCredential)
            .identity(CommunicationUserIdentifier(communicationUserId))
            .displayName(displayName)
            .threadId(threadId)
            .build()
        chatAdapter.connect(applicationContext)
        MainActivity.chatAdapter = chatAdapter
    }

    private fun showChatUI() {
        chatAdapter?.let {
            // Create Chat Composite View
            val chatView = ChatThreadView(this, chatAdapter)
            val chatContainer = findViewById<ConstraintLayout>(R.id.chatContainer)
            chatContainer.removeAllViews()
            chatContainer.addView(
                chatView,
                ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
            )
        }
    }
}

Prerequisiti

Ottenere un esempio completo

È possibile ottenere un progetto di esempio completo da GitHub.

Animazione per mostrare l'esperienza in iOS usando chiamate e chat nella stessa app.

Configurare il progetto

Completare le sezioni seguenti per configurare il progetto della guida introduttiva.

Creare un nuovo progetto Xcode

In Xcode, creare un nuovo progetto:

  1. Nel menu File selezionare Nuovo>Progetto.

  2. In Scegli un modello per il nuovo progetto, selezionare la piattaforma iOS e scegliere il modello applicazione App. La guida introduttiva utilizza gli storyboard UIKit. La guida introduttiva non crea test, quindi è possibile deselezionare la casella di controllo Includi test.

    Screenshot che mostra la finestra di dialogo Xcode Nuovo progetto con iOS e il modello App selezionato.

  3. In Scegli le opzioni per il nuovo progetto, immettere UILibraryQuickStart per il nome del prodotto. Per l'interfaccia, selezionare Storyboard.

    Screenshot che mostra l'impostazione di nuove opzioni di progetto in Xcode.

Installare il pacchetto e le relative dipendenze

  1. (Facoltativo) Per MacBook con M1, installare e abilitare Rosetta in Xcode.

  2. Nella directory radice del progetto, eseguire pod init per creare un Podfile. Se si verifica un errore, aggiornare CocoaPods alla versione corrente.

  3. Aggiungere il codice seguente al Podfile. Sostituire UILibraryQuickStart con il nome del progetto.

    platform :ios, '15.0'
    
    target 'UILibraryQuickStart' do
        use_frameworks!
          pod 'AzureCommunicationUICalling', '1.12.0-beta.1'
          pod 'AzureCommunicationUIChat', '1.0.0-beta.4'
    end
    
  4. Eseguire pod install --repo-update.

  5. In Xcode aprire il file generated.xcworkspace .

Richiedere l'accesso all'hardware del dispositivo

Per accedere all'hardware del dispositivo, incluso il microfono e la webcam, aggiornare l'elenco delle proprietà delle informazioni dell'app. Impostare il valore associato su una stringa inclusa nella finestra di dialogo usata dal sistema per richiedere l'accesso dall'utente.

  1. Fare clic con il pulsante destro del mouse sulla voce Info.plist dell'albero del progetto e scegliere Open As>Source Code (Apri come > Codice sorgente). Aggiungere le righe seguenti alla sezione di primo livello <dict> e quindi salvare il file.

    <key>NSCameraUsageDescription</key>
    <string></string>
    <key>NSMicrophoneUsageDescription</key>
    <string></string>
    

    Ecco un esempio del codice sorgente Info.plist in un file Xcode:

    Screenshot che mostra il codice sorgente di esempio per l'elenco delle proprietà di informazioni in un file Xcode.

  2. Per verificare che le richieste di autorizzazione del dispositivo vengano aggiunte correttamente, selezionare Apri come>Elenco di proprietà. Verificare che l'elenco delle proprietà delle informazioni sia simile all'esempio seguente:

    Screenshot che mostra la privacy del dispositivo webcam e del microfono in Xcode.

Disattivare Bitcode

Nel progetto Xcode, in Impostazioni di compilazione, impostare l'opzione Abilita Bitcode su No. Per individuare l'impostazione, modificare il filtro da Base a Tutto o usare la barra di ricerca.

Screenshot che mostra l'opzione Impostazioni di compilazione per disattivare Bitcode.

Scaricare un'icona chat

  1. Scaricare un'icona dal repository GitHub.
  2. Aprire il file scaricato e passare fill a fill="#FFFFFF".
  3. In Xcode passare ad Asset. Creare un nuovo set di immagini e denominarlo ic_fluent_chat_24_regular. Selezionare il file scaricato come icona universale.

Inizializzare il composito

Per inizializzare il composito, passare a ViewController e aggiornare le impostazioni di connessione:

  • Sostituire TEAM_MEETING_LINK con il collegamento alla riunione di Teams.
  • Sostituire ACS_ENDPOINT con l'endpoint della risorsa Servizi di comunicazione di Azure.
  • Sostituire DISPLAY_NAME con il proprio nome.
  • Sostituire USER_ID con l'ID utente Servizi di comunicazione di Azure.
  • Sostituire USER_ACCESS_TOKEN con il token.

Ottenere un thread di chat riunione di Teams per un utente Servizi di comunicazione di Azure

È possibile recuperare i dettagli delle riunioni di Teams usando le API Graph, come descritto nella documentazione di Graph. L'SDK per chiamate Servizi di comunicazione di Azure accetta un collegamento completo alla riunione di Teams o un ID riunione. Vengono restituiti come parte della onlineMeeting risorsa, accessibile nella proprietà joinWebUrl .

Con le API Graph è anche possibile ottenere il threadID valore. La risposta ha un chatInfo oggetto che contiene il threadID valore .

import UIKit
import AzureCommunicationCalling
import AzureCommunicationUICalling
import AzureCommunicationUIChat
    
class ViewController: UIViewController {
    private let displayName = "USER_NAME"
    private let endpoint = "ACS_ENDPOINT"
    private let teamsMeetingLink = "TEAM_MEETING_LINK"
    private let chatThreadId = "CHAT_THREAD_ID"
    private let communicationUserId = "USER_ID"
    private let userToken = "USER_ACCESS_TOKEN"
    
    
    private var callComposite: CallComposite?
    private var chatAdapter: ChatAdapter?
    private var chatCompositeViewController: ChatCompositeViewController?
    
    private var startCallButton: UIButton?
    private var chatContainerView: UIView?

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        initControlBar()
    }

    @objc private func startCallComposite() {
        let callCompositeOptions = CallCompositeOptions(
            enableMultitasking: true,
            enableSystemPictureInPictureWhenMultitasking: true,
            displayName: displayName)
        
        let communicationTokenCredential = try! CommunicationTokenCredential(token: userToken)

        let callComposite = self.callComposite ?? CallComposite(credential: communicationTokenCredential, withOptions: callCompositeOptions)
        self.callComposite = callComposite
        
        callComposite.events.onCallStateChanged = { [weak self] callState in
            if callState.requestString == CallState.connected.requestString {
                self?.connectChat()
            }
        }
        
        let chatCustomButton = CustomButtonViewData(
            id: UUID().uuidString,
            image: UIImage(named: "ic_fluent_chat_24_regular")!,
            title: "Chat") { [weak self] _ in
                self?.callComposite?.isHidden = true
                self?.showChat()
            }
        let callScreenHeaderViewData = CallScreenHeaderViewData(customButtons: [chatCustomButton])
        let localOptions = LocalOptions(callScreenOptions: CallScreenOptions(headerViewData: callScreenHeaderViewData))
        callComposite.launch(locator: .teamsMeeting(teamsLink: teamsMeetingLink), localOptions: localOptions)
    }
    
    @objc private func connectChat() {
        let communicationIdentifier = CommunicationUserIdentifier(communicationUserId)
        guard let communicationTokenCredential = try? CommunicationTokenCredential(
            token: userToken) else {
            return
        }

        self.chatAdapter = ChatAdapter(
            endpoint: endpoint,
            identifier: communicationIdentifier,
            credential: communicationTokenCredential,
            threadId: chatThreadId,
            displayName: displayName)

        Task { @MainActor in
            guard let chatAdapter = self.chatAdapter else {
                return
            }
            try await chatAdapter.connect()
        }
    }
        
    @objc private func showChat() {
        guard let chatAdapter = self.chatAdapter,
              let chatContainerView = self.chatContainerView,
              self.chatCompositeViewController == nil else {
            return
        }
    
        let chatCompositeViewController = ChatCompositeViewController(with: chatAdapter)
        
        self.addChild(chatCompositeViewController)
        chatContainerView.addSubview(chatCompositeViewController.view)
        
        chatCompositeViewController.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            chatCompositeViewController.view.topAnchor.constraint(equalTo: chatContainerView.topAnchor),
            chatCompositeViewController.view.bottomAnchor.constraint(equalTo: chatContainerView.bottomAnchor),
            chatCompositeViewController.view.leadingAnchor.constraint(equalTo: chatContainerView.leadingAnchor),
            chatCompositeViewController.view.trailingAnchor.constraint(equalTo: chatContainerView.trailingAnchor)
        ])
        
        chatCompositeViewController.didMove(toParent: self)
        self.chatCompositeViewController = chatCompositeViewController
    }
        
    private func initControlBar() {
        let startCallButton = UIButton()
        self.startCallButton = startCallButton
        startCallButton.layer.cornerRadius = 10
        startCallButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 16, bottom: 6, right: 16)
        startCallButton.backgroundColor = .systemBlue
        startCallButton.setTitle("Call", for: .normal)
        startCallButton.addTarget(self, action: #selector(startCallComposite), for: .touchUpInside)
        startCallButton.translatesAutoresizingMaskIntoConstraints = false
                        
        let margin: CGFloat = 32.0
        
        let buttonsContainerView = UIView()
        buttonsContainerView.backgroundColor = .clear
        
        let buttonsStackView = UIStackView(arrangedSubviews: [startCallButton])
        buttonsStackView.axis = .horizontal
        buttonsStackView.alignment = .center
        buttonsStackView.distribution = .equalSpacing
        buttonsStackView.spacing = 10
        buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
        buttonsStackView.heightAnchor.constraint(equalToConstant: 50).isActive = true
        
        buttonsContainerView.addSubview(buttonsStackView)

        buttonsContainerView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            buttonsStackView.topAnchor.constraint(equalTo: buttonsContainerView.topAnchor, constant: 8),
            buttonsStackView.bottomAnchor.constraint(equalTo: buttonsContainerView.bottomAnchor, constant: -8),
            buttonsStackView.leadingAnchor.constraint(equalTo: buttonsContainerView.leadingAnchor, constant: 16),
        ])
        
        let chatContainerView = UIView()
        self.chatContainerView = chatContainerView
        
        let verticalStackView = UIStackView(arrangedSubviews: [
            buttonsContainerView,
            chatContainerView
            ])
        verticalStackView.axis = .vertical
        verticalStackView.alignment = .fill
        verticalStackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(verticalStackView)
        
        let margins = view.safeAreaLayoutGuide
        let constraints = [
            verticalStackView.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
            verticalStackView.trailingAnchor.constraint(equalTo: margins.trailingAnchor),
            verticalStackView.topAnchor.constraint(equalTo: margins.topAnchor, constant: margin),
            verticalStackView.bottomAnchor.constraint(equalTo: margins.bottomAnchor, constant: -margin)
        ]
        NSLayoutConstraint.activate(constraints)
    }
}

Eseguire il codice

Eseguire il codice per compilare ed eseguire l'app nel dispositivo.

Altre funzionalità

L'elenco dei casi d'uso contiene informazioni dettagliate su altre funzionalità.

Aggiungere notifiche all'app per dispositivi mobili

Servizi di comunicazione di Azure si integra con Griglia di eventi di Azure e Hub di notifica di Azure, in modo da poter aggiungere notifiche push alle app in Azure. È possibile usare le notifiche push per inviare informazioni dall'applicazione ai dispositivi mobili degli utenti. Una notifica push può mostrare una finestra di dialogo, riprodurre un suono o visualizzare un'interfaccia utente di chiamata in ingresso.