Przeglądaj źródła

Feature: add Signature to PDF file

cooperku_kdanmobile 5 lat temu
rodzic
commit
6222c2d0a6

+ 4 - 0
reader/src/main/AndroidManifest.xml

@@ -9,6 +9,10 @@
         <activity
             android:name=".screen.ViewerSettingActivity"
             android:theme="@style/ReaderActivityNoActionBarNoTitle" />
+        <activity
+            android:name=".screen.SignatureActivity"
+            android:screenOrientation="landscape"
+            android:theme="@style/ReaderActivityNoActionBarNoTitle" />
     </application>
 
 </manifest>

+ 9 - 0
reader/src/main/java/com/kdanmobile/reader/Config.kt

@@ -1,6 +1,15 @@
 package com.kdanmobile.reader
 
+import android.content.Context
+import java.io.File
+
 object Config {
     var PDF_SDK_LICENSE = ""
     var PDF_SDK_RSA_MSG = ""
+
+    const val SIGNATURE_FOLDER = "Signature"
+
+    fun getSignatureDirectoryPath(context: Context) : String {
+        return "${context.filesDir.absolutePath}${File.separator}$SIGNATURE_FOLDER"
+    }
 }

+ 6 - 0
reader/src/main/java/com/kdanmobile/reader/ReaderActivity.kt

@@ -27,6 +27,7 @@ import com.kdanmobile.reader.utils.DensityUtil
 import com.kdanmobile.reader.screen.ViewerSettingActivity
 import com.kdanmobile.reader.screen.view.*
 import com.kdanmobile.reader.screen.view.edit.ShapeTabView
+import com.kdanmobile.reader.screen.view.edit.SignatureTabView
 import com.kdanmobile.reader.screen.view.edit.StampTabView
 import com.kdanmobile.reader.screen.view.edit.TextBoxTabView
 import kotlinx.android.synthetic.main.activity_reader.*
@@ -376,6 +377,11 @@ abstract class ReaderActivity : AppCompatActivity() {
                     viewModel.setTextBoxAttribute(textBoxTabView.getTextBoxAttribute())
                 }
 
+                override fun onTabSignatureAddButtonClick(signatureTabView: SignatureTabView) {
+                    hideAllToolbars()
+                    viewModel.setSignatureAttribute(signatureTabView.getSignatureAttribute())
+                }
+
                 override fun onTabStampAddButtonClick(stampTabView: StampTabView) {
                     hideAllToolbars()
                     viewModel.setStampAttribute(stampTabView.getStampAttribute())

+ 11 - 1
reader/src/main/java/com/kdanmobile/reader/ReaderViewModel.kt

@@ -13,10 +13,11 @@ import com.kdanmobile.kmpdfkit.globaldata.AnnotConfig
 import com.kdanmobile.kmpdfkit.globaldata.Config
 import com.kdanmobile.kmpdfkit.globaldata.KMPDFAnnotEditMode
 import com.kdanmobile.kmpdfkit.manager.KMPDFFactory
-import com.kdanmobile.kmpdfkit.manager.controller.KMPDFDocumentController
+import com.kdanmobile.kmpdfkit.manager.controller.*
 import com.kdanmobile.kmpdfkit.manager.listener.KMPDFAddAnnotCallback
 import com.kdanmobile.kmpdfkit.pdfcommon.*
 import com.kdanmobile.reader.screen.data.ShapeAttribute
+import com.kdanmobile.reader.screen.data.SignatureAttribute
 import com.kdanmobile.reader.screen.data.StampAttribute
 import com.kdanmobile.reader.screen.data.TextBoxAttribute
 import com.kdanmobile.reader.screen.handler.*
@@ -240,6 +241,15 @@ class ReaderViewModel(private val pdfSdkLicense: String, private val pdfSdkRsaMs
         kmpdfFactory?.setAnnotationEditMode(KMPDFAnnotationBean.AnnotationType.FREETEXT)
     }
 
+    fun setSignatureAttribute(attr: SignatureAttribute) {
+
+        val kmpdfSignatureAnnotationBean = KMPDFSignatureAnnotationBean("", attr.path, KMPDFSignatureController.OnSignImageCreateListener {
+            println("onSignImageCreated")
+        })
+        kmpdfFactory?.setAnnotationAttribute(kmpdfSignatureAnnotationBean)
+        kmpdfFactory?.setAnnotationEditMode(KMPDFAnnotationBean.AnnotationType.SIGNATURE)
+    }
+
     fun setStampAttribute(attr: StampAttribute) {
 
         val kmpdfStampAnnotationBean = when (attr.isStandardStamp()) {

+ 146 - 0
reader/src/main/java/com/kdanmobile/reader/screen/SignatureActivity.kt

@@ -0,0 +1,146 @@
+package com.kdanmobile.reader.screen
+
+import android.support.v7.app.AppCompatActivity
+import android.os.Bundle
+import android.view.MenuItem
+import android.view.View
+import android.widget.SeekBar
+import com.kdanmobile.reader.R
+import com.kdanmobile.reader.screen.view.ColorSelectView
+import com.kdanmobile.reader.screen.view.edit.SignatureDrawView
+import kotlinx.android.synthetic.main.activity_view_signature_create.*
+import android.graphics.Bitmap
+import android.support.v4.content.ContextCompat
+import com.kdanmobile.reader.Config
+import com.kdanmobile.reader.screen.view.edit.OnSignatureAddListener
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+
+class SignatureActivity : AppCompatActivity() {
+
+    companion object {
+        var signatureAddNotifyManager: OnSignatureAddListener? = null
+    }
+
+    private var onSignatureAddListener: OnSignatureAddListener? = null
+    private var noSignature = true
+        set(value) {
+            field = value
+            when (field) {
+                true -> {
+                    tv_writeHere.visibility = View.VISIBLE
+                    btn_save_signature.setTextColor(ContextCompat.getColor(baseContext, android.R.color.darker_gray))
+                    btn_save_signature.isClickable = false
+                }
+                false -> {
+                    tv_writeHere.visibility = View.INVISIBLE
+                    btn_save_signature.setTextColor(ContextCompat.getColor(baseContext, android.R.color.white))
+                    btn_save_signature.isClickable = true
+                }
+            }
+        }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_view_signature_create)
+
+        onSignatureAddListener = signatureAddNotifyManager
+        signatureAddNotifyManager = null
+
+        setupToolbar()
+
+        colorChooser_signature.onColorSelectedListener = object : ColorSelectView.OnColorSelectedListener {
+            override fun onColorSelected(color: Int) {
+                drawView.paintColor = color
+            }
+        }
+        drawView.paintColor = colorChooser_signature.getSelectedColor()
+
+        seekBar_signature.onSeekBarChangeListener = object : SeekBar.OnSeekBarChangeListener {
+            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
+                drawView.strokeWidth = seekBar_signature.currentValue.toFloat()
+            }
+
+            override fun onStartTrackingTouch(seekBar: SeekBar?) {}
+
+            override fun onStopTrackingTouch(seekBar: SeekBar?) {
+                drawView.strokeWidth = seekBar_signature.currentValue.toFloat()
+            }
+        }
+        drawView.strokeWidth = seekBar_signature.currentValue.toFloat()
+
+        btn_save_signature.setOnClickListener {
+            if (!noSignature) {
+                val bitmap = drawView.getBitmap()
+
+                val argb = IntArray(bitmap.width * bitmap.height)
+                bitmap.getPixels(argb, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
+                var minX = bitmap.width
+                var maxX = 0
+                var minY = bitmap.height
+                var maxY = 0
+                var index = 0
+                for (y in 0 until bitmap.height) {
+                    for (x in 0 until bitmap.width) {
+                        val alpha = (argb[index] shr 24) and 0xff
+                        if (alpha > 10) {
+                            minX = Math.min(x, minX)
+                            maxX = Math.max(x, maxX)
+                            minY = Math.min(y, minY)
+                            maxY = Math.max(y, maxY)
+                        }
+                        index++
+                    }
+                }
+
+                val cropBitmap = Bitmap.createBitmap(bitmap, minX, minY, maxX - minX + 1, maxY - minY + 1)
+
+                val path = Config.getSignatureDirectoryPath(baseContext)
+                val dir = File(path)
+                if (!dir.exists() || !dir.isDirectory) {
+                    dir.mkdirs()
+                }
+                val filename = "$path${File.separator}Signature${System.currentTimeMillis()}.png"
+                try {
+                    val fileOutputStream = FileOutputStream(filename)
+                    cropBitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
+                    fileOutputStream.close()
+                } catch (e: IOException) {
+                    e.printStackTrace()
+                }
+
+                onSignatureAddListener?.onSignatureAdd(filename)
+                finish()
+            }
+        }
+
+        btn_clear_signature.setOnClickListener {
+            drawView.clear()
+        }
+
+        drawView.onSignatureDrawListener = object : SignatureDrawView.OnSignatureDrawListener {
+            override fun onClearSignature() {
+                noSignature = true
+            }
+
+            override fun onStartSignature() {
+                noSignature = false
+            }
+        }
+        noSignature = true
+    }
+
+    private fun setupToolbar() {
+        setSupportActionBar(toolbar_signature)
+        supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close)
+        supportActionBar?.setDisplayHomeAsUpEnabled(true)
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
+        when (item?.itemId) {
+            android.R.id.home -> finish()
+        }
+        return super.onOptionsItemSelected(item)
+    }
+}

+ 3 - 0
reader/src/main/java/com/kdanmobile/reader/screen/data/SignatureAttribute.kt

@@ -0,0 +1,3 @@
+package com.kdanmobile.reader.screen.data
+
+data class SignatureAttribute(val path: String)

+ 10 - 6
reader/src/main/java/com/kdanmobile/reader/screen/view/ViewerEditView.kt

@@ -8,10 +8,7 @@ import android.view.LayoutInflater
 import android.view.View
 import android.widget.ImageButton
 import com.kdanmobile.reader.R
-import com.kdanmobile.reader.screen.view.edit.ShapeTabView
-import com.kdanmobile.reader.screen.view.edit.StampTabView
-import com.kdanmobile.reader.screen.view.edit.TextBoxTabView
-import com.kdanmobile.reader.screen.view.edit.TitleButton
+import com.kdanmobile.reader.screen.view.edit.*
 import kotlinx.android.synthetic.main.view_viewer_edit.view.*
 import kotlinx.android.synthetic.main.view_viewer_edit_tab.view.*
 
@@ -97,10 +94,16 @@ class ViewerEditView : ConstraintLayout {
     }
 
     private fun setupSignatureView(view: View) {
-        //TODO implements tab view and add to viewEdit_layout_tab_content
         val changed = setupContent(view as ImageButton, ViewerEditTabType.SIGNATURE)
         if (changed) {
-            LayoutInflater.from(context).inflate(R.layout.view_viewer_edit_tab, viewEdit_layout_tab_content)
+            val signatureTabView = SignatureTabView(context)
+            val params = ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.MATCH_PARENT)
+            viewEdit_layout_tab_content.addView(signatureTabView, params)
+            signatureTabView.onTitleButtonClickListener = object: TitleButton.OnTitleButtonClickListener {
+                override fun onTitleButtonClick() {
+                    onViewerEditTabAddButtonClickListener?.onTabSignatureAddButtonClick(signatureTabView)
+                }
+            }
         }
     }
 
@@ -142,6 +145,7 @@ class ViewerEditView : ConstraintLayout {
 
     interface OnViewerEditTabAddButtonClickListener {
         fun onTabTextBoxAddButtonClick(textBoxTabView: TextBoxTabView)
+        fun onTabSignatureAddButtonClick(signatureTabView: SignatureTabView)
         fun onTabStampAddButtonClick(stampTabView: StampTabView)
         fun onTabShapeAddButtonClick(shapeTabView: ShapeTabView)
     }

+ 5 - 0
reader/src/main/java/com/kdanmobile/reader/screen/view/edit/OnSignatureAddListener.kt

@@ -0,0 +1,5 @@
+package com.kdanmobile.reader.screen.view.edit
+
+interface OnSignatureAddListener {
+    fun onSignatureAdd(path: String)
+}

+ 5 - 0
reader/src/main/java/com/kdanmobile/reader/screen/view/edit/OnSignatureClickListener.kt

@@ -0,0 +1,5 @@
+package com.kdanmobile.reader.screen.view.edit
+
+interface OnSignatureClickListener {
+    fun onSignatureClick(path: String)
+}

+ 5 - 0
reader/src/main/java/com/kdanmobile/reader/screen/view/edit/OnSignatureRemoveListener.kt

@@ -0,0 +1,5 @@
+package com.kdanmobile.reader.screen.view.edit
+
+interface OnSignatureRemoveListener {
+    fun onSignatureRemove(path: String)
+}

+ 127 - 0
reader/src/main/java/com/kdanmobile/reader/screen/view/edit/SignatureAdapter.kt

@@ -0,0 +1,127 @@
+package com.kdanmobile.reader.screen.view.edit
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Color
+import android.graphics.Matrix
+import android.support.v4.util.LruCache
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import com.kdanmobile.reader.R
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import kotlinx.android.synthetic.main.view_viewer_edit_item_signature.view.*
+
+class SignatureAdapter(private val context: Context, private var signaturePaths: ArrayList<String>): RecyclerView.Adapter<SignatureViewHolder>() {
+
+    companion object {
+        val maxMemory = Runtime.getRuntime().maxMemory() / 16
+
+        val mMemoryCache: LruCache<String, Bitmap> = object: LruCache<String, Bitmap>(maxMemory.toInt()) {
+            override fun sizeOf(key: String, value: Bitmap): Int {
+                return value.byteCount
+            }
+        }
+    }
+
+    private var onSignatureClickListener: OnSignatureClickListener? = null
+    private var onSignatureRemoveListener: OnSignatureRemoveListener? = null
+
+    override fun onBindViewHolder(holder: SignatureViewHolder, position: Int) {
+        holder.filename = signaturePaths[position]
+        holder.loadThumbSync()
+        holder.ivSignature.setOnClickListener {
+            onSignatureClickListener?.onSignatureClick(holder.filename)
+        }
+        holder.btnClose.setOnClickListener {
+            onSignatureRemoveListener?.onSignatureRemove(signaturePaths[holder.adapterPosition])
+        }
+    }
+
+    override fun getItemCount(): Int {
+        return signaturePaths.size
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SignatureViewHolder {
+        val view = LayoutInflater.from(context).inflate(R.layout.view_viewer_edit_item_signature, null)
+        return SignatureViewHolder(view)
+    }
+
+    override fun onViewRecycled(holder: SignatureViewHolder) {
+        super.onViewRecycled(holder)
+        holder.stopLoadThumbSync()
+    }
+
+    fun setOnSignatureClickListener(onSignatureClickListener: OnSignatureClickListener?) {
+        this.onSignatureClickListener = onSignatureClickListener
+    }
+
+    fun setOnSignatureRemoveListener(onSignatureRemoveListener: OnSignatureRemoveListener?) {
+        this.onSignatureRemoveListener = onSignatureRemoveListener
+    }
+}
+
+class SignatureViewHolder(view: View): RecyclerView.ViewHolder(view) {
+    val ivSignature: ImageView = view.iv_signature
+    val btnClose: View = view.btn_close
+    var filename: String = ""
+    private var disposable: Disposable? = null
+
+    fun loadThumbSync() {
+        stopLoadThumbSync()
+
+        disposable = Observable.create<Bitmap> { emitter ->
+            val bitmap: Bitmap
+            val value = SignatureAdapter.mMemoryCache[filename]
+            if (null != value) {
+                bitmap = value
+            } else {
+                bitmap = BitmapFactory.decodeFile(filename)
+                SignatureAdapter.mMemoryCache.put(filename, bitmap)
+            }
+            emitter.onNext(bitmap)
+            emitter.onComplete()
+        }
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({
+                    ivSignature.setImageBitmap(it)
+                    ivSignature.invalidate()
+                    setScaleTypeFixLeft(ivSignature)
+                }, {
+                    it.printStackTrace()
+                })
+    }
+
+    private fun setScaleTypeFixLeft(imageView: ImageView) {
+
+        val drawableWidth = imageView.drawable.intrinsicWidth
+        val drawableHeight = imageView.drawable.intrinsicHeight
+        if (drawableWidth != 0 && drawableHeight != 0) {
+            val matrix = Matrix()
+            val viewWidth = imageView.width - imageView.paddingLeft - imageView.paddingRight
+            val viewHeight = imageView.height - imageView.paddingTop - imageView.paddingBottom
+            val scaleRatioX = viewWidth.toFloat() / drawableWidth
+            val scaleRatioY = viewHeight.toFloat() / drawableHeight
+            val scaleFactor = Math.min(scaleRatioX, scaleRatioY)
+            matrix.setScale(scaleFactor, scaleFactor)
+            matrix.postTranslate(0f, (viewHeight - drawableHeight * scaleFactor) * 0.5f)
+            imageView.imageMatrix = matrix
+        }
+    }
+
+    internal fun stopLoadThumbSync() {
+        if (null != disposable && !disposable!!.isDisposed) {
+            disposable?.dispose()
+            disposable = null
+            ivSignature.setImageBitmap(null)
+            ivSignature.invalidate()
+        }
+    }
+}

+ 18 - 0
reader/src/main/java/com/kdanmobile/reader/screen/view/edit/SignatureRecyclerViewItemDecoration.kt

@@ -0,0 +1,18 @@
+package com.kdanmobile.reader.screen.view.edit
+
+import android.graphics.Rect
+import android.view.View
+import android.support.v7.widget.RecyclerView
+
+
+class SignatureRecyclerViewItemDecoration(private val space: Int, private val additionalPadding: Int) : RecyclerView.ItemDecoration() {
+
+    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
+        val position = parent.getChildAdapterPosition(view)
+        val childCount = parent.adapter?.itemCount ?: 0
+        outRect.bottom = when (position == childCount - 1) {
+            true -> space + additionalPadding
+            false -> space
+        }
+    }
+}

+ 101 - 0
reader/src/main/java/com/kdanmobile/reader/screen/view/edit/SignatureTabView.kt

@@ -0,0 +1,101 @@
+package com.kdanmobile.reader.screen.view.edit
+
+import android.content.Context
+import android.content.Intent
+import android.support.constraint.ConstraintLayout
+import android.support.v7.widget.GridLayoutManager
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import com.kdanmobile.reader.Config
+import com.kdanmobile.reader.R
+import com.kdanmobile.reader.screen.SignatureActivity
+import com.kdanmobile.reader.screen.data.SignatureAttribute
+import kotlinx.android.synthetic.main.view_viewer_edit_tab_signature.view.*
+import java.io.File
+import java.util.*
+import android.support.v7.widget.DividerItemDecoration
+import com.kdanmobile.reader.utils.DensityUtil
+
+
+class SignatureTabView : ConstraintLayout {
+
+    var onTitleButtonClickListener: TitleButton.OnTitleButtonClickListener? = null
+
+    private lateinit var signaturePaths: ArrayList<String>
+    private lateinit var selectedSignaturePath: String
+
+    constructor(context: Context) : super(context) {
+        initView()
+    }
+
+    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+        initView()
+    }
+
+    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
+        initView()
+    }
+
+    private fun initView() {
+        LayoutInflater.from(context).inflate(R.layout.view_viewer_edit_tab_signature, this)
+
+        updateSignaturePaths()
+        updateSignatureMessage()
+
+        recyclerView_signature.layoutManager = GridLayoutManager(context, 1)
+        val adapter = SignatureAdapter(context, signaturePaths)
+        adapter.setOnSignatureClickListener(object : OnSignatureClickListener {
+            override fun onSignatureClick(path: String) {
+                selectedSignaturePath = path
+                onTitleButtonClickListener?.onTitleButtonClick()
+            }
+        })
+        adapter.setOnSignatureRemoveListener(object : OnSignatureRemoveListener {
+            override fun onSignatureRemove(path: String) {
+                val position = signaturePaths.indexOf(path)
+                signaturePaths.removeAt(position)
+                adapter.notifyItemRemoved(position)
+                File(path).delete()
+                updateSignatureMessage()
+            }
+        })
+        btn_add_signature.setOnClickListener {
+            SignatureActivity.signatureAddNotifyManager = object : OnSignatureAddListener {
+                override fun onSignatureAdd(path: String) {
+                    signaturePaths.add(path)
+                    adapter.notifyDataSetChanged()
+                    recyclerView_signature.smoothScrollToPosition(signaturePaths.size - 1)
+                    updateSignatureMessage()
+                }
+            }
+            context.startActivity(Intent(context, SignatureActivity::class.java))
+        }
+        recyclerView_signature.adapter = adapter
+        recyclerView_signature.setHasFixedSize(true)
+
+        recyclerView_signature.addItemDecoration(SignatureRecyclerViewItemDecoration(DensityUtil.dp2px(context, 8f), DensityUtil.dp2px(context, 88f)))
+    }
+
+    fun getSignatureAttribute() : SignatureAttribute {
+        return SignatureAttribute(selectedSignaturePath)
+    }
+
+    private fun updateSignaturePaths() {
+        val path = Config.getSignatureDirectoryPath(context)
+        val array = File(path).list { _, name -> name.toLowerCase(Locale.US).endsWith(".png") }
+        signaturePaths = when (null != array) {
+            true -> array.toCollection(ArrayList())
+            false -> arrayListOf()
+        }
+        signaturePaths.sort()
+        for (i in 0 until signaturePaths.size) {
+            signaturePaths[i] = "$path${File.separator}${signaturePaths[i]}"
+        }
+    }
+
+    private fun updateSignatureMessage() {
+        tv_noSignature.visibility = if (signaturePaths.size == 0) View.VISIBLE else View.INVISIBLE
+        recyclerView_signature.visibility = if (signaturePaths.size == 0) View.INVISIBLE else View.VISIBLE
+    }
+}