diff --git a/app/build.gradle b/app/build.gradle index 9268bb0fe..2e4307723 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -126,6 +126,9 @@ dependencies { //Glide implementation 'com.github.bumptech.glide:glide:4.9.0' + //二维码 + implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.6' + //颜色选择 implementation 'com.jaredrummler:colorpicker:1.1.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9682d9f2e..91282f69e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,6 +44,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index aafb71f5c..021b8585c 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt @@ -26,7 +26,7 @@ object Restore { } fun importYueDuData(context: Context) { - val yuedu = File(getSdPath(), "YueDu") + val yuedu = File(FileUtils.getSdPath(), "YueDu") val jsonPath = JsonPath.using( Configuration.builder() .options(Option.SUPPRESS_EXCEPTIONS) diff --git a/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt b/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt new file mode 100644 index 000000000..d77022022 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt @@ -0,0 +1,112 @@ +package io.legado.app.ui.qrcode + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.lifecycle.AndroidViewModel +import cn.bingoogolapple.qrcode.core.QRCodeView +import io.legado.app.R +import io.legado.app.base.BaseActivity +import io.legado.app.help.permission.Permissions +import io.legado.app.help.permission.PermissionsCompat +import io.legado.app.utils.FileUtils +import io.legado.app.utils.getViewModel +import kotlinx.android.synthetic.main.activity_qrcode_capture.* +import kotlinx.android.synthetic.main.view_title_bar.* + +class QrCodeActivity : BaseActivity(), QRCodeView.Delegate { + override val viewModel: AndroidViewModel + get() = getViewModel(AndroidViewModel::class.java) + override val layoutID: Int + get() = R.layout.activity_qrcode_capture + + private val requestQrImage = 202 + private var flashlightIsOpen: Boolean = false + + override fun onViewModelCreated(viewModel: AndroidViewModel, savedInstanceState: Bundle?) { + setSupportActionBar(toolbar) + zxingview.setDelegate(this) + fab_flashlight.setOnClickListener { + if (flashlightIsOpen) { + flashlightIsOpen = false + zxingview.closeFlashlight() + } else { + flashlightIsOpen = true + zxingview.openFlashlight() + } + } + } + + override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.qr_code_scan, menu) + return super.onCompatCreateOptionsMenu(menu) + } + + override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_choose_from_gallery -> { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "image/*" + startActivityForResult(intent, requestQrImage) + } + } + return super.onCompatOptionsItemSelected(item) + } + + override fun onStart() { + super.onStart() + startCamera() + } + + private fun startCamera() { + PermissionsCompat.Builder(this) + .addPermissions(*Permissions.Group.CAMERA) + .rationale(R.string.qr_per) + .onGranted { + zxingview.startCamera() // 打开后置摄像头开始预览,但是并未开始识别 + zxingview.startSpotAndShowRect() // 显示扫描框,并开始识别 + }.build() + } + + override fun onStop() { + zxingview.stopCamera() // 关闭摄像头预览,并且隐藏扫描框 + super.onStop() + } + + override fun onDestroy() { + zxingview.onDestroy() // 销毁二维码扫描控件 + super.onDestroy() + } + + override fun onScanQRCodeSuccess(result: String) { + val intent = Intent() + intent.putExtra("result", result) + setResult(RESULT_OK, intent) + finish() + } + + override fun onCameraAmbientBrightnessChanged(isDark: Boolean) { + + } + + override fun onScanQRCodeOpenCameraError() { + + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + data?.let { + zxingview.startSpotAndShowRect() // 显示扫描框,并开始识别 + + if (resultCode == Activity.RESULT_OK && requestCode == requestQrImage) { + val picturePath = FileUtils.getPath(this, it.data) + // 本来就用到 QRCodeView 时可直接调 QRCodeView 的方法,走通用的回调 + zxingview.decodeQRCode(picturePath) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/FileUtils.kt b/app/src/main/java/io/legado/app/utils/FileUtils.kt index 493347429..8c5ba3823 100644 --- a/app/src/main/java/io/legado/app/utils/FileUtils.kt +++ b/app/src/main/java/io/legado/app/utils/FileUtils.kt @@ -1,5 +1,235 @@ package io.legado.app.utils +import android.annotation.TargetApi +import android.content.ContentUris +import android.content.Context +import android.net.Uri +import android.os.Build import android.os.Environment +import android.os.storage.StorageManager +import android.provider.DocumentsContract +import android.provider.MediaStore +import android.util.Log +import androidx.core.content.ContextCompat +import java.io.File +import java.io.IOException +import java.lang.reflect.Array +import java.util.* -fun getSdPath() = Environment.getExternalStorageDirectory().absolutePath + +object FileUtils { + fun getSdPath() = Environment.getExternalStorageDirectory().absolutePath + + fun getFileByPath(filePath: String): File? { + return if (isSpace(filePath)) null else File(filePath) + } + + fun isSpace(s: String?): Boolean { + if (s == null) return true + var i = 0 + val len = s.length + while (i < len) { + if (!Character.isWhitespace(s[i])) { + return false + } + ++i + } + return true + } + + fun getSdCardPath(): String { + var sdCardDirectory = Environment.getExternalStorageDirectory().absolutePath + + try { + sdCardDirectory = File(sdCardDirectory).canonicalPath + } catch (ioe: IOException) { + } + + return sdCardDirectory + } + + fun getStorageData(pContext: Context): ArrayList? { + + val storageManager = pContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager + + try { + val getVolumeList = storageManager.javaClass.getMethod("getVolumeList") + + val storageValumeClazz = Class.forName("android.os.storage.StorageVolume") + val getPath = storageValumeClazz.getMethod("getPath") + + val invokeVolumeList = getVolumeList.invoke(storageManager) + val length = Array.getLength(invokeVolumeList) + + val list = ArrayList() + for (i in 0 until length) { + val storageValume = Array.get(invokeVolumeList, i)//得到StorageVolume对象 + val path = getPath.invoke(storageValume) as String + + list.add(path) + } + return list + } catch (e: Exception) { + e.printStackTrace() + } + + return null + } + + + fun getExtSdCardPaths(con: Context): ArrayList { + val paths = ArrayList() + val files = ContextCompat.getExternalFilesDirs(con, "external") + val firstFile = files[0] + for (file in files) { + if (file != null && file != firstFile) { + val index = file.absolutePath.lastIndexOf("/Android/data") + if (index < 0) { + Log.w("", "Unexpected external file dir: " + file.absolutePath) + } else { + var path = file.absolutePath.substring(0, index) + try { + path = File(path).canonicalPath + } catch (e: IOException) { + // Keep non-canonical path. + } + + paths.add(path) + } + } + } + return paths + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + fun getPath(context: Context, uri: Uri): String? { + val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + val docId = DocumentsContract.getDocumentId(uri) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val type = split[0] + + if ("primary".equals(type, ignoreCase = true)) { + return Environment.getExternalStorageDirectory().toString() + "/" + split[1] + } + + } else if (isDownloadsDocument(uri)) { + val id = DocumentsContract.getDocumentId(uri) + val split = id.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val type = split[0] + if ("raw".equals( + type, + ignoreCase = true + ) + ) { //处理某些机型(比如Goole Pixel )ID是raw:/storage/emulated/0/Download/c20f8664da05ab6b4644913048ea8c83.mp4 + return split[1] + } + + val contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id) + ) + + return getDataColumn(context, contentUri, null, null) + } else if (isMediaDocument(uri)) { + val docId = DocumentsContract.getDocumentId(uri) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val type = split[0] + + var contentUri: Uri? = null + if ("image" == type) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } else if ("video" == type) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI + } else if ("audio" == type) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + } + + val selection = "_id=?" + val selectionArgs = arrayOf(split[1]) + + return getDataColumn(context, contentUri, selection, selectionArgs) + }// MediaProvider + // DownloadsProvider + } else if ("content".equals(uri.scheme!!, ignoreCase = true)) { + + // Return the remote address + return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn( + context, + uri, + null, + null + ) + + } else if ("file".equals(uri.scheme!!, ignoreCase = true)) { + return uri.path + }// File + // MediaStore (and general) + + return null + } + + fun getDataColumn( + context: Context, uri: Uri?, selection: String?, + selectionArgs: kotlin.Array? + ): String? { + + val column = "_data" + val projection = arrayOf(column) + + try { + context.contentResolver.query( + uri!!, + projection, + selection, + selectionArgs, + null + )!!.use { cursor -> + if (cursor != null && cursor.moveToFirst()) { + val index = cursor.getColumnIndexOrThrow(column) + return cursor.getString(index) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + + return null + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + fun isExternalStorageDocument(uri: Uri): Boolean { + return "com.android.externalstorage.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + fun isDownloadsDocument(uri: Uri): Boolean { + return "com.android.providers.downloads.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + fun isMediaDocument(uri: Uri): Boolean { + return "com.android.providers.media.documents" == uri.authority + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + fun isGooglePhotosUri(uri: Uri): Boolean { + return "com.google.android.apps.photos.content" == uri.authority + } + +} diff --git a/app/src/main/res/layout/activity_qrcode_capture.xml b/app/src/main/res/layout/activity_qrcode_capture.xml new file mode 100644 index 000000000..71df5842b --- /dev/null +++ b/app/src/main/res/layout/activity_qrcode_capture.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/qr_code_scan.xml b/app/src/main/res/menu/qr_code_scan.xml new file mode 100644 index 000000000..00bb7ec15 --- /dev/null +++ b/app/src/main/res/menu/qr_code_scan.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file