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 |
||||
|
||||
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<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