diff options
author | Christian Schneppe <christian@pix-art.de> | 2018-03-24 22:38:29 +0100 |
---|---|---|
committer | Christian Schneppe <christian@pix-art.de> | 2018-03-24 22:38:29 +0100 |
commit | 6e49572be6fdd7c54eb0d6f1939dce9fbb537024 (patch) | |
tree | db4eadcb01743f2c2c6cdbfe63c56a3faeadad7d | |
parent | 91507dc103cbb758e49e861f8cff77ccdad09321 (diff) |
WIP & fragment voodoo
20 files changed, 871 insertions, 201 deletions
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 961c4df2a..872a9e9cd 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -77,7 +77,7 @@ </intent-filter> </activity> <activity - android:name=".ui.ConversationActivity" + android:name=".ui.ConversationsMainActivity" android:label="@string/app_name" android:launchMode="singleTask" android:minWidth="300dp" diff --git a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java index 4ad5d2d59..27512a3d1 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java @@ -2,11 +2,9 @@ package de.pixart.messenger.ui; import android.annotation.SuppressLint; import android.app.FragmentTransaction; -import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; -import android.content.IntentSender.SendIntentException; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -31,13 +29,9 @@ import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ListView; -import android.widget.PopupMenu; -import android.widget.PopupMenu.OnMenuItemClickListener; import android.widget.TextView; import android.widget.Toast; -import net.java.otr4j.session.SessionStatus; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -59,7 +53,6 @@ import de.pixart.messenger.ui.adapter.ConversationAdapter; import de.pixart.messenger.utils.ExceptionHelper; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.OnUpdateBlocklist; -import de.pixart.messenger.xmpp.XmppConnection; import de.pixart.messenger.xmpp.chatstate.ChatState; import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; @@ -165,6 +158,7 @@ public class ConversationActivity extends XmppActivity } } + @SuppressLint("ClickableViewAccessibility") @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -186,7 +180,7 @@ public class ConversationActivity extends XmppActivity this.mConversationFragment = new ConversationFragment(); FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.replace(R.id.selected_conversation, this.mConversationFragment, "conversation"); + //transaction.replace(R.id.selected_conversation, this.mConversationFragment, "conversation"); transaction.commit(); listView = findViewById(R.id.list); @@ -214,9 +208,9 @@ public class ConversationActivity extends XmppActivity } }); - mContentView = findViewById(R.id.content_view_spl); + //mContentView = findViewById(R.id.content_view_spl); if (mContentView == null) { - mContentView = findViewById(R.id.content_view_ll); + //mContentView = findViewById(R.id.content_view_ll); } if (mContentView instanceof SlidingPaneLayout) { SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; @@ -252,6 +246,10 @@ public class ConversationActivity extends XmppActivity } } + public boolean useBundledEmoji() { + return getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); + } + private boolean isPackageInstalled(String targetPackage) { List<ApplicationInfo> packages; PackageManager pm; @@ -454,41 +452,6 @@ public class ConversationActivity extends XmppActivity } } - public void verifyOtrSessionDialog(final Conversation conversation, View view) { - if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { - Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show(); - return; - } - if (view == null) { - return; - } - PopupMenu popup = new PopupMenu(this, view); - popup.inflate(R.menu.verification_choices); - popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - Intent intent = new Intent(ConversationActivity.this, VerifyOTRActivity.class); - intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); - intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); - intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); - switch (menuItem.getItemId()) { - case R.id.scan_fingerprint: - intent.putExtra("mode", VerifyOTRActivity.MODE_SCAN_FINGERPRINT); - break; - case R.id.ask_question: - intent.putExtra("mode", VerifyOTRActivity.MODE_ASK_QUESTION); - break; - case R.id.manual_verification: - intent.putExtra("mode", VerifyOTRActivity.MODE_MANUAL_VERIFICATION); - break; - } - startActivity(intent); - return true; - } - }); - popup.show(); - } - @Override public void onBackPressed() { if (!isConversationsOverviewVisable() && mConversationFragment.isSearchFieldVisible()) { @@ -914,11 +877,6 @@ public class ConversationActivity extends XmppActivity } } - public long getMaxHttpUploadSize(Conversation conversation) { - final XmppConnection connection = conversation.getAccount().getXmppConnection(); - return connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize(); - } - private String getBatteryOptimizationPreferenceKey() { @SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); return "show_battery_optimization" + (device == null ? "" : device); @@ -974,30 +932,6 @@ public class ConversationActivity extends XmppActivity listAdapter.notifyDataSetChanged(); } - public void runIntent(PendingIntent pi, int requestCode) { - try { - this.startIntentSenderForResult(pi.getIntentSender(), requestCode, - null, 0, 0, 0); - } catch (final SendIntentException ignored) { - } - } - - public boolean useSendButtonToIndicateStatus() { - return getPreferences().getBoolean("send_button_status", getResources().getBoolean(R.bool.send_button_status)); - } - - public boolean indicateReceived() { - return getPreferences().getBoolean("indicate_received", getResources().getBoolean(R.bool.indicate_received)); - } - - public boolean useWhiteBackground() { - return getPreferences().getBoolean("use_white_background", getResources().getBoolean(R.bool.use_white_background)); - } - - public boolean useBundledEmoji() { - return getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); - } - @Override protected void refreshUiReal() { updateConversationList(); @@ -1040,10 +974,6 @@ public class ConversationActivity extends XmppActivity this.refreshUi(); } - public boolean enterIsSend() { - return getPreferences().getBoolean("enter_is_send", getResources().getBoolean(R.bool.enter_is_send)); - } - @Override public void onShowErrorToast(final int resId) { runOnUiThread(() -> Toast.makeText(ConversationActivity.this, resId, Toast.LENGTH_SHORT).show()); @@ -1066,4 +996,6 @@ public class ConversationActivity extends XmppActivity startActivity(intent); } } + + } diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index 2b27d63cf..742c14495 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -2,13 +2,13 @@ package de.pixart.messenger.ui; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.Fragment; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender.SendIntentException; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.databinding.DataBindingUtil; import android.graphics.Bitmap; @@ -18,6 +18,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.provider.MediaStore; import android.support.media.ExifInterface; import android.support.v13.view.inputmethod.InputConnectionCompat; @@ -91,9 +92,11 @@ import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.services.MessageArchiveService; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.ui.adapter.MessageAdapter; +import de.pixart.messenger.ui.interfaces.OnSwipeTouchListener; import de.pixart.messenger.ui.util.ActivityResult; import de.pixart.messenger.ui.util.AttachmentTool; import de.pixart.messenger.ui.util.ConversationMenuConfigurator; +import de.pixart.messenger.ui.util.PendingItem; import de.pixart.messenger.ui.util.PresenceSelector; import de.pixart.messenger.ui.util.SendButtonAction; import de.pixart.messenger.ui.util.SendButtonTool; @@ -112,7 +115,7 @@ import static de.pixart.messenger.ui.XmppActivity.REQUEST_ANNOUNCE_PGP; import static de.pixart.messenger.ui.XmppActivity.REQUEST_CHOOSE_PGP_ID; import static de.pixart.messenger.xmpp.Patches.ENCRYPTION_EXCEPTIONS; -public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener { +public class ConversationFragment extends XmppFragment implements EditMessage.KeyboardListener { public static final int REQUEST_SEND_MESSAGE = 0x0201; public static final int REQUEST_DECRYPT_PGP = 0x0202; @@ -130,18 +133,21 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public static final int ATTACHMENT_CHOICE_INVALID = 0x0399; public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action"; + public static final String STATE_CONVERSATION_UUID = ConversationFragment.class.getName() + ".uuid"; + public static final String STATE_SCROLL_POSITION = ConversationFragment.class.getName() + ".scroll_position"; final protected List<Message> messageList = new ArrayList<>(); final private List<Uri> mPendingImageUris = new ArrayList<>(); public Uri mPendingEditorContent = null; - protected Conversation conversation; - public FragmentConversationBinding binding; + private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>(); + private final PendingItem<String> pendingConversationsUuid = new PendingItem<>(); protected MessageAdapter messageListAdapter; + private Conversation conversation; + public FragmentConversationBinding binding; protected Message lastHistoryMessage = null; SimpleDateFormat sdf = new SimpleDateFormat("EEEE, dd. MMM yyyy", Locale.getDefault()); private Toast messageLoaderToast; - private ActivityResult postponedActivityResult = null; - private ConversationActivity activity; + private ConversationsMainActivity activity; protected OnClickListener clickToVerify = new OnClickListener() { @Override public void onClick(View v) { @@ -162,7 +168,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onClick(View v) { - activity.endConversation(conversation); + activity.onConversationArchived(conversation); } }; private OnClickListener joinMuc = new OnClickListener() { @@ -904,6 +910,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } break; case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: + //TODO save photo uri final List<Uri> PhotoUris = AttachmentTool.extractUriFromIntent(data); for (Iterator<Uri> i = PhotoUris.iterator(); i.hasNext(); i.remove()) { Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching image to conversations. TAKE_FROM_CAMERA"); @@ -918,7 +925,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa attachFileToConversation(conversation, i.next()); } }; - if (conversation == null || conversation.getMode() == Conversation.MODE_MULTI || FileBackend.allFilesUnderSize(getActivity(), fileUris, activity.getMaxHttpUploadSize(conversation))) { + if (conversation == null || conversation.getMode() == Conversation.MODE_MULTI || FileBackend.allFilesUnderSize(getActivity(), fileUris, getMaxHttpUploadSize(conversation))) { callback.onPresenceSelected(); } else { activity.selectPresence(conversation, callback); @@ -949,7 +956,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (activity != null && activity.xmppConnectionService != null) { handleActivityResult(activityResult); } else { - this.postponedActivityResult = activityResult; + this.postponedActivityResult.push(activityResult); } } @@ -958,13 +965,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } @Override - public void onAttach(Context context) { - if (context instanceof ConversationActivity) { - this.activity = (ConversationActivity) context; + public void onAttach(Activity activity) { + super.onAttach(activity); + Log.d(Config.LOGTAG, "onAttach()"); + if (activity instanceof ConversationsMainActivity) { + this.activity = (ConversationsMainActivity) activity; } else { throw new IllegalStateException("Trying to attach fragment to activity that is not the ConversationActivity"); } - super.onAttach(context); } @Override @@ -1001,7 +1009,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa binding.messagesView.setOnScrollListener(mOnScrollListener); binding.messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL); - messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList); + messageListAdapter = new MessageAdapter((XmppActivity) getActivity(), this.messageList); messageListAdapter.setOnContactPictureClicked(message -> { final boolean received = message.getStatus() <= Message.STATUS_RECEIVED; if (received) { @@ -1074,7 +1082,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa binding.messagesView.setAdapter(messageListAdapter); registerForContextMenu(binding.messagesView); - return binding.getRoot(); } @@ -1253,7 +1260,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa handleAttachmentSelection(item); break; case R.id.action_archive_chat: - activity.endConversation(conversation); + activity.onConversationArchived(conversation); break; case R.id.action_archive_muc: getActivity().runOnUiThread(() -> { @@ -1262,7 +1269,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa builder.setMessage(getString(R.string.leave_conference_warning)); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.action_end_conversation_muc), - (dialog, which) -> activity.endConversation(conversation)); + (dialog, which) -> activity.onConversationArchived(conversation)); builder.create().show(); }); break; @@ -1312,7 +1319,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (conversation == null) { return; } - final ConversationFragment fragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation"); switch (item.getItemId()) { case R.id.encryption_choice_none: conversation.setNextEncryption(Message.ENCRYPTION_NONE); @@ -1345,7 +1351,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa break; } activity.xmppConnectionService.updateConversation(conversation); - fragment.updateChatMsgHint(); + updateChatMsgHint(); getActivity().invalidateOptionsMenu(); activity.refreshUi(); } @@ -1384,7 +1390,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void userInputRequried(PendingIntent pi, Contact contact) { - activity.runIntent(pi, attachmentChoice); + startPendingIntent(pi, attachmentChoice); } @Override @@ -1478,9 +1484,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa builder.setPositiveButton(getString(R.string.delete_messages), (dialog, which) -> { this.activity.xmppConnectionService.clearConversationHistory(conversation); if (endConversationCheckBox.isChecked()) { - this.activity.endConversation(conversation); + this.activity.onConversationArchived(conversation); } else { - activity.updateConversationList(); + activity.onConversationsListItemUpdated(); updateMessages(); } }); @@ -1625,7 +1631,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private void deleteFile(Message message) { if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) { message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - activity.updateConversationList(); + activity.onConversationsListItemUpdated(); updateMessages(); } } @@ -1648,7 +1654,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } else { Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - activity.updateConversationList(); + activity.onConversationsListItemUpdated(); updateMessages(); return; } @@ -1693,7 +1699,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private void retryDecryption(Message message) { message.setEncryption(Message.ENCRYPTION_PGP); - activity.updateConversationList(); + activity.onConversationsListItemUpdated(); updateMessages(); conversation.getAccount().getPgpDecryptionService().decrypt(message, false); } @@ -1748,6 +1754,32 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (conversation != null) { + outState.putString(STATE_CONVERSATION_UUID, conversation.getUuid()); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (savedInstanceState == null) { + return; + } + String uuid = savedInstanceState.getString(STATE_CONVERSATION_UUID); + if (uuid != null) { + this.pendingConversationsUuid.push(uuid); + } + } + + @Override + public void onStart() { + super.onStart(); + reInit(conversation); + } + + @Override public void onStop() { super.onStop(); final Activity activity = getActivity(); @@ -1772,9 +1804,17 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } public boolean reInit(Conversation conversation) { + Log.d(Config.LOGTAG, "reInit()"); if (conversation == null) { + Log.d(Config.LOGTAG, "conversation was null :("); return false; } + if (this.activity == null) { + Log.d(Config.LOGTAG, "activity was null"); + this.conversation = conversation; + return false; + } + setupIme(); if (this.conversation != null) { final String msg = this.binding.textinput.getText().toString(); @@ -1799,6 +1839,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa this.binding.textinput.setKeyboardListener(this); messageListAdapter.updatePreferences(); this.binding.messagesView.setAdapter(messageListAdapter); + this.binding.messagesView.setOnTouchListener(new OnSwipeTouchListener(getContext()) { + @Override + public void onSwipeRight() { + Log.d(Config.LOGTAG, "Swipe right detected"); + activity.onBackPressed(); + } + }); updateMessages(); this.conversation.messagesLoaded.set(true); synchronized (this.messageList) { @@ -1947,15 +1994,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (getView() == null) { return; } - final ConversationActivity activity = (ConversationActivity) getActivity(); if (this.conversation != null) { conversation.populateWithMessages(ConversationFragment.this.messageList); updateSnackBar(conversation); updateStatusMessages(); this.messageListAdapter.notifyDataSetChanged(); updateChatMsgHint(); - if (!activity.isConversationsOverviewVisable() || !activity.isConversationsOverviewHideable()) { - activity.sendReadMarkerIfNecessary(conversation); + if (activity != null) { + activity.onConversationRead(this.conversation); } updateSendButton(); updateEditablity(); @@ -1988,6 +2034,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa mSendingPgpMessage.set(false); } + public long getMaxHttpUploadSize(Conversation conversation) { + final XmppConnection connection = conversation.getAccount().getXmppConnection(); + return connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize(); + } + private void updateEditablity() { boolean canWrite = this.conversation.getMode() == Conversation.MODE_SINGLE || this.conversation.getMucOptions().participating() || this.conversation.getNextCounterpart() != null; this.binding.textinput.setFocusable(canWrite); @@ -1997,11 +2048,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } public void updateSendButton() { + boolean useSendButtonToIndicateStatus = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("send_button_status", getResources().getBoolean(R.bool.send_button_status)); final Conversation c = this.conversation; final Presence.Status status; final String text = this.binding.textinput == null ? "" : this.binding.textinput.getText().toString(); final SendButtonAction action = SendButtonTool.getAction(getActivity(), c, text); - if (activity.useSendButtonToIndicateStatus() && c.getAccount().getStatus() == Account.State.ONLINE) { + if (useSendButtonToIndicateStatus && c.getAccount().getStatus() == Account.State.ONLINE) { if (activity.xmppConnectionService != null && activity.xmppConnectionService.getMessageArchiveService().isCatchingUp(c)) { status = Presence.Status.OFFLINE; } else if (c.getMode() == Conversation.MODE_SINGLE) { @@ -2166,13 +2218,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } protected void sendPlainTextMessage(Message message) { - ConversationActivity activity = (ConversationActivity) getActivity(); activity.xmppConnectionService.sendMessage(message); messageSent(); } protected void sendPgpMessage(final Message message) { - final ConversationActivity activity = (ConversationActivity) getActivity(); final XmppConnectionService xmppService = activity.xmppConnectionService; final Contact contact = message.getConversation().getContact(); if (!activity.hasPgp()) { @@ -2193,7 +2243,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void userInputRequried(PendingIntent pi, Contact contact) { - activity.runIntent(pi, REQUEST_ENCRYPT_MESSAGE); + startPendingIntent(pi, REQUEST_ENCRYPT_MESSAGE); } @Override @@ -2251,7 +2301,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void userInputRequried(PendingIntent pi, Message message) { - activity.runIntent(pi, REQUEST_SEND_MESSAGE); + startPendingIntent(pi, REQUEST_SEND_MESSAGE); } @Override @@ -2288,9 +2338,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } protected void sendAxolotlMessage(final Message message) { - final ConversationActivity activity = (ConversationActivity) getActivity(); - final XmppConnectionService xmppService = activity.xmppConnectionService; - xmppService.sendMessage(message); + activity.xmppConnectionService.sendMessage(message); messageSent(); } @@ -2318,7 +2366,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public boolean onEnterPressed() { - if (activity.enterIsSend()) { + SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(getActivity()); + final boolean enterIsSend = p.getBoolean("enter_is_send", getResources().getBoolean(R.bool.enter_is_send)); + if (enterIsSend) { sendMessage(); return true; } else { @@ -2336,7 +2386,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (broadcastLastActivity) { activity.xmppConnectionService.sendPresence(conversation.getAccount(), false); //send new presence but don't include idle because we are not } - activity.hideConversationsOverview(); updateSendButton(); } @@ -2474,17 +2523,44 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa return m != null && m.getMergedBody().toString().toLowerCase().contains(q.toLowerCase()); } + private void startPendingIntent(PendingIntent pendingIntent, int requestCode) { + try { + getActivity().startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0); + } catch (final SendIntentException ignored) { + } + } + + @Override public void onBackendConnected() { - if (postponedActivityResult != null) { - handleActivityResult(postponedActivityResult); + Log.d(Config.LOGTAG, "ConversationFragment.onBackendConnected()"); + String uuid = pendingConversationsUuid.pop(); + if (uuid != null) { + Conversation conversation = activity.xmppConnectionService.findConversationByUuid(uuid); + if (conversation == null) { + Log.d(Config.LOGTAG, "unable to restore activity"); + clearPending(); + return; + } + reInit(conversation); } - postponedActivityResult = null; + ActivityResult activityResult = postponedActivityResult.pop(); + if (activityResult != null) { + handleActivityResult(activityResult); + } + } + + @Override + void refresh() { + } public void clearPending() { - if (postponedActivityResult != null) { + if (postponedActivityResult.pop() != null) { Log.d(Config.LOGTAG, "cleared pending intent with unhandled result left"); } - postponedActivityResult = null; + } + + public Conversation getConversation() { + return conversation; } } diff --git a/src/main/java/de/pixart/messenger/ui/ConversationsMainActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationsMainActivity.java new file mode 100644 index 000000000..14570af97 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/ConversationsMainActivity.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.os.Bundle; +import android.support.annotation.IdRes; +import android.support.v7.app.ActionBar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.PopupMenu; +import android.widget.Toast; + +import net.java.otr4j.session.SessionStatus; + +import de.pixart.messenger.Config; +import de.pixart.messenger.R; +import de.pixart.messenger.databinding.ActivityConversationsBinding; +import de.pixart.messenger.entities.Conversation; +import de.pixart.messenger.services.EmojiService; +import de.pixart.messenger.ui.interfaces.OnConversationArchived; +import de.pixart.messenger.ui.interfaces.OnConversationRead; +import de.pixart.messenger.ui.interfaces.OnConversationSelected; +import de.pixart.messenger.ui.interfaces.OnConversationsListItemUpdated; + +import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; + +public class ConversationsMainActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead { + + private ActivityConversationsBinding binding; + + @Override + protected void refreshUiReal() { + + } + + @Override + void onBackendConnected() { + notifyFragment(R.id.main_fragment); + notifyFragment(R.id.secondary_fragment); + invalidateActionBarTitle(); + } + + private void notifyFragment(@IdRes int id) { + Fragment mainFragment = getFragmentManager().findFragmentById(id); + if (mainFragment != null && mainFragment instanceof XmppFragment) { + ((XmppFragment) mainFragment).onBackendConnected(); + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + new EmojiService(this).init(useBundledEmoji()); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations); + this.getFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle); + this.initializeFragments(); + this.invalidateActionBarTitle(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.activity_conversations, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public void onConversationSelected(Conversation conversation) { + Log.d(Config.LOGTAG, "selected " + conversation.getName()); + ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment); + if (conversationFragment == null) { + conversationFragment = new ConversationFragment(); + FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + fragmentTransaction.replace(R.id.main_fragment, conversationFragment); + fragmentTransaction.addToBackStack(null); + fragmentTransaction.commit(); + } + conversationFragment.reInit(conversation); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + FragmentManager fm = getFragmentManager(); + if (fm.getBackStackEntryCount() > 0) { + fm.popBackStack(); + return true; + } + break; + } + return super.onOptionsItemSelected(item); + } + + private void initializeFragments() { + FragmentTransaction transaction = getFragmentManager().beginTransaction(); + Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment); + Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment); + if (mainFragment != null) { + Log.d(Config.LOGTAG, "initializeFragment(). main fragment exists"); + if (binding.secondaryFragment != null) { + if (mainFragment instanceof ConversationFragment) { + Log.d(Config.LOGTAG, "gained secondary fragment. moving..."); + getFragmentManager().popBackStack(); + transaction.remove(mainFragment); + transaction.commit(); + getFragmentManager().executePendingTransactions(); + transaction = getFragmentManager().beginTransaction(); + transaction.replace(R.id.secondary_fragment, mainFragment); + transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment()); + transaction.commit(); + return; + } + } else { + if (secondaryFragment != null && secondaryFragment instanceof ConversationFragment) { + Log.d(Config.LOGTAG, "lost secondary fragment. moving..."); + transaction.remove(secondaryFragment); + transaction.commit(); + getFragmentManager().executePendingTransactions(); + transaction = getFragmentManager().beginTransaction(); + transaction.replace(R.id.main_fragment, secondaryFragment); + transaction.addToBackStack(null); + transaction.commit(); + return; + } + } + } else { + transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment()); + } + if (binding.secondaryFragment != null) { + transaction.replace(R.id.secondary_fragment, new ConversationFragment()); + } + transaction.commit(); + } + + private void invalidateActionBarTitle() { + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment); + if (mainFragment != null && mainFragment instanceof ConversationFragment) { + final Conversation conversation = ((ConversationFragment) mainFragment).getConversation(); + if (conversation != null) { + actionBar.setTitle(conversation.getName()); + actionBar.setDisplayHomeAsUpEnabled(true); + return; + } + } + actionBar.setTitle(R.string.app_name); + actionBar.setDisplayHomeAsUpEnabled(false); + } + } + + public boolean useBundledEmoji() { + return getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); + } + + public void verifyOtrSessionDialog(final Conversation conversation, View view) { + if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { + Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show(); + return; + } + if (view == null) { + return; + } + PopupMenu popup = new PopupMenu(this, view); + popup.inflate(R.menu.verification_choices); + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + Intent intent = new Intent(ConversationsMainActivity.this, VerifyOTRActivity.class); + intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); + intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); + switch (menuItem.getItemId()) { + case R.id.scan_fingerprint: + intent.putExtra("mode", VerifyOTRActivity.MODE_SCAN_FINGERPRINT); + break; + case R.id.ask_question: + intent.putExtra("mode", VerifyOTRActivity.MODE_ASK_QUESTION); + break; + case R.id.manual_verification: + intent.putExtra("mode", VerifyOTRActivity.MODE_MANUAL_VERIFICATION); + break; + } + startActivity(intent); + return true; + } + }); + popup.show(); + } + + @Override + public void onConversationArchived(Conversation conversation) { + + } + + @Override + public void onConversationsListItemUpdated() { + + } + + @Override + public void onConversationRead(Conversation conversation) { + Log.d(Config.LOGTAG, "read event for " + conversation.getName() + " received"); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/ConversationsOverviewFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationsOverviewFragment.java new file mode 100644 index 000000000..649bb09a2 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/ConversationsOverviewFragment.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui; + +import android.app.Activity; +import android.databinding.DataBindingUtil; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.List; + +import de.pixart.messenger.Config; +import de.pixart.messenger.R; +import de.pixart.messenger.databinding.FragmentConversationsOverviewBinding; +import de.pixart.messenger.entities.Conversation; +import de.pixart.messenger.ui.adapter.ConversationAdapter; +import de.pixart.messenger.ui.interfaces.OnConversationSelected; + +public class ConversationsOverviewFragment extends XmppFragment { + + private final List<Conversation> conversations = new ArrayList<>(); + private FragmentConversationsOverviewBinding binding; + private ConversationAdapter conversationsAdapter; + private XmppActivity activity; + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + Log.d(Config.LOGTAG, "on attach"); + if (activity instanceof XmppActivity) { + this.activity = (XmppActivity) activity; + } else { + throw new IllegalStateException("Trying to attach fragment to activity that is not an XmppActivity"); + } + } + + @Override + public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log.d(Config.LOGTAG, "onCreateView"); + this.binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversations_overview, container, false); + this.binding.fab.setOnClickListener((view) -> StartConversationActivity.launch(getActivity())); + + this.conversationsAdapter = new ConversationAdapter(this.activity, this.conversations); + this.binding.list.setAdapter(this.conversationsAdapter); + this.binding.list.setOnItemClickListener((parent, view, position, id) -> { + Conversation conversation = this.conversations.get(position); + if (activity instanceof OnConversationSelected) { + ((OnConversationSelected) activity).onConversationSelected(conversation); + } else { + Log.w(ConversationsOverviewFragment.class.getCanonicalName(), "Activity does not implement OnConversationSelected"); + } + }); + + return binding.getRoot(); + } + + @Override + void onBackendConnected() { + Log.d(Config.LOGTAG, "nice!"); + refresh(); + } + + @Override + public void onStart() { + super.onStart(); + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onStart()"); + if (activity.xmppConnectionService != null) { + refresh(); + } + } + + @Override + public void onResume() { + super.onResume(); + Log.d(Config.LOGTAG, "ConversationsOverviewFragment.onResume()"); + } + + @Override + void refresh() { + this.activity.xmppConnectionService.populateWithOrderedConversations(this.conversations); + this.conversationsAdapter.notifyDataSetChanged(); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java index 540673242..5c88dc0d2 100644 --- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java @@ -962,6 +962,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU invalidateOptionsMenu(); } + public static void launch(Context context) { + final Intent intent = new Intent(context, StartConversationActivity.class); + context.startActivity(intent); + } + @Override public void OnUpdateBlocklist(final Status status) { refreshUi(); diff --git a/src/main/java/de/pixart/messenger/ui/StartUI.java b/src/main/java/de/pixart/messenger/ui/StartUI.java index 08bd844ba..78a5754cd 100644 --- a/src/main/java/de/pixart/messenger/ui/StartUI.java +++ b/src/main/java/de/pixart/messenger/ui/StartUI.java @@ -44,7 +44,7 @@ public class StartUI extends AppCompatActivity if (EasyPermissions.hasPermissions(this, perms)) { // Already have permission, start ConversationsActivity Log.d(Config.LOGTAG, "All permissions granted, starting " + getString(R.string.app_name) + "(" + FirstStartTime + ")"); - Intent intent = new Intent(this, ConversationActivity.class); + Intent intent = new Intent(this, ConversationsMainActivity.class); intent.putExtra(PREF_FIRST_START, FirstStartTime); startActivity(intent); finish(); diff --git a/src/main/java/de/pixart/messenger/ui/XmppFragment.java b/src/main/java/de/pixart/messenger/ui/XmppFragment.java new file mode 100644 index 000000000..7c2eeb661 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/XmppFragment.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui; + +import android.app.Fragment; + +public abstract class XmppFragment extends Fragment { + + abstract void onBackendConnected(); + + abstract void refresh(); +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java index 45fbaf50e..ba240192f 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java @@ -2,6 +2,7 @@ package de.pixart.messenger.ui.adapter; import android.content.ActivityNotFoundException; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; @@ -12,6 +13,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; +import android.preference.PreferenceManager; import android.support.annotation.ColorInt; import android.support.v4.content.ContextCompat; import android.text.Spannable; @@ -68,8 +70,9 @@ import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.services.AudioPlayer; import de.pixart.messenger.services.MessageArchiveService; import de.pixart.messenger.services.NotificationService; -import de.pixart.messenger.ui.ConversationActivity; +import de.pixart.messenger.ui.ConversationFragment; import de.pixart.messenger.ui.ShowFullscreenMessageActivity; +import de.pixart.messenger.ui.XmppActivity; import de.pixart.messenger.ui.text.DividerSpan; import de.pixart.messenger.ui.text.FixedURLSpan; import de.pixart.messenger.ui.text.QuoteSpan; @@ -88,6 +91,8 @@ import de.pixart.messenger.xmpp.mam.MamReference; public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextView.CopyHandler { + ConversationFragment mConversationFragment; + private static final int SENT = 0; private static final int RECEIVED = 1; private static final int STATUS = 2; @@ -131,7 +136,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie } }; - private final ConversationActivity activity; + private final XmppActivity activity; private DisplayMetrics metrics; @@ -144,7 +149,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie public final AudioPlayer audioPlayer; private boolean mUseWhiteBackground = false; - public MessageAdapter(ConversationActivity activity, List<Message> messages) { + public MessageAdapter(XmppActivity activity, List<Message> messages) { super(activity, 0, messages); this.audioPlayer = new AudioPlayer(this); this.activity = activity; @@ -296,7 +301,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie @Override public void onClick(View v) { - activity.mConversationFragment.resendMessage(mMessage); + mConversationFragment.resendMessage(mMessage); } }); } else if (!error && type == SENT) { @@ -493,16 +498,18 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie final String nick = UIHelper.getMessageDisplayName(message); SpannableStringBuilder body = message.getMergedBody(); boolean hasMeCommand = message.hasMeCommand(); - String searchQuery = activity.mConversationFragment.binding.searchfieldInput.getText().toString().toLowerCase().trim(); - if (((!searchQuery.isEmpty() || !searchQuery.contains("")) && searchQuery.length() >= 3) && body.toString().toLowerCase().contains(searchQuery)) { - int ofe = body.toString().toLowerCase().indexOf(searchQuery, 0); - for (int ofs = 0; ofs < body.length() && ofe != -1; ofs = ofe + 1) { - ofe = body.toString().toLowerCase().indexOf(searchQuery, ofs); - if (ofe == -1) { - break; - } else { - body.setSpan(new StyleSpan(Typeface.BOLD), ofe, ofe + searchQuery.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - body.setSpan(new BackgroundColorSpan(0xFFFFFF00), ofe, ofe + searchQuery.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + if (mConversationFragment != null) { + String searchQuery = mConversationFragment.binding.searchfieldInput.getText().toString().toLowerCase().trim(); + if (((!searchQuery.isEmpty() || !searchQuery.contains("")) && searchQuery.length() >= 3) && body.toString().toLowerCase().contains(searchQuery)) { + int ofe = body.toString().toLowerCase().indexOf(searchQuery, 0); + for (int ofs = 0; ofs < body.length() && ofe != -1; ofs = ofe + 1) { + ofe = body.toString().toLowerCase().indexOf(searchQuery, ofs); + if (ofe == -1) { + break; + } else { + body.setSpan(new StyleSpan(Typeface.BOLD), ofe, ofe + searchQuery.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + body.setSpan(new BackgroundColorSpan(0xFFFFFF00), ofe, ofe + searchQuery.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } } } } @@ -594,7 +601,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie @Override public void onClick(View v) { - activity.mConversationFragment.startDownloadable(message); + mConversationFragment.startDownloadable(message); } }); } @@ -1171,8 +1178,9 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie } public void updatePreferences() { - this.mIndicateReceived = activity.indicateReceived(); - this.mUseWhiteBackground = activity.useWhiteBackground(); + SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity); + this.mIndicateReceived = p.getBoolean("indicate_received", activity.getResources().getBoolean(R.bool.indicate_received)); + this.mUseWhiteBackground = p.getBoolean("use_white_background", activity.getResources().getBoolean(R.bool.use_white_background)); } public TextView getMessageBody(View view) { diff --git a/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationArchived.java b/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationArchived.java new file mode 100644 index 000000000..beb01e690 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationArchived.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.interfaces; + +import de.pixart.messenger.entities.Conversation; + +public interface OnConversationArchived { + void onConversationArchived(Conversation conversation); +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationRead.java b/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationRead.java new file mode 100644 index 000000000..ab5cc607d --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationRead.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.interfaces; + +import de.pixart.messenger.entities.Conversation; + +public interface OnConversationRead { + void onConversationRead(Conversation conversation); +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationSelected.java b/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationSelected.java new file mode 100644 index 000000000..bd40a22fc --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationSelected.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.interfaces; + +import de.pixart.messenger.entities.Conversation; + +public interface OnConversationSelected { + void onConversationSelected(Conversation conversation); +} diff --git a/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationsListItemUpdated.java b/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationsListItemUpdated.java new file mode 100644 index 000000000..5e509dd54 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/interfaces/OnConversationsListItemUpdated.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.interfaces; + +public interface OnConversationsListItemUpdated { + void onConversationsListItemUpdated(); +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/interfaces/OnSwipeTouchListener.java b/src/main/java/de/pixart/messenger/ui/interfaces/OnSwipeTouchListener.java new file mode 100644 index 000000000..513e0a606 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/interfaces/OnSwipeTouchListener.java @@ -0,0 +1,57 @@ +package de.pixart.messenger.ui.interfaces; + +import android.content.Context; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import de.pixart.messenger.Config; + +public class OnSwipeTouchListener implements View.OnTouchListener { + + private final GestureDetector gestureDetector; + + public OnSwipeTouchListener(Context context) { + gestureDetector = new GestureDetector(context, new GestureListener()); + } + + public void onSwipeLeft() { + } + + public void onSwipeRight() { + } + + public boolean onTouch(View v, MotionEvent event) { + return gestureDetector.onTouchEvent(event); + } + + private final class GestureListener extends GestureDetector.SimpleOnGestureListener { + + private static final int SWIPE_DISTANCE_THRESHOLD = 100; + private static final int SWIPE_VELOCITY_THRESHOLD = 100; + + @Override + public boolean onDown(MotionEvent e) { + return true; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (e1 == null || e2 == null) { + Log.d(Config.LOGTAG, "Error MotionEvent"); + return false; + } + float distanceX = e2.getX() - e1.getX(); + float distanceY = e2.getY() - e1.getY(); + if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) > SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) { + if (distanceX > 0) + onSwipeRight(); + else + onSwipeLeft(); + return true; + } + return false; + } + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/PendingItem.java b/src/main/java/de/pixart/messenger/ui/util/PendingItem.java new file mode 100644 index 000000000..772f5e7e1 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/PendingItem.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.util; + +public class PendingItem<T> { + + private T item = null; + + public synchronized void push(T item) { + this.item = item; + } + + public synchronized T pop() { + final T item = this.item; + this.item = null; + return item; + } +}
\ No newline at end of file diff --git a/src/main/res/layout-w945dp/activity_conversations.xml b/src/main/res/layout-w945dp/activity_conversations.xml new file mode 100644 index 000000000..6084155e7 --- /dev/null +++ b/src/main/res/layout-w945dp/activity_conversations.xml @@ -0,0 +1,22 @@ +<layout xmlns:android="http://schemas.android.com/apk/res/android"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + + <FrameLayout + android:id="@+id/main_fragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" /> + + <FrameLayout + android:id="@+id/secondary_fragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="2" /> + + </LinearLayout> + +</layout>
\ No newline at end of file diff --git a/src/main/res/layout-w945dp/fragment_conversations_overview.xml b/src/main/res/layout-w945dp/fragment_conversations_overview.xml deleted file mode 100644 index 3176ad814..000000000 --- a/src/main/res/layout-w945dp/fragment_conversations_overview.xml +++ /dev/null @@ -1,30 +0,0 @@ -<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/content_view_ll" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_weight="1" - android:layout_height="match_parent" - android:background="@color/grey50" - android:orientation="vertical"> - - <ListView - android:id="@+id/list" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:background="@color/grey50" - android:divider="@color/black12" - android:dividerHeight="1dp" /> - </LinearLayout> - - <LinearLayout - android:id="@+id/selected_conversation" - android:layout_width="fill_parent" - android:layout_height="match_parent" - android:layout_weight="2" - android:orientation="vertical"> - </LinearLayout> - -</android.support.v4.widget.SlidingPaneLayout>
\ No newline at end of file diff --git a/src/main/res/layout/activity_conversations.xml b/src/main/res/layout/activity_conversations.xml new file mode 100644 index 000000000..567b17ac9 --- /dev/null +++ b/src/main/res/layout/activity_conversations.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (c) 2018, Daniel Gultsch All rights reserved. + ~ + ~ Redistribution and use in source and binary forms, with or without modification, + ~ are permitted provided that the following conditions are met: + ~ + ~ 1. Redistributions of source code must retain the above copyright notice, this + ~ list of conditions and the following disclaimer. + ~ + ~ 2. Redistributions in binary form must reproduce the above copyright notice, + ~ this list of conditions and the following disclaimer in the documentation and/or + ~ other materials provided with the distribution. + ~ + ~ 3. Neither the name of the copyright holder nor the names of its contributors + ~ may be used to endorse or promote products derived from this software without + ~ specific prior written permission. + ~ + ~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ~ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + ~ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + ~ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ~ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + ~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + ~ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ~ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ~ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ~ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --> +<layout xmlns:android="http://schemas.android.com/apk/res/android"> + + <FrameLayout + android:id="@+id/main_fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" /> +</layout>
\ No newline at end of file diff --git a/src/main/res/layout/fragment_conversations_overview.xml b/src/main/res/layout/fragment_conversations_overview.xml index 493f962ec..336142428 100644 --- a/src/main/res/layout/fragment_conversations_overview.xml +++ b/src/main/res/layout/fragment_conversations_overview.xml @@ -1,14 +1,8 @@ -<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/content_view_spl" - android:layout_width="match_parent" - android:layout_height="match_parent"> +<layout xmlns:android="http://schemas.android.com/apk/res/android"> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_weight="1" - android:layout_height="match_parent" - android:background="@color/grey50" - android:orientation="vertical"> + <FrameLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> <ListView android:id="@+id/list" @@ -17,14 +11,13 @@ android:background="@color/grey50" android:divider="@color/black12" android:dividerHeight="1dp" /> - </LinearLayout> - <LinearLayout - android:id="@+id/selected_conversation" - android:layout_width="fill_parent" - android:layout_height="match_parent" - android:layout_weight="1" - android:orientation="vertical"> - </LinearLayout> - -</android.support.v4.widget.SlidingPaneLayout>
\ No newline at end of file + <android.support.design.widget.FloatingActionButton + android:id="@+id/fab" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end|bottom" + android:layout_margin="16dp" + android:src="?attr/icon_new" /> + </FrameLayout> +</layout>
\ No newline at end of file diff --git a/src/main/res/menu/activity_conversations.xml b/src/main/res/menu/activity_conversations.xml index 80a4f2bd3..6cd9000fd 100644 --- a/src/main/res/menu/activity_conversations.xml +++ b/src/main/res/menu/activity_conversations.xml @@ -2,21 +2,14 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> <item - android:id="@+id/action_add" - android:icon="?attr/icon_new" - android:orderInCategory="10" - app:showAsAction="always" - android:title="@string/action_add"/> - - <item android:id="@+id/action_accounts" android:orderInCategory="90" - app:showAsAction="never" - android:title="@string/action_accounts"/> + android:title="@string/action_accounts" + app:showAsAction="never" /> <item android:id="@+id/action_settings" android:orderInCategory="100" - app:showAsAction="never" - android:title="@string/action_settings"/> + android:title="@string/action_settings" + app:showAsAction="never" /> </menu>
\ No newline at end of file |