/******************************************************************************* * Copyright 2012 Keith Johnson * * 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.ubergeek42.WeechatAndroid.adapters; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; import android.text.Spannable; import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.BaseAdapter; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; import com.ubergeek42.WeechatAndroid.R; import com.ubergeek42.WeechatAndroid.WeechatActivity; import com.ubergeek42.WeechatAndroid.relay.Buffer; import com.ubergeek42.WeechatAndroid.relay.BufferEye; import com.ubergeek42.WeechatAndroid.relay.Line; import com.ubergeek42.WeechatAndroid.service.P; import com.ubergeek42.weechat.ColorScheme; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ChatLinesAdapter extends BaseAdapter implements ListAdapter, BufferEye, AbsListView.OnScrollListener { private static Logger logger = LoggerFactory.getLogger("ChatLinesAdapter"); final private static boolean DEBUG = false; private WeechatActivity activity = null; private Buffer buffer; private Line[] lines = new Line[0]; private LayoutInflater inflater; private ListView uiListView; private @Nullable Typeface typeface = null; private boolean lastItemVisible = true; public boolean needMoveLastReadMarker = false; public ChatLinesAdapter(FragmentActivity activity, Buffer buffer, ListView uiListView) { if (DEBUG) logger.debug("ChatLinesAdapter({}, {})", activity, buffer); this.activity = (WeechatActivity) activity; this.buffer = buffer; this.inflater = LayoutInflater.from(activity); this.uiListView = uiListView; uiListView.setOnScrollListener(this); } public void setFont(@NonNull String fontPath) { typeface = ("".equals(fontPath)) ? null : Typeface.createFromFile(fontPath); } //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// @Override public int getCount() { return lines.length; } @Override public Object getItem(int position) { return lines[position]; } @Override public long getItemId(int position) { return lines[position].pointer; } @Override public View getView(int position, View convertView, ViewGroup parent) { View retview; // The view to return TextView textview; boolean mustDrawReadMarker = getItemId(position) == buffer.readMarkerLine; // we only want to reuse TextViews, not the special lastLineRead view, // so force view recreation if (mustDrawReadMarker || convertView instanceof RelativeLayout) convertView = null; if (convertView == null) { if (mustDrawReadMarker) { retview = inflater.inflate(R.layout.chatview_line_read_marker, null); textview = (TextView)retview.findViewById(R.id.chatline_message); //noinspection deprecation retview.findViewById(R.id.separator).setBackgroundDrawable( new ColorDrawable(0xFF000000 | ColorScheme.get().chat_read_marker[0])); } else { textview = (TextView) inflater.inflate(R.layout.chatview_line, null); retview = textview; } textview.setTextColor(0xFF000000 | ColorScheme.get().defaul[0]); textview.setMovementMethod(LinkMovementMethod.getInstance()); } else { // convertview is only ever not null for the simple case textview = (TextView) convertView; retview = textview; } textview.setTextSize(P.textSize); Line line = (Line) getItem(position); textview.setText(line.spannable); textview.setTag(line); if (typeface != null) textview.setTypeface(typeface); return retview; } //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// private Spannable oldLastSpannable = null; public void readLinesFromBuffer() { oldLastSpannable = null; onLinesChanged(); } @Override public void onLinesChanged() { if (DEBUG) logger.debug("onLinesChanged()"); final int index, top; final Line[] l; final Spannable lastSpannable; final boolean lineCountUnchanged, lastItemVisible, mustScrollOneLineUp; l = buffer.getLinesCopy(); if (l.length == 0) return; lineCountUnchanged = lines.length == l.length; lastSpannable = l[l.length - 1].spannable; // return if there's nothing to update if (!needMoveLastReadMarker && lineCountUnchanged && lastSpannable == oldLastSpannable) return; oldLastSpannable = lastSpannable; // if last line is visible, scroll to bottom // this is required for earlier versions of android, apparently // if last line is not visible, // scroll one line up accordingly, so we stay in place // TODO: http://chris.banes.me/2013/02/21/listview-keeping-position/ lastItemVisible = this.lastItemVisible; mustScrollOneLineUp = !lastItemVisible && lineCountUnchanged && !needMoveLastReadMarker; //TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! last piece here if (mustScrollOneLineUp) { index = uiListView.getFirstVisiblePosition(); View v = uiListView.getChildAt(0); top = (v == null) ? 0 : v.getTop(); } else index = top = 0; activity.runOnUiThread(new Runnable() { @Override public void run() { lines = l; notifyDataSetChanged(); if (lastItemVisible) uiListView.setSelection(uiListView.getCount() - 1); else if (mustScrollOneLineUp) uiListView.setSelectionFromTop(index - 1, top); } }); needMoveLastReadMarker = false; } @Override public void onLinesListed() {} @Override public void onPropertiesChanged() {} @Override public void onBufferClosed() {} //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// AbsListView.OnScrollListener //////////////////////////////////////////////////////////////////////////////////////////////// boolean userIsScrolling = false; private int prevBottomHidden = 0; @Override public void onScrollStateChanged(AbsListView absListView, int i) { userIsScrolling = (i != SCROLL_STATE_IDLE); } // this determines how many items are not visible in the ListView // the difference, if any, and if user is actually scrolling, is sent to toolbar controller // (this is needed to prevent inadvertent toolbar showing/hiding when changing lines) // also, determine if we are on our last line @Override public void onScroll(AbsListView lw, int firstVisibleItem, int visibleItemCount, int totalItemCount) { final int bottomHidden = totalItemCount - firstVisibleItem - visibleItemCount; lastItemVisible = bottomHidden == 0; if (userIsScrolling) activity.toolbarController.onUserScroll(bottomHidden, prevBottomHidden); prevBottomHidden = bottomHidden; } }