Java tutorial
/* Copyright 2014 Alexander Wang This file is part of Ogame on Android. Ogame on Android 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. Ogame on Android 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 Ogame on Android. If not, see <http://www.gnu.org/licenses/>. */ package com.wikaba.ogapp; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.wikaba.ogapp.agent.FleetAndResources; import com.wikaba.ogapp.agent.FleetEvent; import com.wikaba.ogapp.agent.IntegerMissionMap; import com.wikaba.ogapp.utils.AndroidMissionMap; import com.wikaba.ogapp.utils.NameBridge; import android.app.Activity; import android.support.v4.app.LoaderManager; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; import android.content.res.Resources; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.support.v4.app.Fragment; import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import android.text.format.DateUtils; import android.util.TypedValue; 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.BaseAdapter; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; public class OverviewFragment extends Fragment implements ServiceConnection, LoaderManager.LoaderCallbacks<List<FleetEvent>>, AbsListView.RecyclerListener, Runnable, View.OnClickListener { private static final String HEADER_KEY = "com.wikaba.ogapp.OverviewFragment.HEADER"; static final String USERNAME_KEY = "com.wikaba.ogapp.OverviewFragment.USERNAME"; static final String UNIVERSE_KEY = "com.wikaba.ogapp.OverviewFragment.UNIVERSE"; static final String ACC_ROWID = "account_row_id"; private static final int LOADER_ID = 0; private TextView header; private ListView eventFleetView; private ProgressBar progressWheel; private TextView noEventsText; private Button reload; private HomeActivity act; private Map<TextView, Long> etaTextViews; private Handler handler; private boolean dataIsLoaded; private boolean fragmentRunning; public OverviewFragment() { } @Override public void onAttach(Activity act) { super.onAttach(act); this.act = (HomeActivity) act; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_overview, container, false); eventFleetView = (ListView) root.findViewById(R.id.eventFleetView); progressWheel = (ProgressBar) root.findViewById(R.id.progressBar1); noEventsText = (TextView) root.findViewById(R.id.no_events); reload = (Button) root.findViewById(R.id.refresh_button); header = (TextView) root.findViewById(R.id.name_universe); etaTextViews = new HashMap<TextView, Long>(); handler = new Handler(); dataIsLoaded = false; fragmentRunning = false; setHasOptionsMenu(true); reload.setOnClickListener(this); if (savedInstanceState == null) { Bundle args = this.getArguments(); String username; String universe; if (args != null) { username = args.getString(USERNAME_KEY); universe = args.getString(UNIVERSE_KEY); } else { username = "ERROR"; universe = "ERROR"; } header.setText(universe + ", " + username); } else { String headerText = savedInstanceState.getString(HEADER_KEY); if (headerText == null) { headerText = ""; } header.setText(headerText); } return root; } @Override public void onStart() { super.onStart(); final long timeInMillis = 1000; act.setListener(this); if (act.mBound) { onServiceConnected(null, null); } if (dataIsLoaded) { handler.postDelayed(this, timeInMillis); } fragmentRunning = true; } @Override public void onSaveInstanceState(Bundle outState) { String uniText = header.getText().toString(); outState.putString(HEADER_KEY, uniText); } @Override public void onStop() { super.onStop(); fragmentRunning = false; handler.removeCallbacks(this); act.unsetListener(); } @Override public void onDetach() { super.onDetach(); act = null; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.overview, menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.switch_accounts) { act.goToAccountSelector(); return true; } return super.onOptionsItemSelected(item); } @Override public Loader<List<FleetEvent>> onCreateLoader(int arg0, Bundle arg1) { eventFleetView.setVisibility(View.GONE); progressWheel.setVisibility(View.VISIBLE); FleetEventLoader loader = new FleetEventLoader(act); return loader; } @Override public void onLoadFinished(Loader<List<FleetEvent>> loader, List<FleetEvent> events) { progressWheel.setVisibility(View.GONE); reload.setVisibility(View.VISIBLE); Resources res = getResources(); if (events == null) { noEventsText.setText(res.getString(R.string.error_msg)); events = new ArrayList<FleetEvent>(); Toast.makeText(act, R.string.login_error, Toast.LENGTH_LONG).show(); } else { noEventsText.setText(res.getString(R.string.no_events)); } if (events.size() > 0) { eventFleetView.setVisibility(View.VISIBLE); noEventsText.setVisibility(View.GONE); } else { eventFleetView.setVisibility(View.GONE); noEventsText.setVisibility(View.VISIBLE); } eventFleetView.setAdapter(new EventAdapter(act, events, etaTextViews)); eventFleetView.setRecyclerListener(this); final long timeInMillis = 1000; if (fragmentRunning) handler.postDelayed(this, timeInMillis); dataIsLoaded = true; } @Override public void onLoaderReset(Loader<List<FleetEvent>> arg0) { //Do not have to do anything, since our resource sits in memory. } @Override public void onServiceConnected(ComponentName name, IBinder service) { getLoaderManager().initLoader(LOADER_ID, null, this); } @Override public void onServiceDisconnected(ComponentName name) { getLoaderManager().destroyLoader(LOADER_ID); act.unsetListener(); } @Override public void onMovedToScrapHeap(View v) { EventViewHolder holder = (EventViewHolder) v.getTag(); etaTextViews.remove(holder.eta); } @Override public void run() { Iterator<Entry<TextView, Long>> entryiter = etaTextViews.entrySet().iterator(); while (entryiter.hasNext()) { Entry<TextView, Long> anEntry = entryiter.next(); TextView textview = anEntry.getKey(); long arrivalInEpoch = anEntry.getValue().longValue(); long currentTime = Calendar.getInstance().getTimeInMillis() / 1000; long timeLeft = arrivalInEpoch - currentTime; if (timeLeft <= 0) { textview.setText(getResources().getString(R.string.overview_arrived)); } else { textview.setText(DateUtils.formatElapsedTime(timeLeft)); } } final long timeInMillis = 1000; handler.postDelayed(this, timeInMillis); } @Override public void onClick(View v) { if (v == reload) { eventFleetView.setVisibility(View.GONE); noEventsText.setVisibility(View.GONE); progressWheel.setVisibility(View.VISIBLE); reload.setVisibility(View.INVISIBLE); if (act.mBound) { getLoaderManager().getLoader(LOADER_ID).onContentChanged(); } Toast.makeText(act, R.string.reload_in_progress, Toast.LENGTH_SHORT).show(); } } private static class FleetEventLoader extends AsyncTaskLoader<List<FleetEvent>> { private List<FleetEvent> oldData; private HomeActivity act; public FleetEventLoader(HomeActivity context) { super(context); oldData = null; act = context; } @Override protected void onStartLoading() { if (oldData != null) deliverResult(oldData); boolean contentChanged = takeContentChanged(); if (oldData == null || contentChanged) { this.forceLoad(); } } @Override public void deliverResult(List<FleetEvent> data) { oldData = data; super.deliverResult(data); } @Override protected void onStopLoading() { } @Override protected void onReset() { if (oldData != null) { oldData.clear(); oldData = null; } } @Override public List<FleetEvent> loadInBackground() { return act.mAgent.getFleetEvents(act.accountRowId); } } private static class EventAdapter extends BaseAdapter { private Context context; private List<FleetEvent> eventList; private Map<TextView, Long> textViews; private IntegerMissionMap missionBridge; private NameBridge nameBridge; private Resources res; public EventAdapter(Context ctx, List<FleetEvent> eventList, Map<TextView, Long> textViews) { context = ctx; //Copy elements over to ArrayList to ensure random access to the elements in the list. int size = 0; if (eventList != null) { size = eventList.size(); } this.eventList = new ArrayList<FleetEvent>(size); if (eventList != null) { this.eventList.addAll(eventList); } if (textViews == null) { throw new IllegalArgumentException("Third argument should not be null"); } this.textViews = textViews; res = context.getResources(); missionBridge = new IntegerMissionMap(new AndroidMissionMap(res)); nameBridge = new NameBridge(res); } @Override public int getCount() { if (eventList != null) { return eventList.size(); } return 0; } @Override public Object getItem(int position) { if (eventList != null) { return null; } return eventList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; EventViewHolder holder; if (v == null) { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = inflater.inflate(R.layout.fleet_event_view, parent, false); holder = new EventViewHolder(); holder.eta = (TextView) v.findViewById(R.id.eta); holder.originCoords = (TextView) v.findViewById(R.id.origin_coords); holder.outOrIn = (TextView) v.findViewById(R.id.outgoing_or_incoming); holder.destCoords = (TextView) v.findViewById(R.id.dest_coords); holder.missionType = (TextView) v.findViewById(R.id.mission_type); holder.civilShips = (LinearLayout) v.findViewById(R.id.civil_ship_layout); holder.combatShips = (LinearLayout) v.findViewById(R.id.combat_ship_layout); holder.resources = (LinearLayout) v.findViewById(R.id.resource_layout); v.setTag(holder); } else { holder = (EventViewHolder) v.getTag(); } FleetEvent event = eventList.get(position); TextView eta = holder.eta; TextView originCoords = holder.originCoords; TextView outOrIn = holder.outOrIn; TextView destCoords = holder.destCoords; TextView missionType = holder.missionType; LinearLayout civilShips = holder.civilShips; LinearLayout combatShips = holder.combatShips; LinearLayout resources = holder.resources; //We have to add the text view to the Map whether we are creating a new view or recycling an old one. //The recycled view might have went through the recycler listener (in which case it was removed from the //Map) textViews.put(holder.eta, event.data_arrival_time); long currentTime = Calendar.getInstance().getTimeInMillis() / 1000; long timeLeft = event.data_arrival_time - currentTime; eta.setText(DateUtils.formatElapsedTime(timeLeft)); originCoords.setText(event.coordsOrigin); Resources res = context.getResources(); outOrIn.setText(event.data_return_flight ? res.getString(R.string.overview_incoming) : res.getString(R.string.overview_outgoing)); destCoords.setText(event.destCoords); missionType.setText(missionBridge.getMission(event.data_mission_type)); addCivilData(event, civilShips); addCombatData(event, combatShips); addResourceData(event, resources); return v; } private void addCivilData(FleetEvent eventData, LinearLayout civilShipLayout) { Map<String, Long> data = eventData.fleetResources; final String[] civilShipNames = { FleetAndResources.SC, FleetAndResources.LC, FleetAndResources.COLONY, FleetAndResources.RC, FleetAndResources.EP }; Long num; int layoutIndex = 0; for (String shipName : civilShipNames) { num = data.get(shipName); if (num != null && num.longValue() > 0) { TextView shipEntry; if (layoutIndex < civilShipLayout.getChildCount()) { shipEntry = (TextView) civilShipLayout.getChildAt(layoutIndex); } else { shipEntry = new TextView(context); int size = res.getDimensionPixelSize(R.dimen.fleet_event_ship_text); shipEntry.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); civilShipLayout.addView(shipEntry); } shipName = nameBridge.getName(shipName); StringBuilder textBuilder = new StringBuilder(); textBuilder.append(num.longValue()).append(' ').append(shipName); String shipStr = textBuilder.toString(); shipEntry.setText(shipStr); layoutIndex++; } } //Remove extraneous views /* One way to think about layoutIndex after finishing the above loop * is that layoutIndex is the number of children in the layout with * the updated data. The child views starting at position layoutIndex * to the end should be removed. Thus, we only have to remove * children from the layout when layoutIndex is less than * civilShipLayout.getChildCount(). */ if (layoutIndex < civilShipLayout.getChildCount()) { int count = civilShipLayout.getChildCount() - layoutIndex; civilShipLayout.removeViews(layoutIndex, count); } } private void addCombatData(FleetEvent eventData, LinearLayout combatShipLayout) { Map<String, Long> data = eventData.fleetResources; final String[] combatShipNames = { FleetAndResources.LF, FleetAndResources.HF, FleetAndResources.CR, FleetAndResources.BS, FleetAndResources.BC, FleetAndResources.BB, FleetAndResources.DS, FleetAndResources.RIP }; Long num; int layoutIndex = 0; for (String shipName : combatShipNames) { num = data.get(shipName); if (num != null && num.longValue() > 0) { TextView shipEntry; if (layoutIndex < combatShipLayout.getChildCount()) { shipEntry = (TextView) combatShipLayout.getChildAt(layoutIndex); } else { shipEntry = new TextView(context); int size = res.getDimensionPixelSize(R.dimen.fleet_event_ship_text); shipEntry.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); combatShipLayout.addView(shipEntry); } shipName = nameBridge.getName(shipName); StringBuilder textBuilder = new StringBuilder(); textBuilder.append(num.longValue()).append(' ').append(shipName); String shipStr = textBuilder.toString(); shipEntry.setText(shipStr); layoutIndex++; } } if (layoutIndex < combatShipLayout.getChildCount()) { int count = combatShipLayout.getChildCount() - layoutIndex; combatShipLayout.removeViews(layoutIndex, count); } } private void addResourceData(FleetEvent eventData, LinearLayout resourceLayout) { Map<String, Long> data = eventData.fleetResources; final String[] resourceNames = { FleetAndResources.METAL, FleetAndResources.CRYSTAL, FleetAndResources.DEUT }; Long num; int layoutIndex = 0; for (String resName : resourceNames) { num = data.get(resName); if (num != null) { TextView resEntry; if (layoutIndex < resourceLayout.getChildCount()) { resEntry = (TextView) resourceLayout.getChildAt(layoutIndex); } else { resEntry = new TextView(context); int size = res.getDimensionPixelSize(R.dimen.fleet_event_ship_text); resEntry.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); resourceLayout.addView(resEntry); } resName = nameBridge.getName(resName); StringBuilder textBuilder = new StringBuilder(); textBuilder.append(num.longValue()).append(' ').append(resName); String resStr = textBuilder.toString(); resEntry.setText(resStr); layoutIndex++; } } if (layoutIndex < resourceLayout.getChildCount()) { int count = resourceLayout.getChildCount() - layoutIndex; resourceLayout.removeViews(layoutIndex, count); } } } private static class EventViewHolder { TextView eta; TextView originCoords; TextView outOrIn; TextView destCoords; TextView missionType; LinearLayout civilShips; LinearLayout combatShips; LinearLayout resources; } }