Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
AloqaIM-Android
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
AloqaIM-Android
Commits
05d9453c
Commit
05d9453c
authored
Feb 06, 2018
by
Filipe de Lima Brito
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Show groupable messages.
parent
d31b9ea1
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
95 additions
and
81 deletions
+95
-81
ChatRoomPresenter.kt
...rocket/android/chatroom/presentation/ChatRoomPresenter.kt
+1
-1
ChatRoomView.kt
...chat/rocket/android/chatroom/presentation/ChatRoomView.kt
+1
-2
ChatRoomAdapter.kt
...n/java/chat/rocket/android/chatroom/ui/ChatRoomAdapter.kt
+28
-32
ChatRoomFragment.kt
.../java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
+2
-3
MessageViewModel.kt
...hat/rocket/android/chatroom/viewmodel/MessageViewModel.kt
+62
-31
avatar.xml
app/src/main/res/layout/avatar.xml
+0
-11
item_message.xml
app/src/main/res/layout/item_message.xml
+1
-1
No files found.
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt
View file @
05d9453c
...
@@ -49,7 +49,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
...
@@ -49,7 +49,7 @@ class ChatRoomPresenter @Inject constructor(private val view: ChatRoomView,
}
}
val
messagesViewModels
=
mapper
.
mapToViewModelList
(
messages
,
settings
)
val
messagesViewModels
=
mapper
.
mapToViewModelList
(
messages
,
settings
)
view
.
showMessages
(
messagesViewModels
,
serverInteractor
.
get
()
!!
)
view
.
showMessages
(
messagesViewModels
)
// Subscribe after getting the first page of messages from REST
// Subscribe after getting the first page of messages from REST
if
(
offset
==
0L
)
{
if
(
offset
==
0L
)
{
...
...
app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt
View file @
05d9453c
...
@@ -10,9 +10,8 @@ interface ChatRoomView : LoadingView, MessageView {
...
@@ -10,9 +10,8 @@ interface ChatRoomView : LoadingView, MessageView {
* Shows the chat room messages.
* Shows the chat room messages.
*
*
* @param dataSet The data set to show.
* @param dataSet The data set to show.
* @param serverUrl The server URL.
*/
*/
fun
showMessages
(
dataSet
:
List
<
MessageViewModel
>
,
serverUrl
:
String
)
fun
showMessages
(
dataSet
:
List
<
MessageViewModel
>)
/**
/**
* Send a message to a chat room.
* Send a message to a chat room.
...
...
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomAdapter.kt
View file @
05d9453c
...
@@ -3,7 +3,6 @@ package chat.rocket.android.chatroom.ui
...
@@ -3,7 +3,6 @@ package chat.rocket.android.chatroom.ui
import
android.support.v7.widget.RecyclerView
import
android.support.v7.widget.RecyclerView
import
android.view.View
import
android.view.View
import
android.view.ViewGroup
import
android.view.ViewGroup
import
android.widget.ImageView
import
android.widget.TextView
import
android.widget.TextView
import
chat.rocket.android.R
import
chat.rocket.android.R
import
chat.rocket.android.chatroom.viewmodel.AttachmentType
import
chat.rocket.android.chatroom.viewmodel.AttachmentType
...
@@ -11,33 +10,33 @@ import chat.rocket.android.chatroom.viewmodel.MessageViewModel
...
@@ -11,33 +10,33 @@ import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import
chat.rocket.android.player.PlayerActivity
import
chat.rocket.android.player.PlayerActivity
import
chat.rocket.android.util.inflate
import
chat.rocket.android.util.inflate
import
chat.rocket.android.util.setVisible
import
chat.rocket.android.util.setVisible
import
chat.rocket.common.util.ifNull
import
com.facebook.drawee.view.SimpleDraweeView
import
com.facebook.drawee.view.SimpleDraweeView
import
com.stfalcon.frescoimageviewer.ImageViewer
import
com.stfalcon.frescoimageviewer.ImageViewer
import
kotlinx.android.synthetic.main.avatar.view.*
import
kotlinx.android.synthetic.main.avatar.view.*
import
kotlinx.android.synthetic.main.item_message.view.*
import
kotlinx.android.synthetic.main.item_message.view.*
import
kotlinx.android.synthetic.main.message_attachment.view.*
import
kotlinx.android.synthetic.main.message_attachment.view.*
class
ChatRoomAdapter
(
private
val
serverUrl
:
String
)
:
RecyclerView
.
Adapter
<
ChatRoomAdapter
.
ViewHolder
>()
{
class
ChatRoomAdapter
:
RecyclerView
.
Adapter
<
ChatRoomAdapter
.
ViewHolder
>()
{
private
val
dataSet
=
ArrayList
<
MessageViewModel
>()
init
{
init
{
setHasStableIds
(
true
)
setHasStableIds
(
true
)
}
}
val
dataSet
=
ArrayList
<
MessageViewModel
>()
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
ViewHolder
=
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
ViewHolder
=
ViewHolder
(
parent
.
inflate
(
R
.
layout
.
item_message
)
,
serverUrl
)
ViewHolder
(
parent
.
inflate
(
R
.
layout
.
item_message
))
override
fun
onBindViewHolder
(
holder
:
ViewHolder
,
position
:
Int
)
=
holder
.
bind
(
dataSet
[
position
])
override
fun
onBindViewHolder
(
holder
:
ViewHolder
,
position
:
Int
)
=
holder
.
bind
(
dataSet
[
position
],
dataSet
.
getOrNull
(
position
+
1
))
override
fun
onBindViewHolder
(
holder
:
ViewHolder
,
position
:
Int
,
payloads
:
MutableList
<
Any
>?)
{
override
fun
getItemCount
():
Int
=
onBindViewHolder
(
holder
,
position
)
dataSet
.
size
}
override
fun
getItemCount
():
Int
=
dataSet
.
size
override
fun
getItemViewType
(
position
:
Int
):
Int
=
position
override
fun
getItemViewType
(
position
:
Int
):
Int
=
position
override
fun
getItemId
(
position
:
Int
):
Long
=
dataSet
[
position
].
id
.
hashCode
().
toLong
()
fun
addDataSet
(
dataSet
:
List
<
MessageViewModel
>)
{
fun
addDataSet
(
dataSet
:
List
<
MessageViewModel
>)
{
val
previousDataSetSize
=
this
.
dataSet
.
size
val
previousDataSetSize
=
this
.
dataSet
.
size
...
@@ -55,22 +54,28 @@ class ChatRoomAdapter(private val serverUrl: String) : RecyclerView.Adapter<Chat
...
@@ -55,22 +54,28 @@ class ChatRoomAdapter(private val serverUrl: String) : RecyclerView.Adapter<Chat
notifyItemChanged
(
index
)
notifyItemChanged
(
index
)
}
}
override
fun
getItemId
(
position
:
Int
):
Long
{
class
ViewHolder
(
itemView
:
View
)
:
RecyclerView
.
ViewHolder
(
itemView
)
{
return
dataSet
[
position
].
id
.
hashCode
().
toLong
()
}
class
ViewHolder
(
itemView
:
View
,
val
serverUrl
:
String
)
:
RecyclerView
.
ViewHolder
(
itemView
)
{
fun
bind
(
message
:
MessageViewModel
)
=
with
(
itemView
)
{
fun
bind
(
message
:
MessageViewModel
,
nextMessage
:
MessageViewModel
?
)
=
with
(
itemView
)
{
bindUserAvatar
(
message
,
image_avatar
,
image_unknown_avatar
)
image_avatar
.
setImageURI
(
message
.
avatarUri
)
text_
user_name
.
text
=
message
.
sender
text_
sender
.
text
=
message
.
senderName
text_message_time
.
text
=
message
.
time
text_message_time
.
text
=
message
.
time
text_content
.
text
=
message
.
content
bindAttachment
(
message
,
message_attachment
,
image_attachment
,
audio_video_attachment
,
if
(
nextMessage
!=
null
)
{
file_name
)
if
(
isSequential
(
message
,
nextMessage
))
{
image_avatar
.
setVisible
(
false
)
text_sender
.
setVisible
(
false
)
text_message_time
.
setVisible
(
false
)
}
}
text_content
.
text
=
message
.
content
bindAttachment
(
message
,
message_attachment
,
image_attachment
,
audio_video_attachment
,
file_name
)
}
}
private
fun
isSequential
(
message
:
MessageViewModel
,
nextMessage
:
MessageViewModel
):
Boolean
=
(
message
.
isGroupable
&&
nextMessage
.
isGroupable
)
&&
(
message
.
senderId
==
nextMessage
.
senderId
)
private
fun
bindAttachment
(
message
:
MessageViewModel
,
private
fun
bindAttachment
(
message
:
MessageViewModel
,
attachment_container
:
View
,
attachment_container
:
View
,
image_attachment
:
SimpleDraweeView
,
image_attachment
:
SimpleDraweeView
,
...
@@ -113,14 +118,5 @@ class ChatRoomAdapter(private val serverUrl: String) : RecyclerView.Adapter<Chat
...
@@ -113,14 +118,5 @@ class ChatRoomAdapter(private val serverUrl: String) : RecyclerView.Adapter<Chat
file_name
.
text
=
message
.
attachmentTitle
file_name
.
text
=
message
.
attachmentTitle
}
}
}
}
private
fun
bindUserAvatar
(
message
:
MessageViewModel
,
drawee
:
SimpleDraweeView
,
imageUnknownAvatar
:
ImageView
)
=
message
.
getAvatarUrl
(
serverUrl
).
let
{
drawee
.
setImageURI
(
it
.
toString
())
drawee
.
setVisible
(
true
)
imageUnknownAvatar
.
setVisible
(
false
)
}.
ifNull
{
drawee
.
setVisible
(
false
)
imageUnknownAvatar
.
setVisible
(
true
)
}
}
}
}
}
\ No newline at end of file
app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt
View file @
05d9453c
...
@@ -39,7 +39,6 @@ private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
...
@@ -39,7 +39,6 @@ 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"
class
ChatRoomFragment
:
Fragment
(),
ChatRoomView
{
class
ChatRoomFragment
:
Fragment
(),
ChatRoomView
{
@Inject
lateinit
var
presenter
:
ChatRoomPresenter
@Inject
lateinit
var
presenter
:
ChatRoomPresenter
private
lateinit
var
chatRoomId
:
String
private
lateinit
var
chatRoomId
:
String
private
lateinit
var
chatRoomName
:
String
private
lateinit
var
chatRoomName
:
String
...
@@ -75,10 +74,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
...
@@ -75,10 +74,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
super
.
onDestroyView
()
super
.
onDestroyView
()
}
}
override
fun
showMessages
(
dataSet
:
List
<
MessageViewModel
>
,
serverUrl
:
String
)
{
override
fun
showMessages
(
dataSet
:
List
<
MessageViewModel
>)
{
activity
?.
apply
{
activity
?.
apply
{
if
(
recycler_view
.
adapter
==
null
)
{
if
(
recycler_view
.
adapter
==
null
)
{
adapter
=
ChatRoomAdapter
(
serverUrl
)
adapter
=
ChatRoomAdapter
()
recycler_view
.
adapter
=
adapter
recycler_view
.
adapter
=
adapter
val
linearLayoutManager
=
LinearLayoutManager
(
context
,
LinearLayoutManager
.
VERTICAL
,
true
)
val
linearLayoutManager
=
LinearLayoutManager
(
context
,
LinearLayoutManager
.
VERTICAL
,
true
)
recycler_view
.
layoutManager
=
linearLayoutManager
recycler_view
.
layoutManager
=
linearLayoutManager
...
...
app/src/main/java/chat/rocket/android/chatroom/viewmodel/MessageViewModel.kt
View file @
05d9453c
...
@@ -26,22 +26,26 @@ data class MessageViewModel(val context: Context,
...
@@ -26,22 +26,26 @@ data class MessageViewModel(val context: Context,
private
val
message
:
Message
,
private
val
message
:
Message
,
private
val
settings
:
Map
<
String
,
Value
<
Any
>>?)
{
private
val
settings
:
Map
<
String
,
Value
<
Any
>>?)
{
val
id
:
String
=
message
.
id
val
id
:
String
=
message
.
id
val
isGroupable
=
message
.
groupable
val
senderId
=
message
.
sender
?.
id
val
avatarUri
:
String
?
val
time
:
CharSequence
val
time
:
CharSequence
val
sender
:
CharSequence
val
sender
Name
:
CharSequence
val
content
:
CharSequence
val
content
:
CharSequence
var
attachmentUrl
:
String
?
=
null
var
attachmentUrl
:
String
?
=
null
var
attachmentTitle
:
CharSequence
?
=
null
var
attachmentTitle
:
CharSequence
?
=
null
var
attachmentType
:
AttachmentType
?
=
null
var
attachmentType
:
AttachmentType
?
=
null
private
val
baseUrl
=
settings
?.
get
(
SITE_URL
)
init
{
init
{
sender
=
getSenderName
()
avatarUri
=
getUserAvatar
()
content
=
getContent
(
context
)
time
=
getTime
()
time
=
getTime
()
senderName
=
getSender
()
content
=
getContent
(
context
)
message
.
attachments
?.
let
{
message
.
attachments
?.
let
{
if
(
it
.
isEmpty
()
||
it
[
0
]
==
null
)
return
@let
if
(
it
.
isEmpty
())
return
@let
val
attachment
=
it
[
0
]
as
FileAttachment
val
attachment
=
it
[
0
]
as
FileAttachment
val
baseUrl
=
settings
?.
get
(
SITE_URL
)
baseUrl
?.
let
{
baseUrl
?.
let
{
attachmentUrl
=
attachmentUrl
(
"${baseUrl.value}${attachment.url}"
)
attachmentUrl
=
attachmentUrl
(
"${baseUrl.value}${attachment.url}"
)
attachmentTitle
=
attachment
.
title
attachmentTitle
=
attachment
.
title
...
@@ -56,36 +60,55 @@ data class MessageViewModel(val context: Context,
...
@@ -56,36 +60,55 @@ data class MessageViewModel(val context: Context,
}
}
}
}
fun
getAvatarUrl
(
serverUrl
:
String
):
String
?
{
private
fun
getUserAvatar
():
String
?
{
return
message
.
sender
?.
username
.
let
{
val
username
=
message
.
sender
?.
username
?:
"?"
return
@let
UrlHelper
.
getAvatarUrl
(
serverUrl
,
it
.
toString
())
return
baseUrl
?.
let
{
UrlHelper
.
getAvatarUrl
(
baseUrl
.
value
.
toString
(),
username
)
}
}
}
}
fun
getTime
()
=
DateTimeHelper
.
getTime
(
DateTimeHelper
.
getLocalDateTime
(
message
.
timestamp
))
private
fun
getTime
()
=
DateTimeHelper
.
getTime
(
DateTimeHelper
.
getLocalDateTime
(
message
.
timestamp
))
fun
getSenderName
():
CharSequence
{
private
fun
getSender
():
CharSequence
{
val
useRealName
=
settings
?.
get
(
USE_REALNAME
)
?.
value
as
Boolean
val
useRealName
=
settings
?.
get
(
USE_REALNAME
)
?.
value
as
Boolean
val
username
=
message
.
sender
?.
username
val
username
=
message
.
sender
?.
username
val
realName
=
message
.
sender
?.
name
val
realName
=
message
.
sender
?.
name
val
senderName
=
if
(
useRealName
)
realName
else
username
val
senderName
=
if
(
useRealName
)
realName
else
username
return
senderName
?:
username
.
toString
(
)
return
senderName
?:
context
.
getString
(
R
.
string
.
msg_unknown
)
}
}
fun
getContent
(
context
:
Context
):
CharSequence
{
private
fun
getContent
(
context
:
Context
):
CharSequence
{
val
contentMessage
:
CharSequence
val
contentMessage
:
CharSequence
when
(
message
.
type
)
{
when
(
message
.
type
)
{
//TODO: Add implementation for Welcome type.
//TODO: Add implementation for Welcome type.
is
MessageRemoved
->
contentMessage
=
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_removed
))
is
MessageRemoved
->
{
is
UserJoined
->
contentMessage
=
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_user_joined_channel
))
contentMessage
=
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_removed
))
is
UserLeft
->
contentMessage
=
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_user_left
))
}
is
UserAdded
->
contentMessage
=
getSystemMessage
(
is
UserJoined
->
{
context
.
getString
(
R
.
string
.
message_user_added_by
,
message
.
message
,
message
.
sender
?.
username
))
contentMessage
=
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_user_joined_channel
))
is
RoomNameChanged
->
contentMessage
=
getSystemMessage
(
}
context
.
getString
(
R
.
string
.
message_room_name_changed
,
message
.
message
,
message
.
sender
?.
username
))
is
UserLeft
->
{
is
UserRemoved
->
contentMessage
=
getSystemMessage
(
contentMessage
=
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_user_left
))
context
.
getString
(
R
.
string
.
message_user_removed_by
,
message
.
message
,
message
.
sender
?.
username
))
}
else
->
contentMessage
=
getNormalMessage
()
is
UserAdded
->
{
contentMessage
=
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_user_added_by
,
message
.
message
,
message
.
sender
?.
username
))
}
is
RoomNameChanged
->
{
contentMessage
=
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_room_name_changed
,
message
.
message
,
message
.
sender
?.
username
))
}
is
UserRemoved
->
{
contentMessage
=
getSystemMessage
(
context
.
getString
(
R
.
string
.
message_user_removed_by
,
message
.
message
,
message
.
sender
?.
username
))
}
else
->
{
contentMessage
=
getNormalMessage
()
}
}
}
return
contentMessage
return
contentMessage
}
}
...
@@ -94,10 +117,14 @@ data class MessageViewModel(val context: Context,
...
@@ -94,10 +117,14 @@ data class MessageViewModel(val context: Context,
private
fun
getSystemMessage
(
content
:
String
):
CharSequence
{
private
fun
getSystemMessage
(
content
:
String
):
CharSequence
{
val
spannableMsg
=
SpannableString
(
content
)
val
spannableMsg
=
SpannableString
(
content
)
spannableMsg
.
setSpan
(
StyleSpan
(
Typeface
.
ITALIC
),
0
,
spannableMsg
.
length
,
spannableMsg
.
setSpan
(
StyleSpan
(
Typeface
.
ITALIC
),
0
)
0
,
spannableMsg
.
setSpan
(
ForegroundColorSpan
(
Color
.
GRAY
),
0
,
spannableMsg
.
length
,
spannableMsg
.
length
,
0
)
0
)
spannableMsg
.
setSpan
(
ForegroundColorSpan
(
Color
.
GRAY
),
0
,
spannableMsg
.
length
,
0
)
val
username
=
message
.
sender
?.
username
val
username
=
message
.
sender
?.
username
val
message
=
message
.
message
val
message
=
message
.
message
...
@@ -108,13 +135,17 @@ data class MessageViewModel(val context: Context,
...
@@ -108,13 +135,17 @@ data class MessageViewModel(val context: Context,
val
messageTextEndIndex
=
messageTextStartIndex
+
message
.
length
val
messageTextEndIndex
=
messageTextStartIndex
+
message
.
length
if
(
usernameTextStartIndex
>
-
1
)
{
if
(
usernameTextStartIndex
>
-
1
)
{
spannableMsg
.
setSpan
(
StyleSpan
(
Typeface
.
BOLD_ITALIC
),
usernameTextStartIndex
,
usernameTextEndIndex
,
spannableMsg
.
setSpan
(
StyleSpan
(
Typeface
.
BOLD_ITALIC
),
0
)
usernameTextStartIndex
,
usernameTextEndIndex
,
0
)
}
}
if
(
messageTextStartIndex
>
-
1
)
{
if
(
messageTextStartIndex
>
-
1
)
{
spannableMsg
.
setSpan
(
StyleSpan
(
Typeface
.
BOLD_ITALIC
),
messageTextStartIndex
,
messageTextEndIndex
,
spannableMsg
.
setSpan
(
StyleSpan
(
Typeface
.
BOLD_ITALIC
),
0
)
messageTextStartIndex
,
messageTextEndIndex
,
0
)
}
}
return
spannableMsg
return
spannableMsg
...
...
app/src/main/res/layout/avatar.xml
View file @
05d9453c
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
<LinearLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
xmlns:tools=
"http://schemas.android.com/tools"
android:layout_width=
"match_parent"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:layout_height=
"match_parent"
android:orientation=
"vertical"
>
android:orientation=
"vertical"
>
...
@@ -12,14 +11,4 @@
...
@@ -12,14 +11,4 @@
android:layout_height=
"40dp"
android:layout_height=
"40dp"
app:roundedCornerRadius=
"2dp"
/>
app:roundedCornerRadius=
"2dp"
/>
<!-- TODO define the correct bg color for this-->
<ImageView
android:id=
"@+id/image_unknown_avatar"
android:layout_width=
"40dp"
android:layout_height=
"40dp"
android:src=
"@drawable/ic_help_black_24dp"
android:tint=
"@color/colorAccent"
android:visibility=
"gone"
tools:ignore=
"contentDescription"
/>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
app/src/main/res/layout/item_message.xml
View file @
05d9453c
...
@@ -27,7 +27,7 @@
...
@@ -27,7 +27,7 @@
app:layout_constraintLeft_toRightOf=
"@+id/layout_avatar"
>
app:layout_constraintLeft_toRightOf=
"@+id/layout_avatar"
>
<TextView
<TextView
android:id=
"@+id/text_
user_name
"
android:id=
"@+id/text_
sender
"
style=
"@style/TextAppearance.AppCompat.Title"
style=
"@style/TextAppearance.AppCompat.Title"
android:layout_width=
"wrap_content"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_height=
"wrap_content"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment