/*
* Copyright (C) 2010-2011 Dmitry Petuhov
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.pvoid.apteryxaustralis.ui;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Hashtable;
import android.content.*;
import android.os.AsyncTask;
import android.text.format.DateUtils;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.widget.*;
import org.pvoid.apteryxaustralis.*;
import org.pvoid.apteryxaustralis.storage.ICommandResult;
import org.pvoid.apteryxaustralis.storage.States;
import org.pvoid.apteryxaustralis.types.Account;
import org.pvoid.apteryxaustralis.types.Group;
import org.pvoid.apteryxaustralis.preference.Preferences;
import org.pvoid.apteryxaustralis.storage.IStorage;
import org.pvoid.apteryxaustralis.storage.osmp.OsmpStorage;
import org.pvoid.apteryxaustralis.types.ITerminal;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AnimationUtils;
import org.pvoid.apteryxaustralis.preference.AddAccountActivity;
import org.pvoid.apteryxaustralis.preference.CommonSettings;
import org.pvoid.apteryxaustralis.types.TerminalAction;
import org.pvoid.common.views.SlideBand;
public class MainActivity extends Activity implements OnClickListener,
AdapterView.OnItemClickListener,
SlideBand.OnCurrentViewChangeListener,
AdapterView.OnItemLongClickListener,
ICommandResult
{
private static final int SETTINGS_MENU_ID = Menu.FIRST+1;
private static final int REFRESH_MENU_ID = Menu.FIRST+2;
private IStorage _mStorage;
private States _mStates;
private int _mSpinnerCount = 0;
private Animation _mSpinnerAnimation;
private ArrayList<TerminalsArrayAdapter> _mGroups;
private SlideBand _mSlider;
private AlertDialog _mAgentsDialog;
private GroupsArrayAdapter _mDialogAdapter;
/**
* Получатель события изменения данных о статусах терминалов
*/
public BroadcastReceiver UpdateMessageReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
(new LoadFromStorageTask()).execute();
}
};
/**
* Компаратор для сортировки терминалов
*
* 1) по статусу
* 2) по заголовку
*/
private static final Comparator<ITerminal> _mTerminalComparator = new Comparator<ITerminal>()
{
@Override
public int compare(ITerminal left, ITerminal right)
{
int res = right.getState() - left.getState();
if(res!=0)
return(res);
return left.getTitle().compareTo(right.getTitle());
}
};
/**
* Activity создается. Настроим внешний вид.
* @param savedInstanceState предыдущее состаяние
*/
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
_mSlider = (SlideBand) findViewById(R.id.groups);
_mSlider.setOnCurrentViewChangeListener(this);
_mSpinnerAnimation = AnimationUtils.loadAnimation(this,R.anim.rotation);
/////////
_mGroups = new ArrayList<TerminalsArrayAdapter>();
_mStorage = new OsmpStorage(this);
_mStates = new States(this);
/////////
if(Preferences.getAutoUpdate(this))
{
Intent serviceIntent = new Intent(this,UpdateStatusService.class);
startService(serviceIntent);
}
/////////
//Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
}
/**
* Activity снова видима. Обновим данные
*/
@Override
public void onResume()
{
super.onResume();
/////////
if(_mStorage.isEmpty())
ShowSettingsAlarm();
else
(new LoadFromStorageTask()).execute();
/////////
IntentFilter filter = new IntentFilter(StatesReceiver.REFRESH_BROADCAST_MESSAGE);
registerReceiver(UpdateMessageReceiver, filter);
}
/**
* Activity более не видна, приостановим breadcast-receiver
*/
@Override
public void onPause()
{
super.onPause();
unregisterReceiver(UpdateMessageReceiver);
}
/**
* Восстановление Activity, чистмм уведомления
*/
@Override
public void onStart()
{
super.onStart();
Notifier.hideNotification(this,true);
}
/**
* Создаем меню
* @param menu собственно само меню
* @return true если его можно показывать
*/
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
boolean result = super.onCreateOptionsMenu(menu);
if(result)
{
MenuItem item = menu.add(Menu.NONE, REFRESH_MENU_ID, Menu.NONE, R.string.refresh);
item.setIcon(R.drawable.menu_refresh);
item = menu.add(Menu.NONE, SETTINGS_MENU_ID, Menu.NONE, R.string.settings);
item.setIcon(android.R.drawable.ic_menu_preferences);
}
return(result);
}
/**
* Отображение или крытие круглого бесконечного пргресса. Можно взывать рекурсивно, скроется только когда
* число устновок будет равно числу скрытий
* @param visible показать или спрятать вертушку
*/
private void setSpinnerVisibility(boolean visible)
{
View spinner = findViewById(R.id.refresh_spinner);
if(!visible)
{
if(_mSpinnerCount>0)
--_mSpinnerCount;
}
else
++_mSpinnerCount;
if(_mSpinnerCount==0)
{
spinner.clearAnimation();
spinner.setVisibility(View.GONE);
}
else
{
spinner.setVisibility(View.VISIBLE);
spinner.startAnimation(_mSpinnerAnimation);
}
}
/**
* Открывает Activity с настройками
*/
private void ShowPreferencesActivity()
{
Intent intent = new Intent(this,CommonSettings.class);
startActivityForResult(intent, 0);
}
/**
* Обновление данных из БД. Операция долгая, запускать строго в фоне
*/
protected void refreshData()
{
ArrayList<Account> accounts = new ArrayList<Account>();
ArrayList<Group> groups = new ArrayList<Group>();
_mStorage.getAccounts(accounts);
////////
for(Account account : accounts)
{
groups.clear();
_mStorage.getGroups(account.id, groups);
for(Group group : groups)
{
TerminalsArrayAdapter adapter = null;
/////////////////
for(int i=0,len=_mGroups.size();i<len;++i)
{
if(_mGroups.get(i).getGroup().id== group.id)
{
adapter = _mGroups.get(i);
break;
}
}
////////////////
if(adapter==null)
{
adapter = new TerminalsArrayAdapter(this, group);
_mGroups.add(adapter);
}
else
{
adapter.setGroup(group);
}
//////////////// Вытащим терминалы
_mStorage.getTerminals(account.id, group, adapter);
/////////////// Сортируем
if(!adapter.isEmpty())
adapter.sort(_mTerminalComparator);
}
}
/////////////// Вытащим статусы групп
Hashtable<Long,Integer> states = new Hashtable<Long,Integer>();
if(_mStates.getGroupsStates(states))
{
for(TerminalsArrayAdapter adapter : _mGroups)
{
if(states.containsKey(adapter.getGroup().id))
adapter.setState(states.get(adapter.getGroup().id));
else
adapter.setState(ITerminal.STATE_OK);
}
}
}
/**
* Заполнение адаптеров данными. Запускать строго в UI потоке, иначе упадет
*/
private void fillAgents()
{
int index = 0;
for(TerminalsArrayAdapter adapter : _mGroups)
{
if(adapter.isEmpty())
continue;
//adapter.sort(_mTerminalComparator);
if(_mSlider.getChildCount()<=index)
{
ListView list = new ListView(this);
list.setAdapter(adapter);
list.setOnItemClickListener(this);
list.setOnItemLongClickListener(this);
list.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
_mSlider.addView(list);
}
else
{
ListView list = ((ListView)_mSlider.getChildAt(index));
if(list.getAdapter()!=adapter)
list.setAdapter(adapter);
else
adapter.notifyList();
}
++index;
}
/////////
while(index<_mSlider.getChildCount())
_mSlider.removeViewAt(_mSlider.getChildCount()-1);
}
/**
* Отоюражение данных по агунту в списке
* @param view текущая активная вьюшка
*/
private void setCurrentAgentInfo(View view)
{
ListView list = (ListView)view;
if(list==null)
list = (ListView)_mSlider.getCurrentView();
if(list==null)
return;
TerminalsArrayAdapter adapter = (TerminalsArrayAdapter)list.getAdapter();
if(adapter==null)
return;
Group group = adapter.getGroup();
//////////
TextView text = (TextView) findViewById(R.id.agent_name);
text.setText(group.name);
//////////
text = (TextView) findViewById(R.id.agent_balance);
StringBuilder balance = new StringBuilder(getText(R.string.balance));
balance.append(": ").append(TextFormat.formatMoney(group.balance,false));
if(group.overdraft!=0)
balance.append(" ").append(getString(R.string.overdraft)).append(": ").append(group.overdraft);
else
balance.append(" / ").append(TextFormat.formatMoney(adapter.getCash(),true));
text.setText(balance.toString());
//////////
text = (TextView) findViewById(R.id.agent_update_time);
text.setText(getString(R.string.refreshed) + " " +
DateUtils.getRelativeTimeSpanString(group.lastUpdate,
System.currentTimeMillis(),
DateUtils.SECOND_IN_MILLIS,
DateUtils.FORMAT_ABBREV_ALL));
////////// пометим что мы уже видели статусы этого агента
_mStates.updateGroupState(group.id,ITerminal.STATE_OK);
adapter.setState(ITerminal.STATE_OK);
}
/**
* Щелчок по кнопке со списком агентов
* @param view сама кнопка вызывающая список агентов
*/
@SuppressWarnings("unused")
public void agentsListClick(View view)
{
GroupsArrayAdapter adapter;
if(_mAgentsDialog==null)
{
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
ArrayList<Group> agents = new ArrayList<Group>();
_mStorage.getGroups(agents);
_mDialogAdapter = new GroupsArrayAdapter(this);
for(Group agent : agents)
{
_mDialogAdapter.add(agent);
}
dialog.setAdapter(_mDialogAdapter,this);
dialog.setTitle(R.string.agents_list);
_mAgentsDialog = dialog.create();
}
///////////// проставим состояния
Hashtable<Long,Integer> states = new Hashtable<Long,Integer>();
if(_mStates.getGroupsStates(states))
for(int index=0,length=_mDialogAdapter.getCount();index<length;++index)
{
Group agent = _mDialogAdapter.getItem(index);
if(states.containsKey(agent.id))
agent.state = states.get(agent.id);
else
agent.state = ITerminal.STATE_OK;
}
/////////////
_mAgentsDialog.show();
_mAgentsDialog.getListView().setSelection(_mSlider.getCurrentViewIndex());
}
/**
* Отображение диалога с необходимостью настроек
*/
private void ShowSettingsAlarm()
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.error);
builder.setMessage(getString(R.string.add_account_message))
.setPositiveButton(R.string.add_account,new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
Intent intent = new Intent(MainActivity.this,AddAccountActivity.class);
startActivityForResult(intent, 0);
}
})
.show();
}
/**
* Выбран пункт меню
* @param item выбранный пункт
* @return true если мы отработали выбор
*/
public boolean onOptionsItemSelected(MenuItem item)
{
switch(item.getItemId())
{
case SETTINGS_MENU_ID:
ShowPreferencesActivity();
break;
case REFRESH_MENU_ID:
(new RefreshDataTask()).execute();
break;
}
return(super.onOptionsItemSelected(item));
}
/**
* Выбор агента в выпадающем списке агентов
* @param dialogInterface диалог
* @param index порядковый номер выбранного агента
*/
@Override
public void onClick(DialogInterface dialogInterface, int index)
{
_mSlider.setCurrentView(index);
}
/**
* Выбор терминала из спика
* @param adapterView список терминала
* @param view вьюшка с терминалом
* @param index порядковый индекс терминала
* @param id идентификатор терминала
*/
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int index, long id)
{
ITerminal terminal = (ITerminal) adapterView.getAdapter().getItem(index);
Intent intent = new Intent(this,FullInfo.class);
intent.putExtra(FullInfo.TERMINAL_EXTRA,terminal.getId());
startActivity(intent);
}
/**
* Изменена текущая отображаемая вьюшка в листалке
* @param v вьюшка с терминалами
*/
@Override
public void CurrentViewChanged(View v)
{
setCurrentAgentInfo(v);
}
/**
* Долгий щелчок по элементу в списке терминалов. Вываливает всплывающее меню с командами
* @param adapterView список
* @param view вьюшка с терминалом
* @param index индеск терминала
* @param l идентификатор. не используется
* @return возвращает true если мы обработали щелчок
*/
@Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int index, long l)
{
final ITerminal terminal = (ITerminal) adapterView.getAdapter().getItem(index);
ArrayList<TerminalAction> actions = new ArrayList<TerminalAction>();
////////
terminal.getActions(this, actions);
if(actions.size()==0)
return false;
////////
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
final ArrayAdapter<TerminalAction> actionsList = new ArrayAdapter<TerminalAction>(this, android.R.layout.select_dialog_item);
for(TerminalAction action : actions)
actionsList.add(action);
dialog.setTitle(terminal.getTitle());
dialog.setAdapter(actionsList, new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
TerminalAction action = actionsList.getItem(which);
setSpinnerVisibility(true);
terminal.runAction(_mStorage,action.id,MainActivity.this);
}
});
dialog.setCancelable(true);
dialog.show();
return(true);
}
/**
* Результат запуска команды на терминале
* @param success признак усеха
* @param message сообщение
* @param title человечье имя терминала
*/
@Override
public void onCommandResult(boolean success, int message, String title)
{
setSpinnerVisibility(false);
Toast.makeText(this,title + ": " + getString(message),Toast.LENGTH_LONG).show();
}
/**
* Фоновое обновление данных из БД
*/
private class LoadFromStorageTask extends AsyncTask<Void,Integer,Boolean>
{
@Override
protected void onPreExecute()
{
setSpinnerVisibility(true);
}
@Override
protected Boolean doInBackground(Void... voids)
{
refreshData();
return true;
}
@Override
protected void onPostExecute(Boolean aBoolean)
{
if(aBoolean)
{
fillAgents();
setCurrentAgentInfo(_mSlider.getCurrentView());
}
setSpinnerVisibility(false);
}
}
// TODO: Lock на обновление
private class RefreshDataTask extends AsyncTask<Void,Integer,Boolean>
{
@Override
protected void onPreExecute()
{
setSpinnerVisibility(true);
}
@Override
protected Boolean doInBackground(Void... voids)
{
if(StatesReceiver.refreshData(MainActivity.this)>-1)
{
refreshData();
return Boolean.TRUE;
}
return Boolean.FALSE;
}
@Override
protected void onPostExecute(Boolean result)
{
if(result == Boolean.TRUE)
{
fillAgents();
setCurrentAgentInfo(_mSlider.getCurrentView());
}
setSpinnerVisibility(false);
}
}
/*
public class ExceptionHandler implements Thread.UncaughtExceptionHandler
{
public String joinStackTrace(Throwable e)
{
StringWriter writer = null;
try
{
writer = new StringWriter();
joinStackTrace(e, writer);
return writer.toString();
}
finally
{
if(writer != null)
try
{
writer.close();
}
catch(IOException e1)
{
// ignore
}
}
}
public void joinStackTrace(Throwable e, StringWriter writer)
{
PrintWriter printer = null;
try
{
printer = new PrintWriter(writer);
while (e != null)
{
printer.println(e);
StackTraceElement[] trace = e.getStackTrace();
for(StackTraceElement aTrace : trace)
printer.println("\tat " + aTrace);
e = e.getCause();
if (e != null)
printer.println("Caused by:\r\n");
}
}
finally
{
if(printer != null)
printer.close();
}
}
@Override
public void uncaughtException(Thread t, Throwable e)
{
e.printStackTrace();
/////////
Intent sendIntent;
sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_SUBJECT,"Apteryx промахнулся");
sendIntent.putExtra(Intent.EXTRA_TEXT,joinStackTrace(e));
sendIntent.putExtra(Intent.EXTRA_EMAIL,new String[] {"apteryx.project@gmail.com"});
sendIntent.setType("text/plain");
MainActivity.this.startActivity(sendIntent);
e.printStackTrace();
android.os.Process.killProcess(android.os.Process.myPid());
}
}*/
}