/* * VoIP.ms SMS * Copyright (C) 2015-2016 Michael Kourlas * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.kourlas.voipms_sms.activities; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.view.ViewCompat; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.view.ActionMode; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; import android.support.v7.widget.Toolbar; import android.text.InputType; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import net.kourlas.voipms_sms.R; import net.kourlas.voipms_sms.adapters.ConversationsRecyclerViewAdapter; import net.kourlas.voipms_sms.billing.Billing; import net.kourlas.voipms_sms.db.Database; import net.kourlas.voipms_sms.model.Message; import net.kourlas.voipms_sms.notifications.PushNotifications; import net.kourlas.voipms_sms.preferences.Preferences; import net.kourlas.voipms_sms.receivers.SynchronizationIntervalReceiver; import net.kourlas.voipms_sms.utils.Utils; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; public class ConversationsActivity extends AppCompatActivity implements ActionMode.Callback, View.OnClickListener, View.OnLongClickListener, ActivityCompat.OnRequestPermissionsResultCallback { private static final int PERM_REQ_CONTACTS = 0; private final ConversationsActivity conversationsActivity = this; private PushNotifications pushNotifications; private Billing billing; private Database database; private Preferences preferences; private RecyclerView recyclerView; private ConversationsRecyclerViewAdapter adapter; private Menu menu; private ActionMode actionMode; private boolean actionModeEnabled; private AlertDialog firstRunDialog; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.conversations); pushNotifications = PushNotifications .getInstance(getApplicationContext()); billing = Billing.getInstance(getApplicationContext()); database = Database.getInstance(getApplicationContext()); preferences = Preferences.getInstance(getApplicationContext()); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); ViewCompat.setElevation(toolbar, getResources() .getDimension(R.dimen.toolbar_elevation)); setSupportActionBar(toolbar); LinearLayoutManager layoutManager = new LinearLayoutManager(this); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); adapter = new ConversationsRecyclerViewAdapter(this, layoutManager); recyclerView = (RecyclerView) findViewById(R.id.list); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(adapter); actionMode = null; actionModeEnabled = false; SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout); swipeRefreshLayout .setOnRefreshListener(this::preFullUpdate); swipeRefreshLayout.setColorSchemeResources(R.color.accent); FloatingActionButton button = (FloatingActionButton) findViewById(R.id.new_button); button.setOnClickListener(v -> { if (!preferences.getEmail().equals("") && !preferences .getPassword().equals("") && !preferences.getDid().equals("")) { Intent intent = new Intent(conversationsActivity, NewConversationActivity.class); startActivity(intent); } }); SynchronizationIntervalReceiver .setupSynchronizationInterval(getApplicationContext()); if (ContextCompat.checkSelfPermission( this, android.Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, new String[] {android.Manifest.permission.READ_CONTACTS}, PERM_REQ_CONTACTS); } } @Override protected void onDestroy() { super.onDestroy(); ActivityMonitor.getInstance().deleteReferenceToActivity(this); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 1001 && resultCode == RESULT_OK) { try { String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); JSONObject json = new JSONObject(purchaseData); String token = json.getString("purchaseToken"); billing.postDonation(token, this); } catch (Exception ignored) { // Do nothing. } } } @Override public void onBackPressed() { if (actionModeEnabled) { actionMode.finish(); } else if (menu != null) { MenuItem searchItem = menu.findItem(R.id.search_button); SearchView searchView = (SearchView) searchItem.getActionView(); if (!searchView.isIconified()) { searchItem.collapseActionView(); } else { super.onBackPressed(); } } } @Override protected void onPause() { super.onPause(); ActivityMonitor.getInstance().deleteReferenceToActivity(this); if (firstRunDialog != null) { firstRunDialog.dismiss(); } } @Override public void onResume() { super.onResume(); ActivityMonitor.getInstance().setCurrentActivity(this); if (!(preferences.getEmail().equals("") || preferences.getPassword() .equals("") || preferences.getDid().equals(""))) { preRecentUpdate(); if (ContextCompat.checkSelfPermission( this, android.Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { adapter.notifyItemRangeChanged(0, adapter.getItemCount()); } } else { firstRunDialog = Utils.showAlertDialog( this, null, getString( R.string.conversations_first_run_dialog_text), getString(R.string.preferences_name), (dialog, id) -> { Intent preferencesIntent = new Intent(conversationsActivity, PreferencesActivity .class); startActivity(preferencesIntent); }, getString(R.string.help_name), (dialog, id) -> { Intent helpIntent = new Intent(conversationsActivity, HelpActivity.class); startActivity(helpIntent); }); } } public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERM_REQ_CONTACTS) { for (int i = 0; i < permissions.length; i++) { if (permissions[i].equals( android.Manifest.permission.READ_CONTACTS)) { if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { adapter.notifyItemRangeChanged(0, adapter.getItemCount()); } else { Utils.showPermissionSnackbar( this, R.id.new_button, getString( R.string.conversations_perm_denied_contacts)); } } } } } /** * Initiates a partial update of the message database. This update only * retrieves messages dated after the most * recent message. */ private void preRecentUpdate() { adapter.refresh(); pushNotifications .registerForFcm(conversationsActivity, null, false, false); database.synchronize(true, false, conversationsActivity); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.conversations, menu); this.menu = menu; SearchView searchView = (SearchView) menu.findItem(R.id.search_button).getActionView(); searchView.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { return false; } @Override public boolean onQueryTextChange(String newText) { adapter.refresh(newText); return true; } }); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.preferences_button: Intent preferencesIntent = new Intent(this, PreferencesActivity.class); startActivity(preferencesIntent); return true; case R.id.help_button: Intent helpIntent = new Intent(this, HelpActivity.class); startActivity(helpIntent); return true; case R.id.credits_button: Intent creditsIntent = new Intent(this, CreditsActivity.class); startActivity(creditsIntent); return true; case R.id.donate_button: billing.preDonation(this); default: return super.onOptionsItemSelected(item); } } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.conversations_secondary, menu); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } /** * Called when an action mode item is clicked. * * @param mode The action mode containing the item that is clicked. * @param item The item that is clicked. * @return Returns true if the method handles the item clicked. */ @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.mark_read_unread_button: for (int i = 0; i < adapter.getItemCount(); i++) { if (adapter.isItemChecked(i)) { Message message = adapter.getItem(i); if (item.getTitle().equals(getResources().getString( R.string.conversations_action_mark_unread))) { database.markConversationAsUnread( preferences.getDid(), message.getContact()); } else { database.markConversationAsRead( preferences.getDid(), message.getContact()); } } } adapter.refresh(); mode.finish(); return true; case R.id.delete_button: return deleteButtonHandler(mode); default: return false; } } /** * Called when the action mode is destroyed. * * @param mode The action mode to be destroyed. */ @Override public void onDestroyActionMode(ActionMode mode) { for (int i = 0; i < adapter.getItemCount(); i++) { adapter.setItemChecked(i, false); } actionModeEnabled = false; } private boolean deleteButtonHandler(ActionMode mode) { List<String> contacts = new ArrayList<>(); for (int i = 0; i < adapter.getItemCount(); i++) { if (adapter.isItemChecked(i)) { contacts.add(adapter.getItem(i).getContact()); } } Utils.showAlertDialog( this, getString(R.string.conversations_delete_confirm_title), getString(R.string.conversations_delete_confirm_message), getString(R.string.delete), (dialog, which) -> { for (String contact : contacts) { database.deleteMessages(preferences.getDid(), contact); } adapter.refresh(); }, getString(R.string.cancel), null); mode.finish(); return true; } /** * Called when any item in the RecyclerView is short-clicked. * <p/> * This method only toggles the selected item (if the action mode is * enabled) or opens the ConversationActivity * for that item (if the action mode is not enabled). * * @param view The item to toggle or open. */ @Override public void onClick(View view) { if (actionModeEnabled) { toggleItem(view); } else { Message message = adapter.getItem(recyclerView.getChildAdapterPosition(view)); String contact = message.getContact(); Intent intent = new Intent(this, ConversationActivity.class); intent.putExtra(getString(R.string.conversation_extra_contact), contact); startActivity(intent); } } /** * Toggles the item associated with the specified view. Activates and * deactivates the action mode depending on the * checked item count. * * @param view The specified view. */ private void toggleItem(View view) { adapter.toggleItemChecked(recyclerView.getChildAdapterPosition(view)); if (adapter.getCheckedItemCount() == 0) { if (actionMode != null) { actionMode.finish(); } actionModeEnabled = false; return; } if (!actionModeEnabled) { actionMode = startSupportActionMode(this); actionModeEnabled = true; } updateButtons(); } /** * Switches between "mark as read" and "mark as unread" buttons for the * action mode depending on which items in * the RecyclerView are selected. */ private void updateButtons() { int read = 0; int unread = 0; for (int i = 0; i < adapter.getItemCount(); i++) { if (adapter.isItemChecked(i)) { if (adapter.getItem(i).isUnread()) { unread++; } else { read++; } } } MenuItem item = actionMode.getMenu().findItem(R.id.mark_read_unread_button); if (read > unread) { item.setIcon(R.drawable.ic_markunread_white_24dp); item.setTitle(R.string.conversations_action_mark_unread); } else { item.setIcon(R.drawable.ic_drafts_white_24dp); item.setTitle(R.string.conversations_action_mark_read); } } /** * Called when any item in the RecyclerView is long-clicked. * <p/> * This method only toggles the selected item. * * @param view The item to toggle. * @return Always returns true. */ @Override public boolean onLongClick(View view) { toggleItem(view); return true; } /** * Initiates a full update of the message database. This update follows * all synchronization rules set in the * application's settings. */ private void preFullUpdate() { adapter.refresh(); pushNotifications .registerForFcm(conversationsActivity, null, false, false); database.synchronize(false, true, conversationsActivity); } /** * Updates this activity's user interface after a database update. * <p/> * Called by the Api class after updating the SMS database if this * activity made the update request or, if the * update request was initiated by the GCM service, if this activity is * currently visible to the user. */ public void postUpdate() { SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout); swipeRefreshLayout.setRefreshing(false); adapter.refresh(); } }