create a conversation fragment
In the futur, we want to integrate the current conversation in the
home page. To do this, it is necessary to create a conversation
fragment.
The conversation activity calls the conversation fragment. The
activity manages the LocalService and the fragment manages the
conversation management.
Change-Id: If37d76840b0ada744d9da811092d23a5351a3847
Tuleap: #1431
diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml
index 17de2e5..3c2e941 100644
--- a/ring-android/app/src/main/AndroidManifest.xml
+++ b/ring-android/app/src/main/AndroidManifest.xml
@@ -231,7 +231,7 @@
</activity>
<activity
android:name=".client.ConversationActivity"
- android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
+ android:configChanges="screenSize|screenLayout|smallestScreenSize"
android:label="@string/app_name"
android:parentActivityName=".client.HomeActivity"
android:screenOrientation="fullUser"
diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
index d8cb273..85f0d23 100644
--- a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
@@ -27,284 +27,39 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
-import android.support.design.widget.Snackbar;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.DefaultItemAnimator;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
-import android.util.Pair;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.EditorInfo;
-import android.widget.EditText;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import java.util.List;
-
-import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
-import butterknife.OnClick;
-import butterknife.OnEditorAction;
import cx.ring.R;
-import cx.ring.adapters.ContactDetailsTask;
-import cx.ring.adapters.ConversationAdapter;
-import cx.ring.adapters.NumberAdapter;
-import cx.ring.application.RingApplication;
-import cx.ring.model.Account;
-import cx.ring.model.CallContact;
-import cx.ring.model.Conference;
-import cx.ring.model.Conversation;
-import cx.ring.model.Phone;
-import cx.ring.model.Uri;
+import cx.ring.fragments.ConversationFragment;
import cx.ring.service.LocalService;
-import cx.ring.services.AccountService;
-import cx.ring.services.CallService;
-import cx.ring.utils.ActionHelper;
-import cx.ring.utils.ClipboardHelper;
-import cx.ring.utils.ContentUriHandler;
-public class ConversationActivity extends AppCompatActivity implements
- Conversation.ConversationActionCallback,
- ClipboardHelper.ClipboardHelperCallback,
- ContactDetailsTask.DetailsLoadedCallback {
-
- @Inject
- CallService mCallService;
-
- @Inject
- AccountService mAccountService;
+public class ConversationActivity extends AppCompatActivity {
@BindView(R.id.main_toolbar)
Toolbar mToolbar;
- @BindView(R.id.msg_input_txt)
- EditText mMsgEditTxt;
-
- @BindView(R.id.msg_send)
- View mMsgSendBtn;
-
- @BindView(R.id.ongoingcall_pane)
- ViewGroup mBottomPane;
-
- @BindView(R.id.hist_list)
- RecyclerView mHistList;
-
- @BindView(R.id.number_selector)
- Spinner mNumberSpinner;
-
private static final String TAG = ConversationActivity.class.getSimpleName();
- private static final String CONVERSATION_DELETE = "CONVERSATION_DELETE";
-
- public static final int REQ_ADD_CONTACT = 42;
static final long REFRESH_INTERVAL_MS = 30 * 1000;
private boolean mBound = false;
- private boolean mVisible = false;
- private AlertDialog mDeleteDialog;
- private boolean mDeleteConversation = false;
-
private LocalService mService = null;
- private Conversation mConversation = null;
- private Uri mPreferredNumber = null;
-
- private MenuItem mAddContactBtn = null;
-
- private ConversationAdapter mAdapter = null;
- private NumberAdapter mNumberAdapter = null;
-
private final Handler mRefreshTaskHandler = new Handler();
-
- static private Pair<Conversation, Uri> getConversation(LocalService s, Intent i) {
- if (s == null || i == null || i.getData() == null)
- return new Pair<>(null, null);
-
- String conv_id = i.getData().getLastPathSegment();
- Uri number = new Uri(i.getStringExtra("number"));
-
- Log.d(TAG, "getConversation " + conv_id + " " + number);
- Conversation conv = s.getConversation(conv_id);
- if (conv == null) {
- long contact_id = CallContact.contactIdFromId(conv_id);
- Log.d(TAG, "no conversation found, contact_id " + contact_id);
- CallContact contact = null;
- if (contact_id >= 0)
- contact = s.findContactById(contact_id);
- if (contact == null) {
- Uri conv_uri = new Uri(conv_id);
- if (!number.isEmpty()) {
- contact = s.findContactByNumber(number);
- if (contact == null)
- contact = CallContact.buildUnknown(conv_uri);
- } else {
- contact = s.findContactByNumber(conv_uri);
- if (contact == null) {
- contact = CallContact.buildUnknown(conv_uri);
- number = contact.getPhones().get(0).getNumber();
- } else {
- number = conv_uri;
- }
- }
- }
- conv = s.startConversation(contact);
- }
-
- Log.d(TAG, "returning " + conv.getContact().getDisplayName() + " " + number);
- return new Pair<>(conv, number);
- }
-
- static private int getIndex(Spinner spinner, Uri myString) {
- for (int i = 0, n = spinner.getCount(); i < n; i++)
- if (((Phone) spinner.getItemAtPosition(i)).getNumber().equals(myString))
- return i;
- return 0;
- }
+ private ConversationFragment mConversationFragment;
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
- refreshView(0);
+ mConversationFragment.refreshView(0);
}
- private void refreshView(long refreshed) {
- Pair<Conversation, Uri> conv = getConversation(mService, getIntent());
- mConversation = conv.first;
- mPreferredNumber = conv.second;
-
- if (mConversation == null) {
- finish();
- return;
- }
-
- ActionBar ab = getSupportActionBar();
- if (ab != null) {
- if (!mConversation.getContact().getPhones().isEmpty()) {
- CallContact contact = mCallService.getContact(mConversation.getContact().getPhones().get(0).getNumber());
- if (contact != null) {
- mConversation.setContact(contact);
- }
- }
-
- ab.setTitle(mConversation.getContact().getDisplayName());
- }
-
- final CallContact contact = mConversation.getContact();
- if (contact != null) {
- new ContactDetailsTask(this, contact, this).run();
- }
-
- Conference conf = mConversation.getCurrentCall();
- mBottomPane.setVisibility(conf == null ? View.GONE : View.VISIBLE);
- if (conf != null) {
- Log.d(TAG, "ConversationActivity refreshView " + conf.getId() + " "
- + mConversation.getCurrentCall());
- }
-
- mAdapter.updateDataset(mConversation.getAggregateHistory(), refreshed);
-
- if (mConversation.getContact().getPhones().size() > 1) {
- mNumberSpinner.setVisibility(View.VISIBLE);
- mNumberAdapter = new NumberAdapter(ConversationActivity.this,
- mConversation.getContact(),
- false);
- mNumberSpinner.setAdapter(mNumberAdapter);
- if (mPreferredNumber == null || mPreferredNumber.isEmpty()) {
- mPreferredNumber = new Uri(
- mConversation.getLastNumberUsed(mConversation.getLastAccountUsed())
- );
- }
- mNumberSpinner.setSelection(getIndex(mNumberSpinner, mPreferredNumber));
- } else {
- mNumberSpinner.setVisibility(View.GONE);
- mPreferredNumber = mConversation.getContact().getPhones().get(0).getNumber();
- }
-
- invalidateOptionsMenu();
- }
-
- private ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder binder) {
- Log.d(TAG, "ConversationActivity onServiceConnected " + className.getClassName());
- mService = ((LocalService.LocalBinder) binder).getService();
-
- mAdapter = new ConversationAdapter(ConversationActivity.this,
- mService.get40dpContactCache(),
- mService.getThreadPool());
-
- if (mHistList != null) {
- mHistList.setAdapter(mAdapter);
- }
-
- refreshView(0);
-
- IntentFilter filter = new IntentFilter(LocalService.ACTION_CONF_UPDATE);
- registerReceiver(receiver, filter);
-
- mBound = true;
- if (mVisible && mConversation != null && !mConversation.isVisible()) {
- mConversation.setVisible(true);
- mService.readConversation(mConversation);
- }
-
- if (mDeleteConversation) {
- mDeleteDialog = ActionHelper.launchDeleteAction(ConversationActivity.this, mConversation, ConversationActivity.this);
- }
-
- mRefreshTaskHandler.postDelayed(refreshTask, REFRESH_INTERVAL_MS);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName arg0) {
- Log.d(TAG, "ConversationActivity onServiceDisconnected " + arg0.getClassName());
- mBound = false;
- mRefreshTaskHandler.removeCallbacks(refreshTask);
- if (mConversation != null) {
- mConversation.setVisible(false);
- }
- }
- };
-
- private final BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "onReceive " + intent.getAction() + " " + intent.getDataString());
- refreshView(intent.getLongExtra(LocalService.ACTION_CONF_UPDATE_EXTRA_MSG, 0));
- if (mAdapter.getItemCount() > 0)
- mHistList.smoothScrollToPosition(mAdapter.getItemCount() - 1);
- }
- };
-
- private final Runnable refreshTask = new Runnable() {
- private long lastRefresh = 0;
-
- public void run() {
- if (lastRefresh == 0)
- lastRefresh = SystemClock.uptimeMillis();
- else
- lastRefresh += REFRESH_INTERVAL_MS;
-
- mAdapter.notifyDataSetChanged();
-
- mRefreshTaskHandler.postAtTime(this, lastRefresh + REFRESH_INTERVAL_MS);
- }
- };
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -315,25 +70,13 @@
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- // Dependency injection
- ((RingApplication) getApplication()).getRingInjectionComponent().inject(this);
-
- if (mBottomPane != null) {
- mBottomPane.setVisibility(View.GONE);
+ if (mConversationFragment == null) {
+ mConversationFragment = new ConversationFragment();
+ getFragmentManager().beginTransaction()
+ .replace(R.id.main_frame, mConversationFragment, null)
+ .commit();
}
- LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
- mLayoutManager.setStackFromEnd(true);
-
- if (mHistList != null) {
- mHistList.setLayoutManager(mLayoutManager);
- mHistList.setAdapter(mAdapter);
- mHistList.setItemAnimator(new DefaultItemAnimator());
- }
-
- // reload delete conversation state (before rotation)
- mDeleteConversation = savedInstanceState != null && savedInstanceState.getBoolean(CONVERSATION_DELETE);
-
if (!mBound) {
Log.d(TAG, "onCreate: Binding service...");
Intent intent = new Intent(this, LocalService.class);
@@ -342,243 +85,80 @@
}
}
- @OnClick(R.id.msg_send)
- public void sendMessageText(View sender){
- CharSequence txt = mMsgEditTxt.getText();
- if (txt.length() > 0) {
- onSendTextMessage(txt.toString());
- mMsgEditTxt.setText("");
- }
- }
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder binder) {
+ Log.d(TAG, "ConversationActivity onServiceConnected " + className.getClassName());
+ mService = ((LocalService.LocalBinder) binder).getService();
- @OnEditorAction(R.id.msg_input_txt)
- public boolean actionSendMsgText(TextView view, int actionId, KeyEvent event){
- switch (actionId) {
- case EditorInfo.IME_ACTION_SEND:
- CharSequence txt = mMsgEditTxt.getText();
- if (txt.length() > 0) {
- onSendTextMessage(mMsgEditTxt.getText().toString());
- mMsgEditTxt.setText("");
- }
- return true;
- }
- return false;
- }
+ IntentFilter filter = new IntentFilter(LocalService.ACTION_CONF_UPDATE);
+ registerReceiver(receiver, filter);
- @OnClick(R.id.ongoingcall_pane)
- public void onClick(View v) {
- startActivity(new Intent(Intent.ACTION_VIEW)
- .setClass(getApplicationContext(), CallActivity.class)
- .setData(android.net.Uri.withAppendedPath(ContentUriHandler.CONFERENCE_CONTENT_URI,
- mConversation.getCurrentCall().getId())));
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- Log.d(TAG, "onPause");
- mVisible = false;
- if (mConversation != null) {
- mService.readConversation(mConversation);
- mConversation.setVisible(false);
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- Log.d(TAG, "onResume " + mConversation);
- mVisible = true;
- if (mConversation != null) {
- mConversation.setVisible(true);
- if (mBound && mService != null) {
- mService.readConversation(mConversation);
+ if (mConversationFragment != null) {
+ mConversationFragment.setCallback(mService);
+ mConversationFragment.refreshView(0);
}
+
+ mBound = true;
+
+ mRefreshTaskHandler.postDelayed(refreshTask, REFRESH_INTERVAL_MS);
}
- }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ Log.d(TAG, "ConversationActivity onServiceDisconnected " + arg0.getClassName());
+ mBound = false;
+ mRefreshTaskHandler.removeCallbacks(refreshTask);
+ }
+ };
+
+ private final Runnable refreshTask = new Runnable() {
+ private long lastRefresh = 0;
+
+ public void run() {
+ if (lastRefresh == 0) {
+ lastRefresh = SystemClock.uptimeMillis();
+ } else {
+ lastRefresh += REFRESH_INTERVAL_MS;
+ }
+
+ mRefreshTaskHandler.postAtTime(this, lastRefresh + REFRESH_INTERVAL_MS);
+ }
+ };
+
+ private final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive " + intent.getAction() + " " + intent.getDataString());
+ mConversationFragment.refreshView(intent.getLongExtra(LocalService.ACTION_CONF_UPDATE_EXTRA_MSG, 0));
+ }
+ };
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
- case REQ_ADD_CONTACT:
- if (mService != null) mService.refreshConversations();
+ case ConversationFragment.REQ_ADD_CONTACT:
+ if (mService != null) {
+ mService.refreshConversations();
+ }
break;
}
}
@Override
- protected void onDestroy() {
- if (mBound) {
- unregisterReceiver(receiver);
- unbindService(mConnection);
- mBound = false;
- }
-
- if (mDeleteConversation) {
- mDeleteDialog.dismiss();
- }
-
- super.onDestroy();
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- // persist the delete popup state in case of Activity rotation
- mDeleteConversation = mDeleteDialog != null && mDeleteDialog.isShowing();
- outState.putBoolean(CONVERSATION_DELETE, mDeleteConversation);
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- if (mAddContactBtn != null) {
- mAddContactBtn.setVisible(mConversation != null && mConversation.getContact().getId() < 0);
- }
- return super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.conversation_actions, menu);
- mAddContactBtn = menu.findItem(R.id.menuitem_addcontact);
- return super.onCreateOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- startActivity(new Intent(this, HomeActivity.class));
- finish();
- return true;
- case R.id.conv_action_audiocall:
- onCallWithVideo(false);
- return true;
- case R.id.conv_action_videocall:
- onCallWithVideo(true);
- return true;
- case R.id.menuitem_addcontact:
- startActivityForResult(ActionHelper.getAddNumberIntentForContact(mConversation.getContact()), REQ_ADD_CONTACT);
- return true;
- case R.id.menuitem_delete:
- mDeleteDialog = ActionHelper.launchDeleteAction(this, this.mConversation, this);
- return true;
- case R.id.menuitem_copy_content:
- ActionHelper.launchCopyNumberToClipboardFromContact(this,
- this.mConversation.getContact(), this);
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- /**
- * Guess account and number to use to initiate a call
- */
- private Pair<Account, Uri> guess() {
- Uri number = mNumberAdapter == null ?
- mPreferredNumber : ((Phone) mNumberSpinner.getSelectedItem()).getNumber();
- Account a = mAccountService.getAccount(mConversation.getLastAccountUsed());
-
- // Guess account from number
- if (a == null && number != null)
- a = mAccountService.guessAccount(number);
-
- // Guess number from account/call history
- if (a != null && (number == null/* || number.isEmpty()*/))
- number = new Uri(mConversation.getLastNumberUsed(a.getAccountID()));
-
- // If no account found, use first active
- if (a == null) {
- List<Account> accs = mAccountService.getAccounts();
- if (accs.isEmpty()) {
- finish();
- return null;
- } else
- a = accs.get(0);
- }
-
- // If no number found, use first from contact
- if (number == null || number.isEmpty())
- number = mConversation.getContact().getPhones().get(0).getNumber();
-
- return new Pair<>(a, number);
- }
-
- private void onSendTextMessage(String txt) {
- Conference conf = mConversation == null ? null : mConversation.getCurrentCall();
- if (conf == null || !conf.isOnGoing()) {
- Pair<Account, Uri> g = guess();
- if (g == null || g.first == null)
- return;
- mService.sendTextMessage(g.first.getAccountID(), g.second, txt);
- } else {
- mService.sendTextMessage(conf, txt);
- }
- }
-
- private void onCallWithVideo(boolean has_video) {
- Conference conf = mConversation.getCurrentCall();
- if (conf != null) {
- startActivity(new Intent(Intent.ACTION_VIEW)
- .setClass(getApplicationContext(), CallActivity.class)
- .setData(android.net.Uri.withAppendedPath(ContentUriHandler.CONFERENCE_CONTENT_URI, conf.getId())));
- return;
- }
- Pair<Account, Uri> g = guess();
- if (g == null || g.first == null)
- return;
-
- try {
- Intent intent = new Intent(CallActivity.ACTION_CALL)
- .setClass(getApplicationContext(), CallActivity.class)
- .putExtra("account", g.first.getAccountID())
- .putExtra("video", has_video)
- .setData(android.net.Uri.parse(g.second.getRawUriString()));
- startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL);
- } catch (Exception e) {
- e.printStackTrace();
- Log.e(TAG, e.toString());
- }
- }
-
- @Override
- public void deleteConversation(Conversation conversation) {
- if (mService != null) {
- mService.deleteConversation(conversation);
- }
- finish();
- }
-
- @Override
- public void copyContactNumberToClipboard(String contactNumber) {
- ClipboardHelper.copyNumberToClipboard(this, contactNumber, this);
- }
-
- @Override
- public void clipBoardDidCopyNumber(String copiedNumber) {
- View view = this.findViewById(android.R.id.content);
- if (view != null) {
- String snackbarText = getString(R.string.conversation_action_copied_peer_number_clipboard,
- Phone.getShortenedNumber(copiedNumber));
- Snackbar.make(view, snackbarText, Snackbar.LENGTH_LONG).show();
- }
- }
-
- @Override
- public void onDetailsLoaded(Bitmap bmp, String formattedName) {
- ActionBar ab = getSupportActionBar();
- if (ab != null && formattedName != null) {
- ab.setTitle(formattedName);
- }
- }
-
- @Override
public void onBackPressed() {
startActivity(new Intent(this, HomeActivity.class));
finish();
}
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mBound) {
+ unregisterReceiver(receiver);
+ unbindService(mConnection);
+ mBound = false;
+ }
+ }
}
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
index 0577fda..f31dccb 100755
--- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
+++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/RingInjectionComponent.java
@@ -27,12 +27,12 @@
import cx.ring.client.AccountEditionActivity;
import cx.ring.client.AccountWizard;
import cx.ring.client.CallActivity;
-import cx.ring.client.ConversationActivity;
import cx.ring.client.HomeActivity;
import cx.ring.fragments.AccountCreationFragment;
import cx.ring.fragments.AccountMigrationFragment;
import cx.ring.fragments.AccountsManagementFragment;
import cx.ring.fragments.CallFragment;
+import cx.ring.fragments.ConversationFragment;
import cx.ring.fragments.DeviceAccountFragment;
import cx.ring.fragments.MediaPreferenceFragment;
import cx.ring.fragments.ProfileCreationFragment;
@@ -69,8 +69,6 @@
void inject(CallActivity activity);
- void inject(ConversationActivity activity);
-
void inject(HomeActivity activity);
void inject(AccountWizard activity);
@@ -105,6 +103,8 @@
void inject(RegisterNameDialog dialog);
+ void inject(ConversationFragment fragment);
+
void inject(LocalService service);
void inject(DRingService service);
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
index 6266f61..1d2010c 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
@@ -452,7 +452,7 @@
break;
}
startActivityForResult(ActionHelper.getAddNumberIntentForContact(firstParticipant.getContact()),
- ConversationActivity.REQ_ADD_CONTACT);
+ ConversationFragment.REQ_ADD_CONTACT);
break;
case R.id.menuitem_speaker:
audioManager.setSpeakerphoneOn(!audioManager.isSpeakerphoneOn());
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
new file mode 100644
index 0000000..b6e721d
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
@@ -0,0 +1,478 @@
+package cx.ring.fragments;
+
+import android.app.Fragment;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.OnEditorAction;
+import cx.ring.R;
+import cx.ring.adapters.ContactDetailsTask;
+import cx.ring.adapters.ConversationAdapter;
+import cx.ring.adapters.NumberAdapter;
+import cx.ring.application.RingApplication;
+import cx.ring.client.CallActivity;
+import cx.ring.client.ConversationActivity;
+import cx.ring.client.HomeActivity;
+import cx.ring.model.Account;
+import cx.ring.model.CallContact;
+import cx.ring.model.Conference;
+import cx.ring.model.Conversation;
+import cx.ring.model.Phone;
+import cx.ring.model.Uri;
+import cx.ring.service.LocalService;
+import cx.ring.services.AccountService;
+import cx.ring.services.CallService;
+import cx.ring.utils.ActionHelper;
+import cx.ring.utils.ClipboardHelper;
+import cx.ring.utils.ContentUriHandler;
+
+public class ConversationFragment extends Fragment implements
+ Conversation.ConversationActionCallback,
+ ClipboardHelper.ClipboardHelperCallback,
+ ContactDetailsTask.DetailsLoadedCallback {
+
+ @Inject
+ CallService mCallService;
+
+ @Inject
+ AccountService mAccountService;
+
+ @BindView(R.id.msg_input_txt)
+ EditText mMsgEditTxt;
+
+ @BindView(R.id.ongoingcall_pane)
+ ViewGroup mBottomPane;
+
+ @BindView(R.id.hist_list)
+ RecyclerView mHistList;
+
+ @BindView(R.id.number_selector)
+ Spinner mNumberSpinner;
+
+ private static final String TAG = ConversationFragment.class.getSimpleName();
+ private static final String CONVERSATION_DELETE = "CONVERSATION_DELETE";
+
+ public static final int REQ_ADD_CONTACT = 42;
+
+ private boolean mVisible = false;
+ private AlertDialog mDeleteDialog;
+ private boolean mDeleteConversation = false;
+
+ private LocalService mService = null;
+ private Conversation mConversation = null;
+ private Uri mPreferredNumber = null;
+
+ private MenuItem mAddContactBtn = null;
+
+ private ConversationAdapter mAdapter = null;
+ private NumberAdapter mNumberAdapter = null;
+
+ private static Pair<Conversation, Uri> getConversation(LocalService service, Intent intent) {
+ if (service == null || intent == null || intent.getData() == null) {
+ return new Pair<>(null, null);
+ }
+
+ String conversationId = intent.getData().getLastPathSegment();
+ Uri number = new Uri(intent.getStringExtra("number"));
+
+ Log.d(TAG, "getConversation " + conversationId + " " + number);
+ Conversation conversation = service.getConversation(conversationId);
+ if (conversation == null) {
+ long contactId = CallContact.contactIdFromId(conversationId);
+ Log.d(TAG, "no conversation found, contact_id " + contactId);
+ CallContact contact = null;
+ if (contactId >= 0) {
+ contact = service.findContactById(contactId);
+ }
+ if (contact == null) {
+ Uri convUri = new Uri(conversationId);
+ if (!number.isEmpty()) {
+ contact = service.findContactByNumber(number);
+ if (contact == null) {
+ contact = CallContact.buildUnknown(convUri);
+ }
+ } else {
+ contact = service.findContactByNumber(convUri);
+ if (contact == null) {
+ contact = CallContact.buildUnknown(convUri);
+ number = contact.getPhones().get(0).getNumber();
+ } else {
+ number = convUri;
+ }
+ }
+ }
+ conversation = service.startConversation(contact);
+ }
+
+ Log.d(TAG, "returning " + conversation.getContact().getDisplayName() + " " + number);
+ return new Pair<>(conversation, number);
+ }
+
+ private static int getIndex(Spinner spinner, Uri myString) {
+ for (int i = 0, n = spinner.getCount(); i < n; i++)
+ if (((Phone) spinner.getItemAtPosition(i)).getNumber().equals(myString)) {
+ return i;
+ }
+ return 0;
+ }
+
+ public void refreshView(long refreshed) {
+ if (mService == null) {
+ return;
+ }
+ Pair<Conversation, Uri> conversation = getConversation(mService, getActivity().getIntent());
+ mConversation = conversation.first;
+ mPreferredNumber = conversation.second;
+
+ if (mConversation == null) {
+ return;
+ }
+
+ if (!mConversation.getContact().getPhones().isEmpty()) {
+ CallContact contact = mCallService.getContact(mConversation.getContact().getPhones().get(0).getNumber());
+ if (contact != null) {
+ mConversation.setContact(contact);
+ }
+ if (((ConversationActivity) getActivity()).getSupportActionBar() != null) {
+ ((ConversationActivity) getActivity()).getSupportActionBar().setTitle(mConversation.getContact().getDisplayName());
+ }
+ }
+
+ final CallContact contact = mConversation.getContact();
+ if (contact != null) {
+ new ContactDetailsTask(getActivity(), contact, this).run();
+ }
+
+ Conference conference = mConversation.getCurrentCall();
+ mBottomPane.setVisibility(conference == null ? View.GONE : View.VISIBLE);
+ if (conference != null) {
+ Log.d(TAG, "ConversationFragment refreshView " + conference.getId() + " "
+ + mConversation.getCurrentCall());
+ }
+
+ mAdapter.updateDataset(mConversation.getAggregateHistory(), refreshed);
+
+ if (mConversation.getContact().getPhones().size() > 1) {
+ mNumberSpinner.setVisibility(View.VISIBLE);
+ mNumberAdapter = new NumberAdapter(getActivity(),
+ mConversation.getContact(),
+ false);
+ mNumberSpinner.setAdapter(mNumberAdapter);
+ if (mPreferredNumber == null || mPreferredNumber.isEmpty()) {
+ mPreferredNumber = new Uri(
+ mConversation.getLastNumberUsed(mConversation.getLastAccountUsed())
+ );
+ }
+ mNumberSpinner.setSelection(getIndex(mNumberSpinner, mPreferredNumber));
+ } else {
+ mNumberSpinner.setVisibility(View.GONE);
+ mPreferredNumber = mConversation.getContact().getPhones().get(0).getNumber();
+ }
+
+ if (mAdapter.getItemCount() > 0) {
+ mHistList.smoothScrollToPosition(mAdapter.getItemCount() - 1);
+ }
+
+ getActivity().invalidateOptionsMenu();
+ }
+
+ public void setCallback(LocalService callback) {
+ mService = callback;
+
+ mAdapter = new ConversationAdapter(getActivity(),
+ mService.get40dpContactCache(),
+ mService.getThreadPool());
+
+ if (mHistList != null) {
+ mHistList.setAdapter(mAdapter);
+ }
+
+ if (mVisible && mConversation != null && !mConversation.isVisible()) {
+ mConversation.setVisible(true);
+ mService.readConversation(mConversation);
+ }
+
+ if (mDeleteConversation) {
+ mDeleteDialog = ActionHelper.launchDeleteAction(getActivity(), mConversation, this);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+
+ View inflatedView = inflater.inflate(R.layout.frag_conversation, container, false);
+
+ ButterKnife.bind(this, inflatedView);
+
+ // Dependency injection
+ ((RingApplication) getActivity().getApplication()).getRingInjectionComponent().inject(this);
+
+ if (mBottomPane != null) {
+ mBottomPane.setVisibility(View.GONE);
+ }
+
+ LinearLayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
+ mLayoutManager.setStackFromEnd(true);
+
+ if (mHistList != null) {
+ mHistList.setLayoutManager(mLayoutManager);
+ mHistList.setAdapter(mAdapter);
+ mHistList.setItemAnimator(new DefaultItemAnimator());
+ }
+
+ // reload delete conversation state (before rotation)
+ mDeleteConversation = savedInstanceState != null && savedInstanceState.getBoolean(CONVERSATION_DELETE);
+
+ setHasOptionsMenu(true);
+ return inflatedView;
+ }
+
+ @OnClick(R.id.msg_send)
+ public void sendMessageText(View sender) {
+ CharSequence txt = mMsgEditTxt.getText();
+ if (txt.length() > 0) {
+ onSendTextMessage(txt.toString());
+ mMsgEditTxt.setText("");
+ }
+ }
+
+ @OnEditorAction(R.id.msg_input_txt)
+ public boolean actionSendMsgText(TextView view, int actionId, KeyEvent event) {
+ switch (actionId) {
+ case EditorInfo.IME_ACTION_SEND:
+ CharSequence txt = mMsgEditTxt.getText();
+ if (txt.length() > 0) {
+ onSendTextMessage(mMsgEditTxt.getText().toString());
+ mMsgEditTxt.setText("");
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @OnClick(R.id.ongoingcall_pane)
+ public void onClick(View view) {
+ startActivity(new Intent(Intent.ACTION_VIEW)
+ .setClass(getActivity().getApplicationContext(), CallActivity.class)
+ .setData(android.net.Uri.withAppendedPath(ContentUriHandler.CONFERENCE_CONTENT_URI,
+ mConversation.getCurrentCall().getId())));
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ Log.d(TAG, "onPause");
+ mVisible = false;
+ if (mConversation != null) {
+ mService.readConversation(mConversation);
+ mConversation.setVisible(false);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ Log.d(TAG, "onResume " + mConversation);
+ mVisible = true;
+ if (mConversation != null) {
+ mConversation.setVisible(true);
+ if (mService != null) {
+ mService.readConversation(mConversation);
+ }
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mDeleteConversation) {
+ mDeleteDialog.dismiss();
+ }
+
+ super.onDestroy();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ // persist the delete popup state in case of Activity rotation
+ mDeleteConversation = mDeleteDialog != null && mDeleteDialog.isShowing();
+ outState.putBoolean(CONVERSATION_DELETE, mDeleteConversation);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ if (mAddContactBtn != null) {
+ mAddContactBtn.setVisible(mConversation != null && mConversation.getContact().getId() < 0);
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.conversation_actions, menu);
+ mAddContactBtn = menu.findItem(R.id.menuitem_addcontact);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ startActivity(new Intent(getActivity(), HomeActivity.class));
+ return true;
+ case R.id.conv_action_audiocall:
+ onCallWithVideo(false);
+ return true;
+ case R.id.conv_action_videocall:
+ onCallWithVideo(true);
+ return true;
+ case R.id.menuitem_addcontact:
+ startActivityForResult(ActionHelper.getAddNumberIntentForContact(mConversation.getContact()), REQ_ADD_CONTACT);
+ return true;
+ case R.id.menuitem_delete:
+ mDeleteDialog = ActionHelper.launchDeleteAction(getActivity(),
+ this.mConversation,
+ this);
+ return true;
+ case R.id.menuitem_copy_content:
+ ActionHelper.launchCopyNumberToClipboardFromContact(getActivity(),
+ this.mConversation.getContact(),
+ this);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * Guess account and number to use to initiate a call
+ */
+ private Pair<Account, Uri> guess() {
+ Uri number = mNumberAdapter == null ?
+ mPreferredNumber : ((Phone) mNumberSpinner.getSelectedItem()).getNumber();
+ Account account = mAccountService.getAccount(mConversation.getLastAccountUsed());
+
+ // Guess account from number
+ if (account == null && number != null) {
+ account = mAccountService.guessAccount(number);
+ }
+
+ // Guess number from account/call history
+ if (account != null && number == null) {
+ number = new Uri(mConversation.getLastNumberUsed(account.getAccountID()));
+ }
+
+ // If no account found, use first active
+ if (account == null) {
+ List<Account> accounts = mAccountService.getAccounts();
+ if (accounts.isEmpty()) {
+ return null;
+ } else
+ account = accounts.get(0);
+ }
+
+ // If no number found, use first from contact
+ if (number == null || number.isEmpty()) {
+ number = mConversation.getContact().getPhones().get(0).getNumber();
+ }
+
+ return new Pair<>(account, number);
+ }
+
+ private void onCallWithVideo(boolean has_video) {
+ Conference conf = mConversation.getCurrentCall();
+ if (conf != null) {
+ startActivity(new Intent(Intent.ACTION_VIEW)
+ .setClass(getActivity().getApplicationContext(), CallActivity.class)
+ .setData(android.net.Uri.withAppendedPath(ContentUriHandler.CONFERENCE_CONTENT_URI, conf.getId())));
+ return;
+ }
+ Pair<Account, Uri> guess = guess();
+ if (guess == null || guess.first == null) {
+ return;
+ }
+
+ try {
+ Intent intent = new Intent(CallActivity.ACTION_CALL)
+ .setClass(getActivity().getApplicationContext(), CallActivity.class)
+ .putExtra("account", guess.first.getAccountID())
+ .putExtra("video", has_video)
+ .setData(android.net.Uri.parse(guess.second.getRawUriString()));
+ startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL);
+ } catch (Exception e) {
+ Log.e(TAG, "Error during call", e);
+ }
+ }
+
+ private void onSendTextMessage(String txt) {
+ Conference conference = mConversation == null ? null : mConversation.getCurrentCall();
+ if (conference == null || !conference.isOnGoing()) {
+ Pair<Account, Uri> guess = guess();
+ if (guess == null || guess.first == null) {
+ return;
+ }
+ mService.sendTextMessage(guess.first.getAccountID(), guess.second, txt);
+ } else {
+ mService.sendTextMessage(conference, txt);
+ }
+ }
+
+ @Override
+ public void deleteConversation(Conversation conversation) {
+ if (mService != null) {
+ mService.deleteConversation(conversation);
+ getActivity().finish();
+ }
+ }
+
+ @Override
+ public void copyContactNumberToClipboard(String contactNumber) {
+ ClipboardHelper.copyNumberToClipboard(getActivity(), contactNumber, this);
+ }
+
+ @Override
+ public void clipBoardDidCopyNumber(String copiedNumber) {
+ View view = getActivity().findViewById(android.R.id.content);
+ if (view != null) {
+ String snackbarText = getString(R.string.conversation_action_copied_peer_number_clipboard,
+ Phone.getShortenedNumber(copiedNumber));
+ Snackbar.make(view, snackbarText, Snackbar.LENGTH_LONG).show();
+ }
+ }
+
+ @Override
+ public void onDetailsLoaded(Bitmap bmp, String formattedName) {
+ ActionBar actionBar = ((ConversationActivity) getActivity()).getSupportActionBar();
+ if (actionBar != null && formattedName != null) {
+ actionBar.setTitle(formattedName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/activity_conversation.xml b/ring-android/app/src/main/res/layout/activity_conversation.xml
index 90ed88f..a2a928a 100644
--- a/ring-android/app/src/main/res/layout/activity_conversation.xml
+++ b/ring-android/app/src/main/res/layout/activity_conversation.xml
@@ -5,7 +5,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ebeff0"
- android:orientation="vertical"
tools:context=".client.ConversationActivity">
<android.support.v7.widget.Toolbar
@@ -25,90 +24,11 @@
app:elevation="4dp"
app:titleTextAppearance="@style/ToolbarTitle" />
- <android.support.v7.widget.RecyclerView
- android:id="@+id/hist_list"
+ <FrameLayout
+ android:id="@+id/main_frame"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_alignParentEnd="true"
+ android:layout_height="match_parent"
android:layout_alignParentLeft="true"
- android:layout_alignParentRight="true"
android:layout_alignParentStart="true"
- android:layout_below="@id/main_toolbar"
- android:clipToPadding="false"
- android:divider="@null"
- android:listSelector="@android:color/transparent"
- android:paddingBottom="60dp"
- android:paddingTop="8dp"
- tools:listitem="@layout/item_conv_msg_peer" />
-
- <RelativeLayout
- android:id="@+id/ongoingcall_pane"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:layout_below="@id/main_toolbar"
- android:background="#e3c1c1">
-
- <TextView
- android:id="@+id/textView2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"
- android:layout_margin="10dp"
- android:text="@string/ongoing_call"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@color/text_color_primary" />
- </RelativeLayout>
-
- <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
- android:id="@+id/userInputMessageCardView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_alignParentEnd="true"
- android:layout_alignParentLeft="true"
- android:layout_alignParentRight="true"
- android:layout_alignParentStart="true"
- android:layout_marginBottom="16dp"
- android:layout_marginLeft="8dp"
- android:layout_marginRight="8dp"
- card_view:cardCornerRadius="2dp">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
-
- <Spinner
- android:id="@+id/number_selector"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:visibility="visible"
- tools:listitem="@layout/item_number_selected" />
-
- <EditText
- android:id="@+id/msg_input_txt"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:background="@null"
- android:hint="@string/write_a_message"
- android:imeOptions="actionSend|flagNoExtractUi"
- android:inputType="textShortMessage|textImeMultiLine|text|textMultiLine|textCapSentences"
- android:maxLines="5"
- android:padding="8dp" />
-
- <ImageButton
- android:id="@+id/msg_send"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:background="?selectableItemBackgroundBorderless"
- android:contentDescription="@string/send_message"
- android:padding="8dp"
- android:src="@drawable/ic_send_black"
- android:tint="@android:color/darker_gray" />
- </LinearLayout>
- </android.support.v7.widget.CardView>
-
+ android:layout_below="@+id/main_toolbar" />
</RelativeLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/frag_conversation.xml b/ring-android/app/src/main/res/layout/frag_conversation.xml
new file mode 100644
index 0000000..90e2b22
--- /dev/null
+++ b/ring-android/app/src/main/res/layout/frag_conversation.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/hist_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:clipToPadding="false"
+ android:divider="@null"
+ android:listSelector="@android:color/transparent"
+ android:paddingBottom="60dp"
+ android:paddingTop="8dp"
+ tools:listitem="@layout/item_conv_msg_peer" />
+
+ <RelativeLayout
+ android:id="@+id/ongoingcall_pane"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_below="@id/main_toolbar"
+ android:background="#e3c1c1">
+
+ <TextView
+ android:id="@+id/textView2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:layout_margin="10dp"
+ android:text="@string/ongoing_call"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/text_color_primary" />
+ </RelativeLayout>
+
+ <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/userInputMessageCardView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentStart="true"
+ android:layout_marginBottom="16dp"
+ android:layout_marginLeft="8dp"
+ android:layout_marginRight="8dp"
+ card_view:cardCornerRadius="2dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <Spinner
+ android:id="@+id/number_selector"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:visibility="visible"
+ tools:listitem="@layout/item_number_selected" />
+
+ <EditText
+ android:id="@+id/msg_input_txt"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:background="@null"
+ android:hint="@string/write_a_message"
+ android:imeOptions="actionSend|flagNoExtractUi"
+ android:inputType="textShortMessage|textImeMultiLine|text|textMultiLine|textCapSentences"
+ android:maxLines="5"
+ android:padding="8dp" />
+
+ <ImageButton
+ android:id="@+id/msg_send"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:background="?selectableItemBackgroundBorderless"
+ android:contentDescription="@string/send_message"
+ android:padding="8dp"
+ android:src="@drawable/ic_send_black"
+ android:tint="@android:color/darker_gray" />
+ </LinearLayout>
+ </android.support.v7.widget.CardView>
+</RelativeLayout>
\ No newline at end of file