/* * 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; } } }