From 6bd0abcd8ed3aee7a727c8d50139eb123124643c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 21 Feb 2018 23:07:53 +0100 Subject: [PATCH] fragment voodoo --- .../ui/ConversationFragment.java | 141 ++++++++++++------ .../ui/ConversationsMainActivity.java | 87 ++++++++++- .../ui/ConversationsOverviewFragment.java | 36 ++++- .../siacs/conversations/ui/XmppFragment.java | 2 + .../conversations/ui/util/PendingItem.java | 45 ++++++ src/main/res/menu/activity_conversations.xml | 18 +-- 6 files changed, 255 insertions(+), 74 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/util/PendingItem.java diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 48a3ee8b9..fd3eb0d15 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -7,6 +7,7 @@ import android.content.pm.PackageManager; import android.databinding.DataBindingUtil; import android.net.Uri; import android.os.Build; +import android.os.Parcelable; import android.preference.Preference; import android.preference.PreferenceManager; import android.provider.MediaStore; @@ -62,6 +63,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; @@ -92,6 +94,7 @@ import eu.siacs.conversations.ui.adapter.MessageAdapter; import eu.siacs.conversations.ui.util.ActivityResult; import eu.siacs.conversations.ui.util.AttachmentTool; import eu.siacs.conversations.ui.util.ConversationMenuConfigurator; +import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.ui.util.PresenceSelector; import eu.siacs.conversations.ui.util.SendButtonAction; import eu.siacs.conversations.ui.util.SendButtonTool; @@ -129,19 +132,18 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke public static final int ATTACHMENT_CHOICE_RECORD_VIDEO = 0x0307; 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 messageList = new ArrayList<>(); - protected Conversation conversation; - - private FragmentConversationBinding binding; - - protected MessageAdapter messageListAdapter; - private Toast messageLoaderToast; - - private ActivityResult postponedActivityResult = null; + private final PendingItem postponedActivityResult = new PendingItem<>(); + private final PendingItem pendingConversationsUuid = new PendingItem<>(); public Uri mPendingEditorContent = null; - + protected MessageAdapter messageListAdapter; + private Conversation conversation; + private FragmentConversationBinding binding; + private Toast messageLoaderToast; private ConversationsMainActivity activity; private OnClickListener clickToMuc = new OnClickListener() { @@ -268,7 +270,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke inputContentInfo.requestPermission(); } catch (Exception e) { Log.e(Config.LOGTAG, "InputContentInfoCompat#requestPermission() failed.", e); - Toast.makeText(getActivity(),activity.getString(R.string.no_permission_to_access_x, inputContentInfo.getDescription()), Toast.LENGTH_LONG + Toast.makeText(getActivity(), activity.getString(R.string.no_permission_to_access_x, inputContentInfo.getDescription()), Toast.LENGTH_LONG ).show(); return false; } @@ -658,10 +660,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke selectPresenceToAttachFile(choice); break; case REQUEST_CHOOSE_PGP_ID: - long id = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID,0); + long id = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, 0); if (id != 0) { conversation.getAccount().setPgpSignId(id); - activity.announcePgp(conversation.getAccount(),null,null,activity.onOpenPGPKeyPublished); + activity.announcePgp(conversation.getAccount(), null, null, activity.onOpenPGPKeyPublished); } else { activity.choosePgpSignId(conversation.getAccount()); } @@ -713,11 +715,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke @Override public void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); - ActivityResult activityResult = ActivityResult.of(requestCode,resultCode,data); + ActivityResult activityResult = ActivityResult.of(requestCode, resultCode, data); if (activity != null && activity.xmppConnectionService != null) { handleActivityResult(activityResult); } else { - this.postponedActivityResult = activityResult; + this.postponedActivityResult.push(activityResult); } } @@ -726,14 +728,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } @Override - public void onAttach(Context context) { - Log.d(Config.LOGTAG,"onAttach()"); - if (context instanceof ConversationsMainActivity) { - this.activity = (ConversationsMainActivity) 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 @@ -776,7 +778,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke @Override public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - this.binding = DataBindingUtil.inflate(inflater,R.layout.fragment_conversation,container,false); + this.binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversation, container, false); binding.getRoot().setOnClickListener(null); //TODO why the fuck did we do this? binding.textinput.addTextChangedListener(new StylingHelper.MessageEditorStyler(binding.textinput)); @@ -1180,10 +1182,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke .findFragmentByTag("conversation"); if (fragment != null) { fragment.showNoPGPKeyDialog(false, (dialog, which) -> { - conversation.setNextEncryption(Message.ENCRYPTION_NONE); - activity.xmppConnectionService.updateConversation(conversation); - selectPresenceToAttachFile(attachmentChoice); - }); + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + activity.xmppConnectionService.updateConversation(conversation); + selectPresenceToAttachFile(attachmentChoice); + }); } } } else { @@ -1530,6 +1532,27 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } + + @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(); @@ -1561,13 +1584,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } public boolean reInit(Conversation conversation) { - Log.d(Config.LOGTAG,"reInit()"); + 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"); + Log.d(Config.LOGTAG, "activity was null"); this.conversation = conversation; return false; } @@ -1768,7 +1792,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke 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); + final SendButtonAction action = SendButtonTool.getAction(getActivity(), c, text); if (useSendButtonToIndicateStatus && c.getAccount().getStatus() == Account.State.ONLINE) { if (activity.xmppConnectionService != null && activity.xmppConnectionService.getMessageArchiveService().isCatchingUp(c)) { status = Presence.Status.OFFLINE; @@ -1959,8 +1983,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke new UiCallback() { @Override - public void userInputRequried(PendingIntent pi,Contact contact) { - startPendingIntent(pi,REQUEST_ENCRYPT_MESSAGE); + public void userInputRequried(PendingIntent pi, Contact contact) { + startPendingIntent(pi, REQUEST_ENCRYPT_MESSAGE); } @Override @@ -1980,12 +2004,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } else { showNoPGPKeyDialog(false, (dialog, which) -> { - conversation.setNextEncryption(Message.ENCRYPTION_NONE); - xmppService.updateConversation(conversation); - message.setEncryption(Message.ENCRYPTION_NONE); - xmppService.sendMessage(message); - messageSent(); - }); + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + xmppService.updateConversation(conversation); + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.sendMessage(message); + messageSent(); + }); } } else { if (conversation.getMucOptions().pgpKeysInUse()) { @@ -2000,12 +2024,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke encryptTextMessage(message); } else { showNoPGPKeyDialog(true, (dialog, which) -> { - conversation.setNextEncryption(Message.ENCRYPTION_NONE); - message.setEncryption(Message.ENCRYPTION_NONE); - xmppService.updateConversation(conversation); - xmppService.sendMessage(message); - messageSent(); - }); + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + message.setEncryption(Message.ENCRYPTION_NONE); + xmppService.updateConversation(conversation); + xmppService.sendMessage(message); + messageSent(); + }); } } } @@ -2030,7 +2054,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke public void error(final int error, Message message) { getActivity().runOnUiThread(() -> { doneSendingPgpMessage(); - Toast.makeText(getActivity(),R.string.unable_to_connect_to_keychain,Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.unable_to_connect_to_keychain, Toast.LENGTH_SHORT).show(); }); } @@ -2152,23 +2176,42 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke private void startPendingIntent(PendingIntent pendingIntent, int requestCode) { try { - getActivity().startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode,null, 0, 0, 0); + 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) { - Log.d(Config.LOGTAG,"cleared pending intent with unhandled result left"); + 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/eu/siacs/conversations/ui/ConversationsMainActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsMainActivity.java index c6caf8385..57058ead2 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsMainActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsMainActivity.java @@ -31,11 +31,15 @@ package eu.siacs.conversations.ui; import android.app.Fragment; +import android.app.FragmentManager; import android.app.FragmentTransaction; 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 eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -60,6 +64,7 @@ public class ConversationsMainActivity extends XmppActivity implements OnConvers void onBackendConnected() { notifyFragment(R.id.main_fragment); notifyFragment(R.id.secondary_fragment); + invalidateActionBarTitle(); } private void notifyFragment(@IdRes int id) { @@ -73,32 +78,104 @@ public class ConversationsMainActivity extends XmppActivity implements OnConvers protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); new EmojiService(this).init(); - this.binding = DataBindingUtil.setContentView(this,R.layout.activity_conversations); + 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()); + 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.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(); - transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment()); + 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); + } + } + @Override public void onConversationArchived(Conversation conversation) { @@ -111,6 +188,6 @@ public class ConversationsMainActivity extends XmppActivity implements OnConvers @Override public void onConversationRead(Conversation conversation) { - Log.d(Config.LOGTAG,"read event for "+conversation.getName()+" received"); + Log.d(Config.LOGTAG, "read event for " + conversation.getName() + " received"); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java index cee9710bd..a2605e2d9 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java @@ -29,6 +29,7 @@ package eu.siacs.conversations.ui; +import android.app.Activity; import android.content.Context; import android.databinding.DataBindingUtil; import android.os.Bundle; @@ -36,7 +37,6 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import java.util.ArrayList; import java.util.List; @@ -55,20 +55,22 @@ public class ConversationsOverviewFragment extends XmppFragment { private final List conversations = new ArrayList<>(); private ConversationAdapter conversationsAdapter; - private ConversationsMainActivity activity; + private XmppActivity activity; @Override - public void onAttach(Context context) { - if (context instanceof ConversationsMainActivity) { - this.activity = (ConversationsMainActivity) context; + 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 the ConversationActivity"); + throw new IllegalStateException("Trying to attach fragment to activity that is not an XmppActivity"); } - super.onAttach(context); } @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())); @@ -90,6 +92,26 @@ public class ConversationsOverviewFragment extends XmppFragment { @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(); } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppFragment.java b/src/main/java/eu/siacs/conversations/ui/XmppFragment.java index dca18050c..7bb345e39 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppFragment.java @@ -34,4 +34,6 @@ import android.app.Fragment; public abstract class XmppFragment extends Fragment { abstract void onBackendConnected(); + + abstract void refresh(); } diff --git a/src/main/java/eu/siacs/conversations/ui/util/PendingItem.java b/src/main/java/eu/siacs/conversations/ui/util/PendingItem.java new file mode 100644 index 000000000..45933e483 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/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 eu.siacs.conversations.ui.util; + +public class PendingItem { + + 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; + } +} diff --git a/src/main/res/menu/activity_conversations.xml b/src/main/res/menu/activity_conversations.xml index 80a4f2bd3..885fa45a7 100644 --- a/src/main/res/menu/activity_conversations.xml +++ b/src/main/res/menu/activity_conversations.xml @@ -1,22 +1,14 @@ - - - + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:title="@string/action_accounts" + app:showAsAction="never"/> + android:title="@string/action_settings" + app:showAsAction="never"/> \ No newline at end of file