ConversationActivity.java

  1/*
  2 * Copyright (c) 2018, Daniel Gultsch All rights reserved.
  3 *
  4 * Redistribution and use in source and binary forms, with or without modification,
  5 * are permitted provided that the following conditions are met:
  6 *
  7 * 1. Redistributions of source code must retain the above copyright notice, this
  8 * list of conditions and the following disclaimer.
  9 *
 10 * 2. Redistributions in binary form must reproduce the above copyright notice,
 11 * this list of conditions and the following disclaimer in the documentation and/or
 12 * other materials provided with the distribution.
 13 *
 14 * 3. Neither the name of the copyright holder nor the names of its contributors
 15 * may be used to endorse or promote products derived from this software without
 16 * specific prior written permission.
 17 *
 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 22 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28 */
 29
 30package eu.siacs.conversations.ui;
 31
 32
 33import android.app.Fragment;
 34import android.app.FragmentManager;
 35import android.app.FragmentTransaction;
 36import android.content.Context;
 37import android.content.Intent;
 38import android.databinding.DataBindingUtil;
 39import android.os.Bundle;
 40import android.support.annotation.IdRes;
 41import android.support.v7.app.ActionBar;
 42import android.util.Log;
 43import android.view.Menu;
 44import android.view.MenuItem;
 45import android.widget.Toast;
 46
 47import eu.siacs.conversations.Config;
 48import eu.siacs.conversations.R;
 49import eu.siacs.conversations.databinding.ActivityConversationsBinding;
 50import eu.siacs.conversations.entities.Conversation;
 51import eu.siacs.conversations.services.XmppConnectionService;
 52import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
 53import eu.siacs.conversations.ui.interfaces.OnConversationRead;
 54import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
 55import eu.siacs.conversations.ui.interfaces.OnConversationsListItemUpdated;
 56import eu.siacs.conversations.ui.service.EmojiService;
 57import eu.siacs.conversations.ui.util.PendingItem;
 58import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 59
 60public class ConversationActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast {
 61
 62	public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW";
 63	public static final String EXTRA_CONVERSATION = "conversationUuid";
 64	public static final String EXTRA_DOWNLOAD_UUID = "eu.siacs.conversations.download_uuid";
 65	public static final String EXTRA_TEXT = "text";
 66	public static final String EXTRA_NICK = "nick";
 67	public static final String EXTRA_IS_PRIVATE_MESSAGE = "pm";
 68
 69
 70	//secondary fragment (when holding the conversation, must be initialized before refreshing the overview fragment
 71	private static final @IdRes
 72	int[] FRAGMENT_ID_NOTIFICATION_ORDER = {R.id.secondary_fragment, R.id.main_fragment};
 73	private final PendingItem<Intent> pendingViewIntent = new PendingItem<>();
 74	private ActivityConversationsBinding binding;
 75
 76	private static boolean isViewIntent(Intent i) {
 77		return i != null && ACTION_VIEW_CONVERSATION.equals(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION);
 78	}
 79
 80	private static Intent createLauncherIntent(Context context) {
 81		final Intent intent = new Intent(context, ConversationActivity.class);
 82		intent.setAction(Intent.ACTION_MAIN);
 83		intent.addCategory(Intent.CATEGORY_LAUNCHER);
 84		return intent;
 85	}
 86
 87	@Override
 88	protected void refreshUiReal() {
 89		for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
 90			refreshFragment(id);
 91		}
 92	}
 93
 94	@Override
 95	void onBackendConnected() {
 96		for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
 97			notifyFragmentOfBackendConnected(id);
 98		}
 99		invalidateActionBarTitle();
100		Intent intent = pendingViewIntent.pop();
101		if (intent != null) {
102			processViewIntent(intent);
103		}
104	}
105
106	private void notifyFragmentOfBackendConnected(@IdRes int id) {
107		final Fragment fragment = getFragmentManager().findFragmentById(id);
108		if (fragment != null && fragment instanceof XmppFragment) {
109			((XmppFragment) fragment).onBackendConnected();
110		}
111	}
112
113	private void refreshFragment(@IdRes int id) {
114		final Fragment fragment = getFragmentManager().findFragmentById(id);
115		if (fragment != null && fragment instanceof XmppFragment) {
116			((XmppFragment) fragment).refresh();
117		}
118	}
119
120	private void processViewIntent(Intent intent) {
121		String uuid = intent.getStringExtra(EXTRA_CONVERSATION);
122		Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null;
123		if (conversation == null) {
124			Log.d(Config.LOGTAG, "unable to view conversation with uuid:" + uuid);
125			return;
126		}
127		openConversation(conversation, intent.getExtras());
128	}
129
130	@Override
131	protected void onCreate(final Bundle savedInstanceState) {
132		super.onCreate(savedInstanceState);
133		new EmojiService(this).init();
134		this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations);
135		this.getFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle);
136		this.initializeFragments();
137		this.invalidateActionBarTitle();
138		final Intent intent = getIntent();
139		if (isViewIntent(intent)) {
140			pendingViewIntent.push(intent);
141			setIntent(createLauncherIntent(this));
142		}
143	}
144
145	@Override
146	public boolean onCreateOptionsMenu(Menu menu) {
147		getMenuInflater().inflate(R.menu.activity_conversations, menu);
148		return super.onCreateOptionsMenu(menu);
149	}
150
151	@Override
152	public void onConversationSelected(Conversation conversation) {
153		Log.d(Config.LOGTAG, "selected " + conversation.getName());
154		openConversation(conversation, null);
155	}
156
157	private void openConversation(Conversation conversation, Bundle extras) {
158		ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment);
159		final boolean mainNeedsRefresh;
160		if (conversationFragment == null) {
161			mainNeedsRefresh = false;
162			conversationFragment = new ConversationFragment();
163			FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
164			fragmentTransaction.replace(R.id.main_fragment, conversationFragment);
165			fragmentTransaction.addToBackStack(null);
166			fragmentTransaction.commit();
167		} else {
168			mainNeedsRefresh = true;
169		}
170		conversationFragment.reInit(conversation);
171		if (mainNeedsRefresh) {
172			refreshFragment(R.id.main_fragment);
173		}
174	}
175
176	@Override
177	public boolean onOptionsItemSelected(MenuItem item) {
178		switch (item.getItemId()) {
179			case android.R.id.home:
180				FragmentManager fm = getFragmentManager();
181				if (fm.getBackStackEntryCount() > 0) {
182					fm.popBackStack();
183					return true;
184				}
185				break;
186		}
187		return super.onOptionsItemSelected(item);
188	}
189
190	@Override
191	protected void onNewIntent(final Intent intent) {
192		if (isViewIntent(intent)) {
193			if (xmppConnectionService != null) {
194				processViewIntent(intent);
195			} else {
196				pendingViewIntent.push(intent);
197			}
198		}
199	}
200
201	private void initializeFragments() {
202		FragmentTransaction transaction = getFragmentManager().beginTransaction();
203		Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
204		Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment);
205		if (mainFragment != null) {
206			Log.d(Config.LOGTAG, "initializeFragment(). main fragment exists");
207			if (binding.secondaryFragment != null) {
208				if (mainFragment instanceof ConversationFragment) {
209					Log.d(Config.LOGTAG, "gained secondary fragment. moving...");
210					getFragmentManager().popBackStack();
211					transaction.remove(mainFragment);
212					transaction.commit();
213					getFragmentManager().executePendingTransactions();
214					transaction = getFragmentManager().beginTransaction();
215					transaction.replace(R.id.secondary_fragment, mainFragment);
216					transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
217					transaction.commit();
218					return;
219				}
220			} else {
221				if (secondaryFragment != null && secondaryFragment instanceof ConversationFragment) {
222					Log.d(Config.LOGTAG, "lost secondary fragment. moving...");
223					transaction.remove(secondaryFragment);
224					transaction.commit();
225					getFragmentManager().executePendingTransactions();
226					transaction = getFragmentManager().beginTransaction();
227					transaction.replace(R.id.main_fragment, secondaryFragment);
228					transaction.addToBackStack(null);
229					transaction.commit();
230					return;
231				}
232			}
233		} else {
234			transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
235		}
236		if (binding.secondaryFragment != null && secondaryFragment == null) {
237			transaction.replace(R.id.secondary_fragment, new ConversationFragment());
238		}
239		transaction.commit();
240	}
241
242	private void invalidateActionBarTitle() {
243		final ActionBar actionBar = getSupportActionBar();
244		if (actionBar != null) {
245			Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
246			if (mainFragment != null && mainFragment instanceof ConversationFragment) {
247				final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
248				if (conversation != null) {
249					actionBar.setTitle(conversation.getName());
250					actionBar.setDisplayHomeAsUpEnabled(true);
251					return;
252				}
253			}
254			actionBar.setTitle(R.string.app_name);
255			actionBar.setDisplayHomeAsUpEnabled(false);
256		}
257	}
258
259	@Override
260	public void onConversationArchived(Conversation conversation) {
261
262	}
263
264	@Override
265	public void onConversationsListItemUpdated() {
266		Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
267		if (fragment != null && fragment instanceof ConversationsOverviewFragment) {
268			((ConversationsOverviewFragment) fragment).refresh();
269		}
270	}
271
272	@Override
273	public void onConversationRead(Conversation conversation) {
274		Log.d(Config.LOGTAG, "read event for " + conversation.getName() + " received");
275	}
276
277	@Override
278	public void onAccountUpdate() {
279		this.refreshUi();
280	}
281
282	@Override
283	public void onConversationUpdate() {
284		this.refreshUi();
285	}
286
287	@Override
288	public void onRosterUpdate() {
289		this.refreshUi();
290	}
291
292	@Override
293	public void OnUpdateBlocklist(OnUpdateBlocklist.Status status) {
294		this.refreshUi();
295	}
296
297	@Override
298	public void onShowErrorToast(int resId) {
299		runOnUiThread(() -> Toast.makeText(this, resId, Toast.LENGTH_SHORT).show());
300	}
301}