LobbyJoinFragment.kt (5878B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2021--2025 GNUnet e.V. 4 5 GNUnet is free software: you can redistribute it and/or modify it 6 under the terms of the GNU Affero General Public License as published 7 by the Free Software Foundation, either version 3 of the License, 8 or (at your option) any later version. 9 10 GNUnet is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 SPDX-License-Identifier: AGPL3.0-or-later 19 */ 20 /* 21 * @author t3sserakt 22 * @file GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/contact/LobbyJoinFragment.kt 23 */ 24 25 package org.gnunet.gnunetmessenger.ui.contact 26 27 import android.Manifest 28 import android.content.pm.PackageManager 29 import android.os.Bundle 30 import android.util.Log 31 import android.view.* 32 import android.widget.Button 33 import android.widget.EditText 34 import androidx.annotation.OptIn 35 import androidx.camera.core.* 36 import androidx.camera.lifecycle.ProcessCameraProvider 37 import androidx.camera.view.PreviewView 38 import androidx.core.app.ActivityCompat 39 import androidx.core.content.ContextCompat 40 import androidx.fragment.app.Fragment 41 import androidx.lifecycle.lifecycleScope 42 import androidx.navigation.fragment.findNavController 43 import org.gnunet.gnunetmessenger.R 44 import com.google.mlkit.vision.barcode.BarcodeScanning 45 import com.google.mlkit.vision.common.InputImage 46 import org.gnunet.gnunetmessenger.MainActivity 47 import java.util.concurrent.Executors 48 import kotlinx.coroutines.launch 49 50 class LobbyJoinFragment : Fragment() { 51 52 private lateinit var previewView: PreviewView 53 private lateinit var qrText: EditText 54 private lateinit var joinButton: Button 55 private lateinit var cancelButton: Button 56 57 private val cameraExecutor = Executors.newSingleThreadExecutor() 58 59 override fun onCreateView( 60 inflater: LayoutInflater, 61 container: ViewGroup?, 62 savedInstanceState: Bundle? 63 ): View { 64 val view = inflater.inflate(R.layout.fragment_join_lobby, container, false) 65 66 previewView = view.findViewById(R.id.camera_preview) 67 qrText = view.findViewById(R.id.qr_text) 68 joinButton = view.findViewById(R.id.join_button) 69 cancelButton = view.findViewById(R.id.cancel_button) 70 71 joinButton.setOnClickListener { 72 val lobbyId = qrText.text.toString() 73 val activity = activity as MainActivity 74 val gnunetChat = activity.getGnunetChatInstance() 75 val handle = activity.getChatHandle() 76 viewLifecycleOwner.lifecycleScope.launch { 77 try { 78 gnunetChat.lobbyJoin(handle, lobbyId) 79 findNavController().popBackStack() 80 } catch (t: Throwable) { 81 android.util.Log.e("LobbyJoinFragment", "lobbyJoin failed", t) 82 } 83 } 84 } 85 86 cancelButton.setOnClickListener { 87 findNavController().popBackStack() 88 } 89 90 if (allPermissionsGranted()) { 91 startCamera() 92 } else { 93 requestPermissions(arrayOf(Manifest.permission.CAMERA), 10) 94 } 95 96 return view 97 } 98 99 private fun startCamera() { 100 val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) 101 102 cameraProviderFuture.addListener({ 103 val cameraProvider = cameraProviderFuture.get() 104 val preview = Preview.Builder().build().also { 105 it.setSurfaceProvider(previewView.surfaceProvider) 106 } 107 108 val barcodeScanner = BarcodeScanning.getClient() 109 110 val analysis = ImageAnalysis.Builder() 111 .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) 112 .build() 113 114 analysis.setAnalyzer(cameraExecutor) { imageProxy -> 115 processImageProxy(barcodeScanner, imageProxy) 116 } 117 118 val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA 119 120 try { 121 cameraProvider.unbindAll() 122 cameraProvider.bindToLifecycle(this, cameraSelector, preview, analysis) 123 } catch (e: Exception) { 124 Log.e("LobbyJoinFragment", "Camera binding failed", e) 125 } 126 127 }, ContextCompat.getMainExecutor(requireContext())) 128 } 129 130 @OptIn(ExperimentalGetImage::class) 131 private fun processImageProxy(scanner: com.google.mlkit.vision.barcode.BarcodeScanner, imageProxy: ImageProxy) { 132 val mediaImage = imageProxy.image ?: run { 133 imageProxy.close() 134 return 135 } 136 137 val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) 138 scanner.process(inputImage) 139 .addOnSuccessListener { barcodes -> 140 for (barcode in barcodes) { 141 val rawValue = barcode.rawValue 142 if (!rawValue.isNullOrBlank()) { 143 requireActivity().runOnUiThread { 144 qrText.setText(rawValue) 145 } 146 break 147 } 148 } 149 } 150 .addOnFailureListener { 151 Log.e("LobbyJoinFragment", "Barcode scan failed", it) 152 } 153 .addOnCompleteListener { 154 imageProxy.close() 155 } 156 } 157 158 private fun allPermissionsGranted() = 159 ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED 160 161 override fun onDestroy() { 162 super.onDestroy() 163 cameraExecutor.shutdown() 164 } 165 }