Merge branch '822-group-creation-workflow' into 'master'

Revisit private group creation workflow

Closes #822

See merge request !553
This commit is contained in:
Torsten Grote
2017-07-04 17:19:11 +00:00
12 changed files with 175 additions and 165 deletions

View File

@@ -179,7 +179,7 @@
android:name=".android.forum.CreateForumActivity" android:name=".android.forum.CreateForumActivity"
android:label="@string/create_forum_title" android:label="@string/create_forum_title"
android:parentActivityName=".android.navdrawer.NavDrawerActivity" android:parentActivityName=".android.navdrawer.NavDrawerActivity"
android:windowSoftInputMode="stateVisible"> android:windowSoftInputMode="adjustResize">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".android.navdrawer.NavDrawerActivity" android:value=".android.navdrawer.NavDrawerActivity"

View File

@@ -39,7 +39,7 @@ import org.briarproject.briar.android.privategroup.conversation.GroupConversatio
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity; import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment; import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment;
import org.briarproject.briar.android.privategroup.creation.CreateGroupMessageFragment; import org.briarproject.briar.android.privategroup.creation.CreateGroupMessageFragment;
import org.briarproject.briar.android.privategroup.creation.GroupCreateModule; import org.briarproject.briar.android.privategroup.creation.CreateGroupModule;
import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity; import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity;
import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment; import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity; import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
@@ -71,7 +71,7 @@ import dagger.Component;
@Component( @Component(
modules = {ActivityModule.class, ForumModule.class, SharingModule.class, modules = {ActivityModule.class, ForumModule.class, SharingModule.class,
BlogModule.class, ContactModule.class, GroupListModule.class, BlogModule.class, ContactModule.class, GroupListModule.class,
GroupCreateModule.class, GroupInvitationModule.class, CreateGroupModule.class, GroupInvitationModule.class,
GroupConversationModule.class, GroupMemberModule.class, GroupConversationModule.class, GroupMemberModule.class,
GroupRevealModule.class}, GroupRevealModule.class},
dependencies = AndroidComponent.class) dependencies = AndroidComponent.class)

View File

@@ -38,8 +38,7 @@ import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LEN
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class CreateForumActivity extends BriarActivity public class CreateForumActivity extends BriarActivity {
implements OnEditorActionListener, OnClickListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(CreateForumActivity.class.getName()); Logger.getLogger(CreateForumActivity.class.getName());
@@ -60,11 +59,7 @@ public class CreateForumActivity extends BriarActivity
setContentView(R.layout.activity_create_forum); setContentView(R.layout.activity_create_forum);
nameEntry = (EditText) findViewById(R.id.createForumNameEntry); nameEntry = (EditText) findViewById(R.id.createForumNameEntry);
TextWatcher nameEntryWatcher = new TextWatcher() { nameEntry.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
}
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, public void beforeTextChanged(CharSequence s, int start, int count,
@@ -72,21 +67,41 @@ public class CreateForumActivity extends BriarActivity
} }
@Override @Override
public void onTextChanged(CharSequence text, int start, public void onTextChanged(CharSequence s, int start,
int lengthBefore, int lengthAfter) { int lengthBefore, int lengthAfter) {
enableOrDisableCreateButton(); enableOrDisableCreateButton();
} }
};
nameEntry.setOnEditorActionListener(this); @Override
nameEntry.addTextChangedListener(nameEntryWatcher); public void afterTextChanged(Editable s) {
}
});
nameEntry.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent e) {
createForum();
return true;
}
});
feedback = (TextView) findViewById(R.id.createForumFeedback); feedback = (TextView) findViewById(R.id.createForumFeedback);
createForumButton = (Button) findViewById(R.id.createForumButton); createForumButton = (Button) findViewById(R.id.createForumButton);
createForumButton.setOnClickListener(this); createForumButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
createForum();
}
});
progress = (ProgressBar) findViewById(R.id.createForumProgressBar); progress = (ProgressBar) findViewById(R.id.createForumProgressBar);
}
@Override
public void onStart() {
super.onStart();
showSoftKeyboard(nameEntry);
} }
@Override @Override
@@ -95,16 +110,10 @@ public class CreateForumActivity extends BriarActivity
} }
private void enableOrDisableCreateButton() { private void enableOrDisableCreateButton() {
if (progress == null) return; // Not created yet if (createForumButton == null) return; // Not created yet
createForumButton.setEnabled(validateName()); createForumButton.setEnabled(validateName());
} }
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
hideSoftKeyboard(textView);
return true;
}
private boolean validateName() { private boolean validateName() {
String name = nameEntry.getText().toString(); String name = nameEntry.getText().toString();
int length = StringUtils.toUtf8(name).length; int length = StringUtils.toUtf8(name).length;
@@ -116,15 +125,12 @@ public class CreateForumActivity extends BriarActivity
return length > 0; return length > 0;
} }
@Override private void createForum() {
public void onClick(View view) { if (!validateName()) return;
if (view == createForumButton) { hideSoftKeyboard(nameEntry);
hideSoftKeyboard(view); createForumButton.setVisibility(GONE);
if (!validateName()) return; progress.setVisibility(VISIBLE);
createForumButton.setVisibility(GONE); storeForum(nameEntry.getText().toString());
progress.setVisibility(VISIBLE);
storeForum(nameEntry.getText().toString());
}
} }
private void storeForum(final String name) { private void storeForum(final String name) {

View File

@@ -1,58 +0,0 @@
package org.briarproject.briar.android.privategroup.creation;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.contactselection.ContactSelectorActivity;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener;
import java.util.Collection;
import javax.inject.Inject;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public abstract class BaseGroupInviteActivity
extends ContactSelectorActivity implements MessageFragmentListener {
@Inject
CreateGroupController controller;
@Override
public void contactsSelected(Collection<ContactId> contacts) {
super.contactsSelected(contacts);
showNextFragment(new CreateGroupMessageFragment());
}
@Override
public boolean onButtonClick(String message) {
if (groupId == null)
throw new IllegalStateException("GroupId was not initialized");
controller.sendInvitation(groupId, contacts, message,
new UiResultExceptionHandler<Void, DbException>(this) {
@Override
public void onResultUi(Void result) {
setResult(RESULT_OK);
supportFinishAfterTransition();
}
@Override
public void onExceptionUi(DbException exception) {
setResult(RESULT_CANCELED);
handleDbException(exception);
}
});
return true;
}
@Override
public int getMaximumMessageLength() {
return MAX_GROUP_INVITATION_MSG_LENGTH;
}
}

View File

@@ -9,16 +9,20 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity; import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class CreateGroupActivity extends BaseGroupInviteActivity implements public class CreateGroupActivity extends BriarActivity
CreateGroupListener, MessageFragmentListener { implements CreateGroupListener {
@Inject
CreateGroupController controller;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
@@ -29,32 +33,20 @@ public class CreateGroupActivity extends BaseGroupInviteActivity implements
public void onCreate(@Nullable Bundle bundle) { public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
setContentView(R.layout.activity_fragment_container);
if (bundle == null) { if (bundle == null) {
showInitialFragment(new CreateGroupFragment()); showInitialFragment(new CreateGroupFragment());
} }
} }
@Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
// At this point, the group had been created already,
// so don't allow to create it again.
openNewGroup();
overridePendingTransition(R.anim.screen_old_in,
R.anim.screen_new_out);
} else {
super.onBackPressed();
}
}
@Override @Override
public void onGroupNameChosen(String name) { public void onGroupNameChosen(String name) {
controller.createGroup(name, controller.createGroup(name,
new UiResultExceptionHandler<GroupId, DbException>(this) { new UiResultExceptionHandler<GroupId, DbException>(this) {
@Override @Override
public void onResultUi(GroupId g) { public void onResultUi(GroupId g) {
groupId = g; openNewGroup(g);
switchToContactSelectorFragment(g);
} }
@Override @Override
@@ -64,16 +56,10 @@ public class CreateGroupActivity extends BaseGroupInviteActivity implements
}); });
} }
private void switchToContactSelectorFragment(GroupId g) { private void openNewGroup(GroupId g) {
showNextFragment(GroupInviteFragment.newInstance(g));
}
private void openNewGroup() {
Intent i = new Intent(this, GroupActivity.class); Intent i = new Intent(this, GroupActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes()); i.putExtra(GROUP_ID, g.getBytes());
startActivity(i); startActivity(i);
// finish this activity, so we can't come back to it
finish(); finish();
} }
} }

View File

@@ -4,11 +4,15 @@ import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -22,8 +26,9 @@ public class CreateGroupFragment extends BaseFragment {
public final static String TAG = CreateGroupFragment.class.getName(); public final static String TAG = CreateGroupFragment.class.getName();
private CreateGroupListener listener; private CreateGroupListener listener;
private EditText name; private EditText nameEntry;
private Button button; private Button createGroupButton;
private TextView feedback;
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
@@ -35,32 +40,42 @@ public class CreateGroupFragment extends BaseFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
// inflate view
View v = inflater.inflate(R.layout.fragment_create_group, container, View v = inflater.inflate(R.layout.fragment_create_group, container,
false); false);
name = (EditText) v.findViewById(R.id.name); nameEntry = (EditText) v.findViewById(R.id.name);
name.addTextChangedListener(new TextWatcher() { nameEntry.addTextChangedListener(new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, public void beforeTextChanged(CharSequence s, int start, int count,
int after) { int after) {
} }
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, public void onTextChanged(CharSequence s, int start,
int count) { int lengthBefore, int lengthAfter) {
validateName(); enableOrDisableCreateButton();
} }
@Override @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {
} }
}); });
button = (Button) v.findViewById(R.id.button); nameEntry.setOnEditorActionListener(new OnEditorActionListener() {
button.setOnClickListener(new View.OnClickListener() { @Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent e) {
createGroup();
return true;
}
});
feedback = (TextView) v.findViewById(R.id.feedback);
createGroupButton = (Button) v.findViewById(R.id.button);
createGroupButton.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
listener.hideSoftKeyboard(name); createGroup();
listener.onGroupNameChosen(name.getText().toString());
} }
}); });
@@ -70,7 +85,7 @@ public class CreateGroupFragment extends BaseFragment {
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
listener.showSoftKeyboard(name); listener.showSoftKeyboard(nameEntry);
} }
@Override @Override
@@ -83,12 +98,25 @@ public class CreateGroupFragment extends BaseFragment {
return TAG; return TAG;
} }
private void validateName() { private void enableOrDisableCreateButton() {
String name = this.name.getText().toString(); if (createGroupButton == null) return; // Not created yet
if (name.length() < 1 || StringUtils.utf8IsTooLong(name, MAX_GROUP_NAME_LENGTH)) createGroupButton.setEnabled(validateName());
button.setEnabled(false);
else if (!button.isEnabled())
button.setEnabled(true);
} }
private boolean validateName() {
String name = nameEntry.getText().toString();
int length = StringUtils.toUtf8(name).length;
if (length > MAX_GROUP_NAME_LENGTH) {
feedback.setText(R.string.name_too_long);
return false;
}
feedback.setText("");
return length > 0;
}
private void createGroup() {
if (!validateName()) return;
listener.hideSoftKeyboard(nameEntry);
listener.onGroupNameChosen(nameEntry.getText().toString());
}
} }

View File

@@ -6,7 +6,7 @@ import dagger.Module;
import dagger.Provides; import dagger.Provides;
@Module @Module
public class GroupCreateModule { public class CreateGroupModule {
@ActivityScope @ActivityScope
@Provides @Provides

View File

@@ -3,25 +3,43 @@ package org.briarproject.briar.android.privategroup.creation;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contactselection.ContactSelectorActivity;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener; import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener;
public class GroupInviteActivity extends BaseGroupInviteActivity import java.util.Collection;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class GroupInviteActivity extends ContactSelectorActivity
implements MessageFragmentListener { implements MessageFragmentListener {
@Inject
CreateGroupController controller;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
component.inject(this); component.inject(this);
} }
@Override @Override
public void onCreate(Bundle bundle) { public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
Intent i = getIntent(); Intent i = getIntent();
byte[] g = i.getByteArrayExtra(GROUP_ID); byte[] g = i.getByteArrayExtra(GROUP_ID);
if (g == null) throw new IllegalStateException("No GroupId in intent."); if (g == null) throw new IllegalStateException("No GroupId in intent");
groupId = new GroupId(g); groupId = new GroupId(g);
if (bundle == null) { if (bundle == null) {
@@ -29,4 +47,36 @@ public class GroupInviteActivity extends BaseGroupInviteActivity
} }
} }
@Override
public void contactsSelected(Collection<ContactId> contacts) {
super.contactsSelected(contacts);
showNextFragment(new CreateGroupMessageFragment());
}
@Override
public boolean onButtonClick(String message) {
if (groupId == null)
throw new IllegalStateException("GroupId was not initialized");
controller.sendInvitation(groupId, contacts, message,
new UiResultExceptionHandler<Void, DbException>(this) {
@Override
public void onResultUi(Void result) {
setResult(RESULT_OK);
supportFinishAfterTransition();
}
@Override
public void onExceptionUi(DbException exception) {
setResult(RESULT_CANCELED);
handleDbException(exception);
}
});
return true;
}
@Override
public int getMaximumMessageLength() {
return MAX_GROUP_INVITATION_MSG_LENGTH;
}
} }

View File

@@ -1,37 +1,30 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:padding="20dp" > android:orientation="vertical"
android:layout_margin="@dimen/margin_large">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="@dimen/text_size_medium"
android:text="@string/choose_forum_name" />
<EditText <EditText
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/createForumNameEntry" android:id="@+id/createForumNameEntry"
android:maxLines="1" android:maxLines="1"
android:inputType="text|textCapSentences" /> android:inputType="text|textCapSentences"
android:hint="@string/choose_forum_hint" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/createForumFeedback" android:id="@+id/createForumFeedback"
android:gravity="center" android:gravity="center" />
android:paddingLeft="50dp"
android:paddingRight="50dp" />
<Button <Button
style="@style/BriarButton" style="@style/BriarButton"
android:id="@+id/createForumButton" android:id="@+id/createForumButton"
android:enabled="false"
android:text="@string/create_forum_button" /> android:text="@string/create_forum_button" />
<ProgressBar <ProgressBar

View File

@@ -3,22 +3,27 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/margin_medium"> android:layout_margin="@dimen/margin_large">
<EditText <EditText
android:id="@+id/name" android:id="@+id/name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="bottom"
android:maxLines="1" android:maxLines="1"
android:inputType="text|textCapSentences" android:inputType="text|textCapSentences"
android:hint="@string/groups_create_group_hint"/> android:hint="@string/groups_create_group_hint"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/feedback"
android:gravity="center" />
<Button <Button
android:id="@+id/button"
style="@style/BriarButton" style="@style/BriarButton"
android:id="@+id/button"
android:enabled="false" android:enabled="false"
android:text="@string/groups_create_group_button"/> android:text="@string/groups_create_group_button"/>

View File

@@ -10,16 +10,16 @@
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/action_group_reveal" android:id="@+id/action_group_invite"
android:icon="@drawable/ic_visibility_white" android:icon="@drawable/social_share_white"
android:title="@string/groups_reveal_contacts" android:title="@string/groups_invite_members"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/action_group_invite" android:id="@+id/action_group_reveal"
android:icon="@drawable/ic_add_white" android:icon="@drawable/ic_visibility_white"
android:title="@string/groups_invite_members" android:title="@string/groups_reveal_contacts"
app:showAsAction="ifRoom"/> app:showAsAction="never"/>
<item <item
android:id="@+id/action_group_leave" android:id="@+id/action_group_leave"

View File

@@ -204,8 +204,8 @@
<!-- Forums --> <!-- Forums -->
<string name="no_forums">You don\'t have any forums yet.\n\nWhy don\'t you create a new one yourself by tapping the + icon at the top?\n\nYou can also ask your contacts to share forums with you.</string> <string name="no_forums">You don\'t have any forums yet.\n\nWhy don\'t you create a new one yourself by tapping the + icon at the top?\n\nYou can also ask your contacts to share forums with you.</string>
<string name="create_forum_title">New Forum</string> <string name="create_forum_title">Create Forum</string>
<string name="choose_forum_name">Choose a name for your forum:</string> <string name="choose_forum_hint">Choose a name for your forum</string>
<string name="create_forum_button">Create Forum</string> <string name="create_forum_button">Create Forum</string>
<string name="forum_created_toast">Forum created</string> <string name="forum_created_toast">Forum created</string>
<string name="no_forum_posts">This forum is empty.\n\nUse the pen icon at the top to compose the first post.\n\nFeeling lonely here? Share this forum with more of your contacts!</string> <string name="no_forum_posts">This forum is empty.\n\nUse the pen icon at the top to compose the first post.\n\nFeeling lonely here? Share this forum with more of your contacts!</string>