diff --git a/.gitignore b/.gitignore index 2f53f69b..6a84d08d 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,6 @@ atlassian-ide-plugin.xml # Mongo Explorer plugin .idea/mongoSettings.xml + +# Sentry +/sentry.properties diff --git a/CHANGELOG.md b/CHANGELOG.md index c872974f..6c815a8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## 1.6 +* Add a setting to avoid duplicates in history +* Add an option to manually save to history +* Add an option to set a name to a history item +* Bug fix: app shortcuts were not working +* Bug fix: Wi-Fi QR code passwords were scanned wrong + ## 1.5 * Add Chinese (Taiwan) translation * Add a Quick Settings tile for the app diff --git a/app/build.gradle b/app/build.gradle index be90c9c2..3f646a82 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.example.barcodescanner" minSdkVersion 21 targetSdkVersion 29 - versionCode 7 - versionName "1.5" + versionCode 8 + versionName "1.6" multiDexEnabled true vectorDrawables.useSupportLibrary true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -24,6 +24,10 @@ android { } } + Properties properties = new Properties() + properties.load(project.rootProject.file("local.properties").newDataInputStream()) + + resValue "string", "sentryDSN", properties.getProperty("sentryDSN") buildConfigField "boolean", "ERROR_REPORTS_ENABLED_BY_DEFAULT", "true" } @@ -76,7 +80,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // Android - implementation 'androidx.core:core-ktx:1.3.1' + implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.1' diff --git a/app/src/amazon/res/xml/shortcuts.xml b/app/src/amazon/res/xml/shortcuts.xml new file mode 100644 index 00000000..161045fa --- /dev/null +++ b/app/src/amazon/res/xml/shortcuts.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/aptoide/res/xml/shortcuts.xml b/app/src/aptoide/res/xml/shortcuts.xml new file mode 100644 index 00000000..70aefae2 --- /dev/null +++ b/app/src/aptoide/res/xml/shortcuts.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/googlePlay/res/xml/shortcuts.xml b/app/src/googlePlay/res/xml/shortcuts.xml new file mode 100644 index 00000000..70aefae2 --- /dev/null +++ b/app/src/googlePlay/res/xml/shortcuts.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f7cd1634..2c0bd085 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,12 +23,12 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" - android:name="com.example.barcodescanner.App" + android:name=".App" > toggleIsFavorite() R.id.item_show_barcode_image -> navigateToBarcodeImageActivity() + R.id.item_save -> saveBarcode() R.id.item_delete -> showDeleteBarcodeConfirmationDialog() } return@setOnMenuItemClickListener true @@ -169,6 +181,8 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene } private fun handleButtonsClicked() { + button_edit_name.setOnClickListener { showEditBarcodeNameDialog() } + button_search_on_rate_and_goods.setOnClickListener { searchBarcodeTextOnRateAndGoods() } button_search_on_amazon.setOnClickListener { searchBarcodeTextOnAmazon() } button_search_on_ebay.setOnClickListener { searchBarcodeTextOnEbay() } @@ -213,14 +227,14 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene private fun toggleIsFavorite() { - val newBarcode = originalBarcode.copy(isFavorite = originalBarcode.isFavorite.not()) + val newBarcode = originalBarcode.copy(isFavorite = barcode.isFavorite.not()) barcodeDatabase.save(newBarcode) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { - originalBarcode.isFavorite = newBarcode.isFavorite + barcode.isFavorite = newBarcode.isFavorite showBarcodeIsFavorite(newBarcode.isFavorite) }, {} @@ -228,6 +242,65 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene .addTo(disposable) } + private fun updateBarcodeName(name: String) { + if (name.isBlank()) { + return + } + + val newBarcode = originalBarcode.copy( + id = barcode.id, + name = name + ) + + barcodeDatabase.save(newBarcode) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + barcode.name = name + showBarcodeName(name) + }, + ::showError + ) + .addTo(disposable) + } + + private fun saveBarcode() { + toolbar?.menu?.findItem(R.id.item_save)?.isVisible = false + + barcodeDatabase.save(originalBarcode, settings.doNotSaveDuplicates) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { id -> + barcode.id = id + button_edit_name.isVisible = true + toolbar?.menu?.findItem(R.id.item_delete)?.isVisible = true + }, + { error -> + toolbar?.menu?.findItem(R.id.item_save)?.isVisible = true + showError(error) + } + ) + .addTo(disposable) + } + + private fun deleteBarcode() { + showLoading(true) + + barcodeDatabase.delete(barcode.id) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { finish() }, + { error -> + showLoading(false) + showError(error) + } + ) + .addTo(disposable) + } + private fun addToCalendar() { val intent = Intent(Intent.ACTION_INSERT).apply { data = CalendarContract.Events.CONTENT_URI @@ -479,22 +552,6 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene SaveBarcodeAsImageActivity.start(this, originalBarcode) } - private fun deleteBarcode() { - showLoading(true) - - barcodeDatabase.delete(barcode.id) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { finish() }, - { error -> - showLoading(false) - showError(error) - } - ) - .addTo(disposable) - } - private fun showBarcode() { showBarcodeMenuIfNeeded() @@ -502,6 +559,7 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene showBarcodeImageIfNeeded() showBarcodeDate() showBarcodeFormat() + showBarcodeName() showBarcodeText() showBarcodeCountry() } @@ -512,6 +570,7 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene findItem(R.id.item_increase_brightness).isVisible = isCreated findItem(R.id.item_add_to_favorites)?.isVisible = barcode.isInDb findItem(R.id.item_show_barcode_image)?.isVisible = isCreated.not() + findItem(R.id.item_save)?.isVisible = barcode.isInDb.not() findItem(R.id.item_delete)?.isVisible = barcode.isInDb } } @@ -562,6 +621,15 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene toolbar.setTitle(format) } + private fun showBarcodeName() { + showBarcodeName(barcode.name) + } + + private fun showBarcodeName(name: String?) { + text_view_barcode_name.isVisible = name.isNullOrBlank().not() + text_view_barcode_name.text = name.orEmpty() + } + private fun showBarcodeText() { text_view_barcode_text.text = if (isCreated) { barcode.text @@ -606,6 +674,7 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene private fun showOrHideButtons() { button_search.isVisible = isCreated.not() + button_edit_name.isVisible = barcode.isInDb if (isCreated) { return @@ -670,6 +739,11 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene dialog.show(supportFragmentManager, "") } + private fun showEditBarcodeNameDialog() { + val dialog = EditBarcodeNameDialogFragment.newInstance(barcode.name) + dialog.show(supportFragmentManager, "") + } + private fun showSearchEnginesDialog() { val dialog = ChooseSearchEngineDialogFragment() dialog.show(supportFragmentManager, "") diff --git a/app/src/main/java/com/example/barcodescanner/feature/barcode/otp/OtpActivity.kt b/app/src/main/java/com/example/barcodescanner/feature/barcode/otp/OtpActivity.kt index 88a429fb..536dd507 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/barcode/otp/OtpActivity.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/barcode/otp/OtpActivity.kt @@ -12,6 +12,7 @@ import com.example.barcodescanner.extension.orZero import com.example.barcodescanner.feature.BaseActivity import com.example.barcodescanner.model.schema.OtpAuth import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.addTo import kotlinx.android.synthetic.main.activity_barcode_otp.* @@ -106,13 +107,13 @@ class OtpActivity : BaseActivity() { val secondsPassed = currentTimeInSeconds % period val secondsLeft = period - secondsPassed - Observable .interval(1, TimeUnit.SECONDS) .map { it + 1 } .take(secondsLeft) .map { secondsLeft - it } .startWith(secondsLeft) + .observeOn(AndroidSchedulers.mainThread()) .doOnComplete { showOtp() } .subscribe(::showTime) .addTo(disposable) diff --git a/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ChooseSearchEngineDialogFragment.kt b/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ChooseSearchEngineDialogFragment.kt index d8ee82da..27a21a79 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ChooseSearchEngineDialogFragment.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ChooseSearchEngineDialogFragment.kt @@ -3,6 +3,7 @@ package com.example.barcodescanner.feature.common.dialog import android.app.Dialog import android.os.Bundle import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import com.example.barcodescanner.R import com.example.barcodescanner.model.SearchEngine @@ -45,7 +46,7 @@ class ChooseSearchEngineDialogFragment : DialogFragment() { .create() dialog.setOnShowListener { - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(resources.getColor(R.color.red)) + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(ContextCompat.getColor(requireContext(), R.color.red)) } return dialog diff --git a/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ConfirmBarcodeDialogFragment.kt b/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ConfirmBarcodeDialogFragment.kt index e8a3a6e1..6c121a4a 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ConfirmBarcodeDialogFragment.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ConfirmBarcodeDialogFragment.kt @@ -3,6 +3,7 @@ package com.example.barcodescanner.feature.common.dialog import android.app.Dialog import android.os.Bundle import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import com.example.barcodescanner.R import com.example.barcodescanner.extension.toStringId @@ -34,20 +35,20 @@ class ConfirmBarcodeDialogFragment : DialogFragment() { val messageId = barcode.format.toStringId() val dialog = AlertDialog.Builder(requireActivity(), R.style.DialogTheme) - .setTitle(R.string.fragment_scan_barcode_from_camera_confirm_barcode_dialog_title) + .setTitle(R.string.dialog_confirm_barcode_title) .setMessage(messageId) .setCancelable(false) - .setPositiveButton(R.string.fragment_scan_barcode_from_camera_confirm_barcode_dialog_positive_button) { _, _ -> + .setPositiveButton(R.string.dialog_confirm_barcode_positive_button) { _, _ -> listener?.onBarcodeConfirmed(barcode) } - .setNegativeButton(R.string.fragment_scan_barcode_from_camera_confirm_barcode_dialog_negative_button) { _, _ -> + .setNegativeButton(R.string.dialog_confirm_barcode_negative_button) { _, _ -> listener?.onBarcodeDeclined() } .create() dialog.setOnShowListener { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(resources.getColor(R.color.blue)) - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(resources.getColor(R.color.red)) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ContextCompat.getColor(requireContext(), R.color.blue)) + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(ContextCompat.getColor(requireContext(), R.color.red)) } return dialog diff --git a/app/src/main/java/com/example/barcodescanner/feature/common/dialog/DeleteConfirmationDialogFragment.kt b/app/src/main/java/com/example/barcodescanner/feature/common/dialog/DeleteConfirmationDialogFragment.kt index 4352a048..35de86c6 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/common/dialog/DeleteConfirmationDialogFragment.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/common/dialog/DeleteConfirmationDialogFragment.kt @@ -3,6 +3,7 @@ package com.example.barcodescanner.feature.common.dialog import android.app.Dialog import android.os.Bundle import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import com.example.barcodescanner.R import com.example.barcodescanner.extension.orZero @@ -37,8 +38,8 @@ class DeleteConfirmationDialogFragment : DialogFragment() { .create() dialog.setOnShowListener { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(resources.getColor(R.color.red)) - dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(resources.getColor(R.color.blue)) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ContextCompat.getColor(requireContext(), R.color.red)) + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(ContextCompat.getColor(requireContext(), R.color.blue)) } return dialog diff --git a/app/src/main/java/com/example/barcodescanner/feature/common/dialog/EditBarcodeNameDialogFragment.kt b/app/src/main/java/com/example/barcodescanner/feature/common/dialog/EditBarcodeNameDialogFragment.kt new file mode 100644 index 00000000..68eff130 --- /dev/null +++ b/app/src/main/java/com/example/barcodescanner/feature/common/dialog/EditBarcodeNameDialogFragment.kt @@ -0,0 +1,70 @@ +package com.example.barcodescanner.feature.common.dialog + +import android.app.Activity +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.fragment.app.DialogFragment +import com.example.barcodescanner.R +import kotlinx.android.synthetic.main.dialog_edit_barcode_name.view.* + +class EditBarcodeNameDialogFragment : DialogFragment() { + + interface Listener { + fun onNameConfirmed(name: String) + } + + companion object { + private const val NAME_KEY = "NAME_KEY" + + fun newInstance(name: String?): EditBarcodeNameDialogFragment { + return EditBarcodeNameDialogFragment().apply { + arguments = Bundle().apply { + putString(NAME_KEY, name) + } + } + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val listener = requireActivity() as? Listener + val name = arguments?.getString(NAME_KEY).orEmpty() + + val view = LayoutInflater + .from(requireContext()) + .inflate(R.layout.dialog_edit_barcode_name, null, false) + + val dialog = AlertDialog.Builder(requireActivity(), R.style.DialogTheme) + .setTitle(R.string.dialog_edit_barcode_name_title) + .setView(view) + .setPositiveButton(R.string.dialog_confirm_barcode_positive_button) { _, _ -> + val newName = view.edit_text_barcode_name.text.toString() + listener?.onNameConfirmed(newName) + } + .setNegativeButton(R.string.dialog_confirm_barcode_negative_button, null) + .create() + + dialog.setOnShowListener { + initNameEditText(view.edit_text_barcode_name, name) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ContextCompat.getColor(requireContext(), R.color.blue)) + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(ContextCompat.getColor(requireContext(), R.color.red)) + } + + return dialog + } + + private fun initNameEditText(editText: EditText, name: String) { + editText.apply { + setText(name) + setSelection(name.length) + requestFocus() + } + + val manager = requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE) as? InputMethodManager + manager?.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ErrorDialogFragment.kt b/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ErrorDialogFragment.kt index 68c40438..e58e596a 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ErrorDialogFragment.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/common/dialog/ErrorDialogFragment.kt @@ -4,6 +4,7 @@ import android.app.Dialog import android.content.Context import android.os.Bundle import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat import androidx.fragment.app.DialogFragment import com.example.barcodescanner.R @@ -48,7 +49,7 @@ class ErrorDialogFragment : DialogFragment() { .create() dialog.setOnShowListener { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(resources.getColor(R.color.blue)) + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ContextCompat.getColor(requireContext(), R.color.blue)) } return dialog diff --git a/app/src/main/java/com/example/barcodescanner/feature/common/view/IconButton.kt b/app/src/main/java/com/example/barcodescanner/feature/common/view/IconButton.kt index 9ad325ea..835e1e4a 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/common/view/IconButton.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/common/view/IconButton.kt @@ -8,6 +8,7 @@ import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout import androidx.appcompat.content.res.AppCompatResources +import androidx.core.content.ContextCompat import com.example.barcodescanner.R import kotlinx.android.synthetic.main.layout_icon_button.view.* @@ -41,7 +42,10 @@ class IconButton : FrameLayout { } private fun showIconBackgroundColor(attributes: TypedArray) { - val color = attributes.getColor(R.styleable.IconButton_iconBackground, view.context.resources.getColor(R.color.green)) + val color = attributes.getColor( + R.styleable.IconButton_iconBackground, + ContextCompat.getColor(view.context, R.color.green) + ) (view.layout_image.background.mutate() as GradientDrawable).setColor(color) } diff --git a/app/src/main/java/com/example/barcodescanner/feature/common/view/ResultPointsView.kt b/app/src/main/java/com/example/barcodescanner/feature/common/view/ResultPointsView.kt new file mode 100644 index 00000000..7877d7f2 --- /dev/null +++ b/app/src/main/java/com/example/barcodescanner/feature/common/view/ResultPointsView.kt @@ -0,0 +1,88 @@ +package com.example.barcodescanner.feature.common.view + +import android.content.Context +import android.content.res.Resources +import android.graphics.* +import android.util.AttributeSet +import android.util.TypedValue +import android.view.View +import androidx.core.content.ContextCompat +import com.example.barcodescanner.R +import com.google.zxing.Result + +class ResultPointsView : View { + + private val pointsPaint = Paint().apply { + style = Paint.Style.STROKE + color = Color.BLUE + strokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, Resources.getSystem().displayMetrics) + strokeCap = Paint.Cap.ROUND + } + + private var resultPoints = floatArrayOf() + private var rect = RectF() + + + constructor(context: Context?) : this(context, null) + constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, 0) + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + context?.obtainStyledAttributes(attrs, R.styleable.ResultPointsView)?.apply { + pointsPaint.color = getColor( + R.styleable.ResultPointsView_resultPointColor, + ContextCompat.getColor(context, R.color.blue) + ) + + pointsPaint.strokeWidth = getDimension( + R.styleable.ResultPointsView_resultPointSize, + pointsPaint.strokeWidth + ) + + recycle() + } + } + + + override fun onDraw(canvas: Canvas) { + canvas.drawPoints(resultPoints, pointsPaint) + +// if (BuildConfig.DEBUG) { +// canvas.drawRect(rect, pointsPaint) +// } + } + + fun showResult(result: Result, imageWidth: Int, imageHeight: Int, imageRotation: Int) { + val localMatrix = createMatrix(imageWidth.toFloat(), imageHeight.toFloat(), imageRotation) + + resultPoints = result.resultPoints.flatMap { listOf(it.x, it.y) }.toFloatArray() + localMatrix.mapPoints(resultPoints) + +// if (BuildConfig.DEBUG) { +// rect = RectF(0f, 0f, imageWidth.toFloat(), imageHeight.toFloat()) +// localMatrix.mapRect(rect) +// } + + postInvalidate() + } + + private fun createMatrix(imageWidth: Float, imageHeight: Float, imageRotation: Int) = Matrix().apply { + preTranslate((width - imageWidth) / 2f, (height - imageHeight) / 2f) + preRotate(imageRotation.toFloat(), imageWidth / 2f, imageHeight / 2f) + + val wScale: Float + val hScale: Float + + if (imageRotation % 180 == 0) { + wScale = width.toFloat() / imageWidth + hScale = height.toFloat() / imageHeight + } else { + wScale = height.toFloat() / imageWidth + hScale = width.toFloat() / imageHeight + + } + + val scale = Math.max(wScale, hScale) + preScale(scale, scale, imageWidth / 2f, imageHeight / 2f) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/barcodescanner/feature/tabs/BottomTabsActivity.kt b/app/src/main/java/com/example/barcodescanner/feature/tabs/BottomTabsActivity.kt index 37188bca..85d3185e 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/tabs/BottomTabsActivity.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/tabs/BottomTabsActivity.kt @@ -3,6 +3,7 @@ package com.example.barcodescanner.feature.tabs import android.os.Bundle import android.view.MenuItem import androidx.fragment.app.Fragment +import com.example.barcodescanner.BuildConfig import com.example.barcodescanner.R import com.example.barcodescanner.extension.applySystemWindowInsets import com.example.barcodescanner.feature.BaseActivity @@ -16,8 +17,8 @@ import kotlinx.android.synthetic.main.activity_bottom_tabs.* class BottomTabsActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelectedListener { companion object { - private const val ACTION_CREATE_BARCODE = "com.example.barcodescanner.CREATE_BARCODE" - private const val ACTION_HISTORY = "com.example.barcodescanner.HISTORY" + private const val ACTION_CREATE_BARCODE = "${BuildConfig.APPLICATION_ID}.CREATE_BARCODE" + private const val ACTION_HISTORY = "${BuildConfig.APPLICATION_ID}.HISTORY" } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/com/example/barcodescanner/feature/tabs/create/CreateBarcodeActivity.kt b/app/src/main/java/com/example/barcodescanner/feature/tabs/create/CreateBarcodeActivity.kt index ff42f27a..27733208 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/tabs/create/CreateBarcodeActivity.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/tabs/create/CreateBarcodeActivity.kt @@ -8,6 +8,7 @@ import android.net.Uri import android.os.Bundle import android.provider.ContactsContract import android.widget.Toast +import androidx.core.content.ContextCompat import com.example.barcodescanner.R import com.example.barcodescanner.di.* import com.example.barcodescanner.extension.applySystemWindowInsets @@ -23,6 +24,7 @@ import com.example.barcodescanner.model.schema.App import com.example.barcodescanner.model.schema.BarcodeSchema import com.example.barcodescanner.model.schema.Schema import com.example.barcodescanner.usecase.Logger +import com.example.barcodescanner.usecase.save import com.google.zxing.BarcodeFormat import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable @@ -79,7 +81,7 @@ class CreateBarcodeActivity : BaseActivity(), AppAdapter.Listener { } toolbar.menu?.findItem(R.id.item_create_barcode)?.apply { - icon = getDrawable(iconId) + icon = ContextCompat.getDrawable(this@CreateBarcodeActivity, iconId) isEnabled = enabled } } @@ -313,7 +315,7 @@ class CreateBarcodeActivity : BaseActivity(), AppAdapter.Listener { return } - barcodeDatabase.save(barcode) + barcodeDatabase.save(barcode, settings.doNotSaveDuplicates) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( diff --git a/app/src/main/java/com/example/barcodescanner/feature/tabs/history/BarcodeHistoryAdapter.kt b/app/src/main/java/com/example/barcodescanner/feature/tabs/history/BarcodeHistoryAdapter.kt index d327ee2d..c8bbd129 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/tabs/history/BarcodeHistoryAdapter.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/tabs/history/BarcodeHistoryAdapter.kt @@ -61,7 +61,7 @@ class BarcodeHistoryAdapter(private val listener: Listener) : PagedListAdapter(0, 350).toLongArray() private val disposable = CompositeDisposable() private var maxZoom: Int = 0 @@ -268,7 +269,7 @@ class ScanBarcodeFromCameraFragment : Fragment(), ConfirmBarcodeDialogFragment.L } private fun saveScannedBarcode(barcode: Barcode) { - barcodeDatabase.save(barcode) + barcodeDatabase.save(barcode, settings.doNotSaveDuplicates) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( @@ -316,11 +317,11 @@ class ScanBarcodeFromCameraFragment : Fragment(), ConfirmBarcodeDialogFragment.L } private fun requestPermissions() { - permissionsHelper.requestNotGrantedPermissions(requireActivity() as AppCompatActivity, permissions, PERMISSION_REQUEST_CODE) + permissionsHelper.requestNotGrantedPermissions(requireActivity() as AppCompatActivity, PERMISSIONS, PERMISSION_REQUEST_CODE) } private fun areAllPermissionsGranted(): Boolean { - return permissionsHelper.areAllPermissionsGranted(requireActivity(), permissions) + return permissionsHelper.areAllPermissionsGranted(requireActivity(), PERMISSIONS) } private fun areAllPermissionsGranted(grantResults: IntArray): Boolean { diff --git a/app/src/main/java/com/example/barcodescanner/feature/tabs/scan/file/ScanBarcodeFromFileActivity.kt b/app/src/main/java/com/example/barcodescanner/feature/tabs/scan/file/ScanBarcodeFromFileActivity.kt index edaa52dd..7bd3f0f0 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/tabs/scan/file/ScanBarcodeFromFileActivity.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/tabs/scan/file/ScanBarcodeFromFileActivity.kt @@ -18,6 +18,7 @@ import com.example.barcodescanner.extension.showError import com.example.barcodescanner.feature.BaseActivity import com.example.barcodescanner.feature.barcode.BarcodeActivity import com.example.barcodescanner.model.Barcode +import com.example.barcodescanner.usecase.save import com.google.zxing.Result import com.isseiaoki.simplecropview.CropImageView import io.reactivex.android.schedulers.AndroidSchedulers @@ -225,7 +226,7 @@ class ScanBarcodeFromFileActivity : BaseActivity() { showLoading(true) - barcodeDatabase.save(barcode) + barcodeDatabase.save(barcode, settings.doNotSaveDuplicates) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( diff --git a/app/src/main/java/com/example/barcodescanner/feature/tabs/settings/SettingsFragment.kt b/app/src/main/java/com/example/barcodescanner/feature/tabs/settings/SettingsFragment.kt index 30ccd98f..13a19039 100644 --- a/app/src/main/java/com/example/barcodescanner/feature/tabs/settings/SettingsFragment.kt +++ b/app/src/main/java/com/example/barcodescanner/feature/tabs/settings/SettingsFragment.kt @@ -71,6 +71,7 @@ class SettingsFragment : Fragment(), DeleteConfirmationDialogFragment.Listener { button_confirm_scans_manually.setCheckedChangedListener { settings.confirmScansManually = it } button_save_scanned_barcodes.setCheckedChangedListener { settings.saveScannedBarcodesToHistory = it } button_save_created_barcodes.setCheckedChangedListener { settings.saveCreatedBarcodesToHistory = it } + button_do_not_save_duplicates.setCheckedChangedListener { settings.doNotSaveDuplicates = it } button_enable_error_reports.setCheckedChangedListener { settings.areErrorReportsEnabled = it } } @@ -115,6 +116,7 @@ class SettingsFragment : Fragment(), DeleteConfirmationDialogFragment.Listener { button_confirm_scans_manually.isChecked = confirmScansManually button_save_scanned_barcodes.isChecked = saveScannedBarcodesToHistory button_save_created_barcodes.isChecked = saveCreatedBarcodesToHistory + button_do_not_save_duplicates.isChecked = doNotSaveDuplicates button_enable_error_reports.isChecked = areErrorReportsEnabled } } diff --git a/app/src/main/java/com/example/barcodescanner/model/Barcode.kt b/app/src/main/java/com/example/barcodescanner/model/Barcode.kt index daa73b37..90cb2d66 100644 --- a/app/src/main/java/com/example/barcodescanner/model/Barcode.kt +++ b/app/src/main/java/com/example/barcodescanner/model/Barcode.kt @@ -12,13 +12,14 @@ import java.io.Serializable @TypeConverters(BarcodeDatabaseTypeConverter::class) data class Barcode( @PrimaryKey(autoGenerate = true) val id: Long = 0, + val name: String? = null, val text: String, val formattedText: String, val format: BarcodeFormat, val schema: BarcodeSchema, val date: Long, val isGenerated: Boolean = false, - var isFavorite: Boolean = false, + val isFavorite: Boolean = false, val errorCorrectionLevel: String? = null, val country: String? = null ) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/example/barcodescanner/model/ParsedBarcode.kt b/app/src/main/java/com/example/barcodescanner/model/ParsedBarcode.kt index b3c4d208..46352dfa 100644 --- a/app/src/main/java/com/example/barcodescanner/model/ParsedBarcode.kt +++ b/app/src/main/java/com/example/barcodescanner/model/ParsedBarcode.kt @@ -4,13 +4,14 @@ import com.example.barcodescanner.model.schema.* import com.google.zxing.BarcodeFormat class ParsedBarcode(barcode: Barcode) { - val id = barcode.id + var id = barcode.id + var name = barcode.name val text = barcode.text val formattedText = barcode.formattedText val format = barcode.format val schema = barcode.schema val date = barcode.date - val isFavorite = barcode.isFavorite + var isFavorite = barcode.isFavorite val country = barcode.country var firstName: String? = null diff --git a/app/src/main/java/com/example/barcodescanner/model/schema/VCard.kt b/app/src/main/java/com/example/barcodescanner/model/schema/VCard.kt index c04c8ab1..ac5b501b 100644 --- a/app/src/main/java/com/example/barcodescanner/model/schema/VCard.kt +++ b/app/src/main/java/com/example/barcodescanner/model/schema/VCard.kt @@ -1,5 +1,6 @@ package com.example.barcodescanner.model.schema +import com.example.barcodescanner.extension.joinToStringNotNullOrBlank import com.example.barcodescanner.extension.joinToStringNotNullOrBlankWithLineSeparator import com.example.barcodescanner.extension.startsWithIgnoreCase import ezvcard.Ezvcard @@ -26,19 +27,21 @@ data class VCard( val secondaryPhoneType: String? = null, val tertiaryPhone: String? = null, val tertiaryPhoneType: String? = null, + val address: String? = null, val geoUri: String? = null, val url: String? = null ) : Schema { companion object { private const val SCHEMA_PREFIX = "BEGIN:VCARD" + private const val ADDRESS_SEPARATOR = "," fun parse(text: String): VCard? { if (text.startsWithIgnoreCase(SCHEMA_PREFIX).not()) { return null } - val vCard = Ezvcard.parse(text).first() + val vCard = Ezvcard.parse(text).first() ?: return null val firstName = vCard.structuredName?.given val lastName = vCard.structuredName?.family val nickname = vCard.nickname?.values?.firstOrNull() @@ -58,6 +61,7 @@ data class VCard( var secondaryPhoneType: String? = null var tertiaryPhone: String? = null var tertiaryPhoneType: String? = null + var address: String? = null vCard.emails?.getOrNull(0)?.apply { email = value @@ -85,6 +89,16 @@ data class VCard( tertiaryPhoneType = types?.firstOrNull()?.value } + vCard.addresses.firstOrNull()?.apply { + address = listOf( + country, + postalCode, + region, + locality, + streetAddress + ).joinToStringNotNullOrBlank(ADDRESS_SEPARATOR) + } + return VCard( firstName, lastName, @@ -103,6 +117,7 @@ data class VCard( secondaryPhoneType, tertiaryPhone, tertiaryPhoneType, + address, geoUri, url ) @@ -123,6 +138,7 @@ data class VCard( "${email.orEmpty()} ${emailType.orEmpty()}", "${secondaryEmail.orEmpty()} ${secondaryEmailType.orEmpty()}", "${tertiaryEmail.orEmpty()} ${tertiaryEmailType.orEmpty()}", + address, geoUri, url ).joinToStringNotNullOrBlankWithLineSeparator() diff --git a/app/src/main/java/com/example/barcodescanner/model/schema/Wifi.kt b/app/src/main/java/com/example/barcodescanner/model/schema/Wifi.kt index c7275e64..b1d274b4 100644 --- a/app/src/main/java/com/example/barcodescanner/model/schema/Wifi.kt +++ b/app/src/main/java/com/example/barcodescanner/model/schema/Wifi.kt @@ -1,6 +1,7 @@ package com.example.barcodescanner.model.schema import com.example.barcodescanner.extension.* +import java.util.* class Wifi( val encryption: String? = null, @@ -14,6 +15,8 @@ class Wifi( ) : Schema { companion object { + private val WIFI_REGEX = """^WIFI:((?:.+?:(?:[^\\;]|\\.)*;)+);?$""".toRegex() + private val PAIR_REGEX = """(.+?):((?:[^\\;]|\\.)*);""".toRegex() private const val SCHEMA_PREFIX = "WIFI:" private const val ENCRYPTION_PREFIX = "T:" private const val NAME_PREFIX = "S:" @@ -30,68 +33,23 @@ class Wifi( return null } - var encryption: String? = null - var name: String? = null - var password: String? = null - var isHidden: Boolean? = null - var anonymousIdentity: String? = null - var identity: String? = null - var eapMethod: String? = null - var phase2Method: String? = null - - text.removePrefixIgnoreCase(SCHEMA_PREFIX) - .split(SEPARATOR) - .forEach { part -> - if (part.startsWithIgnoreCase(ENCRYPTION_PREFIX)) { - encryption = part.removePrefixIgnoreCase(ENCRYPTION_PREFIX) - return@forEach - } - - if (part.startsWithIgnoreCase(NAME_PREFIX)) { - name = part.removePrefixIgnoreCase(NAME_PREFIX) - return@forEach - } - - if (part.startsWithIgnoreCase(PASSWORD_PREFIX)) { - password = part.removePrefixIgnoreCase(PASSWORD_PREFIX) - return@forEach - } - - if (part.startsWithIgnoreCase(IS_HIDDEN_PREFIX)) { - isHidden = part.removePrefixIgnoreCase(IS_HIDDEN_PREFIX).toBoolean() - return@forEach - } - - if (part.startsWithIgnoreCase(ANONYMOUS_IDENTITY_PREFIX)) { - anonymousIdentity = part.removePrefixIgnoreCase(ANONYMOUS_IDENTITY_PREFIX) - return@forEach - } - - if (part.startsWithIgnoreCase(IDENTITY_PREFIX)) { - identity = part.removePrefixIgnoreCase(IDENTITY_PREFIX) - return@forEach - } - - if (part.startsWithIgnoreCase(EAP_PREFIX)) { - eapMethod = part.removePrefixIgnoreCase(EAP_PREFIX) - return@forEach - } - - if (part.startsWithIgnoreCase(PHASE2_PREFIX)) { - phase2Method = part.removePrefixIgnoreCase(PHASE2_PREFIX) - return@forEach - } + val keysAndValuesSubstring = WIFI_REGEX.matchEntire(text)?.groupValues?.get(1) ?: return null + val keysAndValues = PAIR_REGEX + .findAll(keysAndValuesSubstring) + .map { pair -> + "${pair.groupValues[1].toUpperCase(Locale.US)}:" to pair.groupValues[2] } + .toMap() return Wifi( - encryption?.unescape(), - name?.unescape(), - password?.unescape(), - isHidden, - anonymousIdentity?.unescape(), - identity?.unescape(), - eapMethod, - phase2Method + keysAndValues[ENCRYPTION_PREFIX]?.unescape(), + keysAndValues[NAME_PREFIX]?.unescape(), + keysAndValues[PASSWORD_PREFIX]?.unescape(), + keysAndValues[IS_HIDDEN_PREFIX].toBoolean(), + keysAndValues[ANONYMOUS_IDENTITY_PREFIX]?.unescape(), + keysAndValues[IDENTITY_PREFIX]?.unescape(), + keysAndValues[EAP_PREFIX], + keysAndValues[PHASE2_PREFIX] ) } } @@ -99,7 +57,7 @@ class Wifi( override val schema = BarcodeSchema.WIFI override fun toFormattedText(): String { - return listOf(name, password).joinToStringNotNullOrBlankWithLineSeparator() + return listOf(name, encryption, password).joinToStringNotNullOrBlankWithLineSeparator() } override fun toBarcodeText(): String { diff --git a/app/src/main/java/com/example/barcodescanner/usecase/BarcodeDatabase.kt b/app/src/main/java/com/example/barcodescanner/usecase/BarcodeDatabase.kt index a3e591d9..c89a5e88 100644 --- a/app/src/main/java/com/example/barcodescanner/usecase/BarcodeDatabase.kt +++ b/app/src/main/java/com/example/barcodescanner/usecase/BarcodeDatabase.kt @@ -3,6 +3,8 @@ package com.example.barcodescanner.usecase import android.content.Context import androidx.paging.DataSource import androidx.room.* +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import com.example.barcodescanner.model.Barcode import com.example.barcodescanner.model.ExportBarcode import com.example.barcodescanner.model.schema.BarcodeSchema @@ -35,7 +37,7 @@ class BarcodeDatabaseTypeConverter { } -@Database(entities = [Barcode::class], version = 1) +@Database(entities = [Barcode::class], version = 2) abstract class BarcodeDatabaseFactory : RoomDatabase() { abstract fun getBarcodeDatabase(): BarcodeDatabase } @@ -50,6 +52,11 @@ interface BarcodeDatabase { fun getInstance(context: Context): BarcodeDatabase { return INSTANCE ?: Room .databaseBuilder(context.applicationContext, BarcodeDatabaseFactory::class.java, "db") + .addMigrations(object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE codes ADD COLUMN name TEXT") + } + }) .build() .getBarcodeDatabase().apply { INSTANCE = this @@ -58,14 +65,17 @@ interface BarcodeDatabase { } @Query("SELECT * FROM codes ORDER BY date DESC") - fun getAll(): DataSource.Factory + @Query("SELECT * FROM codes WHERE isFavorite = 1 ORDER BY date DESC") fun getFavorites(): DataSource.Factory @Query("SELECT date, format, text FROM codes ORDER BY date DESC") fun getAllForExport(): Single> + @Query("SELECT * FROM codes WHERE format = :format AND text = :text LIMIT 1") + fun find(format: String, text: String): Single> + @Insert(onConflict = OnConflictStrategy.REPLACE) fun save(barcode: Barcode): Single @@ -75,3 +85,22 @@ interface BarcodeDatabase { @Query("DELETE FROM codes") fun deleteAll(): Completable } + +fun BarcodeDatabase.save(barcode: Barcode, doNotSaveDuplicates: Boolean): Single { + return if (doNotSaveDuplicates) { + saveIfNotPresent(barcode) + } else { + save(barcode) + } +} + +fun BarcodeDatabase.saveIfNotPresent(barcode: Barcode): Single { + return find(barcode.format.name, barcode.text) + .flatMap { found -> + if (found.isEmpty()) { + save(barcode) + } else { + Single.just(found[0].id) + } + } +} diff --git a/app/src/main/java/com/example/barcodescanner/usecase/BarcodeImageGenerator.kt b/app/src/main/java/com/example/barcodescanner/usecase/BarcodeImageGenerator.kt index 0cc8806d..b9b125be 100644 --- a/app/src/main/java/com/example/barcodescanner/usecase/BarcodeImageGenerator.kt +++ b/app/src/main/java/com/example/barcodescanner/usecase/BarcodeImageGenerator.kt @@ -39,14 +39,18 @@ object BarcodeImageGenerator { codeColor: Int = Color.BLACK, backgroundColor: Int = Color.WHITE ): Bitmap { - val matrix = encoder.encode( - barcode.text, - barcode.format, - width, - height, - createHints(barcode.errorCorrectionLevel, margin) - ) - return createBitmap(matrix, codeColor, backgroundColor) + try { + val matrix = encoder.encode( + barcode.text, + barcode.format, + width, + height, + createHints(barcode.errorCorrectionLevel, margin) + ) + return createBitmap(matrix, codeColor, backgroundColor) + } catch (ex: Exception) { + throw Exception("Unable to generate barcode image, ${barcode.format}, ${barcode.text}", ex) + } } fun generateSvgAsync(barcode: Barcode, width: Int, height: Int, margin: Int = 0): Single { diff --git a/app/src/main/java/com/example/barcodescanner/usecase/Settings.kt b/app/src/main/java/com/example/barcodescanner/usecase/Settings.kt index 72ae06f1..ce2803d3 100644 --- a/app/src/main/java/com/example/barcodescanner/usecase/Settings.kt +++ b/app/src/main/java/com/example/barcodescanner/usecase/Settings.kt @@ -38,6 +38,7 @@ class Settings(private val context: Context) { IS_BACK_CAMERA, SAVE_SCANNED_BARCODES_TO_HISTORY, SAVE_CREATED_BARCODES_TO_HISTORY, + DO_NOT_SAVE_DUPLICATES, SEARCH_ENGINE, ERROR_REPORTS, } @@ -112,6 +113,10 @@ class Settings(private val context: Context) { get() = get(Key.SAVE_CREATED_BARCODES_TO_HISTORY, true) set(value) = set(Key.SAVE_CREATED_BARCODES_TO_HISTORY, value) + var doNotSaveDuplicates: Boolean + get() = get(Key.DO_NOT_SAVE_DUPLICATES, false) + set(value) = set(Key.DO_NOT_SAVE_DUPLICATES, value) + var searchEngine: SearchEngine get() = get(Key.SEARCH_ENGINE, SearchEngine.NONE) set(value) = set(Key.SEARCH_ENGINE, value) diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 00000000..5b56f6f2 --- /dev/null +++ b/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml new file mode 100644 index 00000000..731bd9fe --- /dev/null +++ b/app/src/main/res/drawable/ic_save.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/layout/activity_all_permissions.xml b/app/src/main/res/layout/activity_all_permissions.xml index 56154299..15313d55 100644 --- a/app/src/main/res/layout/activity_all_permissions.xml +++ b/app/src/main/res/layout/activity_all_permissions.xml @@ -26,6 +26,52 @@ android:layout_height="wrap_content" android:orientation="vertical" > + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_barcode.xml b/app/src/main/res/layout/activity_barcode.xml index 03deb5f5..3d9f0c21 100644 --- a/app/src/main/res/layout/activity_barcode.xml +++ b/app/src/main/res/layout/activity_barcode.xml @@ -50,14 +50,42 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 4e570282..f841eebf 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -154,6 +154,13 @@ android:layout_height="wrap_content" app:text="@string/fragment_settings_save_created_barcodes_to_history" /> + @@ -50,6 +50,7 @@ android:maxLines="1" tools:text="Hello World!" android:includeFontPadding="false" + android:ellipsize="end" style="@style/DefaultTextViewStyle" /> - @@ -24,21 +22,19 @@ android:layout_width="@dimen/icon_button_icon_size" android:layout_height="@dimen/icon_button_icon_size" android:layout_gravity="center_vertical" - android:tint="@color/white" + app:tint="@color/white" tools:src="@drawable/ic_copy" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_barcode.xml b/app/src/main/res/menu/menu_barcode.xml index 5994c4d1..bf31cbdc 100644 --- a/app/src/main/res/menu/menu_barcode.xml +++ b/app/src/main/res/menu/menu_barcode.xml @@ -23,12 +23,22 @@ android:id="@+id/item_add_to_favorites" android:title="@string/activity_barcode_add_to_favorites" android:icon="@drawable/ic_favorite_unchecked" + android:visible="false" app:showAsAction="ifRoom" /> + @@ -36,6 +46,7 @@ android:id="@+id/item_delete" android:title="@string/activity_barcode_delete" android:icon="@drawable/ic_delete" + android:visible="false" app:iconTint="@color/toolbar_menu_color" app:showAsAction="ifRoom" /> diff --git a/app/src/main/res/menu/menu_scan_barcode_from_image.xml b/app/src/main/res/menu/menu_scan_barcode_from_image.xml index 709d93ca..0be5f29e 100644 --- a/app/src/main/res/menu/menu_scan_barcode_from_image.xml +++ b/app/src/main/res/menu/menu_scan_barcode_from_image.xml @@ -8,20 +8,20 @@ android:title="@string/activity_scan_barcode_from_file_rotate_left" android:icon="@drawable/ic_rotate_left" app:iconTint="@color/toolbar_menu_color" - app:showAsAction="always" + app:showAsAction="ifRoom" /> \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 95e4bf55..7f8c8639 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -23,6 +23,16 @@ Verlauf löschen? Löschen? + + Bestätigen + OK + Abbrechen + + + Edit name + OK + Cancel + Scannen Erstellen @@ -33,9 +43,6 @@ Bild lesen Blitz Gespeichert - Bestätigen - OK - Abbrechen Scannen diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 34cc25ef..4db3bcbd 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -1,6 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index b5c12e75..78a2c35b 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -22,6 +22,16 @@ Excluir o histórico? Excluir? + + Confirmar + OK + Cancelar + + + Edit name + OK + Cancel + Escanear Criar @@ -32,9 +42,6 @@ Escanear a imagem Flash Salvado - Confirmar - OK - Cancelar Escanear diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a41ecd8f..187552b0 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -22,6 +22,16 @@ Очистить историю? Удалить? + + Подтвердить + OK + Отмена + + + Изменить название + OK + Отмена + Сканер Создать @@ -32,9 +42,6 @@ Сканировать\nфайл Фонарик Сохранено - Подтвердить - OK - Отмена Сканировать @@ -196,6 +203,8 @@ История Сохранять отсканированные штрихкоды в историю Сохранять созданные штрихкоды в историю + Не сохранять дубликаты + Не сохранять дубликаты в истории Очистить историю Расширенные настройки Поисковые системы @@ -236,12 +245,20 @@ Разрешения + Обычные + Вибрация + Используется для вибрирования при сканировании + Wi-Fi + Используется для подключения к Wi-Fi с помощью QR кода + Интернет + Используется для отправки отчетов об ошибках. Может быть отключено в настройках. + Запрашиваемые Камера - Нужно для сканирования QR и штрихкодов при помощи камеры + Используется для сканирования QR и штрихкодов при помощи камеры Контакты - Нужно для создания QR-кода контакта + Используется для создания QR-кодов контактов Память - Нужно для сохранения QR и штрихкодов + Используется для сохранения QR и штрихкодов Поиск на Rate&Goods diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9c94b944..1ab9ae3a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -191,9 +191,9 @@ 11 位數字 7 位數字 已儲存 - 取消 - OK - 確認 + 取消 + OK + 確認 閃光燈 掃描圖片 關於 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 417ccfe3..8708e6d0 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -7,11 +7,15 @@ + + + + + - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 08a66e6b..c28e44af 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -56,7 +56,7 @@ #B2B2B2 #424242 #6B7D7D7D - #FDD70A + #FDD70A #00B1FF #9EDBF7 #00BFC9 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 23c48af9..9c830b71 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -33,4 +33,6 @@ 2dp 4dp + 20dp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f80670fa..c11aedf9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,6 +22,16 @@ Delete history? Delete? + + Confirm + OK + Cancel + + + Edit name + OK + Cancel + Scan Create @@ -32,9 +42,6 @@ Scan image Flash Saved - Confirm - OK - Cancel Scan @@ -217,6 +224,8 @@ History Save scanned barcodes to history Save created barcodes to history + Do not save duplicates + Avoid duplicates in history Clear history Advanced Search Engines @@ -257,6 +266,14 @@ Permissions + Normal + Vibrate + Used to vibrate on scan + Wi-Fi + Used to connect to Wi-Fi by scanning a QR code + Internet + Used to send error reports. This can be disabled in settings. + Runtime Camera Used to scan QR codes and barcodes from camera Read contacts diff --git a/build.gradle b/build.gradle index a80b4c09..39cf4763 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' + ext.kotlin_version = '1.4.10' repositories { google() jcenter() @@ -9,9 +9,9 @@ buildscript { maven { url "https://jitpack.io" } } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:4.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'io.sentry:sentry-android-gradle-plugin:1.7.25' + classpath 'io.sentry:sentry-android-gradle-plugin:1.7.28' } } diff --git a/fastlane/metadata/android/en-US/changelogs/8.txt b/fastlane/metadata/android/en-US/changelogs/8.txt new file mode 100644 index 00000000..ac65efdd --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/8.txt @@ -0,0 +1,5 @@ +* Add a setting to avoid duplicates in history +* Add an option to manually save to history +* Add an option to set a name to a history item +* Bug fix: app shortcuts were not working +* Bug fix: Wi-Fi QR code passwords were scanned wrong \ No newline at end of file diff --git a/fastlane/metadata/android/ru/changelogs/8.txt b/fastlane/metadata/android/ru/changelogs/8.txt new file mode 100644 index 00000000..388added --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/8.txt @@ -0,0 +1,4 @@ +* Добавлена настройка для сохранения дубликатов в истории +* Добавлена возможность для выборочного сохранения в историю +* Добавлена возможность задать название для отсканированного кода в истории +* Исправление багов \ No newline at end of file