/* Copyright (C) 2014 Prasanna Thirumalai This file is part of StackX. StackX is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. StackX is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with StackX. If not, see <http://www.gnu.org/licenses/>. */ package com.prasanna.android.stacknetwork; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.regex.Pattern; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Typeface; import android.os.Bundle; import android.os.Handler; import android.text.Editable; import android.text.Html; import android.text.TextWatcher; import android.view.Menu; import android.view.View; import android.view.View.OnFocusChangeListener; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Filter; import android.widget.ImageView; import android.widget.ListView; import android.widget.MultiAutoCompleteTextView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.prasanna.android.cache.BitmapCache; import com.prasanna.android.stacknetwork.adapter.ItemListAdapter; import com.prasanna.android.stacknetwork.adapter.ItemListAdapter.ListItemView; import com.prasanna.android.stacknetwork.model.InboxItem; import com.prasanna.android.stacknetwork.model.Site; import com.prasanna.android.stacknetwork.model.StackXPage; import com.prasanna.android.stacknetwork.receiver.RestQueryResultReceiver; import com.prasanna.android.stacknetwork.receiver.RestQueryResultReceiver.StackXRestQueryResultReceiver; import com.prasanna.android.stacknetwork.service.UserIntentService; import com.prasanna.android.stacknetwork.utils.AppUtils; import com.prasanna.android.stacknetwork.utils.DateTimeUtils; import com.prasanna.android.stacknetwork.utils.StackUri; import com.prasanna.android.stacknetwork.utils.StringConstants; import com.prasanna.android.task.AsyncTaskCompletionNotifier; import com.prasanna.android.task.AsyncTaskExecutor; import com.prasanna.android.task.GetImageAsyncTask; public class UserInboxActivity extends AbstractUserActionBarActivity implements OnScrollListener, StackXRestQueryResultReceiver, ListItemView<InboxItem> { private ProgressBar progressBar; private ListView listView; private MultiAutoCompleteTextView searchInputText; private Button clearFilterInputText; private Intent intent = null; private int page = 0; private RestQueryResultReceiver receiver; private ItemListAdapter<InboxItem> itemListAdapter; private boolean serviceRunning = false; protected List<StackXPage<InboxItem>> pages = new ArrayList<StackXPage<InboxItem>>(); private StackXPage<InboxItem> currentPageObject; private CharSequence searchHint; private HashSet<String> autocompleteOptions = new HashSet<String>(); private ArrayAdapter<String> searchOptionsAdapter; public class InboxFilter extends Filter { private Object filterLock = new Object(); @Override protected FilterResults performFiltering(CharSequence constraint) { synchronized (filterLock) { if (constraint != null && constraint.length() > 0) return filterList(constraint); else return clearFilter(); } } private FilterResults filterList(CharSequence constraint) { FilterResults result = new FilterResults(); ArrayList<InboxItem> filteredInboxItems = new ArrayList<InboxItem>(); for (StackXPage<InboxItem> page : pages) { for (InboxItem inboxItem : page.items) { String[] words = constraint.toString().split(","); boolean match = true; for (String word : words) { String trimmedWord = word.trim(); boolean titleMatch = Pattern.compile(Pattern.quote(word), Pattern.CASE_INSENSITIVE).matcher(inboxItem.title).find(); if (!inboxItem.itemType.getRepr().contains(trimmedWord) && !inboxItem.site.apiSiteParameter.contains(trimmedWord) && !titleMatch) { match = false; break; } } if (match) filteredInboxItems.add(inboxItem); } } result.count = filteredInboxItems.size(); result.values = filteredInboxItems; return result; } private FilterResults clearFilter() { FilterResults result = new FilterResults(); ArrayList<InboxItem> filteredInboxItems = new ArrayList<InboxItem>(); for (StackXPage<InboxItem> page : pages) { for (InboxItem inboxItem : page.items) { filteredInboxItems.add(inboxItem); } } result.count = filteredInboxItems.size(); result.values = filteredInboxItems; return result; } @Override @SuppressWarnings("unchecked") protected void publishResults(CharSequence constraint, FilterResults results) { ArrayList<InboxItem> filteredTags = (ArrayList<InboxItem>) results.values; itemListAdapter.notifyDataSetChanged(); itemListAdapter.clear(); itemListAdapter.addAll(filteredTags); itemListAdapter.notifyDataSetInvalidated(); } } static class InboxItemViewHolder { TextView title; TextView body; TextView creationTime; TextView itemType; TextView itemSite; ImageView siteIcon; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.inbox); receiver = new RestQueryResultReceiver(new Handler()); receiver.setReceiver(this); if (progressBar == null) progressBar = (ProgressBar) getLayoutInflater().inflate(R.layout.progress_bar, null); setupSearch(); setupListView(); startIntentService(); } private void setupSearch() { searchOptionsAdapter = new ArrayAdapter<String>(this, R.layout.tag_include_exclude, new ArrayList<String>()); searchInputText = (MultiAutoCompleteTextView) findViewById(R.id.search); searchInputText.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer()); searchInputText.setAdapter(searchOptionsAdapter); searchHint = searchInputText.getHint(); clearFilterInputText = (Button) findViewById(R.id.clearTextAndFocus); searchInputText.setOnFocusChangeListener(new OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { searchInputText.setHint(""); clearFilterInputText.setVisibility(View.VISIBLE); } else clearFilterInputText.setVisibility(View.GONE); } }); searchInputText.addTextChangedListener(new TextWatcher() { private int lastCount; @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { lastCount = count; } @Override public void afterTextChanged(Editable s) { if (s.length() < lastCount || s.length() > 3) itemListAdapter.getFilter().filter(s); } }); clearFilterInputText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { searchInputText.setText(""); searchInputText.setHint(searchHint); listView.requestFocus(); AppUtils.hideSoftInput(getApplicationContext(), v); } }); } private void setupListView() { listView = (ListView) findViewById(android.R.id.list); listView.addFooterView(progressBar); itemListAdapter = new ItemListAdapter<InboxItem>(getApplicationContext(), R.layout.inbox_item, new ArrayList<InboxItem>(), this); itemListAdapter.setFilter(new InboxFilter()); listView.setAdapter(itemListAdapter); listView.setOnScrollListener(this); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { InboxItem item = (InboxItem) adapter.getItemAtPosition(position); if (item.itemType != null) { if (item.itemType.equals(InboxItem.ItemType.COMMENT) || item.itemType.equals(InboxItem.ItemType.NEW_ANSWER)) { Intent intent = new Intent(UserInboxActivity.this, InboxItemActivity.class); intent.putExtra(StringConstants.INBOX_ITEM, item); startActivity(intent); } else { Toast.makeText(UserInboxActivity.this, "Sorry, this message type not supported by application", Toast.LENGTH_LONG).show(); } } } }); } private void startIntentService() { if (!serviceRunning && AppUtils.inAuthenticatedRealm(getApplicationContext())) { intent = new Intent(getApplicationContext(), UserIntentService.class); intent.setAction(StringConstants.INBOX_ITEMS); intent.putExtra(StringConstants.ACTION, UserIntentService.GET_USER_INBOX); intent.putExtra(StringConstants.PAGE, ++page); intent.putExtra(StringConstants.RESULT_RECEIVER, receiver); progressBar.setVisibility(View.VISIBLE); startService(intent); serviceRunning = true; } } @Override protected void setActionBarTitle(String title) { getActionBar().setTitle(getString(R.string.inbox)); } @Override protected void setActionBarHomeIcon(Site site) { getActionBar().setIcon(R.drawable.icon); getActionBar().setHomeButtonEnabled(true); } @Override public void onResume() { super.onResume(); if (itemListAdapter != null) itemListAdapter.notifyDataSetChanged(); } @Override public boolean onCreateOptionsMenu(Menu menu) { boolean ret = super.onCreateOptionsMenu(menu); menu.removeItem(R.id.menu_my_inbox); return ret & true; } @Override public void refresh() { itemListAdapter.clear(); page = 0; startIntentService(); } @Override protected boolean shouldSearchViewBeEnabled() { return false; } @SuppressWarnings("unchecked") @Override public void onReceiveResult(int resultCode, Bundle resultData) { serviceRunning = false; progressBar.setVisibility(View.GONE); currentPageObject = (StackXPage<InboxItem>) resultData.getSerializable(StringConstants.INBOX_ITEMS); if (currentPageObject != null) { pages.add(currentPageObject); updateAutoCompleteOptions(); itemListAdapter.addAll(currentPageObject.items); } } private void updateAutoCompleteOptions() { for (InboxItem inboxItem : currentPageObject.items) { boolean notifyAutoCompleteAdapter = false; if (!autocompleteOptions.contains(inboxItem.itemType.getRepr())) { autocompleteOptions.add(inboxItem.itemType.getRepr()); notifyAutoCompleteAdapter = true; } if (!autocompleteOptions.contains(inboxItem.site.apiSiteParameter)) { autocompleteOptions.add(inboxItem.site.apiSiteParameter); notifyAutoCompleteAdapter = true; } if (notifyAutoCompleteAdapter) { searchOptionsAdapter.clear(); searchOptionsAdapter.notifyDataSetInvalidated(); searchOptionsAdapter.addAll(autocompleteOptions); searchOptionsAdapter.notifyDataSetChanged(); } } } @Override public View getView(InboxItem item, int position, View convertView, ViewGroup parent) { InboxItemViewHolder inboxItemViewHolder; if (convertView == null) { inboxItemViewHolder = new InboxItemViewHolder(); convertView = getLayoutInflater().inflate(R.layout.inbox_item, null); inboxItemViewHolder.title = (TextView) convertView.findViewById(R.id.itemTitle); inboxItemViewHolder.body = (TextView) convertView.findViewById(R.id.itemBodyPreview); inboxItemViewHolder.creationTime = (TextView) convertView.findViewById(R.id.itemCreationTime); inboxItemViewHolder.itemType = (TextView) convertView.findViewById(R.id.itemType); inboxItemViewHolder.itemSite = (TextView) convertView.findViewById(R.id.itemSite); inboxItemViewHolder.siteIcon = (ImageView) convertView.findViewById(R.id.siteIcon); convertView.setTag(inboxItemViewHolder); } else inboxItemViewHolder = (InboxItemViewHolder) convertView.getTag(); loadSiteIcon(convertView, inboxItemViewHolder.siteIcon, item.site); inboxItemViewHolder.title.setText(Html.fromHtml(item.title)); inboxItemViewHolder.creationTime.setText(DateTimeUtils.toDateString(item.creationDate)); inboxItemViewHolder.itemType.setText(item.itemType.getRepr()); if (item.body != null) inboxItemViewHolder.body.setText(Html.fromHtml(item.body)); if (item.site != null) inboxItemViewHolder.itemSite.setText(item.site.name); else inboxItemViewHolder.itemSite.setVisibility(View.GONE); if (item.unread) { inboxItemViewHolder.title.setTypeface(null, Typeface.BOLD); inboxItemViewHolder.body.setTypeface(null, Typeface.BOLD); } else { inboxItemViewHolder.title.setTypeface(null, Typeface.NORMAL); inboxItemViewHolder.body.setTypeface(null, Typeface.NORMAL); } return convertView; } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (!serviceRunning && totalItemCount >= StackUri.QueryParamDefaultValues.PAGE_SIZE && (totalItemCount - visibleItemCount) <= (firstVisibleItem + 1)) { if (currentPageObject != null && currentPageObject.hasMore) { progressBar.setVisibility(View.VISIBLE); startIntentService(); } } } private void loadSiteIcon(final View parent, final ImageView imageView, final Site site) { if (!BitmapCache.getInstance().containsKey(site.iconUrl)) { final ProgressBar siteIconLoadProgress = (ProgressBar) parent.findViewById(R.id.siteIconLoadProgress); siteIconLoadProgress.setVisibility(View.VISIBLE); AsyncTaskCompletionNotifier<Bitmap> imageFetchAsyncTaskCompleteNotiferImpl = new AsyncTaskCompletionNotifier<Bitmap>() { @Override public void notifyOnCompletion(Bitmap result) { siteIconLoadProgress.setVisibility(View.GONE); displayImage(imageView, result); } }; AsyncTaskExecutor.getInstance().executeInThreadPoolExecutor( new GetImageAsyncTask(imageFetchAsyncTaskCompleteNotiferImpl), site.iconUrl); } else { displayImage(imageView, BitmapCache.getInstance().get(site.iconUrl)); } } private void displayImage(final ImageView imageView, final Bitmap bitmap) { imageView.setVisibility(View.VISIBLE); imageView.setImageBitmap(bitmap); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } }