Commit 3bfd149d authored by Divyanshu Bhargava's avatar Divyanshu Bhargava

android draw library

parent c97f822e
...@@ -53,6 +53,7 @@ dependencies { ...@@ -53,6 +53,7 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':player') implementation project(':player')
implementation project(':draw')
implementation libraries.kotlin implementation libraries.kotlin
implementation libraries.coroutines implementation libraries.coroutines
......
package chat.rocket.android.chatroom.ui package chat.rocket.android.chatroom.ui
import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.os.Handler import android.os.Handler
import android.support.annotation.DrawableRes import android.support.annotation.DrawableRes
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
...@@ -26,6 +30,8 @@ import chat.rocket.android.chatroom.viewmodel.MessageViewModel ...@@ -26,6 +30,8 @@ import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.ChatRoomSuggestionViewModel import chat.rocket.android.chatroom.viewmodel.suggestion.ChatRoomSuggestionViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.CommandSuggestionViewModel import chat.rocket.android.chatroom.viewmodel.suggestion.CommandSuggestionViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.PeopleSuggestionViewModel import chat.rocket.android.chatroom.viewmodel.suggestion.PeopleSuggestionViewModel
import chat.rocket.android.draw.DrawingActivity
import chat.rocket.android.helper.AndroidPermissionsHelper
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.KeyboardHelper import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
...@@ -40,6 +46,10 @@ import kotlinx.android.synthetic.main.fragment_chat_room.* ...@@ -40,6 +46,10 @@ import kotlinx.android.synthetic.main.fragment_chat_room.*
import kotlinx.android.synthetic.main.message_attachment_options.* import kotlinx.android.synthetic.main.message_attachment_options.*
import kotlinx.android.synthetic.main.message_composer.* import kotlinx.android.synthetic.main.message_composer.*
import kotlinx.android.synthetic.main.message_list.* import kotlinx.android.synthetic.main.message_list.*
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject import javax.inject.Inject
...@@ -71,6 +81,7 @@ private const val BUNDLE_CHAT_ROOM_NAME = "chat_room_name" ...@@ -71,6 +81,7 @@ private const val BUNDLE_CHAT_ROOM_NAME = "chat_room_name"
private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type" private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
private const val BUNDLE_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only" private const val BUNDLE_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only"
private const val REQUEST_CODE_FOR_PERFORM_SAF = 42 private const val REQUEST_CODE_FOR_PERFORM_SAF = 42
private const val REQUEST_CODE_FOR_DRAW = 101
private const val BUNDLE_CHAT_ROOM_LAST_SEEN = "chat_room_last_seen" private const val BUNDLE_CHAT_ROOM_LAST_SEEN = "chat_room_last_seen"
private const val BUNDLE_CHAT_ROOM_IS_SUBSCRIBED = "chat_room_is_subscribed" private const val BUNDLE_CHAT_ROOM_IS_SUBSCRIBED = "chat_room_is_subscribed"
private const val BUNDLE_CHAT_ROOM_IS_OWNER = "chat_room_is_owner" private const val BUNDLE_CHAT_ROOM_IS_OWNER = "chat_room_is_owner"
...@@ -180,13 +191,34 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -180,13 +191,34 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF && resultCode == Activity.RESULT_OK) { if (resultData != null && resultCode == Activity.RESULT_OK) {
if (resultData != null) { when(requestCode){
REQUEST_CODE_FOR_PERFORM_SAF -> {
uploadFile(resultData.data) uploadFile(resultData.data)
} }
REQUEST_CODE_FOR_DRAW -> {
val result= resultData.getByteArrayExtra("bitmap")
val bitmap = BitmapFactory.decodeByteArray(result, 0, result.size)
val uri = saveImage(bitmap)
uploadFile(uri)
}
}
} }
} }
private fun saveImage(bitmap: Bitmap): Uri {
val imageDir = "${Environment.DIRECTORY_PICTURES}/Rocket.Chat Images/"
val path = Environment.getExternalStoragePublicDirectory(imageDir)
val file = File(path, UUID.randomUUID().toString()+".png")
path.mkdirs()
file.createNewFile()
val outputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG,100,outputStream)
outputStream.flush()
outputStream.close()
return Uri.fromFile(file)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.chatroom_actions, menu) inflater.inflate(R.menu.chatroom_actions, menu)
...@@ -675,6 +707,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -675,6 +707,18 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
button_add_reaction.setOnClickListener { view -> button_add_reaction.setOnClickListener { view ->
openEmojiKeyboardPopup() openEmojiKeyboardPopup()
} }
button_drawing.setOnClickListener {
if (!canWriteToExternalStorage()) {
checkWritingPermission()
}else{
val intent = Intent(activity,DrawingActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_FOR_DRAW)
}
handler.postDelayed({
hideAttachmentOptions()
}, 400)
}
} }
} }
...@@ -793,4 +837,16 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -793,4 +837,16 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private fun setupToolbar(toolbarTitle: String) { private fun setupToolbar(toolbarTitle: String) {
(activity as ChatRoomActivity).setupToolbarTitle(toolbarTitle) (activity as ChatRoomActivity).setupToolbarTitle(toolbarTitle)
} }
private fun canWriteToExternalStorage(): Boolean {
return context?.let { AndroidPermissionsHelper.checkPermission(it, Manifest.permission.WRITE_EXTERNAL_STORAGE) }!!
}
private fun checkWritingPermission() {
activity?.let {
AndroidPermissionsHelper.requestPermission(it,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
AndroidPermissionsHelper.WRITE_EXTERNAL_STORAGE_CODE)
}
}
} }
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF1D74F5"
android:pathData="M4.59,6.89c0.7,-0.71 1.4,-1.35 1.71,-1.22 0.5,0.2 0,1.03 -0.3,1.52 -0.25,0.42 -2.86,3.89 -2.86,6.31 0,1.28 0.48,2.34 1.34,2.98 0.75,0.56 1.74,0.73 2.64,0.46 1.07,-0.31 1.95,-1.4 3.06,-2.77 1.21,-1.49 2.83,-3.44 4.08,-3.44 1.63,0 1.65,1.01 1.76,1.79 -3.78,0.64 -5.38,3.67 -5.38,5.37 0,1.7 1.44,3.09 3.21,3.09 1.63,0 4.29,-1.33 4.69,-6.1L21,14.88v-2.5h-2.47c-0.15,-1.65 -1.09,-4.2 -4.03,-4.2 -2.25,0 -4.18,1.91 -4.94,2.84 -0.58,0.73 -2.06,2.48 -2.29,2.72 -0.25,0.3 -0.68,0.84 -1.11,0.84 -0.45,0 -0.72,-0.83 -0.36,-1.92 0.35,-1.09 1.4,-2.86 1.85,-3.52 0.78,-1.14 1.3,-1.92 1.3,-3.28C8.95,3.69 7.31,3 6.44,3 5.12,3 3.97,4 3.72,4.25c-0.36,0.36 -0.66,0.66 -0.88,0.93l1.75,1.71zM13.88,18.55c-0.31,0 -0.74,-0.26 -0.74,-0.72 0,-0.6 0.73,-2.2 2.87,-2.76 -0.3,2.69 -1.43,3.48 -2.13,3.48z"/>
</vector>
...@@ -15,4 +15,14 @@ ...@@ -15,4 +15,14 @@
android:gravity="start|center" android:gravity="start|center"
android:text="@string/action_files" /> android:text="@string/action_files" />
<Button
android:id="@+id/button_drawing"
style="?android:attr/borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="20dp"
android:drawableStart="@drawable/ic_gesture_black_24dp"
android:gravity="start|center"
android:text="Drawing" />
</LinearLayout> </LinearLayout>
\ No newline at end of file
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
<string name="action_away">Ausente</string> <string name="action_away">Ausente</string>
<string name="action_busy">Ocupado</string> <string name="action_busy">Ocupado</string>
<string name="action_invisible">Invisible</string> <string name="action_invisible">Invisible</string>
<string name="action_drawing">Dibujo</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
<string name="action_away">Loin</string> <string name="action_away">Loin</string>
<string name="action_busy">Occupé</string> <string name="action_busy">Occupé</string>
<string name="action_invisible">Invisible</string> <string name="action_invisible">Invisible</string>
<string name="action_drawing">Dessin</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
<string name="action_away">दूर</string> <string name="action_away">दूर</string>
<string name="action_busy">व्यस्त</string> <string name="action_busy">व्यस्त</string>
<string name="action_invisible">अदृश्य</string> <string name="action_invisible">अदृश्य</string>
<string name="action_drawing">चित्रकारी</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
<string name="action_away">Ausente</string> <string name="action_away">Ausente</string>
<string name="action_busy">Ocupado</string> <string name="action_busy">Ocupado</string>
<string name="action_invisible">Invisível</string> <string name="action_invisible">Invisível</string>
<string name="action_drawing">Desenhando</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
<string name="action_away">Away</string> <string name="action_away">Away</string>
<string name="action_busy">Busy</string> <string name="action_busy">Busy</string>
<string name="action_invisible">Invisible</string> <string name="action_invisible">Invisible</string>
<string name="action_drawing">Drawing</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
<string name="action_away">Away</string> <string name="action_away">Away</string>
<string name="action_busy">Busy</string> <string name="action_busy">Busy</string>
<string name="action_invisible">Invisible</string> <string name="action_invisible">Invisible</string>
<string name="action_drawing">Drawing</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
......
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.2.31'
apply from: rootProject.file('dependencies.gradle') apply from: rootProject.file('dependencies.gradle')
repositories { repositories {
...@@ -19,7 +21,7 @@ buildscript { ...@@ -19,7 +21,7 @@ buildscript {
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" }
} }
allprojects { allprojects {
......
apply plugin: 'com.android.library'
android {
compileSdkVersion 27
defaultConfig {
minSdkVersion 21
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation libraries.appCompat
implementation libraries.constraintLayout
implementation libraries.kotlin
implementation libraries.androidKtx
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package chat.rocket.android.draw;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("chat.rocket.android.draw.test", appContext.getPackageName());
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android.draw">
<application>
<activity
android:name=".DrawingActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
</application>
</manifest>
\ No newline at end of file
package chat.rocket.android.draw
import android.content.res.Resources
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v4.content.res.ResourcesCompat
import android.view.View
import android.widget.SeekBar
import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.activity_drawing.*
import kotlinx.android.synthetic.main.color_palette_view.*
import android.content.Intent
import android.graphics.Bitmap
import android.app.Activity
import java.io.ByteArrayOutputStream
class DrawingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_drawing)
image_close_drawing.setOnClickListener {
finish()
}
image_send_drawing.setOnClickListener {
val bStream = ByteArrayOutputStream()
val bitmap = custom_draw_view.getBitmap()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bStream)
val byteArray = bStream.toByteArray()
val returnIntent = Intent()
returnIntent.putExtra("bitmap", byteArray)
setResult(Activity.RESULT_OK,returnIntent)
finish()
}
setUpDrawTools()
colorSelector()
setPaintAlpha()
setPaintWidth()
}
private fun setUpDrawTools() {
image_draw_eraser.setOnClickListener {
custom_draw_view.clearCanvas()
toggleDrawTools(draw_tools,false)
}
image_draw_width.setOnClickListener {
if (draw_tools.translationY == (56).toPx){
toggleDrawTools(draw_tools,true)
}else if (draw_tools.translationY == (0).toPx && seekBar_width.isVisible){
toggleDrawTools(draw_tools,false)
}
seekBar_width.isVisible = true
seekBar_opacity.isVisible = false
draw_color_palette.isVisible = false
}
image_draw_opacity.setOnClickListener {
if (draw_tools.translationY == (56).toPx){
toggleDrawTools(draw_tools,true)
}else if (draw_tools.translationY == (0).toPx && seekBar_opacity.isVisible){
toggleDrawTools(draw_tools,false)
}
seekBar_width.isVisible = false
seekBar_opacity.isVisible = true
draw_color_palette.isVisible = false
}
image_draw_color.setOnClickListener {
if (draw_tools.translationY == (56).toPx){
toggleDrawTools(draw_tools,true)
}else if (draw_tools.translationY == (0).toPx && draw_color_palette.isVisible){
toggleDrawTools(draw_tools,false)
}
seekBar_width.isVisible = false
seekBar_opacity.isVisible = false
draw_color_palette.isVisible = true
}
image_draw_undo.setOnClickListener {
custom_draw_view.undo()
toggleDrawTools(draw_tools,false)
}
image_draw_redo.setOnClickListener {
custom_draw_view.redo()
toggleDrawTools(draw_tools,false)
}
}
private fun toggleDrawTools(view: View, showView: Boolean = true) {
if (showView){
view.animate().translationY((0).toPx)
}else{
view.animate().translationY((56).toPx)
}
}
private fun colorSelector() {
image_color_black.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_black,null))
scaleColorView(image_color_black)
}
image_color_red.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_red,null))
scaleColorView(image_color_red)
}
image_color_yellow.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_yellow,null))
scaleColorView(image_color_yellow)
}
image_color_green.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_green,null))
scaleColorView(image_color_green)
}
image_color_blue.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_blue,null))
scaleColorView(image_color_blue)
}
image_color_pink.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_pink,null))
scaleColorView(image_color_pink)
}
image_color_brown.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_brown,null))
scaleColorView(image_color_brown)
}
}
private fun scaleColorView(view: View) {
//reset scale of all views
image_color_black.scaleX = 1f
image_color_black.scaleY = 1f
image_color_red.scaleX = 1f
image_color_red.scaleY = 1f
image_color_yellow.scaleX = 1f
image_color_yellow.scaleY = 1f
image_color_green.scaleX = 1f
image_color_green.scaleY = 1f
image_color_blue.scaleX = 1f
image_color_blue.scaleY = 1f
image_color_pink.scaleX = 1f
image_color_pink.scaleY = 1f
image_color_brown.scaleX = 1f
image_color_brown.scaleY = 1f
//set scale of selected view
view.scaleX = 1.5f
view.scaleY = 1.5f
}
private fun setPaintWidth() {
seekBar_width.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
custom_draw_view.setStrokeWidth(progress.toFloat())
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
}
private fun setPaintAlpha() {
seekBar_opacity.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
custom_draw_view.setAlpha(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
}
private val Int.toPx: Float
get() = (this * Resources.getSystem().displayMetrics.density)
}
package chat.rocket.android.draw.widget
import android.graphics.Path
import java.io.Serializable
import java.io.Writer
interface Action : Serializable {
fun perform(path: Path)
fun perform(writer: Writer)
}
package chat.rocket.android.draw.widget
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import java.util.*
import android.support.v4.graphics.ColorUtils
import android.support.annotation.ColorInt
class CustomDrawView(context: Context, attrs: AttributeSet) : View(context, attrs) {
var mPaths = LinkedHashMap<MyPath, PaintOptions>()
private var mLastPaths = LinkedHashMap<MyPath, PaintOptions>()
private var mUndonePaths = LinkedHashMap<MyPath, PaintOptions>()
private var mPaint = Paint()
private var mPath = MyPath()
private var mPaintOptions = PaintOptions()
private var mCurX = 0f
private var mCurY = 0f
private var mStartX = 0f
private var mStartY = 0f
private var mIsSaving = false
private var mIsStrokeWidthBarEnabled = false
init {
mPaint.apply {
color = mPaintOptions.color
style = Paint.Style.STROKE
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
strokeWidth = mPaintOptions.strokeWidth
isAntiAlias = true
}
}
fun undo() {
if (mPaths.isEmpty() && mLastPaths.isNotEmpty()) {
mPaths = mLastPaths.clone() as LinkedHashMap<MyPath, PaintOptions>
mLastPaths.clear()
invalidate()
return
}
if (mPaths.isEmpty()) {
return
}
val lastPath = mPaths.values.lastOrNull()
val lastKey = mPaths.keys.lastOrNull()
mPaths.remove(lastKey)
if (lastPath != null && lastKey != null) {
mUndonePaths[lastKey] = lastPath
}
invalidate()
}
fun redo() {
if (mUndonePaths.keys.isEmpty()) {
return
}
val lastKey = mUndonePaths.keys.last()
addPath(lastKey, mUndonePaths.values.last())
mUndonePaths.remove(lastKey)
invalidate()
}
fun setColor(newColor: Int) {
@ColorInt
val alphaColor = ColorUtils.setAlphaComponent(newColor, mPaintOptions.alpha)
mPaintOptions.color = alphaColor
if (mIsStrokeWidthBarEnabled) {
invalidate()
}
}
fun setAlpha(newAlpha: Int) {
val alpha = (newAlpha*255)/100
mPaintOptions.alpha = alpha
setColor(mPaintOptions.color)
}
fun setStrokeWidth(newStrokeWidth: Float) {
mPaintOptions.strokeWidth = newStrokeWidth
if (mIsStrokeWidthBarEnabled) {
invalidate()
}
}
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[path] = options
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
for ((key, value) in mPaths) {
changePaint(value)
canvas.drawPath(key, mPaint)
}
changePaint(mPaintOptions)
canvas.drawPath(mPath, mPaint)
}
private fun changePaint(paintOptions: PaintOptions) {
mPaint.color = paintOptions.color
mPaint.strokeWidth = paintOptions.strokeWidth
}
fun clearCanvas() {
mLastPaths = mPaths.clone() as LinkedHashMap<MyPath, PaintOptions>
mPath.reset()
mPaths.clear()
invalidate()
}
private fun actionDown(x: Float, y: Float) {
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)
mCurX = x
mCurY = y
}
private fun actionUp() {
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)
}
mPaths.put(mPath, mPaintOptions)
mPath = MyPath()
mPaintOptions = PaintOptions(mPaintOptions.color, mPaintOptions.strokeWidth, mPaintOptions.alpha)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val x = event.x
val y = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mStartX = x
mStartY = y
actionDown(x, y)
mUndonePaths.clear()
}
MotionEvent.ACTION_MOVE -> actionMove(x, y)
MotionEvent.ACTION_UP -> actionUp()
}
invalidate()
return true
}
}
\ No newline at end of file
package chat.rocket.android.draw.widget
import android.graphics.Path
import java.io.Writer
import java.security.InvalidParameterException
class Line(val x: Float, val y: Float) : Action {
override fun perform(path: Path) {
path.lineTo(x, y)
}
override fun perform(writer: Writer) {
writer.write("L$x,$y")
}
}
\ No newline at end of file
package chat.rocket.android.draw.widget
import android.graphics.Path
import java.io.Writer
import java.security.InvalidParameterException
class Move(val x: Float, val y: Float) : Action {
override fun perform(path: Path) {
path.moveTo(x, y)
}
override fun perform(writer: Writer) {
writer.write("M$x,$y")
}
}
\ No newline at end of file
package chat.rocket.android.draw.widget
import android.graphics.Path
import java.io.ObjectInputStream
import java.io.Serializable
import java.util.*
class MyPath : Path(), Serializable {
val actions = LinkedList<Action>()
private fun readObject(inputStream: ObjectInputStream) {
inputStream.defaultReadObject()
val copiedActions = actions.map { it }
copiedActions.forEach {
it.perform(this)
}
}
override fun reset() {
actions.clear()
super.reset()
}
override fun moveTo(x: Float, y: Float) {
actions.add(Move(x, y))
super.moveTo(x, y)
}
override fun lineTo(x: Float, y: Float) {
actions.add(Line(x, y))
super.lineTo(x, y)
}
override fun quadTo(x1: Float, y1: Float, x2: Float, y2: Float) {
actions.add(Quad(x1, y1, x2, y2))
super.quadTo(x1, y1, x2, y2)
}
}
\ No newline at end of file
package chat.rocket.android.draw.widget
import android.graphics.Color
data class PaintOptions(var color: Int = Color.BLACK, var strokeWidth: Float = 8f, var alpha: Int = 255)
package chat.rocket.android.draw.widget
import android.graphics.Path
import java.io.Writer
class Quad(private val x1: Float, private val y1: Float, private val x2: Float, private val y2: Float) : Action {
override fun perform(path: Path) {
path.quadTo(x1, y1, x2, y2)
}
override fun perform(writer: Writer) {
writer.write("Q$x1,$y1 $x2,$y2")
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/color_black" />
<corners android:radius="50dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/color_blue" />
<corners android:radius="50dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/color_brown" />
<corners android:radius="50dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/color_green" />
<corners android:radius="50dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/color_pink" />
<corners android:radius="50dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/color_red" />
<corners android:radius="50dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/color_yellow" />
<corners android:radius="50dp" />
</shape>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.49,2 2,6.49 2,12s4.49,10 10,10 10,-4.49 10,-10S17.51,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM15,12c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3 1.34,-3 3,-3 3,1.34 3,3z"/>
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M15.14,3C14.63,3 14.12,3.2 13.73,3.59L2.59,14.73C1.81,15.5 1.81,16.77 2.59,17.56L5.03,20H12.69L21.41,11.27C22.2,10.5 22.2,9.23 21.41,8.44L16.56,3.59C16.17,3.2 15.65,3 15.14,3M17,18L15,20H22V18"/>
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M17.66,8L12,2.35 6.34,8C4.78,9.56 4,11.64 4,13.64s0.78,4.11 2.34,5.67 3.61,2.35 5.66,2.35 4.1,-0.79 5.66,-2.35S20,15.64 20,13.64 19.22,9.56 17.66,8zM6,14c0.01,-2 0.62,-3.27 1.76,-4.4L12,5.27l4.24,4.38C17.38,10.77 17.99,12 18,14H6z"/>
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z"/>
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12.5,8c-2.65,0 -5.05,0.99 -6.9,2.6L2,7v9h9l-3.62,-3.62c1.39,-1.16 3.16,-1.88 5.12,-1.88 3.54,0 6.55,2.31 7.6,5.5l2.37,-0.78C21.08,11.03 17.15,8 12.5,8z"/>
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DrawingActivity">
<chat.rocket.android.draw.widget.CustomDrawView
android:id="@+id/custom_draw_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_white" />
<ImageView
android:id="@+id/image_close_drawing"
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@drawable/ic_close_black_24dp"
android:tint="@color/icon_color"
android:padding="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="@color/color_white"
android:foreground="?selectableItemBackgroundBorderless" />
<ImageView
android:id="@+id/image_send_drawing"
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@drawable/ic_send_black_24dp"
android:padding="16dp"
android:tint="@color/colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="@color/color_white"
android:foreground="?selectableItemBackgroundBorderless" />
<android.support.constraint.ConstraintLayout
android:id="@+id/draw_tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@color/color_white"
android:elevation="4dp"
android:translationY="56dp" >
<ImageView
android:id="@+id/image_draw_eraser"
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@drawable/ic_eraser_black_24dp"
android:padding="16dp"
android:tint="@color/icon_color"
android:foreground="?selectableItemBackground"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/image_draw_width"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/image_draw_width"
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@drawable/ic_adjust_black_24dp"
android:padding="16dp"
android:tint="@color/icon_color"
android:foreground="?selectableItemBackground"
app:layout_constraintStart_toEndOf="@id/image_draw_eraser"
app:layout_constraintEnd_toStartOf="@id/image_draw_color"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/image_draw_color"
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@drawable/ic_color_lens_black_24dp"
android:padding="16dp"
android:tint="@color/icon_color"
android:foreground="?selectableItemBackground"
app:layout_constraintStart_toEndOf="@id/image_draw_width"
app:layout_constraintEnd_toStartOf="@id/image_draw_opacity"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/image_draw_opacity"
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@drawable/ic_opacity_black_24dp"
android:padding="16dp"
android:tint="@color/icon_color"
android:foreground="?selectableItemBackground"
app:layout_constraintStart_toEndOf="@id/image_draw_color"
app:layout_constraintEnd_toStartOf="@id/image_draw_undo"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/image_draw_undo"
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@drawable/ic_undo_black_24dp"
android:padding="16dp"
android:tint="@color/icon_color"
android:foreground="?selectableItemBackground"
app:layout_constraintStart_toEndOf="@id/image_draw_opacity"
app:layout_constraintEnd_toStartOf="@id/image_draw_redo"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/image_draw_redo"
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@drawable/ic_redo_black_24dp"
android:padding="16dp"
android:tint="@color/icon_color"
android:foreground="?selectableItemBackground"
app:layout_constraintStart_toEndOf="@id/image_draw_undo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<SeekBar
android:id="@+id/seekBar_width"
android:layout_width="0dp"
android:layout_height="56dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_draw_eraser"
android:progressTint="@color/colorAccent"
android:thumbTint="@color/colorAccent"
android:progress="8"
android:paddingStart="16dp"
android:paddingEnd="16dp" />
<SeekBar
android:id="@+id/seekBar_opacity"
android:layout_width="0dp"
android:layout_height="56dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_draw_eraser"
android:progress="100"
android:progressTint="@color/colorAccent"
android:thumbTint="@color/colorAccent"
android:paddingStart="16dp"
android:paddingEnd="16dp" />
<include
android:id="@+id/draw_color_palette"
layout="@layout/color_palette_view"
android:layout_width="0dp"
android:layout_height="56dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_draw_eraser" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="56dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/image_color_black"
android:layout_width="48dp"
android:layout_height="match_parent"
android:src="@drawable/circle_black"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:scaleY="1.5"
android:scaleX="1.5"/>
<ImageView
android:id="@+id/image_color_red"
android:layout_width="48dp"
android:layout_height="match_parent"
android:src="@drawable/circle_red"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingStart="12dp"
android:paddingEnd="12dp" />
<ImageView
android:id="@+id/image_color_yellow"
android:layout_width="48dp"
android:layout_height="match_parent"
android:src="@drawable/circle_yellow"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingStart="12dp"
android:paddingEnd="12dp" />
<ImageView
android:id="@+id/image_color_green"
android:layout_width="48dp"
android:layout_height="match_parent"
android:src="@drawable/circle_green"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingStart="12dp"
android:paddingEnd="12dp" />
<ImageView
android:id="@+id/image_color_blue"
android:layout_width="48dp"
android:layout_height="match_parent"
android:src="@drawable/circle_blue"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingStart="12dp"
android:paddingEnd="12dp" />
<ImageView
android:id="@+id/image_color_pink"
android:layout_width="48dp"
android:layout_height="match_parent"
android:src="@drawable/circle_pink"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingStart="12dp"
android:paddingEnd="12dp" />
<ImageView
android:id="@+id/image_color_brown"
android:layout_width="48dp"
android:layout_height="match_parent"
android:src="@drawable/circle_brown"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingStart="12dp"
android:paddingEnd="12dp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorAccent">FF1976D2</color>
<color name="icon_color">#C2C2C2</color>
<color name="color_white">#FFFFFF</color>
<color name="color_black">#000</color>
<color name="color_red">#FF5252</color>
<color name="color_yellow">#FFEB3B</color>
<color name="color_green">#00C853</color>
<color name="color_blue">#00B0FF</color>
<color name="color_pink">#D500F9</color>
<color name="color_brown">#8D6E63</color>
</resources>
\ No newline at end of file
<resources>
<string name="app_name">Draw</string>
</resources>
package chat.rocket.android.draw;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
include ':app', ':player' include ':app', ':player', ':draw'
\ No newline at end of file \ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment