Commit 7b220038 authored by Divyanshu Bhargava's avatar Divyanshu Bhargava

Merge branch 'android-draw-new' into android-draw

# Conflicts:
#	app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
parents b250364f 23df6281
......@@ -53,6 +53,7 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':player')
implementation project(':draw')
implementation libraries.kotlin
implementation libraries.coroutines
......
package chat.rocket.android.authentication.server.presentation
import okhttp3.HttpUrl
interface VersionCheckView {
/**
* Alerts the user about the server version not meeting the recommended server version.
......@@ -26,4 +28,9 @@ interface VersionCheckView {
* Alters the user this protocol is invalid. This is optional.
*/
fun errorInvalidProtocol() {}
/**
* Updates the server URL after a URL redirection
*/
fun updateServerUrl(url: HttpUrl) {}
}
\ No newline at end of file
......@@ -20,6 +20,7 @@ import chat.rocket.android.util.extensions.*
import chat.rocket.common.util.ifNull
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_server.*
import okhttp3.HttpUrl
import javax.inject.Inject
class ServerFragment : Fragment(), ServerView {
......@@ -41,6 +42,7 @@ class ServerFragment : Fragment(), ServerView {
}
private var protocol = "https://"
private var ignoreChange = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......@@ -72,22 +74,27 @@ class ServerFragment : Fragment(), ServerView {
protocol = "https://"
}
1 -> {
ui{
AlertDialog.Builder(it)
.setTitle(R.string.msg_warning)
.setMessage(R.string.msg_http_insecure)
.setPositiveButton(R.string.msg_proceed) { _, _ ->
protocol = "http://"
}
.setNegativeButton(R.string.msg_cancel) { _, _ ->
text_server_protocol.setSelection(0)
}
.setCancelable(false)
.create()
.show()
if (ignoreChange) {
protocol = "http://"
} else {
ui {
AlertDialog.Builder(it)
.setTitle(R.string.msg_warning)
.setMessage(R.string.msg_http_insecure)
.setPositiveButton(R.string.msg_proceed) { _, _ ->
protocol = "http://"
}
.setNegativeButton(R.string.msg_cancel) { _, _ ->
text_server_protocol.setSelection(0)
}
.setCancelable(false)
.create()
.show()
}
}
}
}
ignoreChange = false
}
override fun onNothingSelected(parent: AdapterView<*>?) {
......@@ -174,13 +181,23 @@ class ServerFragment : Fragment(), ServerView {
showMessage(R.string.msg_invalid_server_protocol)
}
override fun updateServerUrl(url: HttpUrl) {
if (activity != null && view != null) {
if (url.scheme() == "https") text_server_protocol.setSelection(0) else text_server_protocol.setSelection(1)
protocol = "${url.scheme()}://"
val serverUrl = url.toString().removePrefix("${url.scheme()}://")
text_server_url.textContent = serverUrl
}
}
private fun performConnect() {
ui {
deepLinkInfo?.let {
presenter.deepLink(it)
}.ifNull {
val url = text_server_url.textContent.ifEmpty(text_server_url.hintContent)
presenter.connect("${protocol}${url.sanitize()}")
presenter.connect("$protocol${url.sanitize()}")
}
}
}
......
......@@ -18,13 +18,12 @@ import com.google.android.flexbox.FlexboxLayoutManager
import ru.whalemare.sheetmenu.extension.inflate
import ru.whalemare.sheetmenu.extension.toList
abstract class BaseViewHolder<T : BaseViewModel<*>>(
itemView: View,
private val listener: ActionsListener,
var reactionListener: EmojiReactionListener? = null
itemView: View,
private val listener: ActionsListener,
var reactionListener: EmojiReactionListener? = null
) : RecyclerView.ViewHolder(itemView),
MenuItem.OnMenuItemClickListener {
MenuItem.OnMenuItemClickListener {
var data: T? = null
init {
......@@ -78,14 +77,21 @@ abstract class BaseViewHolder<T : BaseViewModel<*>>(
private val onClickListener = { view: View ->
if (data?.message?.isSystemMessage() == false) {
val menuItems = view.context.inflate(R.menu.message_actions).toList()
menuItems.find { it.itemId == R.id.action_menu_msg_pin_unpin }?.apply {
val isPinned = data?.message?.pinned ?: false
setTitle(if (isPinned) R.string.action_msg_unpin else R.string.action_msg_pin)
isChecked = isPinned
data?.message?.let {
val menuItems = view.context.inflate(R.menu.message_actions).toList()
menuItems.find { it.itemId == R.id.action_message_unpin }?.apply {
setTitle(if (it.pinned) R.string.action_msg_unpin else R.string.action_msg_pin)
isChecked = it.pinned
}
menuItems.find { it.itemId == R.id.action_message_star }?.apply {
val isStarred = it.starred?.isNotEmpty() ?: false
setTitle(if (isStarred) R.string.action_msg_unstar else R.string.action_msg_star)
isChecked = isStarred
}
val adapter = ActionListAdapter(menuItems, this@BaseViewHolder)
BottomSheetMenu(adapter).show(view.context)
}
val adapter = ActionListAdapter(menuItems, this@BaseViewHolder)
BottomSheetMenu(adapter).show(view.context)
}
}
......
......@@ -181,25 +181,39 @@ class ChatRoomAdapter(
}
private val actionsListener = object : BaseViewHolder.ActionsListener {
override fun isActionsEnabled(): Boolean = enableActions
override fun onActionSelected(item: MenuItem, message: Message) {
message.apply {
when (item.itemId) {
R.id.action_menu_msg_delete -> presenter?.deleteMessage(roomId, id)
R.id.action_menu_msg_quote -> presenter?.citeMessage(roomType, id, false)
R.id.action_menu_msg_reply -> presenter?.citeMessage(roomType, id, true)
R.id.action_menu_msg_copy -> presenter?.copyMessage(id)
R.id.action_menu_msg_edit -> presenter?.editMessage(roomId, id, message.message)
R.id.action_menu_msg_pin_unpin -> {
with(item) {
if (!isChecked) {
presenter?.pinMessage(id)
} else {
presenter?.unpinMessage(id)
}
R.id.action_message_reply -> {
presenter?.citeMessage(roomType, id, true)
}
R.id.action_message_quote -> {
presenter?.citeMessage(roomType, id, false)
}
R.id.action_message_copy -> {
presenter?.copyMessage(id)
}
R.id.action_message_edit -> {
presenter?.editMessage(roomId, id, message.message)
}
R.id.action_message_star -> {
if (!item.isChecked) {
presenter?.starMessage(id)
} else {
presenter?.unstarMessage(id)
}
}
R.id.action_message_unpin -> {
if (!item.isChecked) {
presenter?.pinMessage(id)
} else {
presenter?.unpinMessage(id)
}
}
R.id.action_message_delete -> presenter?.deleteMessage(roomId, id)
R.id.action_menu_msg_react -> presenter?.showReactions(id)
else -> TODO("Not implemented")
}
......
......@@ -3,6 +3,7 @@ package chat.rocket.android.chatroom.adapter
import android.graphics.Color
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.core.view.isVisible
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.widget.emoji.EmojiReactionListener
......@@ -35,10 +36,9 @@ class MessageViewHolder(
text_content.setTextColor(
if (data.isTemporary) Color.GRAY else Color.BLACK
)
if (!data.message.isSystemMessage() && data.message.editedBy != null){
text_edit_indicator.setVisible(true)
}else{
text_edit_indicator.setVisible(false)
data.message.let {
text_edit_indicator.isVisible = it.isSystemMessage() && it.editedBy != null
image_star_indicator.isVisible = it.starred?.isNotEmpty() ?: false
}
}
}
......
......@@ -29,6 +29,8 @@ import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.realtime.setTypingStatus
import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.internal.realtime.subscribeTypingStatus
import chat.rocket.core.internal.realtime.unsubscribe
import chat.rocket.core.internal.rest.*
import chat.rocket.core.model.Command
import chat.rocket.core.model.Message
......@@ -70,8 +72,11 @@ class ChatRoomPresenter @Inject constructor(
private var chatRoomId: String? = null
private var chatRoomType: String? = null
private val stateChannel = Channel<State>()
private var typingStatusSubscriptionId: String? = null
private var lastState = manager.state
private var typingStatusList = arrayListOf<String>()
fun setupChatRoom() {
launchUI(strategy) {
......@@ -115,6 +120,7 @@ class ChatRoomPresenter @Inject constructor(
view.hideLoading()
}
subscribeTypingStatus()
if (offset == 0L) {
subscribeState()
}
......@@ -152,6 +158,7 @@ class ChatRoomPresenter @Inject constructor(
groupable = false,
parseUrls = false,
pinned = false,
starred = emptyList(),
mentions = emptyList(),
reactions = null,
senderAlias = null,
......@@ -160,9 +167,24 @@ class ChatRoomPresenter @Inject constructor(
urls = null,
isTemporary = true
)
messagesRepository.save(newMessage)
view.showNewMessage(mapper.map(newMessage))
client.sendMessage(id, chatRoomId, text)
try {
val message = client.sendMessage(id, chatRoomId, text)
messagesRepository.save(newMessage)
view.showNewMessage(mapper.map(newMessage))
message
} catch (ex: Exception) {
// Ok, not very beautiful, but the backend sends us a not valid response
// When someone sends a message on a read-only channel, so we just ignore it
// and show a generic error message
// TODO - remove the generic message when we implement :userId:/message subscription
if (ex is IllegalStateException) {
Timber.d(ex, "Probably a read-only problem...")
view.showGenericErrorMessage()
} else {
// some other error, just rethrow it...
throw ex
}
}
} else {
client.updateMessage(chatRoomId, messageId, text)
}
......@@ -311,14 +333,6 @@ class ChatRoomPresenter @Inject constructor(
}
}
fun unsubscribeMessages(chatRoomId: String) {
manager.removeStatusChannel(stateChannel)
manager.unsubscribeRoomMessages(chatRoomId)
// All messages during the subscribed period are assumed to be read,
// and lastSeen is updated as the time when the user leaves the room
markRoomAsRead(chatRoomId)
}
/**
* Delete the message with the given id.
*
......@@ -416,6 +430,34 @@ class ChatRoomPresenter @Inject constructor(
}
}
fun starMessage(messageId: String) {
launchUI(strategy) {
if (!permissions.allowedMessageStarring()) {
view.showMessage(R.string.permission_starring_not_allowed)
return@launchUI
}
try {
retryIO("starMessage($messageId)") { client.starMessage(messageId) }
} catch (e: RocketChatException) {
Timber.e(e)
}
}
}
fun unstarMessage(messageId: String) {
launchUI(strategy) {
if (!permissions.allowedMessageStarring()) {
view.showMessage(R.string.permission_starring_not_allowed)
return@launchUI
}
try {
retryIO("unstarMessage($messageId)") { client.unstarMessage(messageId) }
} catch (e: RocketChatException) {
Timber.e(e)
}
}
}
fun pinMessage(messageId: String) {
launchUI(strategy) {
if (!permissions.allowedMessagePinning()) {
......@@ -636,6 +678,57 @@ class ChatRoomPresenter @Inject constructor(
}
}
fun disconnect() {
unsubscribeTypingStatus()
if (chatRoomId != null) {
unsubscribeMessages(chatRoomId.toString())
}
}
private suspend fun subscribeTypingStatus() {
client.subscribeTypingStatus(chatRoomId.toString()) { _, id ->
typingStatusSubscriptionId = id
}
for (typingStatus in client.typingStatusChannel) {
processTypingStatus(typingStatus)
}
}
private fun processTypingStatus(typingStatus: Pair<String, Boolean>) {
if (!typingStatusList.any { username -> username == typingStatus.first }) {
if (typingStatus.second) {
typingStatusList.add(typingStatus.first)
}
} else {
typingStatusList.find { username -> username == typingStatus.first }?.let {
typingStatusList.remove(it)
if (typingStatus.second) {
typingStatusList.add(typingStatus.first)
}
}
}
if (typingStatusList.isNotEmpty()) {
view.showTypingStatus(typingStatusList)
} else {
view.hideTypingStatusView()
}
}
private fun unsubscribeTypingStatus() {
typingStatusSubscriptionId?.let {
client.unsubscribe(it)
}
}
private fun unsubscribeMessages(chatRoomId: String) {
manager.removeStatusChannel(stateChannel)
manager.unsubscribeRoomMessages(chatRoomId)
// All messages during the subscribed period are assumed to be read,
// and lastSeen is updated as the time when the user leaves the room
markRoomAsRead(chatRoomId)
}
private fun updateMessage(streamedMessage: Message) {
launchUI(strategy) {
val viewModelStreamedMessage = mapper.map(streamedMessage)
......@@ -652,4 +745,4 @@ class ChatRoomPresenter @Inject constructor(
}
}
}
}
}
\ No newline at end of file
......@@ -25,6 +25,18 @@ interface ChatRoomView : LoadingView, MessageView {
*/
fun sendMessage(text: String)
/**
* Shows the username(s) of the user(s) who is/are typing in the chat room.
*
* @param usernameList The list of username to show.
*/
fun showTypingStatus(usernameList: ArrayList<String>)
/**
* Hides the typing status view.
*/
fun hideTypingStatusView()
/**
* Perform file selection with the mime type [filter]
*/
......
package chat.rocket.android.chatroom.ui
import android.Manifest
import android.app.Activity
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.support.annotation.DrawableRes
import android.support.v4.app.Fragment
import android.support.v4.content.res.ResourcesCompat
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.*
import android.widget.ImageView
import android.widget.SeekBar
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.*
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
......@@ -31,7 +23,6 @@ import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.ChatRoomSuggestionViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.CommandSuggestionViewModel
import chat.rocket.android.chatroom.viewmodel.suggestion.PeopleSuggestionViewModel
import chat.rocket.android.helper.AndroidPermissionsHelper
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.MessageParser
......@@ -41,16 +32,11 @@ import chat.rocket.core.internal.realtime.socket.model.State
import dagger.android.support.AndroidSupportInjection
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.color_palette_view.*
import kotlinx.android.synthetic.main.drawing_view.*
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.fragment_chat_room.*
import kotlinx.android.synthetic.main.message_attachment_options.*
import kotlinx.android.synthetic.main.message_composer.*
import kotlinx.android.synthetic.main.message_list.*
import java.io.File
import java.io.FileOutputStream
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
......@@ -645,218 +631,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
button_add_reaction.setOnClickListener { view ->
openEmojiKeyboardPopup()
}
button_drawing.setOnClickListener{
showDrawingView()
handler.postDelayed({
hideAttachmentOptions()
}, 400)
}
}
}
private fun showDrawingView() {
val drawDialog = Dialog(activity,R.style.DrawingDialogStyle)
drawDialog.setContentView(R.layout.drawing_view)
drawDialog.apply {
colorSelector(drawDialog)
setClickListner(drawDialog)
setPaintWidth(drawDialog)
setPaintAlpha(drawDialog)
}
drawDialog.show()
}
private fun setClickListner(drawDialog: Dialog) {
drawDialog.apply {
image_send_drawing.setOnClickListener {
if (!canWriteToExternalStorage()) {
checkWritingPermission()
}else{
val bitmap = custom_draw_view.getBitmap()
val uri = saveImage(bitmap)
if (uri != null) {
uploadFile(uri)
}
drawDialog.dismiss()
}
}
image_close_drawing.setOnClickListener {
drawDialog.dismiss()
}
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.setVisible(true)
seekBar_opacity.setVisible(false)
draw_color_palette.setVisible(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.setVisible(false)
seekBar_opacity.setVisible(true)
draw_color_palette.setVisible(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.setVisible(false)
seekBar_opacity.setVisible(false)
draw_color_palette.setVisible(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 setPaintWidth(drawDialog: Dialog) {
drawDialog.apply {
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(drawDialog: Dialog) {
drawDialog.apply {
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 fun colorSelector(drawDialog: Dialog) {
drawDialog.apply {
image_color_black.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.black,null))
scaleColorView(drawDialog,image_color_black)
}
image_color_red.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_red,null))
scaleColorView(drawDialog,image_color_red)
}
image_color_yellow.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_yellow,null))
scaleColorView(drawDialog,image_color_yellow)
}
image_color_green.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_green,null))
scaleColorView(drawDialog,image_color_green)
}
image_color_blue.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_blue,null))
scaleColorView(drawDialog,image_color_blue)
}
image_color_pink.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_pink,null))
scaleColorView(drawDialog,image_color_pink)
}
image_color_brown.setOnClickListener {
custom_draw_view.setColor(ResourcesCompat.getColor(resources, R.color.color_brown,null))
scaleColorView(drawDialog,image_color_brown)
}
}
}
private fun scaleColorView(drawDialog: Dialog, view: ImageView) {
drawDialog.apply {
//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 toggleDrawTools(view: View, showView: Boolean = true) {
if (showView){
view.animate().translationY((0).toPx)
}else{
view.animate().translationY((56).toPx)
}
}
val Int.toPx: Float
get() = (this * Resources.getSystem().displayMetrics.density)
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)
}
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)
}
}
......
......@@ -7,8 +7,10 @@ import chat.rocket.android.util.extensions.setVisible
/**
* An adapter for bottomsheet menu that lists all the actions that could be taken over a chat message.
*/
class ActionListAdapter(menuItems: List<MenuItem> = emptyList(), callback: MenuItem.OnMenuItemClickListener) :
ListBottomSheetAdapter(menuItems = menuItems, callback = callback) {
class ActionListAdapter(
menuItems: List<MenuItem> = emptyList(),
callback: MenuItem.OnMenuItemClickListener
) : ListBottomSheetAdapter(menuItems = menuItems, callback = callback) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = menuItems[position]
......@@ -25,7 +27,11 @@ class ActionListAdapter(menuItems: List<MenuItem> = emptyList(), callback: MenuI
callback?.onMenuItemClick(item)
}
val deleteTextColor = holder.itemView.context.resources.getColor(R.color.red)
val color = if (item.itemId == R.id.action_menu_msg_delete) deleteTextColor else textColors.get(item.itemId)
val color = if (item.itemId == R.id.action_message_delete) {
deleteTextColor
} else {
textColors.get(item.itemId)
}
holder.textTitle.setTextColor(color)
}
}
\ No newline at end of file
......@@ -325,6 +325,8 @@ class ViewModelMapper @Inject constructor(
is MessageType.MessagePinned -> context.getString(R.string.message_pinned)
is MessageType.UserMuted -> context.getString(R.string.message_muted, message.message, message.sender?.username)
is MessageType.UserUnMuted -> context.getString(R.string.message_unmuted, message.message, message.sender?.username)
is MessageType.SubscriptionRoleAdded -> context.getString(R.string.message_role_add, message.message, message.role, message.sender?.username)
is MessageType.SubscriptionRoleRemoved -> context.getString(R.string.message_role_removed, message.message, message.role, message.sender?.username)
else -> {
throw InvalidParameterException("Invalid message type: ${message.type}")
}
......
......@@ -211,7 +211,8 @@ class ChatRoomsPresenter @Inject constructor(
userMentions = null,
groupMentions = 0L,
lastMessage = null,
client = client
client = client,
broadcast = false
)
}
}
......@@ -244,7 +245,8 @@ class ChatRoomsPresenter @Inject constructor(
userMentions = null,
groupMentions = 0L,
lastMessage = it.lastMessage,
client = client
client = client,
broadcast = it.broadcast
)
}
}
......@@ -335,7 +337,8 @@ class ChatRoomsPresenter @Inject constructor(
userMentions = it.userMentions,
groupMentions = it.groupMentions,
lastMessage = it.lastMessage,
client = client
client = client,
broadcast = it.broadcast
)
chatRoomsList.add(newRoom)
}
......@@ -478,7 +481,8 @@ class ChatRoomsPresenter @Inject constructor(
userMentions = userMentions,
groupMentions = groupMentions,
lastMessage = room.lastMessage,
client = client
client = client,
broadcast = broadcast
)
removeRoom(room.id, chatRooms)
chatRooms.add(newRoom)
......@@ -517,7 +521,8 @@ class ChatRoomsPresenter @Inject constructor(
userMentions = subscription.userMentions,
groupMentions = subscription.groupMentions,
lastMessage = lastMessage,
client = client
client = client,
broadcast = broadcast
)
removeRoom(subscription.roomId, chatRooms)
chatRooms.add(newRoom)
......@@ -591,7 +596,8 @@ class ChatRoomsPresenter @Inject constructor(
userMentions = it.userMentions,
groupMentions = it.groupMentions,
lastMessage = it.lastMessage,
client = client
client = client,
broadcast = it.broadcast
)
getChatRoomsInteractor.remove(currentServer, it)
......
......@@ -14,6 +14,7 @@ import android.support.v7.widget.SearchView
import android.view.*
import android.widget.CheckBox
import android.widget.RadioGroup
import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import chat.rocket.android.chatrooms.presentation.ChatRoomsView
......@@ -166,11 +167,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
/*val diff = async(CommonPool) {
DiffUtil.calculateDiff(RoomsDiffCallback(adapter.baseAdapter.dataSet, newDataSet))
}.await()*/
if (newDataSet.isEmpty()) {
text_no_search.visibility = View.VISIBLE
}else{
text_no_search.visibility = View.GONE
}
text_no_search.isVisible = newDataSet.isEmpty()
if (isActive) {
adapter.baseAdapter.updateRooms(newDataSet)
// TODO - fix crash to re-enable diff.dispatchUpdatesTo(adapter)
......
......@@ -8,7 +8,7 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.getRoomPinnedMessages
import chat.rocket.core.internal.rest.getPinnedMessages
import chat.rocket.core.model.isSystemMessage
import timber.log.Timber
import javax.inject.Inject
......@@ -37,7 +37,7 @@ class PinnedMessagesPresenter @Inject constructor(
chatRoom?.let { room ->
view.showLoading()
val pinnedMessages =
client.getRoomPinnedMessages(roomId, room.type, pinnedMessagesListOffset)
client.getPinnedMessages(roomId, room.type, pinnedMessagesListOffset)
pinnedMessagesListOffset = pinnedMessages.offset.toInt()
val messageList = mapper.map(pinnedMessages.result.filterNot { it.isSystemMessage() })
view.showPinnedMessages(messageList)
......
......@@ -291,7 +291,7 @@ class PushManager @Inject constructor(
.setLabel(replyTextHint)
.build()
val pendingIntent = getReplyPendingIntent(pushMessage)
val replyAction = NotificationCompat.Action.Builder(R.drawable.ic_reply_black_24px, replyTextHint, pendingIntent)
val replyAction = NotificationCompat.Action.Builder(R.drawable.ic_action_message_reply_24dp, replyTextHint, pendingIntent)
.addRemoteInput(replyRemoteInput)
.setAllowGeneratedReplies(true)
.build()
......
......@@ -32,20 +32,25 @@ class PermissionsInteractor @Inject constructor(
}
/**
* Check whether user is allowed to delete a message.
* Check whether the user is allowed to delete a message.
*/
fun allowedMessageDeleting() = publicSettings()?.allowedMessageDeleting() ?: false
/**
* Checks whether user is allowed to edit a message.
* Checks whether the user is allowed to edit a message.
*/
fun allowedMessageEditing() = publicSettings()?.allowedMessageEditing() ?: false
/**
* Checks whether user is allowed to pin a message to a channel.
* Checks whether the user is allowed to pin a message to a channel.
*/
fun allowedMessagePinning() = publicSettings()?.allowedMessagePinning() ?: false
/**
* Checks whether the user is allowed to star a message.
*/
fun allowedMessageStarring() = publicSettings()?.allowedMessageStarring() ?: false
/**
* Checks whether should show deleted message status.
*/
......
......@@ -24,7 +24,7 @@ class RefreshSettingsInteractor @Inject constructor(
FAVORITE_ROOMS, UPLOAD_STORAGE_TYPE, UPLOAD_MAX_FILE_SIZE, UPLOAD_WHITELIST_MIMETYPES,
HIDE_USER_JOIN, HIDE_USER_LEAVE,
HIDE_TYPE_AU, HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ALLOW_MESSAGE_DELETING,
ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS,
ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING, ALLOW_MESSAGE_STARRING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS,
WIDE_TILE_310, STORE_LAST_MESSAGE)
suspend fun refresh(server: String) {
......
......@@ -49,6 +49,7 @@ const val ALLOW_MESSAGE_EDITING = "Message_AllowEditing"
const val SHOW_DELETED_STATUS = "Message_ShowDeletedStatus"
const val SHOW_EDITED_STATUS = "Message_ShowEditedStatus"
const val ALLOW_MESSAGE_PINNING = "Message_AllowPinning"
const val ALLOW_MESSAGE_STARRING = "Message_AllowStarring"
const val STORE_LAST_MESSAGE = "Store_Last_Message"
/*
......@@ -83,6 +84,7 @@ fun PublicSettings.wideTile(): String? = this[WIDE_TILE_310]?.value as String?
fun PublicSettings.showDeletedStatus(): Boolean = this[SHOW_DELETED_STATUS]?.value == true
fun PublicSettings.showEditedStatus(): Boolean = this[SHOW_EDITED_STATUS]?.value == true
fun PublicSettings.allowedMessagePinning(): Boolean = this[ALLOW_MESSAGE_PINNING]?.value == true
fun PublicSettings.allowedMessageStarring(): Boolean = this[ALLOW_MESSAGE_STARRING]?.value == true
fun PublicSettings.allowedMessageEditing(): Boolean = this[ALLOW_MESSAGE_EDITING]?.value == true
fun PublicSettings.allowedMessageDeleting(): Boolean = this[ALLOW_MESSAGE_DELETING]?.value == true
......
......@@ -110,7 +110,6 @@ class ConnectionManager(internal val client: RocketChatClient) {
Timber.d("Received new Message for room ${message.roomId}")
val channel = roomMessagesChannels[message.roomId]
channel?.send(message)
}
}
......@@ -131,6 +130,7 @@ class ConnectionManager(internal val client: RocketChatClient) {
}
}
}
client.connect()
// Broadcast initial state...
......
......@@ -8,6 +8,7 @@ import chat.rocket.android.util.VersionInfo
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatInvalidProtocolException
import chat.rocket.common.model.ServerInfo
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.serverInfo
import kotlinx.coroutines.experimental.Deferred
......@@ -26,7 +27,13 @@ abstract class CheckServerPresenter constructor(private val strategy: CancelStra
try {
currentServer = serverUrl
client = factory.create(currentServer)
val version = checkServerVersion(serverUrl).await()
val serverInfo = retryIO(description = "serverInfo", times = 5) {
client.serverInfo()
}
if (serverInfo.redirected) {
view.updateServerUrl(serverInfo.url)
}
val version = checkServerVersion(serverInfo)
when (version) {
is Version.VersionOk -> {
Timber.i("Your version is nice! (Requires: 0.62.0, Yours: ${version.version})")
......@@ -55,23 +62,19 @@ abstract class CheckServerPresenter constructor(private val strategy: CancelStra
}
}
internal fun checkServerVersion(serverUrl: String): Deferred<Version> {
currentServer = serverUrl
return async {
val serverInfo = retryIO(description = "serverInfo", times = 5) { client.serverInfo() }
val thisServerVersion = serverInfo.version
val isRequiredVersion = isRequiredServerVersion(thisServerVersion)
val isRecommendedVersion = isRecommendedServerVersion(thisServerVersion)
if (isRequiredVersion) {
if (isRecommendedVersion) {
Timber.i("Your version is nice! (Requires: 0.62.0, Yours: $thisServerVersion)")
return@async Version.VersionOk(thisServerVersion)
} else {
return@async Version.RecommendedVersionWarning(thisServerVersion)
}
private fun checkServerVersion(serverInfo: ServerInfo): Version {
val thisServerVersion = serverInfo.version
val isRequiredVersion = isRequiredServerVersion(thisServerVersion)
val isRecommendedVersion = isRecommendedServerVersion(thisServerVersion)
return if (isRequiredVersion) {
if (isRecommendedVersion) {
Timber.i("Your version is nice! (Requires: 0.62.0, Yours: $thisServerVersion)")
Version.VersionOk(thisServerVersion)
} else {
return@async Version.OutOfDateError(thisServerVersion)
Version.RecommendedVersionWarning(thisServerVersion)
}
} else {
Version.OutOfDateError(thisServerVersion)
}
}
......
<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:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"
android:fillColor="@color/actionMenuColor"/>
</vector>
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="@color/actionMenuColor"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z" />
</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">
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"
android:fillColor="#FF0000"/>
android:fillColor="#FF0000"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
</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">
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"
android:fillColor="@color/actionMenuColor"/>
</vector>
android:fillColor="@color/actionMenuColor"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@color/actionMenuColor"
android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="@color/actionMenuColor"
android:pathData="M6,17h3l2,-4L11,7L5,7v6h3zM14,17h3l2,-4L19,7h-6v6h3z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="@color/actionMenuColor"
android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
</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">
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"
android:fillColor="@color/actionMenuColor"/>
</vector>
android:fillColor="@color/actionMenuColor"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="197.218"
android:viewportHeight="197.218"
android:width="197.218dp"
android:height="197.218dp">
<group
android:translateX="-570.396"
android:translateY="-306.782">
<path
android:pathData="M704.445 306.782l-6.785 6.785c-6.084 6.084 -7.622 14.712 -4.309 21.871l-44.068 35.44 -3.086 -3.086c-7.889 -7.889 -19.525 -7.889 -27.414 0l-8.944 8.953 87.821 87.811 8.934 -8.933c7.899 -7.899 7.899 -19.525 0 -27.433l-3.076 -3.077 36.051 -44.68c6.824 2.466 14.367 1.036 20.037 -4.624l8.008 -5.858 -63.169 -63.169zm-66.867 116.487l-67.182 66.857 0 13.874 13.864 0 66.867 -67.182 -13.549 -13.549z"
android:fillColor="@color/actionMenuColor"
/>
</group>
</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"
<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:id="@+id/root_layout"
......@@ -12,23 +11,23 @@
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone"
app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/message_list_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/text_typing_status"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<include
android:id="@+id/layout_message_list"
......@@ -44,52 +43,65 @@
android:layout_height="100dp"
android:src="@drawable/ic_chat_black_24dp"
android:tint="@color/icon_grey"
app:layout_constraintStart_toStartOf="parent"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/text_chat_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/text_chat_title"
app:layout_constraintVertical_chainStyle="packed"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/text_chat_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/msg_no_chat_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_chat_icon"
app:layout_constraintBottom_toTopOf="@id/text_chat_description"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp"
android:layout_marginTop="24dp"
android:textStyle="bold"
android:textColor="@color/colorSecondaryText"
android:visibility="gone"
tools:visibility="visible"/>
app:layout_constraintBottom_toTopOf="@id/text_chat_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_chat_icon"
tools:visibility="visible" />
<TextView
android:id="@+id/text_chat_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/msg_no_chat_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_chat_title"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:layout_marginTop="16dp"
android:text="@string/msg_no_chat_description"
android:textAlignment="center"
android:textSize="16sp"
android:textColor="@color/colorSecondaryTextLight"
android:textSize="16sp"
android:visibility="gone"
tools:visibility="visible"/>
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_chat_title"
tools:visibility="visible" />
<chat.rocket.android.widget.autocompletion.ui.SuggestionsView
android:id="@+id/suggestions_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/suggestion_background_color"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer" />
<TextView
android:id="@+id/text_typing_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:maxLines="2"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:background="@color/suggestion_background_color" />
app:layout_constraintEnd_toStartOf="parent" />
<include
android:id="@+id/layout_message_composer"
......@@ -102,18 +114,18 @@
android:id="@+id/view_dim"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:background="@color/colorDim"
android:visibility="gone" />
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer" />
<include
android:id="@+id/layout_message_attachment_options"
layout="@layout/message_attachment_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:layout_margin="5dp"
android:visibility="gone" />
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer" />
<TextView
android:id="@+id/connection_status_text"
......
......@@ -36,7 +36,7 @@
android:id="@+id/iv_pin_icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_pin_black_24dp"
android:src="@drawable/ic_action_message_pin_24dp"
android:tint="@color/icon_grey"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
......
......@@ -7,11 +7,11 @@
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding"
android:focusable="true"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding"
android:focusable="true">
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding">
<include
android:id="@+id/layout_avatar"
......@@ -20,36 +20,39 @@
android:layout_height="40dp"
android:layout_marginTop="5dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/new_messages_notif" />
app:layout_constraintTop_toBottomOf="@+id/new_messages_notif" />
<LinearLayout
android:id="@+id/new_messages_notif"
tools:visibility="visible"
android:visibility="gone"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<View
android:layout_gravity="center"
android:layout_height="1dp"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="1dp"
android:layout_gravity="center"
android:layout_marginEnd="4dp"
android:background="@color/red"/>
android:layout_weight="1"
android:background="@color/red" />
<TextView
android:layout_width="wrap_content"
android:text="@string/msg_unread_messages"
android:layout_height="wrap_content"
android:text="@string/msg_unread_messages"
android:textColor="@color/red" />
<View
android:layout_gravity="center"
android:layout_height="1dp"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="1dp"
android:layout_gravity="center"
android:layout_marginStart="4dp"
android:background="@color/red"/>
android:layout_weight="1"
android:background="@color/red" />
</LinearLayout>
<LinearLayout
......@@ -58,8 +61,8 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/new_messages_notif"
app:layout_constraintLeft_toRightOf="@+id/layout_avatar">
app:layout_constraintLeft_toRightOf="@+id/layout_avatar"
app:layout_constraintTop_toBottomOf="@+id/new_messages_notif">
<TextView
android:id="@+id/text_sender"
......@@ -80,11 +83,22 @@
android:id="@+id/text_edit_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/msg_edited"
android:layout_marginStart="8dp"
android:text="@string/msg_edited"
android:textStyle="italic"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/image_star_indicator"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginTop="2dp"
android:src="@drawable/ic_action_message_star_24dp"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
<TextView
......@@ -94,12 +108,13 @@
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_marginTop="5dp"
app:layout_constraintLeft_toLeftOf="@id/top_container"
app:layout_constraintLeft_toLeftOf="@+id/top_container"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/top_container"
tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" />
<include layout="@layout/layout_reactions"
<include
layout="@layout/layout_reactions"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@+id/text_content"
......
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/common_actions">
<item
android:id="@+id/action_menu_msg_reply"
android:icon="@drawable/ic_reply_black_24px"
android:title="@string/action_msg_reply" />
<item
android:id="@+id/action_menu_msg_quote"
android:icon="@drawable/ic_quote_black_24px"
android:title="@string/action_msg_quote" />
<item
android:id="@+id/action_menu_msg_edit"
android:icon="@drawable/ic_edit_black_24px"
android:title="@string/action_msg_edit" />
<item
android:id="@+id/action_menu_msg_copy"
android:icon="@drawable/ic_content_copy_black_24px"
android:title="@string/action_msg_copy" />
<item
android:id="@+id/action_menu_msg_react"
android:icon="@drawable/ic_add_reaction"
android:title="@string/action_msg_add_reaction" />
<!--<item-->
<!--android:id="@+id/action_menu_msg_share"-->
<!--andrtextIconicon="@drawable/ic_share_black_24px"-->
<!--android:title="@string/action_msg_share" />-->
<item
android:id="@+id/action_menu_msg_pin_unpin"
android:icon="@drawable/ic_pin_black_24dp"
android:title="@string/action_msg_pin" />
<!--<item-->
<!--android:id="@+id/action_menu_msg_star"-->
<!--andrtextIconicon="@drawable/ic_star_black_24px"-->
<!--android:title="@string/action_msg_star" />-->
</group>
<group android:id="@+id/dangerous_actions">
<item
android:id="@+id/action_menu_msg_delete"
android:icon="@drawable/ic_delete_black_24px"
android:title="@string/action_msg_delete" />
</group>
<item
android:id="@+id/action_message_reply"
android:icon="@drawable/ic_action_message_reply_24dp"
android:title="@string/action_msg_reply" />
<item
android:id="@+id/action_message_quote"
android:icon="@drawable/ic_action_message_quote_24dp"
android:title="@string/action_msg_quote" />
<item
android:id="@+id/action_message_copy"
android:icon="@drawable/ic_action_message_copy_24dp"
android:title="@string/action_msg_copy" />
<item
android:id="@+id/action_menu_msg_react"
android:icon="@drawable/ic_add_reaction"
android:title="@string/action_msg_add_reaction" />
<!--<item-->
<!--android:id="@+id/action_menu_msg_share"-->
<!--andrtextIconicon="@drawable/ic_share_black_24px"-->
<!--android:title="@string/action_msg_share" />-->
<item
android:id="@+id/action_message_star"
android:icon="@drawable/ic_action_message_star_24dp"
android:title="@string/action_msg_star" />
<item
android:id="@+id/action_message_unpin"
android:icon="@drawable/ic_action_message_pin_24dp"
android:title="@string/action_msg_pin" />
<item
android:id="@+id/action_message_edit"
android:icon="@drawable/ic_action_message_edit_24dp"
android:title="@string/action_msg_edit" />
<item
android:id="@+id/action_message_delete"
android:icon="@drawable/ic_action_message_delete_24dp"
android:title="@string/action_msg_delete" />
</menu>
\ No newline at end of file
......@@ -112,6 +112,14 @@
<string name="msg_no_chat_title">Sin mensajes de chat</string>
<string name="msg_no_chat_description">Comience a conversar para ver\nsus mensajes aquí.</string>
<string name="msg_edited">(editado)</string>
// TODO: Add proper translation.
<string name="msg_and">\u0020and\u0020</string>
// TODO: Add proper translation.
<string name="msg_is_typing">\u0020is typing…</string>
// TODO: Add proper translation.
<string name="msg_are_typing">\u0020are typing…</string>
// TODO: Add proper translation.
<string name="msg_several_users_are_typing">Several users are typing…</string>
<string name="msg_no_search_found">No se han encontrado resultados</string>
<!-- System messages -->
......@@ -125,6 +133,8 @@
<string name="message_pinned">Fijado una mensaje:</string>
<string name="message_muted">Usuario %1$s silenciado por %2$s</string>
<string name="message_unmuted">Usuario %1$s no silenciado por %2$s</string>
<string name="message_role_add">%1$s fue establecido %2$s por %3$s</string>
<string name="message_role_removed">%1$s ya no es %2$s por %3$s</string>
<!-- Message actions -->
<string name="action_msg_reply">Respuesta</string>
......@@ -135,6 +145,8 @@
<string name="action_msg_pin">Fijar mensaje</string>
<string name="action_msg_unpin">Soltar mensaje</string>
<string name="action_msg_star">Star mensaje</string>
// TODO: Add proper translation.
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_share">Compartir</string>
<string name="action_title_editing">Edición de mensaje</string>
<string name="action_msg_add_reaction">Añadir una reacción</string>
......@@ -143,6 +155,8 @@
<string name="permission_editing_not_allowed">La edición no és permitida</string>
<string name="permission_deleting_not_allowed">Eliminar no és permitido</string>
<string name="permission_pinning_not_allowed">Fijar no és permitido</string>
// TODO: Add proper translation.
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Members List -->
<string name="title_members_list">Lista de miembros</string>
......
......@@ -112,6 +112,14 @@
<string name="msg_no_chat_title">Aucun message de discussion</string>
<string name="msg_no_chat_description">Commencez à converser pour voir\nvos messages ici.</string>
<string name="msg_edited">(édité)</string>
// TODO: Add proper translation.
<string name="msg_and">\u0020and\u0020</string>
// TODO: Add proper translation.
<string name="msg_is_typing">\u0020is typing…</string>
// TODO: Add proper translation.
<string name="msg_are_typing">\u0020are typing…</string>
// TODO: Add proper translation.
<string name="msg_several_users_are_typing">Several users are typing…</string>
<string name="msg_no_search_found">Aucun résultat trouvé</string>
<!-- System messages -->
......@@ -125,6 +133,8 @@
<string name="message_pinned">Épinglé un message:</string>
<string name="message_muted">Utilisateur %1$s mis en sourdine par %2$s</string>
<string name="message_unmuted">Utilisateur %1$s non muté par %2$s</string>
<string name="message_role_add">%1$s a été défini %2$s par %3$s</string>
<string name="message_role_removed">%1$s is no longer %2$s par %3$s</string>
<!-- Message actions -->
<string name="action_msg_reply">Répondre</string>
......@@ -134,7 +144,10 @@
<string name="action_msg_delete">Effacer</string>
<string name="action_msg_pin">Épingle message</string>
<string name="action_msg_unpin">Enlever message</string>
// TODO: Add proper translation.
<string name="action_msg_star">Star message</string>
// TODO: Add proper translation.
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_share">Partager</string>
<string name="action_title_editing">Modification du message</string>
<string name="action_msg_add_reaction">Ajouter une réaction</string>
......@@ -143,6 +156,8 @@
<string name="permission_editing_not_allowed">L\'édition n\'est pas autorisée</string>
<string name="permission_deleting_not_allowed">La suppression n\'est pas autorisée</string>
<string name="permission_pinning_not_allowed">L\'épinglage n\'est pas autorisé</string>
// TODO: Add proper translation.
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Members List -->
<string name="title_members_list">Liste des membres</string>
......
......@@ -114,6 +114,14 @@
<string name="msg_no_chat_title">कोई चैट संदेश नहीं</string>
<string name="msg_no_chat_description">यहां अपने संदेश देखने के लिए\nबातचीत शुरू करें।</string>
<string name="msg_edited">(संपादित)</string>
// TODO: Add proper translation.
<string name="msg_and">\u0020and\u0020</string>
// TODO: Add proper translation.
<string name="msg_is_typing">\u0020is typing…</string>
// TODO: Add proper translation.
<string name="msg_are_typing">\u0020are typing…</string>
// TODO: Add proper translation.
<string name="msg_several_users_are_typing">Several users are typing…</string>
<string name="msg_no_search_found">कोई परिणाम नहीं मिला</string>
<!-- System messages -->
......@@ -127,6 +135,8 @@
<string name="message_pinned">एक संदेश पिन किया:</string>
<string name="message_muted">उपयोगकर्ता %1$s %2$s द्वारा म्यूट किया गया</string>
<string name="message_unmuted">उपयोगकर्ता %1$s %2$s द्वारा अनम्यूट किया गया</string>
<string name="message_role_add">%1$s %3$s द्वारा %2$s सेट किया गया था</string>
<string name="message_role_removed">%1$s अब %3$s द्वारा %2$s नहीं है</string>
<!-- Message actions -->
<string name="action_msg_reply">जवाब दें</string>
......@@ -137,6 +147,8 @@
<string name="action_msg_pin">संदेश को पिन करें</string>
<string name="action_msg_unpin">संदेश को पिन से हटाएँ</string>
<string name="action_msg_star">संदेश को स्टार करें</string>
// TODO: Add proper translation.
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_share">शेयर करें</string>
<string name="action_title_editing">संपादन संदेश</string>
<string name="action_msg_add_reaction">प्रतिक्रिया जोड़ें</string>
......@@ -145,6 +157,8 @@
<string name="permission_editing_not_allowed">संपादन की अनुमति नहीं है</string>
<string name="permission_deleting_not_allowed">हटाने की अनुमति नहीं है</string>
<string name="permission_pinning_not_allowed">पिनि करने की अनुमति नहीं है</string>
// TODO: Add proper translation.
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Members List -->
<string name="title_members_list">सदस्यों की सूची</string>
......
......@@ -108,6 +108,10 @@
<string name="msg_image_saved_successfully">Imagem salva na galeria</string>
<string name="msg_image_saved_failed">Falha ao salvar a imagem</string>
<string name="msg_edited">(editado)</string>
<string name="msg_and">\u0020e\u0020</string>
<string name="msg_is_typing">\u0020está digitando…</string>
<string name="msg_are_typing">\u0020estão digitando…</string>
<string name="msg_several_users_are_typing">Vários usuários estão digitando…</string>
<string name="msg_no_search_found">nenhum resultado encontrado</string>
<!-- System messages -->
......@@ -121,6 +125,8 @@
<string name="message_pinned">Pinou uma mensagem:</string>
<string name="message_muted">Usuário %1$s entrou no modo mudo por %2$s</string>
<string name="message_unmuted">Usuário %1$s saiu do modo mudo por %2$s</string>
<string name="message_role_add">%1$s foi definido %2$s por %3$s</string>
<string name="message_role_removed">%1$s não é mais %2$s por %3$s</string>
<!-- Message actions -->
<string name="action_msg_reply">Responder</string>
......@@ -131,6 +137,7 @@
<string name="action_msg_pin">Pinar mensagem</string>
<string name="action_msg_unpin">Despinar mensagem</string>
<string name="action_msg_star">Favoritar mensagem</string>
<string name="action_msg_unstar">Desfavoritar messagem</string>
<string name="action_msg_share">Compartilhar</string>
<string name="action_title_editing">Editando mensagem</string>
<string name="action_msg_add_reaction">Adicionar reação</string>
......@@ -139,6 +146,7 @@
<string name="permission_editing_not_allowed">Edição não permitida</string>
<string name="permission_deleting_not_allowed">Remoção não permitida</string>
<string name="permission_pinning_not_allowed">Pinagem não permitida</string>
<string name="permission_starring_not_allowed">Favoritar não permitido</string>
<!-- Members List -->
<string name="title_members_list">Lista de Membros</string>
......
......@@ -36,6 +36,7 @@
<string name="action_away">Away</string>
<string name="action_busy">Busy</string>
<string name="action_invisible">Invisible</string>
<string name="action_drawing">Drawing</string>
<!-- Settings List -->
<string-array name="settings_actions">
......@@ -131,6 +132,7 @@
<string name="action_msg_pin">Pin Message</string>
<string name="action_msg_unpin">Unpin Message</string>
<string name="action_msg_star">Star Message</string>
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_share">Share</string>
<string name="action_title_editing">Editing Message</string>
<string name="action_msg_add_reaction">Add reaction</string>
......@@ -139,6 +141,8 @@
<string name="permission_editing_not_allowed">Editing is not allowed</string>
<string name="permission_deleting_not_allowed">Deleting is not allowed</string>
<string name="permission_pinning_not_allowed">Pinning is not allowed</string>
// TODO: Add proper translation.
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Members List -->
<string name="title_members_list">Members List</string>
......
......@@ -109,6 +109,10 @@
<string name="msg_image_saved_successfully">Image has been saved to gallery</string>
<string name="msg_image_saved_failed">Failed to save image</string>
<string name="msg_edited">(edited)</string>
<string name="msg_and">\u0020and\u0020</string>
<string name="msg_is_typing">\u0020is typing…</string>
<string name="msg_are_typing">\u0020are typing…</string>
<string name="msg_several_users_are_typing">Several users are typing…</string>
<string name="msg_no_search_found">No result found</string>
<!-- System messages -->
......@@ -122,6 +126,8 @@
<string name="message_pinned">Pinned a message:</string>
<string name="message_muted">User %1$s muted by %2$s</string>
<string name="message_unmuted">User %1$s unmuted by %2$s</string>
<string name="message_role_add">%1$s was set %2$s by %3$s</string>
<string name="message_role_removed">%1$s is no longer %2$s by %3$s</string>
<!-- Message actions -->
<string name="action_msg_reply">Reply</string>
......@@ -132,6 +138,7 @@
<string name="action_msg_pin">Pin Message</string>
<string name="action_msg_unpin">Unpin Message</string>
<string name="action_msg_star">Star Message</string>
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_share">Share</string>
<string name="action_title_editing">Editing Message</string>
<string name="action_msg_add_reaction">Add reaction</string>
......@@ -140,6 +147,7 @@
<string name="permission_editing_not_allowed">Editing is not allowed</string>
<string name="permission_deleting_not_allowed">Deleting is not allowed</string>
<string name="permission_pinning_not_allowed">Pinning is not allowed</string>
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Members List -->
<string name="title_members_list">Members List</string>
......
// 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')
repositories {
......@@ -19,7 +21,7 @@ buildscript {
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" }
}
allprojects {
......@@ -35,4 +37,4 @@ allprojects {
task clean(type: Delete) {
delete rootProject.buildDir
}
\ No newline at end of file
}
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
......@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"
android:fillColor="@color/actionMenuColor"/>
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
......@@ -4,6 +4,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M6,17h3l2,-4L11,7L5,7v6h3zM14,17h3l2,-4L19,7h-6v6h3z"
android:fillColor="@color/actionMenuColor"/>
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'
\ No newline at end of file
include ':app', ':player', ':draw'
\ 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