more code cleanup for xmpp parser. more eventy. nice unknown contact pictures
This commit is contained in:
parent
c3e4f0eaac
commit
43531113b7
|
@ -34,8 +34,7 @@ public final class R {
|
|||
public static final int ic_launcher=0x7f020006;
|
||||
public static final int ic_profile=0x7f020007;
|
||||
public static final int message_border=0x7f020008;
|
||||
public static final int profilemock=0x7f020009;
|
||||
public static final int section_header=0x7f02000a;
|
||||
public static final int section_header=0x7f020009;
|
||||
}
|
||||
public static final class id {
|
||||
public static final int account_confirm_password_desc=0x7f0a0011;
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<gradient
|
||||
android:centerColor="#8B0000"
|
||||
android:endColor="#34FFDD"
|
||||
android:startColor="#FF00FF" />
|
||||
<size android:width="5.0dp" android:height="0.5dp" />
|
||||
</shape>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<gradient
|
||||
android:centerColor="#8B0000"
|
||||
android:endColor="#34FFDD"
|
||||
android:startColor="#FF00FF" />
|
||||
<size android:width="5.0dp" android:height="0.5dp" />
|
||||
</shape>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<gradient
|
||||
android:centerColor="#8B0000"
|
||||
android:endColor="#34FFDD"
|
||||
android:startColor="#FF00FF" />
|
||||
<size android:width="5.0dp" android:height="0.5dp" />
|
||||
</shape>
|
|
@ -2,6 +2,6 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<gradient
|
||||
android:endColor="#cccccc"
|
||||
android:startColor="#f9f9f9" />
|
||||
android:startColor="#eeeeee" />
|
||||
<size android:width="3.0dp" android:height="0.5dp" />
|
||||
</shape>
|
|
@ -6,7 +6,9 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="336dp"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:background="#eeeeee"
|
||||
>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/list"
|
||||
|
@ -14,7 +16,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:divider="#b5b5b5"
|
||||
android:dividerHeight="1dp"
|
||||
android:background="#f9f9f9"
|
||||
android:background="#eeeeee"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -20,9 +20,8 @@ public class Contact implements Serializable {
|
|||
return this.display_name;
|
||||
}
|
||||
|
||||
public Uri getProfilePhoto() {
|
||||
if (photo == null) return null;
|
||||
return Uri.parse(photo);
|
||||
public String getProfilePhoto() {
|
||||
return photo;
|
||||
}
|
||||
|
||||
public String getJid() {
|
||||
|
|
|
@ -33,10 +33,9 @@ public class Conversation extends AbstractEntity {
|
|||
|
||||
private transient List<Message> messages = null;
|
||||
|
||||
public Conversation(String name, Uri profilePhoto, Account account,
|
||||
public Conversation(String name, String profilePhoto, Account account,
|
||||
String contactJid) {
|
||||
this(java.util.UUID.randomUUID().toString(), name, profilePhoto
|
||||
.toString(), account.getUuid(), contactJid, System
|
||||
this(java.util.UUID.randomUUID().toString(), name, profilePhoto, account.getUuid(), contactJid, System
|
||||
.currentTimeMillis(), STATUS_AVAILABLE);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import de.gultsch.chat.R;
|
|||
import de.gultsch.chat.R.id;
|
||||
import de.gultsch.chat.entities.Conversation;
|
||||
import de.gultsch.chat.utils.Beautifier;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Context;
|
||||
|
@ -106,6 +107,16 @@ public class ConversationActivity extends XmppActivity {
|
|||
((TextView) view.findViewById(R.id.conversation_lastmsg)).setText(getItem(position).getLatestMessage());
|
||||
((TextView) view.findViewById(R.id.conversation_lastupdate))
|
||||
.setText(Beautifier.readableTimeDifference(getItem(position).getLatestMessageDate()));
|
||||
|
||||
Uri profilePhoto = getItem(position).getProfilePhotoUri();
|
||||
ImageView imageView = (ImageView) view.findViewById(R.id.conversation_image);
|
||||
if (profilePhoto!=null) {
|
||||
imageView.setImageURI(profilePhoto);
|
||||
} else {
|
||||
imageView.setImageBitmap(Beautifier.getUnknownContactPicture(getItem(position).getName(),200));
|
||||
}
|
||||
|
||||
|
||||
((ImageView) view.findViewById(R.id.conversation_image))
|
||||
.setImageURI(getItem(position).getProfilePhotoUri());
|
||||
return view;
|
||||
|
|
|
@ -9,7 +9,9 @@ import de.gultsch.chat.R;
|
|||
import de.gultsch.chat.entities.Account;
|
||||
import de.gultsch.chat.entities.Contact;
|
||||
import de.gultsch.chat.entities.Conversation;
|
||||
import de.gultsch.chat.utils.Beautifier;
|
||||
import de.gultsch.chat.utils.Validator;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.Editable;
|
||||
|
@ -74,8 +76,7 @@ public class NewConversationActivity extends XmppActivity {
|
|||
|
||||
if (Validator.isValidJid(searchString)) {
|
||||
String name = searchString.split("@")[0];
|
||||
Contact newContact = new Contact(name, searchString,
|
||||
DEFAULT_PROFILE_PHOTO);
|
||||
Contact newContact = new Contact(name, searchString,null);
|
||||
aggregatedContacts.add(newContact);
|
||||
contactsHeader.setText("Create new contact");
|
||||
} else {
|
||||
|
@ -100,8 +101,6 @@ public class NewConversationActivity extends XmppActivity {
|
|||
+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
|
||||
+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
|
||||
+ "\")";
|
||||
protected static final String DEFAULT_PROFILE_PHOTO = "android.resource://de.gultsch.chat/"
|
||||
+ R.drawable.ic_profile;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -150,8 +149,13 @@ public class NewConversationActivity extends XmppActivity {
|
|||
.setText(getItem(position).getDisplayName());
|
||||
((TextView) view.findViewById(R.id.contact_jid))
|
||||
.setText(getItem(position).getJid());
|
||||
((ImageView) view.findViewById(R.id.contact_photo))
|
||||
.setImageURI(getItem(position).getProfilePhoto());
|
||||
String profilePhoto = getItem(position).getProfilePhoto();
|
||||
ImageView imageView = (ImageView) view.findViewById(R.id.contact_photo);
|
||||
if (profilePhoto!=null) {
|
||||
imageView.setImageURI(Uri.parse(profilePhoto));
|
||||
} else {
|
||||
imageView.setImageBitmap(Beautifier.getUnknownContactPicture(getItem(position).getDisplayName(),90));
|
||||
}
|
||||
return view;
|
||||
}
|
||||
};
|
||||
|
@ -222,9 +226,9 @@ public class NewConversationActivity extends XmppActivity {
|
|||
while (cursor.moveToNext()) {
|
||||
String profilePhoto = cursor.getString(cursor
|
||||
.getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI));
|
||||
if (profilePhoto == null) {
|
||||
/*if (profilePhoto == null) {
|
||||
profilePhoto = DEFAULT_PROFILE_PHOTO;
|
||||
}
|
||||
}*/
|
||||
Contact contact = new Contact(
|
||||
cursor.getString(cursor
|
||||
.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)),
|
||||
|
|
|
@ -3,18 +3,24 @@ package de.gultsch.chat.utils;
|
|||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
public class Beautifier {
|
||||
public static String readableTimeDifference(long time) {
|
||||
if (time==0) {
|
||||
if (time == 0) {
|
||||
return "just now";
|
||||
}
|
||||
Date date = new Date(time);
|
||||
long difference = (System.currentTimeMillis() - time) / 1000;
|
||||
if (difference<60) {
|
||||
if (difference < 60) {
|
||||
return "just now";
|
||||
} else if (difference<60*10) {
|
||||
} else if (difference < 60 * 10) {
|
||||
return difference / 60 + " min ago";
|
||||
} else if (difference<60*60*24) {
|
||||
} else if (difference < 60 * 60 * 24) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
|
||||
return sdf.format(date);
|
||||
} else {
|
||||
|
@ -22,4 +28,33 @@ public class Beautifier {
|
|||
return sdf.format(date);
|
||||
}
|
||||
}
|
||||
|
||||
public static Bitmap getUnknownContactPicture(String name, int size) {
|
||||
String firstLetter = name.substring(0, 1).toUpperCase();
|
||||
String centerLetter = name.substring(name.length() / 2,
|
||||
(name.length() / 2) + 1);
|
||||
|
||||
int holoColors[] = { 0xFF1da9da, 0xFFb368d9, 0xFF83b600, 0xFFffa713,
|
||||
0xFFe92727 };
|
||||
|
||||
int color = holoColors[centerLetter.charAt(0) % holoColors.length];
|
||||
|
||||
Bitmap bitmap = Bitmap
|
||||
.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
bitmap.eraseColor(color);
|
||||
|
||||
Paint paint = new Paint();
|
||||
paint.setColor(0xffe5e5e5);
|
||||
paint.setTextSize((float) (size * 0.9));
|
||||
paint.setAntiAlias(true);
|
||||
Rect rect = new Rect();
|
||||
paint.getTextBounds(firstLetter, 0, 1, rect);
|
||||
float width = paint.measureText(firstLetter);
|
||||
canvas.drawText(firstLetter, (size / 2) - (width / 2), (size / 2)
|
||||
+ (rect.height() / 2), paint);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import java.util.ArrayList;
|
|||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class Element {
|
||||
protected String name;
|
||||
protected Hashtable<String, String> attributes = new Hashtable<String, String>();
|
||||
|
@ -26,6 +28,15 @@ public class Element {
|
|||
return this;
|
||||
}
|
||||
|
||||
public boolean hasChild(String name) {
|
||||
for(Element child : this.children) {
|
||||
if (child.getName().equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Element setAttribute(String name, String value) {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
|
@ -36,6 +47,14 @@ public class Element {
|
|||
return this;
|
||||
}
|
||||
|
||||
public String getAttribute(String name) {
|
||||
if (this.attributes.containsKey(name)) {
|
||||
return this.attributes.get(name);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder elementOutput = new StringBuilder();
|
||||
if ((content==null)&&(children.size() == 0)) {
|
||||
|
|
|
@ -12,9 +12,8 @@ public class IqPacket extends Element {
|
|||
super(name);
|
||||
}
|
||||
|
||||
public IqPacket(String id, int type) {
|
||||
public IqPacket(int type) {
|
||||
super("iq");
|
||||
this.setAttribute("id",id);
|
||||
switch (type) {
|
||||
case TYPE_SET:
|
||||
this.setAttribute("type", "set");
|
||||
|
@ -34,4 +33,8 @@ public class IqPacket extends Element {
|
|||
super("iq");
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.getAttribute("id");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package de.gultsch.chat.xmpp;
|
||||
|
||||
public interface OnIqPacketReceived {
|
||||
public void onIqPacketReceived(IqPacket packet);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package de.gultsch.chat.xmpp;
|
||||
|
||||
public interface OnMessagePacketReceived {
|
||||
public void onMessagePacketReceived(MessagePacket packet);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package de.gultsch.chat.xmpp;
|
||||
|
||||
public interface OnPresencePacketReceived {
|
||||
public void onPresencePacketReceived(PresencePacket packet);
|
||||
}
|
|
@ -7,6 +7,7 @@ import java.math.BigInteger;
|
|||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
@ -35,12 +36,19 @@ public class XmppConnection implements Runnable {
|
|||
private XmlReader tagReader;
|
||||
private TagWriter tagWriter;
|
||||
|
||||
private boolean isTlsEncrypted = true;
|
||||
private boolean isTlsEncrypted = false;
|
||||
private boolean isAuthenticated = false;
|
||||
private boolean shouldUseTLS = false;
|
||||
private boolean shouldReConnect = true;
|
||||
private boolean shouldBind = true;
|
||||
private boolean shouldAuthenticate = true;
|
||||
private Element streamFeatures;
|
||||
|
||||
private static final int PACKET_IQ = 0;
|
||||
private static final int PACKET_MESSAGE = 1;
|
||||
private static final int PACKET_PRESENCE = 2;
|
||||
|
||||
private Hashtable<String, OnIqPacketReceived> iqPacketCallbacks = new Hashtable<String, OnIqPacketReceived>();
|
||||
|
||||
public XmppConnection(Account account, PowerManager pm) {
|
||||
this.account = account;
|
||||
|
@ -58,17 +66,6 @@ public class XmppConnection implements Runnable {
|
|||
tagWriter.setOutputStream(out);
|
||||
InputStream in = socket.getInputStream();
|
||||
tagReader.setInputStream(in);
|
||||
} catch (UnknownHostException e) {
|
||||
Log.d(LOGTAG, "error during connect. unknown host");
|
||||
} catch (IOException e) {
|
||||
Log.d(LOGTAG, "error during connect. io exception. falscher port?");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
connect();
|
||||
try {
|
||||
tagWriter.beginDocument();
|
||||
sendStartStream();
|
||||
Tag nextTag;
|
||||
|
@ -77,14 +74,25 @@ public class XmppConnection implements Runnable {
|
|||
processStream(nextTag);
|
||||
} else {
|
||||
Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.d(LOGTAG,
|
||||
"xml error during normal read. maybe missformed xml? "
|
||||
+ e.getMessage());
|
||||
} catch (UnknownHostException e) {
|
||||
Log.d(LOGTAG, "error during connect. unknown host");
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Log.d(LOGTAG, "io exception during read. connection lost?");
|
||||
Log.d(LOGTAG, "error during connect. io exception. falscher port?");
|
||||
return;
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.d(LOGTAG,"xml exception "+e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while(shouldReConnect) {
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,20 +100,11 @@ public class XmppConnection implements Runnable {
|
|||
IOException {
|
||||
Log.d(LOGTAG, "process Stream");
|
||||
Tag nextTag;
|
||||
while ((nextTag = tagReader.readTag()) != null) {
|
||||
while (!(nextTag = tagReader.readTag()).isEnd("stream")) {
|
||||
if (nextTag.isStart("error")) {
|
||||
processStreamError(nextTag);
|
||||
} else if (nextTag.isStart("features")) {
|
||||
processStreamFeatures(nextTag);
|
||||
if (!isTlsEncrypted) {
|
||||
sendStartTLS();
|
||||
}
|
||||
if ((!isAuthenticated) && (isTlsEncrypted)) {
|
||||
sendSaslAuth();
|
||||
}
|
||||
if ((isAuthenticated)&&(isTlsEncrypted)) {
|
||||
sendBindRequest();
|
||||
}
|
||||
} else if (nextTag.isStart("proceed")) {
|
||||
switchOverToTls(nextTag);
|
||||
} else if (nextTag.isStart("success")) {
|
||||
|
@ -121,8 +120,6 @@ public class XmppConnection implements Runnable {
|
|||
Log.d(LOGTAG,processMessage(nextTag).toString());
|
||||
} else if (nextTag.isStart("presence")) {
|
||||
Log.d(LOGTAG,processPresence(nextTag).toString());
|
||||
} else if (nextTag.isEnd("stream")) {
|
||||
break;
|
||||
} else {
|
||||
Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
|
||||
+ " as child of " + currentTag.getName());
|
||||
|
@ -159,7 +156,12 @@ public class XmppConnection implements Runnable {
|
|||
|
||||
|
||||
private IqPacket processIq(Tag currentTag) throws XmlPullParserException, IOException {
|
||||
return (IqPacket) processPacket(currentTag,PACKET_IQ);
|
||||
IqPacket packet = (IqPacket) processPacket(currentTag,PACKET_IQ);
|
||||
if (iqPacketCallbacks.containsKey(packet.getId())) {
|
||||
iqPacketCallbacks.get(packet.getId()).onIqPacketReceived(packet);
|
||||
iqPacketCallbacks.remove(packet.getId());
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
private MessagePacket processMessage(Tag currentTag) throws XmlPullParserException, IOException {
|
||||
|
@ -212,47 +214,44 @@ public class XmppConnection implements Runnable {
|
|||
|
||||
private void processStreamFeatures(Tag currentTag)
|
||||
throws XmlPullParserException, IOException {
|
||||
Log.d(LOGTAG, "processStreamFeatures");
|
||||
|
||||
Element streamFeatures = new Element("features");
|
||||
|
||||
Tag nextTag = tagReader.readTag();
|
||||
while(!nextTag.isEnd("features")) {
|
||||
Element element = tagReader.readElement(nextTag);
|
||||
streamFeatures.addChild(element);
|
||||
nextTag = tagReader.readTag();
|
||||
this.streamFeatures = tagReader.readElement(currentTag);
|
||||
Log.d(LOGTAG,"process stream features "+streamFeatures);
|
||||
if (this.streamFeatures.hasChild("starttls")&&shouldUseTLS) {
|
||||
sendStartTLS();
|
||||
}
|
||||
if (this.streamFeatures.hasChild("mechanisms")&&shouldAuthenticate) {
|
||||
sendSaslAuth();
|
||||
}
|
||||
if (this.streamFeatures.hasChild("bind")&&shouldBind) {
|
||||
sendBindRequest();
|
||||
if (this.streamFeatures.hasChild("session")) {
|
||||
IqPacket startSession = new IqPacket(IqPacket.TYPE_SET);
|
||||
Element session = new Element("session");
|
||||
session.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-session");
|
||||
session.setContent("");
|
||||
startSession.addChild(session);
|
||||
sendIqPacket(startSession, null);
|
||||
tagWriter.writeElement(startSession);
|
||||
tagWriter.flush();
|
||||
}
|
||||
Element presence = new Element("presence");
|
||||
|
||||
tagWriter.writeElement(presence);
|
||||
tagWriter.flush();
|
||||
}
|
||||
Log.d(LOGTAG,streamFeatures.toString());
|
||||
}
|
||||
|
||||
private void sendBindRequest() throws IOException {
|
||||
IqPacket iq = new IqPacket(nextRandomId(),IqPacket.TYPE_SET);
|
||||
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
|
||||
Element bind = new Element("bind");
|
||||
bind.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-bind");
|
||||
iq.addChild(bind);
|
||||
//Element resource = new Element("resource");
|
||||
//resource.setContent("mobile");
|
||||
//bind.addChild(resource);
|
||||
Log.d(LOGTAG,"sending bind request: "+iq.toString());
|
||||
tagWriter.writeElement(iq);
|
||||
tagWriter.flush();
|
||||
|
||||
|
||||
//technically not bind stuff
|
||||
IqPacket startSession = new IqPacket(this.nextRandomId(), IqPacket.TYPE_SET);
|
||||
Element session = new Element("session");
|
||||
session.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-session");
|
||||
session.setContent("");
|
||||
startSession.addChild(session);
|
||||
|
||||
tagWriter.writeElement(startSession);
|
||||
tagWriter.flush();
|
||||
|
||||
Element presence = new Element("presence");
|
||||
|
||||
tagWriter.writeElement(presence);
|
||||
tagWriter.flush();
|
||||
|
||||
this.sendIqPacket(iq, new OnIqPacketReceived() {
|
||||
@Override
|
||||
public void onIqPacketReceived(IqPacket packet) {
|
||||
Log.d(LOGTAG,"answer for our bind was: "+packet.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void processStreamError(Tag currentTag) {
|
||||
|
@ -273,4 +272,15 @@ public class XmppConnection implements Runnable {
|
|||
private String nextRandomId() {
|
||||
return new BigInteger(50, random).toString(32);
|
||||
}
|
||||
|
||||
public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) throws IOException {
|
||||
String id = nextRandomId();
|
||||
packet.setAttribute("id",id);
|
||||
tagWriter.writeElement(packet);
|
||||
tagWriter.flush();
|
||||
if (callback != null) {
|
||||
iqPacketCallbacks.put(id, callback);
|
||||
}
|
||||
Log.d(LOGTAG,"sending: "+packet.toString());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue