/*
* Copyright (C) 2014 Eric Butler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tapchatapp.android.app.fragment;
import android.app.AlertDialog;
import android.app.ListFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.ClipboardManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.collect.Lists;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
import com.tapchatapp.android.R;
import com.tapchatapp.android.app.TapchatApp;
import com.tapchatapp.android.app.event.BufferChangedEvent;
import com.tapchatapp.android.app.event.BufferLineAddedEvent;
import com.tapchatapp.android.app.event.BufferRemovedEvent;
import com.tapchatapp.android.app.event.ConnectionChangedEvent;
import com.tapchatapp.android.app.event.ServiceStateChangedEvent;
import com.tapchatapp.android.client.model.Buffer;
import com.tapchatapp.android.client.model.BufferEvent;
import com.tapchatapp.android.client.model.BufferEventItem;
import com.tapchatapp.android.client.model.ChannelBuffer;
import com.tapchatapp.android.client.model.Connection;
import com.tapchatapp.android.client.model.ConsoleBuffer;
import com.tapchatapp.android.client.TapchatService;
import com.tapchatapp.android.client.message.BufferEventMessage;
import com.tapchatapp.android.app.ui.BufferEventRenderer;
import com.tapchatapp.android.app.ui.ConnectionStatusBar;
import com.tapchatapp.android.app.ui.FilterableListAdapter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.widget.LinearLayout.LayoutParams;
public class BufferFragment extends ListFragment {
public static final String ARG_CONNECTION_ID = "com.tapchatapp.android.arg_connection_id";
public static final String ARG_BUFFER_ID = "com.tapchatapp.android.arg_buffer_id";
private static final Pattern URL_PATTERN = Pattern.compile("\\(?\\bhttps?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]");
protected long mBufferId;
protected Connection mConnection;
protected Buffer mBuffer;
private int mConnectionState;
private long mConnectionId;
private BufferEventRenderer mRenderer;
private BufferEventRenderer mMenuRenderer;
private ConnectionStatusBar mStatusBar;
@Inject Bus mBus;
public static BufferFragment create(int type, long connectionId, long bufferId) {
BufferFragment fragment;
switch (type) {
case Buffer.CHANNEL_TYPE:
fragment = new ChannelBufferFragment();
break;
case Buffer.CONVERSATION_TYPE:
fragment = new QueryBufferFragment();
break;
default:
fragment = new BufferFragment();
break;
}
Bundle args = new Bundle();
args.putLong(BufferFragment.ARG_CONNECTION_ID, connectionId);
args.putLong(BufferFragment.ARG_BUFFER_ID, bufferId);
fragment.setArguments(args);
return fragment;
}
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TapchatApp.get().inject(this);
setHasOptionsMenu(true);
mConnectionId = getArguments().getLong(ARG_CONNECTION_ID);
mBufferId = getArguments().getLong(ARG_BUFFER_ID);
mStatusBar = new ConnectionStatusBar(this, mConnectionId);
}
@Override public void onResume() {
super.onResume();
mBus.register(this);
mStatusBar.registerBus(mBus);
}
@Override public void onPause() {
super.onPause();
mBus.unregister(this);
mStatusBar.unregisterBus(mBus);
if (mBuffer != null) {
mBuffer = null;
}
mConnection = null;
}
@Override public void onDestroy() {
super.onDestroy();
mStatusBar.destroy();
}
@Override public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
}
@Override public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mRenderer = new BufferEventRenderer(getActivity());
mMenuRenderer = new BufferEventRenderer(getActivity(), true);
}
@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.buffer, menu);
}
@Override public void onPrepareOptionsMenu(Menu menu) {
boolean hasBuffer = mBuffer != null && (!(mBuffer instanceof ConsoleBuffer));
boolean isArchived = (hasBuffer && mBuffer.isArchived());
menu.findItem(R.id.archive).setVisible(hasBuffer && !isArchived);
menu.findItem(R.id.unarchive).setVisible(hasBuffer && isArchived);
menu.findItem(R.id.delete).setVisible(hasBuffer);
//menu.findItem(R.id.star).setVisible(hasBuffer);
}
@Override public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.archive) {
mBuffer.archive();
TapchatApp.goHome(getActivity(), mConnection.getId());
return true;
} else if (item.getItemId() == R.id.unarchive) {
mBuffer.unarchive();
return true;
} else if (item.getItemId() == R.id.delete) {
new AlertDialog.Builder(getActivity())
.setMessage(R.string.confirm_delete_buffer)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mBuffer.delete();
TapchatApp.goHome(getActivity(), mConnection.getId());
}
})
.setNegativeButton(android.R.string.no, null)
.show();
return true;
}
return false;
}
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
LinearLayout layout = new LinearLayout(getActivity());
layout.setOrientation(LinearLayout.VERTICAL);
layout.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
View connectionHeaderView = inflater.inflate(R.layout.connection_header, null);
layout.addView(connectionHeaderView, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
View bufferHeaderView = inflater.inflate(R.layout.fragment_buffer_header, null);
layout.addView(bufferHeaderView, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
ListView listView = new ListView(getActivity());
listView.setId(android.R.id.list);
listView.setDivider(null);
listView.setStackFromBottom(true);
listView.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_NORMAL);
listView.setSmoothScrollbarEnabled(false);
// listView.setCacheColorHint(Color.TRANSPARENT);
LayoutParams params = new LayoutParams(MATCH_PARENT, 0);
params.weight = 1;
layout.addView(listView, params);
View footerView = inflater.inflate(R.layout.fragment_buffer_footer, null);
layout.addView(footerView, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
((TextView) footerView.findViewById(R.id.text_entry)).setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (event != null && event.getAction() != KeyEvent.ACTION_DOWN) {
return false;
}
sendMessage();
return true;
}
});
footerView.findViewById(R.id.send_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sendMessage();
}
});
return layout;
}
@Override public void onListItemClick(ListView l, View v, int position, long id) {
final BufferEvent event = ((BufferEventListAdapter) getListAdapter()).getItem(position);
List<ContextMenuItem> menuItems = Lists.newArrayList();
if (event.getItems().size() == 1) {
populateMenuItems(menuItems, event.getFirstItem());
} else {
populateMenuItems(menuItems, event.getItems());
}
showContextMenu(menuItems);
}
@Subscribe public void onServiceStateChanged(ServiceStateChangedEvent event) {
TapchatService service = event.getService();
if (service.getConnectionState() == TapchatService.STATE_LOADED) {
if (mConnection == null && mBuffer == null) {
mConnection = service.getConnection(mConnectionId);
if (mConnection == null) {
throw new IllegalStateException("Connection not found. " + mConnectionId + " connections: " + service.getConnections());
}
mBuffer = mConnection.getBuffer(mBufferId);
if (mBuffer == null) {
throw new IllegalStateException("Buffer not found. " + mBufferId + " buffers: " + mConnection.getBuffers());
}
}
} else {
mConnection = null;
mBuffer = null;
}
mConnectionState = service.getConnectionState();
updateUI();
}
@Subscribe public void onConnectionChanged(ConnectionChangedEvent event) {
if (event.getConnection().getId() == mConnectionId) {
updateUI();
}
}
@Subscribe public void onBufferChanged(BufferChangedEvent event) {
if (event.getBuffer().getId() != mBufferId) {
return;
}
updateUI();
}
@Subscribe public void onBufferLineAdded(final BufferLineAddedEvent event) {
if (event.getBuffer().getId() != mBufferId || mConnectionState != TapchatService.STATE_LOADED) {
return;
}
BufferEventListAdapter adapter = (BufferEventListAdapter) getListAdapter();
if (adapter != null && !adapter.contains(event.getBufferEvent())) {
adapter.addItem(event.getBufferEvent());
}
}
@Subscribe public void onBufferRemoved(BufferRemovedEvent event) {
if (event.getBuffer().getId() == mBufferId) {
TapchatApp.goHome(getActivity());
}
}
public long getBufferId() {
return mBufferId;
}
private void sendMessage() {
EditText textEntry = (EditText) getView().findViewById(R.id.text_entry);
final String text = textEntry.getText().toString();
if (TextUtils.isEmpty(text)) {
return;
}
if (text.startsWith("/")) {
new AlertDialog.Builder(getActivity())
.setMessage(R.string.commands_not_supported)
.setPositiveButton(android.R.string.ok, null)
.show();
return;
}
textEntry.setText("");
mConnection.say(mBuffer.getName(), text, null);
}
private void showContextMenu(List<ContextMenuItem> menuItems) {
new AlertDialog.Builder(getActivity())
.setAdapter(new ContextMenuItemsAdapter(menuItems), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ContextMenuItemsAdapter adapter = (ContextMenuItemsAdapter) ((AlertDialog) dialog).getListView().getAdapter();
ContextMenuItem menuItem = adapter.getItem(which);
menuItem.onClick();
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void populateMenuItems(List<ContextMenuItem> menuItems, List<BufferEventItem> eventItems) {
for (BufferEventItem item : eventItems) {
menuItems.add(new BufferEventItemContextMenuItem(item));
}
}
private void populateMenuItems(List<ContextMenuItem> menuItems, BufferEventItem item) {
menuItems.add(new CopyContextMenuItem(item));
BufferEventMessage message = item.getMessage();
final String nick = TextUtils.isEmpty(message.from) ? message.nick : message.from;
if (!TextUtils.isEmpty(nick)) {
if (mBuffer instanceof ChannelBuffer) {
if (((ChannelBuffer) mBuffer).isInChannel(nick)) {
menuItems.add(new MessageUserContextMenuItem(nick));
menuItems.add(new MentionUserContextMenuItem(nick));
}
} else {
menuItems.add(new MessageUserContextMenuItem(nick));
}
}
if (!TextUtils.isEmpty(message.getMsgString())) {
Matcher matcher = URL_PATTERN.matcher(message.getMsgString());
while (matcher.find()) {
menuItems.add(new URLContextMenuItem(matcher.group(0)));
}
}
}
protected void updateUI() {
if (getView() == null) {
return;
}
if (mConnectionState == TapchatService.STATE_LOADED && mBuffer != null) {
if (getListAdapter() == null) {
setListAdapter(new BufferEventListAdapter(mBuffer.getBacklog()));
} else {
((BufferEventListAdapter) getListAdapter()).updateItems(mBuffer.getBacklog());
}
getView().findViewById(R.id.text_entry).setEnabled(mBuffer.isActive());
getView().findViewById(R.id.send_button).setEnabled(mBuffer.isActive());
} else {
getView().findViewById(R.id.text_entry).setEnabled(false);
getView().findViewById(R.id.send_button).setEnabled(false);
}
}
private class BufferEventListAdapter extends FilterableListAdapter<BufferEvent> {
private int mHighlightBgColor;
private SimpleDateFormat mDateFormat = new SimpleDateFormat("MMMM d, yyyy");
private BufferEventListAdapter(BufferEvent[] items) {
super(Buffer.MAX_EVENTS);
TypedArray typedArray = getActivity().obtainStyledAttributes(R.styleable.Tapchat);
mHighlightBgColor = typedArray.getColor(R.styleable.Tapchat_highlightBgColor, Color.TRANSPARENT);
updateItems(items);
}
@Override
public View getView(int position, View convertView, ViewGroup group) {
if (convertView == null) {
convertView = View.inflate(getActivity(), R.layout.buffer_line, null);
}
BufferEvent event = getItem(position);
BufferEvent prevEvent = (position > 0) ? getItem(position - 1) : null;
BufferEventItem firstItem = event.getFirstItem();
BufferEventMessage firstItemMessage = firstItem.getMessage();
boolean isNewDay = ((prevEvent != null) && (!firstItem.isSameDay(prevEvent.getFirstItem())));
TextView dayView = (TextView) convertView.findViewById(R.id.day);
dayView.setVisibility(isNewDay ? View.VISIBLE : View.GONE);
if (isNewDay) {
dayView.setText(mDateFormat.format(firstItemMessage.getDate()));
}
TextView timestampView = (TextView) convertView.findViewById(R.id.timestamp);
TextView textView = (TextView) convertView.findViewById(R.id.text);
Date date = firstItemMessage.getDate();
if (date != null) {
timestampView.setText(new SimpleDateFormat("h:mm aa").format(date));
timestampView.setVisibility(View.VISIBLE);
} else {
timestampView.setVisibility(View.GONE);
}
CharSequence text = mRenderer.renderEvent(event);
if ((!(mBuffer instanceof ConsoleBuffer)) && (!(text instanceof SpannableString))) {
SpannableString span = new SpannableString(text);
span.setSpan(new StyleSpan(Typeface.ITALIC), 0, span.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
span.setSpan(new ForegroundColorSpan(Color.GRAY), 0, span.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(span);
} else {
textView.setText(text);
}
boolean highlight = firstItem.isHighlight() && (mBuffer instanceof ChannelBuffer);
convertView.findViewById(R.id.inner).setBackgroundColor(highlight ? mHighlightBgColor : Color.TRANSPARENT);
return convertView;
}
}
private class BufferEventItemContextMenuItem extends ContextMenuItem<BufferEventItem> {
protected BufferEventItemContextMenuItem(BufferEventItem eventItem) {
super(eventItem);
}
@Override
public CharSequence getText() {
return mMenuRenderer.renderEventItem(getObject());
}
@Override
public void onClick() {
List<ContextMenuItem> menuItems = Lists.newArrayList();
populateMenuItems(menuItems, getObject());
showContextMenu(menuItems);
}
}
private class MessageUserContextMenuItem extends ContextMenuItem<String> {
public MessageUserContextMenuItem(String nick) {
super(nick);
}
@Override
public CharSequence getText() {
return getString(R.string.message_user_format, getObject());
}
@Override
public void onClick() {
mConnection.openBuffer(getObject());
}
}
private class MentionUserContextMenuItem extends ContextMenuItem<String> {
public MentionUserContextMenuItem(String nick) {
super(nick);
}
@Override
public CharSequence getText() {
return (getString(R.string.mention_user_format, getObject()));
}
@Override
public void onClick() {
final EditText editText = (EditText) getView().findViewById(R.id.text_entry);
editText.requestFocus();
editText.setText(String.format("%s: %s", getObject(), editText.getText()));
editText.setSelection(editText.getText().length());
}
}
private class URLContextMenuItem extends ContextMenuItem<String> {
protected URLContextMenuItem(String object) {
super(object);
}
@Override
public CharSequence getText() {
return getObject();
}
@Override
public void onClick() {
String url = getObject();
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
}
private class CopyContextMenuItem extends ContextMenuItem<BufferEventItem> {
public CopyContextMenuItem(BufferEventItem eventItem) {
super(eventItem);
}
public CharSequence getText() {
return getString(R.string.copy_text);
}
public void onClick() {
@SuppressWarnings("deprecation")
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(mRenderer.renderEventItem(getObject()).toString());
Toast.makeText(getActivity(), R.string.text_copied, Toast.LENGTH_SHORT).show();
}
}
private abstract class ContextMenuItem<T> {
private T mObject;
protected ContextMenuItem(T object) {
mObject = object;
}
public T getObject() {
return mObject;
}
public abstract CharSequence getText();
public abstract void onClick();
}
private class ContextMenuItemsAdapter extends FilterableListAdapter<ContextMenuItem> {
public ContextMenuItemsAdapter(List<ContextMenuItem> items) {
updateItems(items);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = View.inflate(getActivity(), android.R.layout.simple_list_item_1, null);
}
((TextView) convertView).setText(getItem(position).getText());
return convertView;
}
}
}