diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt index a968775ff1..3dc43ec2b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/JoinCommunityFragment.kt @@ -37,6 +37,8 @@ class JoinCommunityFragment : Fragment() { lateinit var delegate: StartConversationDelegate + var lastUrl: String? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -66,45 +68,71 @@ class JoinCommunityFragment : Fragment() { } fun joinCommunityIfPossible(url: String) { - val openGroup = try { - OpenGroupUrlParser.parseUrl(url) - } catch (e: OpenGroupUrlParser.Error) { - when (e) { - is OpenGroupUrlParser.Error.MalformedURL, OpenGroupUrlParser.Error.NoRoom -> { - return Toast.makeText(activity, context?.resources?.getString(R.string.communityJoinError), Toast.LENGTH_SHORT).show() - } - is OpenGroupUrlParser.Error.InvalidPublicKey, OpenGroupUrlParser.Error.NoPublicKey -> { - return Toast.makeText(activity, R.string.communityEnterUrlErrorInvalidDescription, Toast.LENGTH_SHORT).show() + if(lastUrl == url) return + lastUrl = url + + lifecycleScope.launch(Dispatchers.Main) { + val openGroup = try { + OpenGroupUrlParser.parseUrl(url) + } catch (e: OpenGroupUrlParser.Error) { + when (e) { + is OpenGroupUrlParser.Error.MalformedURL, OpenGroupUrlParser.Error.NoRoom -> { + return@launch Toast.makeText( + activity, + context?.resources?.getString(R.string.communityJoinError), + Toast.LENGTH_SHORT + ).show() + } + + is OpenGroupUrlParser.Error.InvalidPublicKey, OpenGroupUrlParser.Error.NoPublicKey -> { + return@launch Toast.makeText( + activity, + R.string.communityEnterUrlErrorInvalidDescription, + Toast.LENGTH_SHORT + ).show() + } } } - } - showLoader() - - lifecycleScope.launch(Dispatchers.IO) { - try { - val sanitizedServer = openGroup.server.removeSuffix("/") - val openGroupID = "$sanitizedServer.${openGroup.room}" - OpenGroupManager.add(sanitizedServer, openGroup.room, openGroup.serverPublicKey, requireContext()) - val storage = MessagingModuleConfiguration.shared.storage - storage.onOpenGroupAdded(sanitizedServer, openGroup.room) - val threadID = GroupManager.getOpenGroupThreadID(openGroupID, requireContext()) - val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray()) - - ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(requireContext()) - withContext(Dispatchers.Main) { - val recipient = Recipient.from(requireContext(), Address.fromSerialized(groupID), false) - openConversationActivity(requireContext(), threadID, recipient) - delegate.onDialogClosePressed() - } - } catch (e: Exception) { - Log.e("Loki", "Couldn't join community.", e) - withContext(Dispatchers.Main) { - hideLoader() - val txt = Phrase.from(context, R.string.groupErrorJoin).put(GROUP_NAME_KEY, url).format().toString() - Toast.makeText(activity, txt, Toast.LENGTH_SHORT).show() + showLoader() + + withContext(Dispatchers.IO) { + try { + val sanitizedServer = openGroup.server.removeSuffix("/") + val openGroupID = "$sanitizedServer.${openGroup.room}" + OpenGroupManager.add( + sanitizedServer, + openGroup.room, + openGroup.serverPublicKey, + requireContext() + ) + val storage = MessagingModuleConfiguration.shared.storage + storage.onOpenGroupAdded(sanitizedServer, openGroup.room) + val threadID = + GroupManager.getOpenGroupThreadID(openGroupID, requireContext()) + val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray()) + + ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded( + requireContext() + ) + withContext(Dispatchers.Main) { + val recipient = Recipient.from( + requireContext(), + Address.fromSerialized(groupID), + false + ) + openConversationActivity(requireContext(), threadID, recipient) + delegate.onDialogClosePressed() + } + } catch (e: Exception) { + Log.e("Loki", "Couldn't join community.", e) + withContext(Dispatchers.Main) { + hideLoader() + val txt = Phrase.from(context, R.string.groupErrorJoin) + .put(GROUP_NAME_KEY, url).format().toString() + Toast.makeText(activity, txt, Toast.LENGTH_SHORT).show() + } } - return@launch } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/qr/ScanListener.java b/app/src/main/java/org/thoughtcrime/securesms/qr/ScanListener.java deleted file mode 100644 index 83faae9907..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/qr/ScanListener.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.thoughtcrime.securesms.qr; - -public interface ScanListener { - public void onQrDataFound(String data); -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/qr/ScanningThread.java b/app/src/main/java/org/thoughtcrime/securesms/qr/ScanningThread.java deleted file mode 100644 index 4e86941c5b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/qr/ScanningThread.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.thoughtcrime.securesms.qr; - -import android.content.res.Configuration; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.zxing.BinaryBitmap; -import com.google.zxing.ChecksumException; -import com.google.zxing.DecodeHintType; -import com.google.zxing.FormatException; -import com.google.zxing.NotFoundException; -import com.google.zxing.PlanarYUVLuminanceSource; -import com.google.zxing.Result; -import com.google.zxing.common.HybridBinarizer; -import com.google.zxing.qrcode.QRCodeReader; - -import org.thoughtcrime.securesms.components.camera.CameraView; -import org.thoughtcrime.securesms.components.camera.CameraView.PreviewFrame; -import org.session.libsignal.utilities.Log; -import org.session.libsession.utilities.Util; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -public class ScanningThread extends Thread implements CameraView.PreviewCallback { - - private static final String TAG = ScanningThread.class.getSimpleName(); - - private final QRCodeReader reader = new QRCodeReader(); - private final AtomicReference scanListener = new AtomicReference<>(); - private final Map hints = new HashMap<>(); - - private boolean scanning = true; - private PreviewFrame previewFrame; - - public void setCharacterSet(String characterSet) { - hints.put(DecodeHintType.CHARACTER_SET, characterSet); - } - - public void setScanListener(ScanListener scanListener) { - this.scanListener.set(scanListener); - } - - @Override - public void onPreviewFrame(@NonNull PreviewFrame previewFrame) { - try { - synchronized (this) { - this.previewFrame = previewFrame; - this.notify(); - } - } catch (RuntimeException e) { - Log.w(TAG, e); - } - } - - - @Override - public void run() { - while (true) { - PreviewFrame ourFrame; - - synchronized (this) { - while (scanning && previewFrame == null) { - Util.wait(this, 0); - } - - if (!scanning) return; - else ourFrame = previewFrame; - - previewFrame = null; - } - - String data = getScannedData(ourFrame.getData(), ourFrame.getWidth(), ourFrame.getHeight(), ourFrame.getOrientation()); - ScanListener scanListener = this.scanListener.get(); - - if (data != null && scanListener != null) { - scanListener.onQrDataFound(data); - return; - } - } - } - - public void stopScanning() { - synchronized (this) { - scanning = false; - notify(); - } - } - - private @Nullable String getScannedData(byte[] data, int width, int height, int orientation) { - try { - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - byte[] rotatedData = new byte[data.length]; - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - rotatedData[x * height + height - y - 1] = data[x + y * width]; - } - } - - int tmp = width; - width = height; - height = tmp; - data = rotatedData; - } - - PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data, width, height, - 0, 0, width, height, - false); - - BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); - Result result = reader.decode(bitmap, hints); - - if (result != null) return result.getText(); - - } catch (NullPointerException | ChecksumException | FormatException | IndexOutOfBoundsException e) { - Log.w(TAG, e); - } catch (NotFoundException e) { - // Thanks ZXing... - } - - return null; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodeFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodeFragment.kt deleted file mode 100644 index 3b551427a1..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodeFragment.kt +++ /dev/null @@ -1,65 +0,0 @@ -package org.thoughtcrime.securesms.util - -import android.content.res.Configuration -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.LinearLayout -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import network.loki.messenger.databinding.FragmentScanQrCodeBinding -import org.thoughtcrime.securesms.qr.ScanListener -import org.thoughtcrime.securesms.qr.ScanningThread - -class ScanQRCodeFragment : Fragment() { - private lateinit var binding: FragmentScanQrCodeBinding - private val scanningThread = ScanningThread() - var scanListener: ScanListener? = null - set(value) { field = value; scanningThread.setScanListener(scanListener) } - var message: CharSequence = "" - - override fun onCreateView(layoutInflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View { - binding = FragmentScanQrCodeBinding.inflate(layoutInflater, viewGroup, false) - return binding.root - } - - override fun onViewCreated(view: View, bundle: Bundle?) { - super.onViewCreated(view, bundle) - when (resources.configuration.orientation) { - Configuration.ORIENTATION_LANDSCAPE -> binding.overlayView.orientation = LinearLayout.HORIZONTAL - else -> binding.overlayView.orientation = LinearLayout.VERTICAL - } - binding.messageTextView.text = message - binding.messageTextView.isVisible = message.isNotEmpty() - } - - override fun onResume() { - super.onResume() - binding.cameraView.onResume() - binding.cameraView.setPreviewCallback(scanningThread) - try { - scanningThread.start() - } catch (exception: Exception) { - // Do nothing - } - scanningThread.setScanListener(scanListener) - } - - override fun onConfigurationChanged(newConfiguration: Configuration) { - super.onConfigurationChanged(newConfiguration) - binding.cameraView.onPause() - when (newConfiguration.orientation) { - Configuration.ORIENTATION_LANDSCAPE -> binding.overlayView.orientation = LinearLayout.HORIZONTAL - else -> binding.overlayView.orientation = LinearLayout.VERTICAL - } - binding.cameraView.onResume() - binding.cameraView.setPreviewCallback(scanningThread) - } - - override fun onPause() { - super.onPause() - this.binding.cameraView.onPause() - this.scanningThread.stopScanning() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodePlaceholderFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodePlaceholderFragment.kt deleted file mode 100644 index a0528ad432..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodePlaceholderFragment.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.thoughtcrime.securesms.util - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import com.squareup.phrase.Phrase -import network.loki.messenger.R -import network.loki.messenger.databinding.FragmentScanQrCodePlaceholderBinding -import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY - -class ScanQRCodePlaceholderFragment: Fragment() { - private lateinit var binding: FragmentScanQrCodePlaceholderBinding - var delegate: ScanQRCodePlaceholderFragmentDelegate? = null - - override fun onCreateView(layoutInflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View { - binding = FragmentScanQrCodePlaceholderBinding.inflate(layoutInflater, viewGroup, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.grantCameraAccessButton.setOnClickListener { delegate?.requestCameraAccess() } - - binding.needCameraPermissionsTV.text = Phrase.from(context, R.string.cameraGrantAccessQr) - .put(APP_NAME_KEY, getString(R.string.app_name)) - .format() - } -} - -interface ScanQRCodePlaceholderFragmentDelegate { - fun requestCameraAccess() -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodeWrapperFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodeWrapperFragment.kt index e5de4c36d9..b17356618b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodeWrapperFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ScanQRCodeWrapperFragment.kt @@ -1,89 +1,27 @@ package org.thoughtcrime.securesms.util -import android.Manifest -import android.content.pm.PackageManager import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment -import com.tbruyelle.rxpermissions2.RxPermissions -import network.loki.messenger.R -import org.thoughtcrime.securesms.qr.ScanListener +import kotlinx.coroutines.flow.emptyFlow +import org.thoughtcrime.securesms.ui.components.QRScannerScreen +import org.thoughtcrime.securesms.ui.createThemedComposeView -class ScanQRCodeWrapperFragment : Fragment(), ScanQRCodePlaceholderFragmentDelegate, ScanListener { +class ScanQRCodeWrapperFragment : Fragment() { companion object { const val FRAGMENT_TAG = "ScanQRCodeWrapperFragment_FRAGMENT_TAG" } var delegate: ScanQRCodeWrapperFragmentDelegate? = null - var message: CharSequence = "" - var enabled: Boolean = true - set(value) { - val shouldUpdate = field != value // update if value changes (view appears or disappears) - field = value - if (shouldUpdate) { - update() - } - } - - @Deprecated("Deprecated in Java") - override fun setUserVisibleHint(isVisibleToUser: Boolean) { - super.setUserVisibleHint(isVisibleToUser) - enabled = isVisibleToUser - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_scan_qr_code_wrapper, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - update() - } - - private fun update() { - if (!this.isAdded) return - - val fragment: Fragment - if (!enabled) { - val manager = childFragmentManager - manager.findFragmentByTag(FRAGMENT_TAG)?.let { existingFragment -> - // remove existing camera fragment (if switching back to other page) - manager.beginTransaction().remove(existingFragment).commit() - } - return - } - if (ContextCompat.checkSelfPermission(requireActivity(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { - val scanQRCodeFragment = ScanQRCodeFragment() - scanQRCodeFragment.scanListener = this - scanQRCodeFragment.message = message - fragment = scanQRCodeFragment - } else { - val scanQRCodePlaceholderFragment = ScanQRCodePlaceholderFragment() - scanQRCodePlaceholderFragment.delegate = this - fragment = scanQRCodePlaceholderFragment - } - val transaction = childFragmentManager.beginTransaction() - transaction.replace(R.id.fragmentContainer, fragment, FRAGMENT_TAG) - transaction.commit() - } - - override fun requestCameraAccess() { - @SuppressWarnings("unused") - val unused = RxPermissions(this).request(Manifest.permission.CAMERA).subscribe { isGranted -> - if (isGranted) { - update() - } - } - } - override fun onQrDataFound(data: String) { - activity?.runOnUiThread { - delegate?.handleQRCodeScanned(data) - } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + createThemedComposeView { + QRScannerScreen(emptyFlow(), onScan = { + delegate?.handleQRCodeScanned(it) + }) } } diff --git a/app/src/main/res/layout/fragment_scan_qr_code.xml b/app/src/main/res/layout/fragment_scan_qr_code.xml deleted file mode 100644 index ea8e1c5fca..0000000000 --- a/app/src/main/res/layout/fragment_scan_qr_code.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_scan_qr_code_placeholder.xml b/app/src/main/res/layout/fragment_scan_qr_code_placeholder.xml deleted file mode 100644 index f88b66800b..0000000000 --- a/app/src/main/res/layout/fragment_scan_qr_code_placeholder.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - -