# Mobile (IOS Webview)

Pertama, pilih file HTML **index-debug-standard.html** dan ganti namanya menjadi **index-mobile.html (**&#x49;ni opsional atau Anda dapat membiarkannya saja seperti itu).

Kemudian, buka file HTML dan salin kode di bawah ini ke index situs web Anda atau masukkan file HTML. Tempel kode di dalam tag **\<head>** Anda.

```
<html>
<head>
  <meta charset="UTF-8">
  <title>Enterprise Web Chat</title>
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1,user-scalable=no" />
  <link rel='stylesheet prefetch' href='https://fonts.googleapis.com/css?family=Open+Sans'>
  <link rel="stylesheet" href="css/indigo.css">
  <link rel="stylesheet" href="css/widget.css">
  <link rel="stylesheet" href="css/lightslider.css">
  <link rel="stylesheet" href="css/intlTelInput.css">
	<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1,user-scalable=no" />
	<style type="text/css">
		html {
			background: url(images/bg-login.svg) no-repeat;
			height: 100%;
			background-size: cover;
		}
	</style>
  <!-- <script src="https://maps.googleapis.com/maps/api/js?key=<KEY>&libraries=places"></script> -->
  <script src="https://www.google.com/recaptcha/api.js"></script>
  <!-- <script src="http://code.responsivevoice.org/responsivevoice.js"></script> -->
  <script src='js/jquery-2.1.3.min.js'></script>
  <script src='js/iframeResizer.contentWindow.js'></script>
  <script src='js/iframeResizer.js'></script>
  <script src='js/lightslider.js'></script>  
  <script src='js/lazyload.min.js'></script>  
  <script src="js/cryptojs/pbkdf2.js"></script>
  <script src="js/cryptojs/aes.js"></script>
  <script src="js/cipher/aes-util.js"></script>
  <script src="js/stp.js"></script>
  <script src="js/sjs.js"></script>
  <script src="js/chat.js"></script>
  <script src="js/fuse.js"></script>
  <script src="js/speech.js"></script>
  <script src="js/intlTelInput.js"></script>
  <script>
  $(document).ready(function() {
	    Chat.init({
	        header: 'Welcome to Our Chat',
	        login_sub_header: 'Please tell us about yourself',
	        connect_message: 'Do you have questions ? <br>Come chat with us, we are here to help you',
	        chat_sub_header: 'Our agent will serve you shortly',
			url: 'https://<host>:<port>',
			client_id: '<client_id>',
			client_secret: '<client_secret>',	
	        type_placeholder: 'Type message...',
			avatar: '<bot_image>',
			icon_avatar: '<icon_image>',
			agent_avatar: '<agent_image>',
	        enable_attachment: true,
	        enable_voice: true,
	        enable_speech: true,
	        enable_queue: true,
	        enable_history: true,
	        compatibility_mode: false,
	        queue_text: "⏰NOMOR URUT: ",
	        enable_campaign: false,
	        campaign_avatar: '<campaign_image>',
			campaign_title: '<campaign_title>',
	        campaign_text: 'Hello 👋, What do you think about our service ?',
	        campaign_timer: 5000,
	        campaign_menu: [{
	            "label": "<Campaign Label>",
	            "value": "<Campaign Payload>",
	            "icon": "<campaign_icon>"
	        }, {
	            "label": "<Campaign Label>",
	            "value": "<Campaign Payload>",
	            "icon": "<campaign_icon>"
	        }, {
	            "label": "<Campaign Label>",
	            "value": "<Campaign Payload>",
	            "icon": "<campaign_icon>"
	        }],
	        max_upload_message: "File size limit exceeded. Maximum filesize is [max_filesize]",
	        selection_topic_placeholder: "Please select a topic",
	        selection_topic_member: ["Product", "Service"],
	        selection_topic_non_member: ["Hai", "Hello"],
			member_mode: false,
	        selection_topic_placeholder: "Please select a topic",
	        enable_service_hour: false,
	        service_hour: [{
	                "days": "sunday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            },
	            {
	                "days": "monday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            },
	            {
	                "days": "tuesday",
	                "startHour": "00:00",
	                "endHour": "15:52",
	                "holiday": true
	            },
	            {
	                "days": "wednesday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            },
	            {
	                "days": "thursday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            },
	            {
	                "days": "friday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            },
	            {
	                "days": "saturday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            }
	        ],
	        channel_id_email: "25b7fe4f7bd81aa0533be4963972ac74", /* channel id email */
	        subject_email: "Of service hour ticket",
	        send_email_success_message: "Email sent successfully !<br/><br/>Thanks, we have received your question or your complaint",
	        process_send_email_message: "Sending Email...",
	        process_send_email_error_message: "Email sent failed !",
			is_waiting_text_icon: true
	    });
	});
  </script>
</head>
<body>  
</body>
</html>
```

Hapus semua file HTML tetapi pertahankan **index-mobile.html** sebagai gantinya.

![Remove HTML File](/files/-MVowMUSM7C6yHkQRSh-)

Salin seluruh direktori live chat yang tersisa ke path yang Anda tentukan dari server hosting web Anda.&#x20;

Asumsikan yang ingin Anda pilih adalah tema indigo, lalu buka **index-mobile.html** dan ubah kode menggunakan warna yang Anda inginkan.

```
<link rel="stylesheet" href="css/indigo.css">
  <link rel="stylesheet" href="css/widget.css">
  <link rel="stylesheet" href="css/lightslider.css">
  <link rel="stylesheet" href="css/intlTelInput.css">
```

Jangan lupa untuk menyesuaikan resource path yang ada di **index-mobile.html** yang digunakan untuk memuat file seperti js, CSS, atau gambar, sesuai dengan tempat resource disimpan di server Anda.

```
<html>
<head>
  <meta charset="UTF-8">
  <title>Enterprise Web Chat</title>
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1,user-scalable=no" />
  <link rel='stylesheet prefetch' href='https://fonts.googleapis.com/css?family=Open+Sans'>
  <link rel="stylesheet" href="css/indigo.css">
  <link rel="stylesheet" href="css/widget.css">
  <link rel="stylesheet" href="css/lightslider.css">
  <link rel="stylesheet" href="css/intlTelInput.css">
	<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1,user-scalable=no" />
	<style type="text/css">
		html {
			background: url(images/bg-login.svg) no-repeat;
			height: 100%;
			background-size: cover;
		}
	</style>
  <!-- <script src="https://maps.googleapis.com/maps/api/js?key=<KEY>&libraries=places"></script> -->
  <script src="https://www.google.com/recaptcha/api.js"></script>
  <!-- <script src="http://code.responsivevoice.org/responsivevoice.js"></script> -->
  <script src='js/jquery-2.1.3.min.js'></script>
  <script src='js/iframeResizer.contentWindow.js'></script>
  <script src='js/iframeResizer.js'></script>
  <script src='js/lightslider.js'></script>  
  <script src='js/lazyload.min.js'></script>  
  <script src="js/cryptojs/pbkdf2.js"></script>
  <script src="js/cryptojs/aes.js"></script>
  <script src="js/cipher/aes-util.js"></script>
  <script src="js/stp.js"></script>
  <script src="js/sjs.js"></script>
  <script src="js/chat.js"></script>
  <script src="js/fuse.js"></script>
  <script src="js/speech.js"></script>
  <script src="js/intlTelInput.js"></script>
  <script>
  $(document).ready(function() {
	    Chat.init({
	        header: 'Welcome to Our Chat',
	        login_sub_header: 'Please tell us about yourself',
	        connect_message: 'Do you have questions ? <br>Come chat with us, we are here to help you',
	        chat_sub_header: 'Our agent will serve you shortly',
			url: 'https://<host>:<port>',
			client_id: '<client_id>',
			client_secret: '<client_secret>',	
	        type_placeholder: 'Type message...',
			avatar: '<bot_image>',
			icon_avatar: '<icon_image>',
			agent_avatar: '<agent_image>',
	        enable_attachment: true,
	        enable_voice: true,
	        enable_speech: true,
	        enable_queue: true,
	        enable_history: true,
	        compatibility_mode: false,
	        queue_text: "⏰NOMOR URUT: ",
	        enable_campaign: false,
	        campaign_avatar: '<campaign_image>',
			campaign_title: '<campaign_title>',
	        campaign_text: 'Hello 👋, What do you think about our service ?',
	        campaign_timer: 5000,
	        campaign_menu: [{
	            "label": "<Campaign Label>",
	            "value": "<Campaign Payload>",
	            "icon": "<campaign_icon>"
	        }, {
	            "label": "<Campaign Label>",
	            "value": "<Campaign Payload>",
	            "icon": "<campaign_icon>"
	        }, {
	            "label": "<Campaign Label>",
	            "value": "<Campaign Payload>",
	            "icon": "<campaign_icon>"
	        }],
	        max_upload_message: "File size limit exceeded. Maximum filesize is [max_filesize]",
	        selection_topic_placeholder: "Please select a topic",
	        selection_topic_member: ["Product", "Service"],
	        selection_topic_non_member: ["Hai", "Hello"],
			member_mode: false,
	        selection_topic_placeholder: "Please select a topic",
	        enable_service_hour: false,
	        service_hour: [{
	                "days": "sunday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            },
	            {
	                "days": "monday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            },
	            {
	                "days": "tuesday",
	                "startHour": "00:00",
	                "endHour": "15:52",
	                "holiday": true
	            },
	            {
	                "days": "wednesday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            },
	            {
	                "days": "thursday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            },
	            {
	                "days": "friday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            },
	            {
	                "days": "saturday",
	                "startHour": "00:00",
	                "endHour": "23:59",
	                "holiday": true
	            }
	        ],
	        channel_id_email: "25b7fe4f7bd81aa0533be4963972ac74", /* channel id email */
	        subject_email: "Of service hour ticket",
	        send_email_success_message: "Email sent successfully !<br/><br/>Thanks, we have received your question or your complaint",
	        process_send_email_message: "Sending Email...",
	        process_send_email_error_message: "Email sent failed !",
			is_waiting_text_icon: true
	    });
	});
  </script>
</head>
<body>  
</body>
</html>
```

Selanjutnya, sesuaikan url, client\_id dan properti client\_secret sesuai dengan konfigurasi channel back-end Anda. Silakan merujuk kembali ke bagian [How to Connect Client to Live Chat Channel](/5.3.0-id/integration/chanel-connector/live-chat/how-to-connect-client-to-live-chat-channel.md)  untuk informasi lebih lanjut.&#x20;

Di bagian sebelumnya ditujukkan untuk meletakkan source code pada situs web yang sudah tersedia, sedangkan di bagian ini kita harus membuat host live chat secara mandiri.

Di bawah ini merupakan sample code untuk mengonfigurasi tampilan web Anda yang menampilkan **index-mobile.html** yang sudah dihosting.

### Configure Web View&#x20;

Untuk mengaktifkan JavaScript dan mengonfigurasi tampilan web, Anda perlu mengatur properti yang diperlukan.

```
let webConfiguration = WKWebViewConfiguration()
webConfiguration.defaultWebpagePreferences.allowsContentJavaScript = true
webConfiguration.websiteDataStore = WKWebsiteDataStore.default()

let webView = WKWebView(frame: .zero, configuration: webConfiguration)
```

Sekarang Anda dapat memuat URL

```
webView.load(URLRequest(url: URL(string: "https://hostname/webchat/indexdebug-standard.html")!))
```

Untuk menyesuaikan setelah halaman selesai dimuat, Anda dapat menjalankan kode ini.

```
func webView(
    _ webView: WKWebView,
    didStartProvisionalNavigation navigation: WKNavigation!
) {
    DispatchQueue.main.async {
        self.parent.progress = 0.3
        self.parent.isLoading = true
    }
}

func webView(
    _ webView: WKWebView,
    didFinish navigation: WKNavigation!
) {
    DispatchQueue.main.async {
        self.parent.progress = 1.0
        self.parent.isLoading = false
    }
}
```

#### Enable Voice to Text (Speech Recognition)

Untuk mengaktifkan fitur ini, WebView perlu meminta izin untuk merekam audio

```
func webView(
    _ webView: WKWebView,
    requestMediaCapturePermissionFor origin: WKSecurityOrigin,
    initiatedByFrame frame: WKFrameInfo,
    type: WKMediaCaptureType,
    decisionHandler: @escaping WKPermissionDecision
) {
    if #available(iOS 17.0, *) {
        AVAudioApplication.requestRecordPermission { granted in
            DispatchQueue.main.async {
                decisionHandler(granted ? .grant : .deny)
            }
        }
    } else {
        AVAudioSession.sharedInstance().requestRecordPermission { granted in
            DispatchQueue.main.async {
                decisionHandler(granted ? .grant : .deny)
            }
        }
    }
}
```

#### Enable Attachment

Untuk mengaktifkan lampiran file, implementasikan pemilih gambar di dalam delegasi WebView.

```
func webView(
    _ webView: WKWebView,
    runJavaScriptConfirmPanelWithMessage message: String,
    initiatedByFrame frame: WKFrameInfo,
    completionHandler: @escaping (Bool) -> Void
) {
    let picker = UIImagePickerController()
    picker.delegate = self
    picker.sourceType = .photoLibrary
    
    if let windowScene = UIApplication.shared.connectedScenes
        .compactMap({ $0 as? UIWindowScene })
        .first,
       let rootViewController = windowScene.windows.first?.rootViewController {
        rootViewController.present(picker, animated: true)
    }
}
```

Setelah sebuah gambar dipilih, URL file yang dipilih harus diproses.

```
func imagePickerController(
    _ picker: UIImagePickerController,
    didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
    picker.dismiss(animated: true, completion: nil)
    
    if let imageURL = info[.imageURL] as? URL {
        filePathCallback?([imageURL])
    } else {
        filePathCallback?(nil)
    }
    
    filePathCallback = nil
}

func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
    picker.dismiss(animated: true, completion: nil)
    filePathCallback?(nil)
    filePathCallback = nil
}

```

Berikut adalah contoh kode akhir jika Anda telah mengikuti langkah-langkah di atas.

```
import SwiftUI
@preconcurrency import WebKit
import AVFoundation

struct WebView: UIViewRepresentable {
    @Binding var progress: Double
    @Binding var isLoading: Bool

    class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        var parent: WebView
        private let audioSession = AVAudioSession.sharedInstance()
        private var filePathCallback: (([URL]?) -> Void)?

        init(_ parent: WebView) {
            self.parent = parent
        }

        func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
            DispatchQueue.main.async {
                self.parent.progress = 0.3
                self.parent.isLoading = true
            }
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            DispatchQueue.main.async {
                self.parent.progress = 1.0
                self.parent.isLoading = false
            }
        }

        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            // Handle messages from JavaScript
        }

        func webView(
            _ webView: WKWebView,
            requestMediaCapturePermissionFor origin: WKSecurityOrigin,
            initiatedByFrame frame: WKFrameInfo,
            type: WKMediaCaptureType,
            decisionHandler: @escaping (WKPermissionDecision) -> Void
        ) {
            if #available(iOS 17.0, *) {
                AVAudioApplication.requestRecordPermission { granted in
                    DispatchQueue.main.async {
                        decisionHandler(granted ? .grant : .deny)
                    }
                }
            } else {
                audioSession.requestRecordPermission { granted in
                    DispatchQueue.main.async {
                        decisionHandler(granted ? .grant : .deny)
                    }
                }
            }
        }

        func webView(
            _ webView: WKWebView,
            runJavaScriptConfirmPanelWithMessage message: String,
            initiatedByFrame frame: WKFrameInfo,
            completionHandler: @escaping (Bool) -> Void
        ) {
            let picker = UIImagePickerController()
            picker.delegate = self
            picker.sourceType = .photoLibrary

            if let windowScene = UIApplication.shared.connectedScenes
                .compactMap({ $0 as? UIWindowScene })
                .first,
               let rootViewController = windowScene.windows.first?.rootViewController {
                rootViewController.present(picker, animated: true)
            }
        }

        func imagePickerController(
            _ picker: UIImagePickerController,
            didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
        ) {
            picker.dismiss(animated: true, completion: nil)

            if let imageURL = info[.imageURL] as? URL {
                filePathCallback?([imageURL])
            } else {
                filePathCallback?(nil)
            }
            filePathCallback = nil
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            picker.dismiss(animated: true, completion: nil)
            filePathCallback?(nil)
            filePathCallback = nil
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> WKWebView {
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.defaultWebpagePreferences.allowsContentJavaScript = true
        webConfiguration.websiteDataStore = WKWebsiteDataStore.default()

        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = context.coordinator
        webView.navigationDelegate = context.coordinator
        webView.load(URLRequest(url: URL(string: "Server URL")!))

        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {}
}

struct ContentView: View {
    @State private var progress: Double = 0.0
    @State private var isLoading: Bool = true

    var body: some View {
        ZStack {
            WebView(progress: $progress, isLoading: $isLoading)

            if isLoading {
                ProgressView(value: progress)
                    .progressViewStyle(LinearProgressViewStyle())
                    .frame(width: UIScreen.main.bounds.width * 0.4, height: 6)
                    .background(Color.gray.opacity(0.3))
                    .cornerRadius(3)
            }
        }
        .edgesIgnoringSafeArea(.all)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

```

Jika Anda ingin mencoba kode kami, silakan klik [ini](https://github.com/3dolphin/3dolphins-webview-ios) untuk memulai mencoba aplikasi kami.&#x20;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.3dolphins.ai/5.3.0-id/integration/chanel-connector/live-chat/memasukkan-live-chat-pada-website-mobile-webview-anda/mobile-webview-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
