diff --git a/app/src/main/kotlin/com/simplemobiletools/draw/MyCanvas.kt b/app/src/main/kotlin/com/simplemobiletools/draw/MyCanvas.kt index 9d6ac12..318355c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/draw/MyCanvas.kt +++ b/app/src/main/kotlin/com/simplemobiletools/draw/MyCanvas.kt @@ -14,12 +14,12 @@ import com.simplemobiletools.commons.extensions.getContrastColor import java.util.* class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { - private val mPaint: Paint - private var mPath: MyPath? = null - private var mPaths: MutableMap? = null - private var mListener: PathsChangedListener? = null + var mPaths: MutableMap + private var mPaint: Paint + private var mPath: MyPath + private var mPaintOptions: PaintOptions - private var mPaintOptions: PaintOptions? = null + private var mListener: PathsChangedListener? = null private var mCurX = 0f private var mCurY = 0f private var mStartX = 0f @@ -28,19 +28,19 @@ class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { private var mIsStrokeWidthBarEnabled = false init { - mPath = MyPath() - mPaint = Paint() mPaintOptions = PaintOptions() - mPaint.color = mPaintOptions!!.color - mPaint.style = Paint.Style.STROKE - mPaint.strokeJoin = Paint.Join.ROUND - mPaint.strokeCap = Paint.Cap.ROUND - mPaint.strokeWidth = mPaintOptions!!.strokeWidth - mPaint.isAntiAlias = true + mPaint = Paint().apply { + color = mPaintOptions.color + style = Paint.Style.STROKE + strokeJoin = Paint.Join.ROUND + strokeCap = Paint.Cap.ROUND + strokeWidth = mPaintOptions.strokeWidth + isAntiAlias = true + } mPaths = LinkedHashMap() - mPaths!!.put(mPath!!, mPaintOptions!!) + mPaths.put(mPath, mPaintOptions) pathsUpdated() } @@ -49,28 +49,25 @@ class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { } fun undo() { - if (mPaths!!.isEmpty()) + if (mPaths.isEmpty()) return - var lastKey: MyPath? = null - for (key in mPaths!!.keys) { - lastKey = key - } + val lastKey: MyPath? = mPaths.keys.lastOrNull() - mPaths!!.remove(lastKey) + mPaths.remove(lastKey) pathsUpdated() invalidate() } fun setColor(newColor: Int) { - mPaintOptions!!.color = newColor + mPaintOptions.color = newColor if (mIsStrokeWidthBarEnabled) { invalidate() } } fun setStrokeWidth(newStrokeWidth: Float) { - mPaintOptions!!.strokeWidth = newStrokeWidth + mPaintOptions.strokeWidth = newStrokeWidth if (mIsStrokeWidthBarEnabled) { invalidate() } @@ -81,35 +78,31 @@ class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { invalidate() } - val bitmap: Bitmap - get() { - val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - canvas.drawColor(Color.WHITE) - mIsSaving = true - draw(canvas) - mIsSaving = false - return bitmap - } - - val paths: Map - get() = mPaths!! + fun getBitmap(): Bitmap { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + canvas.drawColor(Color.WHITE) + mIsSaving = true + draw(canvas) + mIsSaving = false + return bitmap + } fun addPath(path: MyPath, options: PaintOptions) { - mPaths!!.put(path, options) + mPaths.put(path, options) pathsUpdated() } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) - for ((key, value) in mPaths!!) { + for ((key, value) in mPaths) { changePaint(value) canvas.drawPath(key, mPaint) } - changePaint(mPaintOptions!!) - canvas.drawPath(mPath!!, mPaint) + changePaint(mPaintOptions) + canvas.drawPath(mPath, mPaint) if (mIsStrokeWidthBarEnabled && !mIsSaving) { drawPreviewCircle(canvas) @@ -121,15 +114,15 @@ class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { mPaint.style = Paint.Style.FILL var y = height - res.getDimension(R.dimen.preview_dot_offset_y) - canvas.drawCircle((width / 2).toFloat(), y, mPaintOptions!!.strokeWidth / 2, mPaint) + canvas.drawCircle((width / 2).toFloat(), y, mPaintOptions.strokeWidth / 2, mPaint) mPaint.style = Paint.Style.STROKE - mPaint.color = mPaintOptions!!.color.getContrastColor() + mPaint.color = mPaintOptions.color.getContrastColor() mPaint.strokeWidth = res.getDimension(R.dimen.preview_dot_stroke_size) y = height - res.getDimension(R.dimen.preview_dot_offset_y) - val radius = (mPaintOptions!!.strokeWidth + res.getDimension(R.dimen.preview_dot_stroke_size)) / 2 + val radius = (mPaintOptions.strokeWidth + res.getDimension(R.dimen.preview_dot_stroke_size)) / 2 canvas.drawCircle((width / 2).toFloat(), y, radius, mPaint) - changePaint(mPaintOptions!!) + changePaint(mPaintOptions) } private fun changePaint(paintOptions: PaintOptions) { @@ -138,45 +131,43 @@ class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { } fun clearCanvas() { - mPath!!.reset() - mPaths!!.clear() + mPath.reset() + mPaths.clear() pathsUpdated() invalidate() } private fun actionDown(x: Float, y: Float) { - mPath!!.reset() - mPath!!.moveTo(x, y) + mPath.reset() + mPath.moveTo(x, y) mCurX = x mCurY = y } private fun actionMove(x: Float, y: Float) { - mPath!!.quadTo(mCurX, mCurY, (x + mCurX) / 2, (y + mCurY) / 2) + mPath.quadTo(mCurX, mCurY, (x + mCurX) / 2, (y + mCurY) / 2) mCurX = x mCurY = y } private fun actionUp() { - mPath!!.lineTo(mCurX, mCurY) + mPath.lineTo(mCurX, mCurY) // draw a dot on click if (mStartX == mCurX && mStartY == mCurY) { - mPath!!.lineTo(mCurX, mCurY + 2) - mPath!!.lineTo(mCurX + 1, mCurY + 2) - mPath!!.lineTo(mCurX + 1, mCurY) + mPath.lineTo(mCurX, mCurY + 2) + mPath.lineTo(mCurX + 1, mCurY + 2) + mPath.lineTo(mCurX + 1, mCurY) } - mPaths!!.put(mPath!!, mPaintOptions!!) + mPaths.put(mPath, mPaintOptions) pathsUpdated() mPath = MyPath() - mPaintOptions = PaintOptions(mPaintOptions!!.color, mPaintOptions!!.strokeWidth) + mPaintOptions = PaintOptions(mPaintOptions.color, mPaintOptions.strokeWidth) } private fun pathsUpdated() { - if (mListener != null && mPaths != null) { - mListener!!.pathsChanged(mPaths!!.size) - } + mListener?.pathsChanged(mPaths.size) } override fun onTouchEvent(event: MotionEvent): Boolean { @@ -207,7 +198,7 @@ class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { val superState = super.onSaveInstanceState() val savedState = SavedState(superState) - savedState.mPaths = mPaths + savedState.paths = mPaths return savedState } @@ -219,12 +210,12 @@ class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { val savedState = state super.onRestoreInstanceState(savedState.superState) - mPaths = savedState.mPaths + mPaths = savedState.paths pathsUpdated() } internal class SavedState : View.BaseSavedState { - var mPaths: MutableMap? = null + var paths: MutableMap = HashMap() companion object { val CREATOR: Parcelable.Creator = object : Parcelable.Creator { @@ -238,8 +229,8 @@ class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { override fun writeToParcel(out: Parcel, flags: Int) { super.writeToParcel(out, flags) - out.writeInt(mPaths!!.size) - for ((key, paintOptions) in mPaths!!) { + out.writeInt(paths.size) + for ((key, paintOptions) in paths) { out.writeSerializable(key) out.writeInt(paintOptions.color) out.writeFloat(paintOptions.strokeWidth) @@ -251,7 +242,7 @@ class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { for (i in 0..size - 1) { val key = parcel.readSerializable() as MyPath val paintOptions = PaintOptions(parcel.readInt(), parcel.readFloat()) - mPaths!!.put(key, paintOptions) + paths.put(key, paintOptions) } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/draw/Svg.kt b/app/src/main/kotlin/com/simplemobiletools/draw/Svg.kt index 25cbdeb..0125f32 100644 --- a/app/src/main/kotlin/com/simplemobiletools/draw/Svg.kt +++ b/app/src/main/kotlin/com/simplemobiletools/draw/Svg.kt @@ -14,7 +14,7 @@ object Svg { val out = FileOutputStream(output) val writer = BufferedWriter(OutputStreamWriter(out)) - writeSvg(writer, backgroundColor, canvas.paths, canvas.width, canvas.height) + writeSvg(writer, backgroundColor, canvas.mPaths, canvas.width, canvas.height) writer.close() } @@ -32,7 +32,7 @@ object Svg { writer.write("\" height=\"") writer.write(height.toString()) writer.write("\" fill=\"#") - writer.write(Integer.toHexString(backgroundColor).substring(2)) // Skip the alpha FF + writer.write(Integer.toHexString(backgroundColor).substring(2)) writer.write("\"/>") for ((key, value) in paths) { @@ -50,7 +50,7 @@ object Svg { } writer.write("\" fill=\"none\" stroke=\"#") - writer.write(Integer.toHexString(options.color).substring(2)) // Skip the alpha FF + writer.write(Integer.toHexString(options.color).substring(2)) writer.write("\" stroke-width=\"") writer.write(options.strokeWidth.toString()) writer.write("\" stroke-linecap=\"round\"/>") @@ -115,11 +115,11 @@ object Svg { return svg } - private class SSvg internal constructor() : Serializable { - internal var width: Int = 0 - internal var height: Int = 0 - internal var background: SRect? = null - internal val paths: ArrayList = ArrayList() + private class SSvg : Serializable { + var background: SRect? = null + val paths: ArrayList = ArrayList() + private var width = 0 + private var height = 0 internal fun setSize(w: Int, h: Int) { width = w @@ -127,7 +127,7 @@ object Svg { } } - private class SRect internal constructor(internal val width: Int, internal val height: Int, internal val color: Int) : Serializable + private class SRect(val width: Int, val height: Int, val color: Int) : Serializable - private class SPath internal constructor(internal var data: String, internal var color: Int, internal var strokeWidth: Float) : Serializable + private class SPath(var data: String, var color: Int, var strokeWidth: Float) : Serializable } diff --git a/app/src/main/kotlin/com/simplemobiletools/draw/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/draw/activities/MainActivity.kt index 374b86e..59163a6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/draw/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/draw/activities/MainActivity.kt @@ -10,19 +10,18 @@ import android.net.Uri import android.os.Bundle import android.os.Environment import android.support.v4.app.ActivityCompat -import android.support.v4.content.ContextCompat import android.support.v4.content.FileProvider import android.support.v7.app.AlertDialog -import android.util.Log import android.view.Menu import android.view.MenuItem import android.widget.EditText import android.widget.RadioGroup import android.widget.SeekBar -import android.widget.Toast import com.simplemobiletools.commons.dialogs.ColorPickerDialog import com.simplemobiletools.commons.extensions.beVisibleIf +import com.simplemobiletools.commons.extensions.hasWriteStoragePermission import com.simplemobiletools.commons.extensions.toast +import com.simplemobiletools.commons.extensions.value import com.simplemobiletools.commons.helpers.LICENSE_KOTLIN import com.simplemobiletools.draw.BuildConfig import com.simplemobiletools.draw.MyCanvas @@ -43,7 +42,6 @@ class MainActivity : SimpleActivity(), MyCanvas.PathsChangedListener { private var strokeWidth = 0f companion object { - private val TAG = MainActivity::class.java.simpleName private val FOLDER_NAME = "images" private val FILE_NAME = "simple-draw.png" private val SAVE_FOLDER_NAME = "Simple Draw" @@ -64,7 +62,7 @@ class MainActivity : SimpleActivity(), MyCanvas.PathsChangedListener { stroke_width_bar.progress = strokeWidth.toInt() color_picker.setOnClickListener { pickColor() } - undo.setOnClickListener { undo() } + undo.setOnClickListener { my_canvas.undo() } } override fun onResume() { @@ -80,11 +78,6 @@ class MainActivity : SimpleActivity(), MyCanvas.PathsChangedListener { config.brushSize = strokeWidth } - override fun onDestroy() { - super.onDestroy() - config.isFirstRun = false - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu, menu) return true @@ -92,36 +85,15 @@ class MainActivity : SimpleActivity(), MyCanvas.PathsChangedListener { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.menu_save -> { - saveImage() - return true - } - R.id.menu_share -> { - shareImage() - return true - } - R.id.settings -> { - startActivity(Intent(applicationContext, SettingsActivity::class.java)) - return true - } - R.id.clear -> { - my_canvas.clearCanvas() - return true - } - R.id.change_background -> { - val oldColor = (my_canvas.background as ColorDrawable).color - ColorPickerDialog(this, oldColor) { - setBackgroundColor(it) - config.canvasBackgroundColor = it - } - return true - } - R.id.about -> { - launchAbout() - return true - } + R.id.menu_save -> saveImage() + R.id.menu_share -> shareImage() + R.id.clear -> my_canvas.clearCanvas() + R.id.change_background -> changeBackgroundClicked() + R.id.settings -> launchSettings() + R.id.about -> launchAbout() else -> return super.onOptionsItemSelected(item) } + return true } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { @@ -131,17 +103,29 @@ class MainActivity : SimpleActivity(), MyCanvas.PathsChangedListener { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { saveImage() } else { - Toast.makeText(this, resources.getString(R.string.no_permissions), Toast.LENGTH_SHORT).show() + toast(R.string.no_permissions) } } } + private fun launchSettings() { + startActivity(Intent(applicationContext, SettingsActivity::class.java)) + } + private fun launchAbout() { startAboutActivity(R.string.app_name, LICENSE_KOTLIN, BuildConfig.VERSION_NAME) } + private fun changeBackgroundClicked() { + val oldColor = (my_canvas.background as ColorDrawable).color + ColorPickerDialog(this, oldColor) { + setBackgroundColor(it) + config.canvasBackgroundColor = it + } + } + private fun saveImage() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + if (!hasWriteStoragePermission()) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION) return } @@ -163,28 +147,28 @@ class MainActivity : SimpleActivity(), MyCanvas.PathsChangedListener { builder.setPositiveButton(R.string.ok, null) builder.setNegativeButton(R.string.cancel, null) - val alertDialog = builder.create() - alertDialog.show() - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - val fileName = fileNameET.text.toString().trim { it <= ' ' } - if (!fileName.isEmpty()) { - val extension: String - when (fileExtensionRG.checkedRadioButtonId) { - R.id.extension_radio_svg -> extension = ".svg" - else -> extension = ".png" - } + builder.create().apply { + show() + getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val fileName = fileNameET.value + if (!fileName.isEmpty()) { + val extension = when (fileExtensionRG.checkedRadioButtonId) { + R.id.extension_radio_svg -> ".svg" + else -> ".png" + } - if (saveFile(fileName, extension)) { - curFileName = fileName - curExtensionId = fileExtensionRG.checkedRadioButtonId + if (saveFile(fileName, extension)) { + curFileName = fileName + curExtensionId = fileExtensionRG.checkedRadioButtonId - toast(R.string.saving_ok) - alertDialog.dismiss() + toast(R.string.saving_ok) + dismiss() + } else { + toast(R.string.saving_error) + } } else { - toast(R.string.saving_error) + toast(R.string.enter_file_name) } - } else { - toast(R.string.enter_file_name) } } } @@ -201,20 +185,17 @@ class MainActivity : SimpleActivity(), MyCanvas.PathsChangedListener { val file = File(directory, fileName + extension) when (extension) { ".png" -> { - val bitmap = my_canvas.bitmap var out: FileOutputStream? = null try { out = FileOutputStream(file) - bitmap.compress(Bitmap.CompressFormat.PNG, 100, out) + my_canvas.getBitmap().compress(Bitmap.CompressFormat.PNG, 100, out) MediaScannerConnection.scanFile(applicationContext, arrayOf(file.absolutePath), null, null) } catch (e: Exception) { - Log.e(TAG, "MainActivity SaveFile (.png) " + e.message) return false } finally { try { out?.close() } catch (e: IOException) { - Log.e(TAG, "MainActivity SaveFile (.png) 2 " + e.message) } } @@ -223,7 +204,6 @@ class MainActivity : SimpleActivity(), MyCanvas.PathsChangedListener { try { Svg.saveSvg(file, my_canvas) } catch (e: Exception) { - Log.e(TAG, "MainActivity SaveFile (.svg) " + e.message) return false } @@ -236,16 +216,16 @@ class MainActivity : SimpleActivity(), MyCanvas.PathsChangedListener { private fun shareImage() { val shareTitle = resources.getString(R.string.share_via) - val bitmap = my_canvas.bitmap - val sendIntent = Intent() - val uri = getImageUri(bitmap) ?: return + val uri = getImageUri(my_canvas.getBitmap()) ?: return - sendIntent.action = Intent.ACTION_SEND - sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - sendIntent.setDataAndType(uri, contentResolver.getType(uri)) - sendIntent.putExtra(Intent.EXTRA_STREAM, uri) - sendIntent.type = "image/*" - startActivity(Intent.createChooser(sendIntent, shareTitle)) + Intent().apply { + action = Intent.ACTION_SEND + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + setDataAndType(uri, contentResolver.getType(uri)) + putExtra(Intent.EXTRA_STREAM, uri) + type = "image/*" + startActivity(Intent.createChooser(this, shareTitle)) + } } private fun getImageUri(bitmap: Bitmap): Uri? { @@ -264,23 +244,16 @@ class MainActivity : SimpleActivity(), MyCanvas.PathsChangedListener { fileOutputStream = FileOutputStream(file) fileOutputStream.write(bytes.toByteArray()) } catch (e: Exception) { - Log.e(TAG, "getImageUri 1 " + e.message) } finally { try { fileOutputStream?.close() - } catch (e: IOException) { - Log.e(TAG, "getImageUri 2 " + e.message) + } catch (e: Exception) { } - } return FileProvider.getUriForFile(this, "com.simplemobiletools.draw.fileprovider", file) } - fun undo() { - my_canvas.undo() - } - fun pickColor() { ColorPickerDialog(this, color) { setColor(it)