Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Y
YGOMobile
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
fallenstardust
YGOMobile
Commits
97d73337
Commit
97d73337
authored
Jun 09, 2025
by
fallenstardust
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
添加登录信息记录
parent
62919e3c
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
157 additions
and
135 deletions
+157
-135
mobile/src/main/java/cn/garymb/ygomobile/deck_square/DeckSquareFragment.java
...a/cn/garymb/ygomobile/deck_square/DeckSquareFragment.java
+0
-1
mobile/src/main/java/cn/garymb/ygomobile/deck_square/DeckSquareMyDeckFragment.java
...arymb/ygomobile/deck_square/DeckSquareMyDeckFragment.java
+11
-0
mobile/src/main/java/cn/garymb/ygomobile/ui/mycard/mcchat/ChatMessage.java
...ava/cn/garymb/ygomobile/ui/mycard/mcchat/ChatMessage.java
+1
-1
mobile/src/main/java/cn/garymb/ygomobile/ui/mycard/mcchat/management/ServiceManagement.java
...mobile/ui/mycard/mcchat/management/ServiceManagement.java
+135
-128
mobile/src/main/res/layout/fragment_deck_square_my_deck.xml
mobile/src/main/res/layout/fragment_deck_square_my_deck.xml
+10
-5
No files found.
mobile/src/main/java/cn/garymb/ygomobile/deck_square/DeckSquareFragment.java
View file @
97d73337
...
...
@@ -69,7 +69,6 @@ public class DeckSquareFragment extends Fragment {
}
catch
(
NumberFormatException
e
)
{
}
Log
.
d
(
"seesee sortlike"
,
sortLike
.
toString
());
deckSquareListAdapter
.
loadData
(
targetPage
,
30
,
""
,
sortLike
,
false
,
""
);
}
});
...
...
mobile/src/main/java/cn/garymb/ygomobile/deck_square/DeckSquareMyDeckFragment.java
View file @
97d73337
...
...
@@ -7,6 +7,7 @@ import android.os.Bundle;
import
android.view.LayoutInflater
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.widget.ImageView
;
import
androidx.annotation.NonNull
;
import
androidx.fragment.app.Fragment
;
...
...
@@ -21,11 +22,15 @@ import cn.garymb.ygomobile.lite.R;
import
cn.garymb.ygomobile.lite.databinding.FragmentDeckSquareMyDeckBinding
;
import
cn.garymb.ygomobile.ui.activities.WebActivity
;
import
cn.garymb.ygomobile.ui.mycard.MyCard
;
import
cn.garymb.ygomobile.ui.mycard.bean.McUser
;
import
cn.garymb.ygomobile.ui.mycard.mcchat.ChatMessage
;
import
cn.garymb.ygomobile.ui.mycard.mcchat.management.UserManagement
;
import
cn.garymb.ygomobile.ui.plus.VUiKit
;
import
cn.garymb.ygomobile.utils.LogUtil
;
import
cn.garymb.ygomobile.utils.SharedPreferenceUtil
;
import
cn.garymb.ygomobile.utils.YGODeckDialogUtil
;
import
cn.garymb.ygomobile.utils.YGOUtil
;
import
cn.garymb.ygomobile.utils.glide.GlideCompat
;
//打开页面后,先扫描本地的卡组,读取其是否包含deckId,是的话代表平台上可能有
//之后读取平台上的卡组,与本地卡组列表做比较。
...
...
@@ -145,6 +150,12 @@ public class DeckSquareMyDeckFragment extends Fragment {
binding
.
progressBar
.
setVisibility
(
View
.
GONE
);
binding
.
btnLogin
.
setEnabled
(
true
);
YGOUtil
.
showTextToast
(
R
.
string
.
login_succeed
);
McUser
mcUser
=
new
McUser
();
mcUser
.
setUsername
(
result
.
user
.
username
);
mcUser
.
setExternal_id
(
result
.
user
.
id
);
mcUser
.
setAvatar_url
(
ChatMessage
.
getAvatarUrl
(
result
.
user
.
username
));
GlideCompat
.
with
(
getActivity
()).
load
(
mcUser
.
getAvatar_url
()).
into
(
binding
.
myDeckAvatar
);
//刷新头像图片
UserManagement
.
getDx
().
setMcUser
(
mcUser
);
}
else
{
YGOUtil
.
showTextToast
(
R
.
string
.
logining_failed
);
binding
.
llMainUi
.
setVisibility
(
View
.
GONE
);
...
...
mobile/src/main/java/cn/garymb/ygomobile/ui/mycard/mcchat/ChatMessage.java
View file @
97d73337
...
...
@@ -72,7 +72,7 @@ public class ChatMessage {
this
.
avatar
=
getAvatarUrl
(
name
);
}
public
String
getAvatarUrl
(
String
userName
)
{
public
static
String
getAvatarUrl
(
String
userName
)
{
return
"https://sapi.moecube.com:444/avatar/avatar/"
+
userName
+
"/100/ygomobile.png"
;
}
...
...
mobile/src/main/java/cn/garymb/ygomobile/ui/mycard/mcchat/management/ServiceManagement.java
View file @
97d73337
...
...
@@ -2,6 +2,8 @@ package cn.garymb.ygomobile.ui.mycard.mcchat.management;
import
android.annotation.SuppressLint
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.os.Message
;
import
android.text.TextUtils
;
import
android.util.Log
;
...
...
@@ -20,7 +22,9 @@ import org.jxmpp.stringprep.XmppStringprepException;
import
java.io.IOException
;
import
java.net.InetAddress
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.concurrent.ConcurrentHashMap
;
import
cn.garymb.ygomobile.ui.mycard.base.OnJoinChatListener
;
import
cn.garymb.ygomobile.ui.mycard.bean.McUser
;
...
...
@@ -45,142 +49,121 @@ public class ServiceManagement {
public
static
final
int
CHAT_USER_NULL
=
8
;
private
static
final
ServiceManagement
su
=
new
ServiceManagement
();
private
XMPPTCPConnection
con
;
private
MultiUserChat
muc
;
private
boolean
isConnected
=
false
;
private
boolean
isListener
=
false
;
private
boolean
isStartLoading
=
false
;
private
final
List
<
ChatMessage
>
chatMessageList
;
private
final
List
<
ChatListener
>
chatListenerList
;
private
final
List
<
OnJoinChatListener
>
joinChatListenerList
;
private
final
ConcurrentHashMap
<
Integer
,
ChatListener
>
chatListenerList
;
private
final
ConcurrentHashMap
<
Integer
,
OnJoinChatListener
>
joinChatListenerList
;
private
int
chatListenerId
=
0
;
private
int
joinChatListenerId
=
0
;
@SuppressLint
(
"HandlerLeak"
)
Handler
han
=
new
Handler
()
{
private
final
Handler
han
=
new
Handler
(
Looper
.
getMainLooper
())
{
@Override
public
void
handleMessage
(
android
.
os
.
Message
msg
)
{
// TODO: Implement this method
super
.
handleMessage
(
msg
);
int
i
=
0
;
public
void
handleMessage
(
Message
msg
)
{
switch
(
msg
.
what
)
{
case
TYPE_ADD_MESSAGE:
while
(
i
<
chatListenerList
.
size
())
{
ChatListener
chatListener
=
chatListenerList
.
get
(
i
);
if
(
chatListener
.
isListenerEffective
())
{
chatListener
.
addChatMessage
((
ChatMessage
)
msg
.
obj
);
i
++;
}
else
{
chatListenerList
.
remove
(
i
);
}
}
broadcastChatMessage
((
ChatMessage
)
msg
.
obj
);
break
;
case
TYPE_RE_LOGIN:
while
(
i
<
chatListenerList
.
size
())
{
ChatListener
chatListener
=
chatListenerList
.
get
(
i
);
if
(
chatListener
.
isListenerEffective
())
{
chatListener
.
reChatLogin
((
boolean
)
msg
.
obj
);
i
++;
}
else
{
chatListenerList
.
remove
(
i
);
}
}
broadcastReLogin
((
boolean
)
msg
.
obj
);
break
;
case
TYPE_RE_JOIN:
while
(
i
<
chatListenerList
.
size
())
{
ChatListener
chatListener
=
chatListenerList
.
get
(
i
);
if
(
chatListener
.
isListenerEffective
())
{
chatListener
.
reChatJoin
((
boolean
)
msg
.
obj
);
i
++;
}
else
{
chatListenerList
.
remove
(
i
);
}
}
broadcastReJoin
((
boolean
)
msg
.
obj
);
break
;
case
CHAT_LOGIN_EXCEPTION_RE:
// while (i < joinChatListenerList.size()) {
// OnJoinChatListener ou = joinChatListenerList.get(i);
// if (ou.isListenerEffective()) {
// ou.onLoginExceptionClickRe();
// i++;
// } else {
// joinChatListenerList.remove(i);
// }
// }
// break;
// 处理重新登录异常的逻辑
//for (OnJoinChatListener listener : joinChatListenerList.values()) {
// if (listener != null && listener.isListenerEffective()) {
// listener.onLoginExceptionClickRe();
// }
//}
//break;
case
CHAT_LOGIN_OK:
while
(
i
<
joinChatListenerList
.
size
())
{
OnJoinChatListener
onJoinChatListener
=
joinChatListenerList
.
get
(
i
);
if
(
onJoinChatListener
.
isListenerEffective
())
{
onJoinChatListener
.
onChatLogin
(
null
);
i
++;
}
else
{
joinChatListenerList
.
remove
(
i
);
for
(
OnJoinChatListener
listener
:
joinChatListenerList
.
values
())
{
if
(
listener
!=
null
&&
listener
.
isListenerEffective
())
{
listener
.
onChatLogin
(
null
);
}
}
break
;
case
CHAT_LOGIN_EXCEPTION:
while
(
i
<
joinChatListenerList
.
size
())
{
OnJoinChatListener
onJoinChatListener
=
joinChatListenerList
.
get
(
i
);
if
(
onJoinChatListener
.
isListenerEffective
())
{
onJoinChatListener
.
onChatLogin
(
msg
.
obj
+
""
);
i
++;
}
else
{
joinChatListenerList
.
remove
(
i
);
for
(
OnJoinChatListener
listener
:
joinChatListenerList
.
values
())
{
if
(
listener
!=
null
&&
listener
.
isListenerEffective
())
{
listener
.
onChatLogin
(
msg
.
obj
+
""
);
}
}
break
;
case
CHAT_LOGIN_LOADING:
while
(
i
<
joinChatListenerList
.
size
())
{
OnJoinChatListener
onJoinChatListener
=
joinChatListenerList
.
get
(
i
);
if
(
onJoinChatListener
.
isListenerEffective
())
{
onJoinChatListener
.
onChatLoginLoading
();
i
++;
}
else
{
joinChatListenerList
.
remove
(
i
);
for
(
OnJoinChatListener
listener
:
joinChatListenerList
.
values
())
{
if
(
listener
!=
null
&&
listener
.
isListenerEffective
())
{
listener
.
onChatLoginLoading
();
}
}
break
;
case
CHAT_JOIN_ROOM_LOADING:
while
(
i
<
joinChatListenerList
.
size
())
{
OnJoinChatListener
onJoinChatListener
=
joinChatListenerList
.
get
(
i
);
if
(
onJoinChatListener
.
isListenerEffective
())
{
onJoinChatListener
.
onJoinRoomLoading
();
i
++;
}
else
{
joinChatListenerList
.
remove
(
i
);
for
(
OnJoinChatListener
listener
:
joinChatListenerList
.
values
())
{
if
(
listener
!=
null
&&
listener
.
isListenerEffective
())
{
listener
.
onJoinRoomLoading
();
}
}
break
;
case
CHAT_USER_NULL:
while
(
i
<
joinChatListenerList
.
size
())
{
OnJoinChatListener
onJoinChatListener
=
joinChatListenerList
.
get
(
i
);
if
(
onJoinChatListener
.
isListenerEffective
())
{
onJoinChatListener
.
onChatUserNull
();
i
++;
}
else
{
joinChatListenerList
.
remove
(
i
);
for
(
OnJoinChatListener
listener
:
joinChatListenerList
.
values
())
{
if
(
listener
!=
null
&&
listener
.
isListenerEffective
())
{
listener
.
onChatUserNull
();
}
}
break
;
}
}
private
void
broadcastChatMessage
(
ChatMessage
message
)
{
for
(
ChatListener
listener
:
chatListenerList
.
values
())
{
if
(
listener
!=
null
&&
listener
.
isListenerEffective
())
{
listener
.
addChatMessage
(
message
);
}
}
}
private
void
broadcastReLogin
(
boolean
state
)
{
for
(
ChatListener
listener
:
chatListenerList
.
values
())
{
if
(
listener
!=
null
&&
listener
.
isListenerEffective
())
{
listener
.
reChatLogin
(
state
);
}
}
}
private
void
broadcastReJoin
(
boolean
state
)
{
for
(
ChatListener
listener
:
chatListenerList
.
values
())
{
if
(
listener
!=
null
&&
listener
.
isListenerEffective
())
{
listener
.
reChatJoin
(
state
);
}
}
}
};
private
XMPPTCPConnection
con
;
private
MultiUserChat
muc
;
private
boolean
isConnected
=
false
;
private
boolean
isListener
=
false
;
private
boolean
isStartLoading
=
false
;
private
ServiceManagement
()
{
chatMessageList
=
new
ArrayList
<>(
);
chatListenerList
=
new
ArrayList
<>();
joinChatListenerList
=
new
ArrayList
<>();
chatMessageList
=
Collections
.
synchronizedList
(
new
ArrayList
<>()
);
chatListenerList
=
new
ConcurrentHashMap
<>();
joinChatListenerList
=
new
ConcurrentHashMap
<>();
}
public
static
ServiceManagement
getDx
()
{
return
su
;
}
public
void
addListener
(
ChatListener
c
)
{
chatListenerList
.
add
(
c
);
public
int
addListener
(
ChatListener
c
)
{
int
id
=
++
chatListenerId
;
chatListenerList
.
put
(
id
,
c
);
return
id
;
}
public
void
removeListener
(
int
id
)
{
chatListenerList
.
remove
(
id
);
}
public
List
<
ChatMessage
>
getData
()
{
...
...
@@ -233,28 +216,36 @@ public class ServiceManagement {
}
public
void
sendMessage
(
String
message
)
throws
SmackException
.
NotConnectedException
,
InterruptedException
{
if
(
muc
!=
null
&&
isListener
)
{
muc
.
sendMessage
(
message
);
}
}
public
int
getMemberNum
(){
if
(!
isListener
)
public
int
getMemberNum
()
{
if
(!
isListener
||
muc
==
null
)
return
0
;
return
muc
.
getOccupantsCount
();
}
public
void
joinChat
()
throws
SmackException
.
NoResponseException
,
XMPPException
.
XMPPErrorException
,
MultiUserChatException
.
NotAMucServiceException
,
SmackException
.
NotConnectedException
,
XmppStringprepException
,
MultiUserChatException
.
MucAlreadyJoinedException
,
InterruptedException
{
public
void
joinChat
()
throws
SmackException
.
NoResponseException
,
XMPPException
.
XMPPErrorException
,
MultiUserChatException
.
NotAMucServiceException
,
SmackException
.
NotConnectedException
,
XmppStringprepException
,
MultiUserChatException
.
MucAlreadyJoinedException
,
InterruptedException
{
if
(!
isListener
)
{
McUser
mcUser
=
UserManagement
.
getDx
().
getMcUser
();
if
(
mcUser
==
null
||
TextUtils
.
isEmpty
(
mcUser
.
getUsername
()))
{
throw
new
IllegalStateException
(
"User not logged in"
);
}
MultiUserChatManager
multiUserChatManager
=
MultiUserChatManager
.
getInstanceFor
(
getCon
());
muc
=
multiUserChatManager
.
getMultiUserChat
(
JidCreate
.
entityBareFrom
(
GROUP_ADDRESS
));
muc
.
createOrJoin
(
Resourcepart
.
from
(
UserManagement
.
getDx
().
getMcUser
()
.
getUsername
()));
muc
.
createOrJoin
(
Resourcepart
.
from
(
mcUser
.
getUsername
()));
chatMessageList
.
clear
();
muc
.
addMessageListener
(
message
->
{
Log
.
e
(
"接收消息"
,
"接收"
+
message
);
ChatMessage
cm
=
ChatMessage
.
toChatMessage
(
message
);
if
(
cm
!=
null
)
{
chatMessageList
.
add
(
cm
);
HandlerUtil
.
sendMessage
(
han
,
TYPE_ADD_MESSAGE
,
cm
);
HandlerUtil
.
sendMessage
(
han
,
TYPE_ADD_MESSAGE
,
cm
);
}
});
setIsListener
(
true
);
...
...
@@ -262,23 +253,36 @@ public class ServiceManagement {
}
public
void
setReLogin
(
boolean
state
)
{
android
.
os
.
Message
me
=
new
android
.
os
.
Message
();
Message
me
=
new
Message
();
me
.
what
=
TYPE_RE_LOGIN
;
me
.
obj
=
state
;
han
.
sendMessage
(
me
);
}
public
void
setReJoin
(
boolean
state
)
{
android
.
os
.
Message
me
=
new
android
.
os
.
Message
();
Message
me
=
new
Message
();
me
.
what
=
TYPE_RE_JOIN
;
me
.
obj
=
state
;
han
.
sendMessage
(
me
);
}
public
void
disSerVice
()
{
if
(
muc
!=
null
)
{
try
{
if
(
isListener
)
{
muc
.
leave
();
}
}
catch
(
SmackException
.
NotConnectedException
|
InterruptedException
e
)
{
Log
.
e
(
"ServiceManagement"
,
"Error leaving MUC"
,
e
);
}
muc
=
null
;
}
if
(
con
!=
null
)
{
con
.
disconnect
();
con
=
null
;
}
setIsConnected
(
false
);
setIsListener
(
false
);
}
...
...
@@ -290,28 +294,32 @@ public class ServiceManagement {
joinChatListenerList
.
clear
();
}
public
void
addJoinRoomListener
(
OnJoinChatListener
onJoinChatListener
)
{
joinChatListenerList
.
add
(
onJoinChatListener
);
public
int
addJoinRoomListener
(
OnJoinChatListener
onJoinChatListener
)
{
int
id
=
++
joinChatListenerId
;
joinChatListenerList
.
put
(
id
,
onJoinChatListener
);
return
id
;
}
public
void
removeJoinRoomListener
(
OnJoinChatListener
onJoinChatListener
)
{
joinChatListenerList
.
remove
(
onJoinChatListener
);
public
void
removeJoinRoomListener
(
int
id
)
{
joinChatListenerList
.
remove
(
id
);
}
public
void
start
()
{
if
(
isStartLoading
)
return
;
isStartLoading
=
true
;
isStartLoading
=
true
;
String
name
,
password
;
McUser
mcUser
=
UserManagement
.
getDx
().
getMcUser
();
name
=
mcUser
.
getUsername
();
password
=
mcUser
.
getPassword
();
if
(
TextUtils
.
isEmpty
(
name
)
||
TextUtils
.
isEmpty
(
password
))
{
McUser
mcUser
=
UserManagement
.
getDx
().
getMcUser
();
if
(
mcUser
==
null
||
TextUtils
.
isEmpty
(
mcUser
.
getUsername
())
||
TextUtils
.
isEmpty
(
mcUser
.
getPassword
()))
{
isStartLoading
=
false
;
han
.
sendEmptyMessage
(
CHAT_USER_NULL
);
return
;
}
name
=
mcUser
.
getUsername
();
password
=
mcUser
.
getPassword
();
if
(
su
.
isListener
())
{
isStartLoading
=
false
;
han
.
sendEmptyMessage
(
CHAT_LOGIN_OK
);
...
...
@@ -321,52 +329,51 @@ public class ServiceManagement {
new
Thread
(()
->
{
if
(!
su
.
isConnected
())
{
han
.
sendEmptyMessage
(
CHAT_LOGIN_LOADING
);
android
.
os
.
Message
me
=
new
android
.
os
.
Message
();
Message
me
=
new
Message
();
me
.
what
=
CHAT_LOGIN_EXCEPTION
;
try
{
su
.
login
(
name
,
password
);
}
catch
(
InterruptedException
e
)
{
e
.
printStackTrace
(
);
isStartLoading
=
false
;
Log
.
e
(
"ServiceManagement"
,
"Login interrupted"
,
e
);
isStartLoading
=
false
;
me
.
obj
=
"InterruptedException:"
+
e
;
han
.
sendMessage
(
me
);
}
catch
(
IOException
e
)
{
isStartLoading
=
false
;
Log
.
e
(
"ServiceManagement"
,
"IO error during login"
,
e
);
isStartLoading
=
false
;
me
.
obj
=
"IOException:"
+
e
;
e
.
printStackTrace
();
han
.
sendMessage
(
me
);
}
catch
(
SmackException
e
)
{
isStartLoading
=
false
;
Log
.
e
(
"ServiceManagement"
,
"Smack error during login"
,
e
);
isStartLoading
=
false
;
me
.
obj
=
"SmackException:"
+
e
;
e
.
printStackTrace
();
han
.
sendMessage
(
me
);
}
catch
(
XMPPException
e
)
{
isStartLoading
=
false
;
Log
.
e
(
"ServiceManagement"
,
"XMPP error during login"
,
e
);
isStartLoading
=
false
;
me
.
obj
=
"XMPPException:"
+
e
;
e
.
printStackTrace
();
han
.
sendMessage
(
me
);
}
catch
(
Exception
e
)
{
isStartLoading
=
false
;
Log
.
e
(
"ServiceManagement"
,
"Other error during login"
,
e
);
isStartLoading
=
false
;
me
.
obj
=
"otherException:"
+
e
;
e
.
printStackTrace
();
han
.
sendMessage
(
me
);
}
}
if
(
su
.
isConnected
())
{
han
.
sendEmptyMessage
(
CHAT_JOIN_ROOM_LOADING
);
try
{
su
.
joinChat
();
isStartLoading
=
false
;
isStartLoading
=
false
;
han
.
sendEmptyMessage
(
CHAT_LOGIN_OK
);
}
catch
(
Exception
e
)
{
isStartLoading
=
false
;
Log
.
e
(
"ServiceManagement"
,
"Error joining chat"
,
e
);
isStartLoading
=
false
;
HandlerUtil
.
sendMessage
(
han
,
CHAT_LOGIN_EXCEPTION
,
e
);
}
}
// TODO: Implement this method
}).
start
();
}
}
\ No newline at end of file
mobile/src/main/res/layout/fragment_deck_square_my_deck.xml
View file @
97d73337
...
...
@@ -144,19 +144,24 @@
tools:layout_editor_absoluteY=
"16dp"
/>
<LinearLayout
android:layout_width=
"
match_par
ent"
android:layout_width=
"
wrap_cont
ent"
android:layout_height=
"wrap_content"
android:layout_gravity=
"
bottom
"
android:layout_gravity=
"
center
"
android:orientation=
"horizontal"
android:padding=
"16dp"
>
<cn.garymb.ygomobile.ui.mycard.mcchat.view.YuanImage
android:id=
"@+id/my_deck_avatar"
android:layout_width=
"30dp"
android:layout_height=
"30dp"
android:src=
"@drawable/avatar"
/>
<Button
android:id=
"@+id/mc_logout_btn"
android:layout_width=
"
match_par
ent"
android:layout_width=
"
wrap_cont
ent"
android:layout_height=
"match_parent"
android:layout_marginEnd=
"4dp"
android:layout_weight=
"1"
android:background=
"@drawable/ic_radius_bg"
android:background=
"@drawable/button_radius_black_transparents"
android:text=
"注销"
/>
</LinearLayout>
...
...
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