pull/32/head
parent
59f74e05d8
commit
2ba5ccc67d
@ -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<AndroidViewModel>(), 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) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,5 +1,235 @@ |
|||||||
package io.legado.app.utils |
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.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.* |
||||||
|
|
||||||
|
|
||||||
|
object FileUtils { |
||||||
fun getSdPath() = Environment.getExternalStorageDirectory().absolutePath |
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<String>? { |
||||||
|
|
||||||
|
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<String>() |
||||||
|
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<String> { |
||||||
|
val paths = ArrayList<String>() |
||||||
|
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>? |
||||||
|
): 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 |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
@ -0,0 +1,81 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
android:fitsSystemWindows="true"> |
||||||
|
|
||||||
|
<cn.bingoogolapple.qrcode.zxing.ZXingView |
||||||
|
android:id="@+id/zxingview" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
app:qrcv_animTime="1000" |
||||||
|
app:qrcv_barCodeTipText="将条码放入框内,即可自动扫描" |
||||||
|
app:qrcv_barcodeRectHeight="120dp" |
||||||
|
app:qrcv_borderColor="@android:color/white" |
||||||
|
app:qrcv_borderSize="1dp" |
||||||
|
app:qrcv_cornerColor="@color/colorPrimaryDark" |
||||||
|
app:qrcv_cornerDisplayType="center" |
||||||
|
app:qrcv_cornerLength="20dp" |
||||||
|
app:qrcv_cornerSize="3dp" |
||||||
|
app:qrcv_isAutoZoom="true" |
||||||
|
app:qrcv_isBarcode="false" |
||||||
|
app:qrcv_isOnlyDecodeScanBoxArea="false" |
||||||
|
app:qrcv_isScanLineReverse="true" |
||||||
|
app:qrcv_isShowDefaultGridScanLineDrawable="false" |
||||||
|
app:qrcv_isShowDefaultScanLineDrawable="true" |
||||||
|
app:qrcv_isShowLocationPoint="true" |
||||||
|
app:qrcv_isShowTipBackground="true" |
||||||
|
app:qrcv_isShowTipTextAsSingleLine="false" |
||||||
|
app:qrcv_isTipTextBelowRect="false" |
||||||
|
app:qrcv_maskColor="#33FFFFFF" |
||||||
|
app:qrcv_qrCodeTipText="将二维码放入框内,即可自动扫描" |
||||||
|
app:qrcv_rectWidth="300dp" |
||||||
|
app:qrcv_scanLineColor="@color/colorPrimaryDark" |
||||||
|
app:qrcv_scanLineMargin="0dp" |
||||||
|
app:qrcv_scanLineSize="0.5dp" |
||||||
|
app:qrcv_tipTextColor="@android:color/white" |
||||||
|
app:qrcv_tipTextSize="12sp" |
||||||
|
app:qrcv_toolbarHeight="?attr/actionBarSize" |
||||||
|
app:qrcv_topOffset="65dp" |
||||||
|
app:qrcv_verticalBias="-1" /> |
||||||
|
|
||||||
|
|
||||||
|
<io.legado.app.ui.widget.TitleBar |
||||||
|
android:id="@+id/title_bar" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
app:title="@string/scan_qr_code" /> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_alignParentStart="true" |
||||||
|
android:layout_alignParentBottom="true" |
||||||
|
android:padding="16dp"> |
||||||
|
|
||||||
|
<Space |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_weight="1" /> |
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton |
||||||
|
android:id="@+id/fab_flashlight" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_margin="16dp" |
||||||
|
android:contentDescription="@string/read_aloud" |
||||||
|
android:src="@drawable/ic_daytime" |
||||||
|
android:tint="@color/tv_text_default" |
||||||
|
app:backgroundTint="@color/background_menu" |
||||||
|
app:elevation="2dp" |
||||||
|
app:fabSize="mini" |
||||||
|
app:pressedTranslationZ="2dp" /> |
||||||
|
|
||||||
|
<Space |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_weight="1" /> |
||||||
|
|
||||||
|
</LinearLayout> |
||||||
|
</RelativeLayout> |
@ -0,0 +1,10 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"> |
||||||
|
|
||||||
|
<item |
||||||
|
android:id="@+id/action_choose_from_gallery" |
||||||
|
android:title="@string/gallery" |
||||||
|
app:showAsAction="always" /> |
||||||
|
|
||||||
|
</menu> |
Loading…
Reference in new issue