mirror of
https://github.com/binwiederhier/ntfy-android.git
synced 2024-05-17 11:02:36 +12:00
added qr code scanner
This commit is contained in:
parent
c15efff72c
commit
ccf7c1c281
|
@ -128,4 +128,11 @@ dependencies {
|
|||
|
||||
// Image viewer
|
||||
implementation 'com.github.stfalcon-studio:StfalconImageViewer:v1.0.1'
|
||||
|
||||
// QR Scanner
|
||||
implementation 'com.google.mlkit:barcode-scanning:17.0.0'
|
||||
implementation "androidx.camera:camera-camera2:1.0.1"
|
||||
implementation "androidx.camera:camera-lifecycle:1.0.1"
|
||||
implementation "androidx.camera:camera-view:1.0.0-alpha28"
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,11 @@
|
|||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <!-- Only required on SDK <= 28 -->
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <!-- To reschedule the websocket retry -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- As of Android 13, we need to ask for permission to post notifications -->
|
||||
<uses-permission android:name="android.permission.CAMERA" /> <!-- For QR Code scanning -->
|
||||
|
||||
<!-- Features -->
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
||||
<!--
|
||||
Permission REQUEST_INSTALL_PACKAGES (F-Droid only!):
|
||||
|
@ -163,6 +168,9 @@
|
|||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||
android:resource="@drawable/ic_notification"/>
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.vision.DEPENDENCIES"
|
||||
android:value="barcode" />
|
||||
|
||||
<!-- FileProvider required for older Android versions (<= P), to allow passing the file URI in the open intent.
|
||||
Avoids "exposed beyond app through Intent.getData" exception, see see https://stackoverflow.com/a/57288352/1440785 -->
|
||||
|
|
|
@ -1,16 +1,34 @@
|
|||
package io.heckel.ntfy.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import android.util.SparseArray
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.*
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import io.heckel.ntfy.BuildConfig
|
||||
|
@ -21,6 +39,11 @@ import io.heckel.ntfy.msg.ApiService
|
|||
import io.heckel.ntfy.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
||||
class AddFragment : DialogFragment() {
|
||||
private val api = ApiService()
|
||||
|
@ -30,6 +53,9 @@ class AddFragment : DialogFragment() {
|
|||
private lateinit var appBaseUrl: String
|
||||
private var defaultBaseUrl: String? = null
|
||||
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
private lateinit var subscribeCameraPreview: PreviewView
|
||||
|
||||
private lateinit var subscribeView: View
|
||||
private lateinit var loginView: View
|
||||
private lateinit var positiveButton: Button
|
||||
|
@ -48,6 +74,7 @@ class AddFragment : DialogFragment() {
|
|||
private lateinit var subscribeProgress: ProgressBar
|
||||
private lateinit var subscribeErrorText: TextView
|
||||
private lateinit var subscribeErrorTextImage: View
|
||||
private lateinit var subscribeCameraPreviewPermissionText: TextView
|
||||
|
||||
// Login page
|
||||
private lateinit var loginUsernameText: TextInputEditText
|
||||
|
@ -103,6 +130,8 @@ class AddFragment : DialogFragment() {
|
|||
subscribeErrorText.visibility = View.GONE
|
||||
subscribeErrorTextImage = view.findViewById(R.id.add_dialog_subscribe_error_text_image)
|
||||
subscribeErrorTextImage.visibility = View.GONE
|
||||
subscribeCameraPreview = view.findViewById(R.id.add_dialog_subscribe_camera_preview)
|
||||
subscribeCameraPreviewPermissionText = view.findViewById(R.id.add_dialog_subscribe_camera_preview_denied_text)
|
||||
|
||||
// Fields for "login page"
|
||||
loginUsernameText = view.findViewById(R.id.add_dialog_login_username)
|
||||
|
@ -183,11 +212,109 @@ class AddFragment : DialogFragment() {
|
|||
|
||||
// Focus topic text (keyboard is shown too, see above)
|
||||
subscribeTopicText.requestFocus()
|
||||
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
checkIfCameraPermissionIsGranted()
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cameraExecutor.shutdown()
|
||||
}
|
||||
|
||||
private fun checkCameraPermissionsRaw(): Boolean {
|
||||
return ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun checkIfCameraPermissionIsGranted() {
|
||||
if (checkCameraPermissionsRaw()) {
|
||||
startCamera()
|
||||
} else {
|
||||
subscribeCameraPreviewPermissionText.visibility = View.VISIBLE
|
||||
val requiredPermissions = arrayOf(Manifest.permission.CAMERA)
|
||||
requestPermissions(requiredPermissions, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
if (checkCameraPermissionsRaw()) {
|
||||
startCamera()
|
||||
}
|
||||
}
|
||||
|
||||
private fun vibratePhone(context: Context) {
|
||||
// This is deprecated, but we aren't using a high enough version for the new API
|
||||
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
if (vibrator.hasVibrator()) {
|
||||
// Vibrate for 500 milliseconds
|
||||
vibrator.vibrate(200)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startCamera() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
|
||||
|
||||
subscribeCameraPreviewPermissionText.visibility = View.GONE
|
||||
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider = cameraProviderFuture.get()
|
||||
|
||||
val preview = Preview.Builder()
|
||||
.build()
|
||||
.also {
|
||||
it.setSurfaceProvider(subscribeCameraPreview.surfaceProvider)
|
||||
}
|
||||
|
||||
val imageAnalyzer = ImageAnalysis.Builder()
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build()
|
||||
.also {
|
||||
it.setAnalyzer(cameraExecutor, QrCodeAnalyzer { urlString ->
|
||||
var url = URL(urlString)
|
||||
var baseUrl = "${url.protocol}://${url.host}"
|
||||
var route = url.path.replaceFirst("/", "")
|
||||
|
||||
subscribeUseAnotherServerCheckbox.isChecked = true
|
||||
subscribeBaseUrlText.setText(baseUrl)
|
||||
subscribeTopicText.setText(route)
|
||||
validateInputSubscribeView {
|
||||
if (positiveButton.isEnabled) {
|
||||
positiveButtonClick()
|
||||
vibratePhone(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// Select back camera as a default
|
||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
|
||||
try {
|
||||
// Unbind use cases before rebinding
|
||||
cameraProvider.unbindAll()
|
||||
|
||||
// Bind use cases to camera
|
||||
cameraProvider.bindToLifecycle(
|
||||
this, cameraSelector, preview, imageAnalyzer
|
||||
)
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(requireContext()))
|
||||
}
|
||||
|
||||
private fun positiveButtonClick() {
|
||||
val topic = subscribeTopicText.text.toString()
|
||||
val baseUrl = getBaseUrl()
|
||||
|
@ -288,7 +415,7 @@ class AddFragment : DialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun validateInputSubscribeView() {
|
||||
private fun validateInputSubscribeView(onCompletion: () -> Unit = {}) {
|
||||
if (!this::positiveButton.isInitialized) return // As per crash seen in Google Play
|
||||
|
||||
// Show/hide things: This logic is intentionally kept simple. Do not simplify "just because it's pretty".
|
||||
|
@ -333,6 +460,8 @@ class AddFragment : DialogFragment() {
|
|||
} else {
|
||||
positiveButton.isEnabled = validTopic(topic)
|
||||
}
|
||||
|
||||
onCompletion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
58
app/src/main/java/io/heckel/ntfy/util/QrCodeAnalyzer.kt
Normal file
58
app/src/main/java/io/heckel/ntfy/util/QrCodeAnalyzer.kt
Normal file
|
@ -0,0 +1,58 @@
|
|||
package io.heckel.ntfy.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageProxy
|
||||
import com.google.mlkit.vision.barcode.Barcode
|
||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
|
||||
typealias BarcodeScannedCallback = (barcode: String) -> Unit
|
||||
|
||||
class QrCodeAnalyzer(private val onBarcodeScanned: BarcodeScannedCallback) : ImageAnalysis.Analyzer {
|
||||
|
||||
private var lastSuccessfulScanTimestamp: Long = 0
|
||||
private var lastProcessedTimestamp: Long = 0
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
override fun analyze(image: ImageProxy) {
|
||||
if (System.currentTimeMillis() - lastSuccessfulScanTimestamp < 3000) {
|
||||
image.close()
|
||||
return
|
||||
} else if (System.currentTimeMillis() - lastProcessedTimestamp < 100) {
|
||||
image.close()
|
||||
return
|
||||
}
|
||||
|
||||
val img = image.image
|
||||
if (img != null) {
|
||||
val inputImage = InputImage.fromMediaImage(img, image.imageInfo.rotationDegrees)
|
||||
|
||||
// Process image searching for barcodes
|
||||
val options = BarcodeScannerOptions.Builder()
|
||||
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
||||
.build()
|
||||
|
||||
val scanner = BarcodeScanning.getClient(options)
|
||||
|
||||
scanner.process(inputImage)
|
||||
.addOnSuccessListener { barcodes ->
|
||||
for (barcode in barcodes) {
|
||||
if (barcode.valueType !== Barcode.TYPE_URL) continue
|
||||
|
||||
onBarcodeScanned(barcodes[0].url.url)
|
||||
lastSuccessfulScanTimestamp = System.currentTimeMillis()
|
||||
break
|
||||
}
|
||||
}
|
||||
.addOnFailureListener {
|
||||
}
|
||||
.addOnCompleteListener {
|
||||
image.close()
|
||||
}
|
||||
}
|
||||
|
||||
lastProcessedTimestamp = System.currentTimeMillis()
|
||||
}
|
||||
}
|
|
@ -1,33 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
<LinearLayout
|
||||
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"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/add_dialog_subscribe_view">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/add_dialog_subscribe_view">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/add_dialog_subscribe_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:text="@string/add_dialog_title"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:paddingStart="4dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/add_dialog_subscribe_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:text="@string/add_dialog_title"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:paddingStart="4dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
|
@ -35,7 +35,7 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/add_dialog_subscribe_description"
|
||||
android:indeterminate="true" android:layout_marginBottom="5dp" android:visibility="gone"/>
|
||||
<TextView
|
||||
<TextView
|
||||
android:text="@string/add_dialog_description_below"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_description"
|
||||
|
@ -50,7 +50,7 @@
|
|||
android:maxLines="1" android:inputType="text" android:maxLength="64"
|
||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_description"/>
|
||||
<CheckBox
|
||||
<CheckBox
|
||||
android:text="@string/add_dialog_use_another_server"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_use_another_server_checkbox"
|
||||
|
@ -59,111 +59,137 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_topic_text"
|
||||
android:layout_marginTop="-3dp"/>
|
||||
<TextView
|
||||
android:text="@string/add_dialog_use_another_server_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_use_another_server_description"
|
||||
android:paddingStart="4dp" android:paddingTop="0dp"
|
||||
android:visibility="gone" app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_use_another_server_checkbox"
|
||||
android:layout_marginTop="-5dp"/>
|
||||
android:text="@string/add_dialog_use_another_server_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_use_another_server_description"
|
||||
android:paddingStart="4dp" android:paddingTop="0dp"
|
||||
android:visibility="gone" app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_use_another_server_checkbox"
|
||||
android:layout_marginTop="-5dp"/>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu"
|
||||
android:id="@+id/add_dialog_subscribe_base_url_layout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu"
|
||||
android:id="@+id/add_dialog_subscribe_base_url_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="0dp"
|
||||
android:padding="0dp"
|
||||
android:visibility="gone"
|
||||
app:endIconMode="custom"
|
||||
app:hintEnabled="false"
|
||||
app:boxBackgroundColor="@null"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_use_another_server_description">
|
||||
<AutoCompleteTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="0dp"
|
||||
android:padding="0dp"
|
||||
android:visibility="gone"
|
||||
app:endIconMode="custom"
|
||||
app:hintEnabled="false"
|
||||
app:boxBackgroundColor="@null"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_use_another_server_description">
|
||||
<AutoCompleteTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/add_dialog_subscribe_base_url_text"
|
||||
android:hint="@string/app_base_url"
|
||||
android:maxLines="1"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
android:id="@+id/add_dialog_subscribe_base_url_text"
|
||||
android:hint="@string/app_base_url"
|
||||
android:maxLines="1"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<LinearLayout
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_instant_delivery_box"
|
||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_base_url_layout" android:layout_marginTop="-3dp">
|
||||
<CheckBox
|
||||
<CheckBox
|
||||
android:text="@string/add_dialog_instant_delivery"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_instant_delivery_checkbox"
|
||||
android:layout_marginTop="-8dp" android:layout_marginBottom="-5dp"
|
||||
android:layout_marginStart="-3dp"/>
|
||||
<ImageView
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp" app:srcCompat="@drawable/ic_bolt_gray_24dp"
|
||||
android:id="@+id/add_dialog_subscribe_instant_image"
|
||||
app:layout_constraintTop_toTopOf="@+id/main_item_text"
|
||||
app:layout_constraintEnd_toStartOf="@+id/main_item_date" android:paddingTop="3dp"
|
||||
android:layout_marginTop="3dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:text="@string/add_dialog_instant_delivery_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_instant_delivery_description"
|
||||
android:paddingStart="4dp" android:paddingTop="0dp"
|
||||
android:visibility="gone" app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_instant_delivery_box"/>
|
||||
android:text="@string/add_dialog_instant_delivery_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_instant_delivery_description"
|
||||
android:paddingStart="4dp" android:paddingTop="0dp"
|
||||
android:visibility="gone" app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_instant_delivery_box"/>
|
||||
<TextView
|
||||
android:text="@string/add_dialog_foreground_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_foreground_description"
|
||||
android:paddingStart="4dp" app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_instant_delivery_description"/>
|
||||
android:text="@string/add_dialog_foreground_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_foreground_description"
|
||||
android:paddingStart="4dp" app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_instant_delivery_description"/>
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp" app:srcCompat="@drawable/ic_error_red_24dp"
|
||||
android:id="@+id/add_dialog_subscribe_error_text_image"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/add_dialog_subscribe_error_text" android:layout_marginTop="1dp"/>
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/add_dialog_subscribe_error_text"
|
||||
android:layout_marginTop="1dp"/>
|
||||
<TextView
|
||||
android:text="Unable to resolve host example.com"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_error_text"
|
||||
android:paddingStart="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_foreground_description"
|
||||
android:paddingEnd="4dp"
|
||||
android:textAppearance="@style/DangerText"
|
||||
app:layout_constraintStart_toEndOf="@id/add_dialog_subscribe_error_text_image"
|
||||
android:layout_marginTop="5dp"
|
||||
tools:visibility="gone"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
android:text="Unable to resolve host example.com"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_error_text"
|
||||
android:paddingStart="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_foreground_description"
|
||||
android:paddingEnd="4dp"
|
||||
android:textAppearance="@style/DangerText"
|
||||
app:layout_constraintStart_toEndOf="@id/add_dialog_subscribe_error_text_image"
|
||||
android:layout_marginTop="5dp"
|
||||
tools:visibility="gone"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_error_text">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Or Scan ntfy QR code:" />
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/add_dialog_subscribe_camera_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp"
|
||||
android:background="#000" />
|
||||
<TextView
|
||||
android:id="@+id/add_dialog_subscribe_camera_preview_denied_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Camera permission is required to scan QR codes."
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/add_dialog_login_view">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/add_dialog_login_view">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
<TextView
|
||||
android:id="@+id/add_dialog_login_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -174,15 +200,15 @@
|
|||
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:paddingStart="4dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
<TextView
|
||||
/>
|
||||
<TextView
|
||||
android:text="@string/add_dialog_login_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_login_description"
|
||||
android:paddingStart="4dp" android:paddingTop="3dp" app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_login_title" android:paddingEnd="4dp"/>
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/add_dialog_login_username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:hint="@string/add_dialog_login_username_hint"
|
||||
|
@ -190,7 +216,7 @@
|
|||
android:maxLines="1" android:inputType="text" android:maxLength="64"
|
||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="10dp" app:layout_constraintTop_toBottomOf="@+id/add_dialog_login_description"/>
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/add_dialog_login_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" android:hint="@string/add_dialog_login_password_hint"
|
||||
|
@ -198,13 +224,13 @@
|
|||
android:maxLines="1" android:inputType="textPassword" app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/add_dialog_login_username"/>
|
||||
<ImageView
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp" app:srcCompat="@drawable/ic_error_red_24dp"
|
||||
android:id="@+id/add_dialog_login_error_text_image"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="@+id/add_dialog_login_error_text" app:layout_constraintTop_toTopOf="@+id/add_dialog_login_error_text"/>
|
||||
<TextView
|
||||
<TextView
|
||||
android:text="Login failed. User not authorized."
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content" android:id="@+id/add_dialog_login_error_text"
|
||||
|
@ -214,7 +240,7 @@
|
|||
android:paddingEnd="4dp"
|
||||
android:textAppearance="@style/DangerText"
|
||||
app:layout_constraintStart_toEndOf="@id/add_dialog_login_error_text_image"/>
|
||||
<ProgressBar
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
|
@ -222,6 +248,6 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/add_dialog_login_description"
|
||||
android:indeterminate="true" android:layout_marginBottom="5dp"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
|
Loading…
Reference in a new issue