diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml index 17de2e5ef7ad6f11993562b3e5183dba80169707..3c2e941b0b90af16b374343e39d26b7cde8f95a1 100644 --- a/ring-android/app/src/main/AndroidManifest.xml +++ b/ring-android/app/src/main/AndroidManifest.xml @@ -231,7 +231,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </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 d8cb27367030d8614611459b67291aca7a639d81..85f0d2360fd3fa379bc5d781b7149c8b844b4633 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,214 +27,62 @@ import android.content.Context; 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); - } - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_conversation); - ab.setTitle(mConversation.getContact().getDisplayName()); - } + ButterKnife.bind(this); + setSupportActionBar(mToolbar); - final CallContact contact = mConversation.getContact(); - if (contact != null) { - new ContactDetailsTask(this, contact, this).run(); - } + getSupportActionBar().setDisplayHomeAsUpEnabled(true); - Conference conf = mConversation.getCurrentCall(); - mBottomPane.setVisibility(conf == null ? View.GONE : View.VISIBLE); - if (conf != null) { - Log.d(TAG, "ConversationActivity refreshView " + conf.getId() + " " - + mConversation.getCurrentCall()); + if (mConversationFragment == null) { + mConversationFragment = new ConversationFragment(); + getFragmentManager().beginTransaction() + .replace(R.id.main_frame, mConversationFragment, null) + .commit(); } - 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(); + if (!mBound) { + Log.d(TAG, "onCreate: Binding service..."); + Intent intent = new Intent(this, LocalService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + mService = null; } - - invalidateOptionsMenu(); } private ServiceConnection mConnection = new ServiceConnection() { @@ -243,28 +91,15 @@ public class ConversationActivity extends AppCompatActivity implements 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 (mConversationFragment != null) { + mConversationFragment.setCallback(mService); + mConversationFragment.refreshView(0); } - if (mDeleteConversation) { - mDeleteDialog = ActionHelper.launchDeleteAction(ConversationActivity.this, mConversation, ConversationActivity.this); - } + mBound = true; mRefreshTaskHandler.postDelayed(refreshTask, REFRESH_INTERVAL_MS); } @@ -274,19 +109,6 @@ public class ConversationActivity extends AppCompatActivity implements 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); } }; @@ -294,291 +116,49 @@ public class ConversationActivity extends AppCompatActivity implements private long lastRefresh = 0; public void run() { - if (lastRefresh == 0) + if (lastRefresh == 0) { lastRefresh = SystemClock.uptimeMillis(); - else + } else { lastRefresh += REFRESH_INTERVAL_MS; - - mAdapter.notifyDataSetChanged(); + } mRefreshTaskHandler.postAtTime(this, lastRefresh + REFRESH_INTERVAL_MS); } }; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_conversation); - - ButterKnife.bind(this); - setSupportActionBar(mToolbar); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - // Dependency injection - ((RingApplication) getApplication()).getRingInjectionComponent().inject(this); - - if (mBottomPane != null) { - mBottomPane.setVisibility(View.GONE); - } - - 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); - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - mService = null; - } - } - - @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 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); - } + 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); - } + public void onBackPressed() { + startActivity(new Intent(this, HomeActivity.class)); 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); + public void onDestroy() { + super.onDestroy(); + if (mBound) { + unregisterReceiver(receiver); + unbindService(mConnection); + mBound = false; } } - - @Override - public void onBackPressed() { - startActivity(new Intent(this, HomeActivity.class)); - finish(); - } } 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 0577fdab8ece19ccb26392e21620c6ff55610677..f31dccb95d432b6984bb28fc63327834b4cd8c64 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.application.RingApplication; 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 @@ public interface RingInjectionComponent { void inject(CallActivity activity); - void inject(ConversationActivity activity); - void inject(HomeActivity activity); void inject(AccountWizard activity); @@ -105,6 +103,8 @@ public interface RingInjectionComponent { 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 6266f616cda86604c238820a7ad9a4a000a43618..1d2010caf7f4b449d1af46f0919c1a4b39ed5ad4 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 @@ public class CallFragment extends Fragment implements CallInterface, ContactDeta 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 0000000000000000000000000000000000000000..b6e721dbe83532a2ba368adae751d8bbf51095d4 --- /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 90ed88f6c03053561a062bd5094fc006eebc3d8d..a2a928a63250fd156ed55a6dc8a41a5fa89ca803 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_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_height="match_parent" 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 0000000000000000000000000000000000000000..90e2b22bf8cc7fc7254efbcb7ad37fef5635a0a2 --- /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