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			if (processViewIntent(intent)) {
103				return;
104			}
105		}
106		if (binding.secondaryFragment != null && ConversationFragment.getConversation(this) == null) {
107			Conversation conversation = ConversationsOverviewFragment.getSuggestion(this);
108			if (conversation != null) {
109				openConversation(conversation, null);
110			}
111		}
112	}
113
114	private void notifyFragmentOfBackendConnected(@IdRes int id) {
115		final Fragment fragment = getFragmentManager().findFragmentById(id);
116		if (fragment != null && fragment instanceof XmppFragment) {
117			((XmppFragment) fragment).onBackendConnected();
118		}
119	}
120
121	private void refreshFragment(@IdRes int id) {
122		final Fragment fragment = getFragmentManager().findFragmentById(id);
123		if (fragment != null && fragment instanceof XmppFragment) {
124			((XmppFragment) fragment).refresh();
125		}
126	}
127
128	private boolean processViewIntent(Intent intent) {
129		String uuid = intent.getStringExtra(EXTRA_CONVERSATION);
130		Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null;
131		if (conversation == null) {
132			Log.d(Config.LOGTAG, "unable to view conversation with uuid:" + uuid);
133			return false;
134		}
135		openConversation(conversation, intent.getExtras());
136		return true;
137	}
138
139	@Override
140	protected void onCreate(final Bundle savedInstanceState) {
141		super.onCreate(savedInstanceState);
142		new EmojiService(this).init();
143		this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations);
144		this.getFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle);
145		this.initializeFragments();
146		this.invalidateActionBarTitle();
147		final Intent intent = getIntent();
148		if (isViewIntent(intent)) {
149			pendingViewIntent.push(intent);
150			setIntent(createLauncherIntent(this));
151		}
152	}
153
154	@Override
155	public boolean onCreateOptionsMenu(Menu menu) {
156		getMenuInflater().inflate(R.menu.activity_conversations, menu);
157		return super.onCreateOptionsMenu(menu);
158	}
159
160	@Override
161	public void onConversationSelected(Conversation conversation) {
162		Log.d(Config.LOGTAG, "selected " + conversation.getName());
163		openConversation(conversation, null);
164	}
165
166	private void openConversation(Conversation conversation, Bundle extras) {
167		ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment);
168		final boolean mainNeedsRefresh;
169		if (conversationFragment == null) {
170			mainNeedsRefresh = false;
171			conversationFragment = new ConversationFragment();
172			FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
173			fragmentTransaction.replace(R.id.main_fragment, conversationFragment);
174			fragmentTransaction.addToBackStack(null);
175			fragmentTransaction.commit();
176		} else {
177			mainNeedsRefresh = true;
178		}
179		conversationFragment.reInit(conversation);
180		if (mainNeedsRefresh) {
181			refreshFragment(R.id.main_fragment);
182		}
183	}
184
185	@Override
186	public boolean onOptionsItemSelected(MenuItem item) {
187		switch (item.getItemId()) {
188			case android.R.id.home:
189				FragmentManager fm = getFragmentManager();
190				if (fm.getBackStackEntryCount() > 0) {
191					fm.popBackStack();
192					return true;
193				}
194				break;
195		}
196		return super.onOptionsItemSelected(item);
197	}
198
199	@Override
200	protected void onNewIntent(final Intent intent) {
201		if (isViewIntent(intent)) {
202			if (xmppConnectionService != null) {
203				processViewIntent(intent);
204			} else {
205				pendingViewIntent.push(intent);
206			}
207		}
208	}
209
210	private void initializeFragments() {
211		FragmentTransaction transaction = getFragmentManager().beginTransaction();
212		Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
213		Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment);
214		if (mainFragment != null) {
215			Log.d(Config.LOGTAG, "initializeFragment(). main fragment exists");
216			if (binding.secondaryFragment != null) {
217				if (mainFragment instanceof ConversationFragment) {
218					Log.d(Config.LOGTAG, "gained secondary fragment. moving...");
219					getFragmentManager().popBackStack();
220					transaction.remove(mainFragment);
221					transaction.commit();
222					getFragmentManager().executePendingTransactions();
223					transaction = getFragmentManager().beginTransaction();
224					transaction.replace(R.id.secondary_fragment, mainFragment);
225					transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
226					transaction.commit();
227					return;
228				}
229			} else {
230				if (secondaryFragment != null && secondaryFragment instanceof ConversationFragment) {
231					Log.d(Config.LOGTAG, "lost secondary fragment. moving...");
232					transaction.remove(secondaryFragment);
233					transaction.commit();
234					getFragmentManager().executePendingTransactions();
235					transaction = getFragmentManager().beginTransaction();
236					transaction.replace(R.id.main_fragment, secondaryFragment);
237					transaction.addToBackStack(null);
238					transaction.commit();
239					return;
240				}
241			}
242		} else {
243			transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
244		}
245		if (binding.secondaryFragment != null && secondaryFragment == null) {
246			transaction.replace(R.id.secondary_fragment, new ConversationFragment());
247		}
248		transaction.commit();
249	}
250
251	private void invalidateActionBarTitle() {
252		final ActionBar actionBar = getSupportActionBar();
253		if (actionBar != null) {
254			Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
255			if (mainFragment != null && mainFragment instanceof ConversationFragment) {
256				final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
257				if (conversation != null) {
258					actionBar.setTitle(conversation.getName());
259					actionBar.setDisplayHomeAsUpEnabled(true);
260					return;
261				}
262			}
263			actionBar.setTitle(R.string.app_name);
264			actionBar.setDisplayHomeAsUpEnabled(false);
265		}
266	}
267
268	@Override
269	public void onConversationArchived(Conversation conversation) {
270
271	}
272
273	@Override
274	public void onConversationsListItemUpdated() {
275		Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
276		if (fragment != null && fragment instanceof ConversationsOverviewFragment) {
277			((ConversationsOverviewFragment) fragment).refresh();
278		}
279	}
280
281	@Override
282	public void onConversationRead(Conversation conversation) {
283		Log.d(Config.LOGTAG, "read event for " + conversation.getName() + " received");
284	}
285
286	@Override
287	public void onAccountUpdate() {
288		this.refreshUi();
289	}
290
291	@Override
292	public void onConversationUpdate() {
293		this.refreshUi();
294	}
295
296	@Override
297	public void onRosterUpdate() {
298		this.refreshUi();
299	}
300
301	@Override
302	public void OnUpdateBlocklist(OnUpdateBlocklist.Status status) {
303		this.refreshUi();
304	}
305
306	@Override
307	public void onShowErrorToast(int resId) {
308		runOnUiThread(() -> Toast.makeText(this, resId, Toast.LENGTH_SHORT).show());
309	}
310}