Rev 16 | Rev 18 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
8 | PointedEar | 1 | package de.pointedears.converter.app; |
5 | PointedEar | 2 | |
17 | PointedEar | 3 | import java.text.DateFormat; |
4 | import java.text.SimpleDateFormat; |
||
5 | import java.util.Date; |
||
7 | PointedEar | 6 | import java.util.HashMap; |
14 | PointedEar | 7 | import java.util.Map.Entry; |
7 | PointedEar | 8 | |
5 | PointedEar | 9 | import android.app.Activity; |
17 | PointedEar | 10 | import android.content.BroadcastReceiver; |
11 | import android.content.Context; |
||
12 | import android.content.Intent; |
||
13 | import android.content.IntentFilter; |
||
5 | PointedEar | 14 | import android.os.Bundle; |
7 | PointedEar | 15 | import android.text.Editable; |
16 | import android.view.KeyEvent; |
||
8 | PointedEar | 17 | import android.view.Menu; |
18 | import android.view.MenuInflater; |
||
19 | import android.view.MenuItem; |
||
7 | PointedEar | 20 | import android.view.View; |
13 | PointedEar | 21 | import android.view.View.OnClickListener; |
7 | PointedEar | 22 | import android.view.View.OnKeyListener; |
23 | import android.widget.AdapterView; |
||
24 | import android.widget.AdapterView.OnItemSelectedListener; |
||
13 | PointedEar | 25 | import android.widget.Button; |
7 | PointedEar | 26 | import android.widget.EditText; |
27 | import android.widget.Spinner; |
||
14 | PointedEar | 28 | import android.widget.TableLayout; |
29 | import android.widget.TableRow; |
||
30 | import android.widget.TextView; |
||
8 | PointedEar | 31 | import de.pointedears.converter.R; |
17 | PointedEar | 32 | import de.pointedears.converter.db.ConversionData; |
11 | PointedEar | 33 | import de.pointedears.converter.db.CurrenciesDatabase; |
17 | PointedEar | 34 | import de.pointedears.converter.helpers.Notifier; |
35 | import de.pointedears.converter.helpers.UpdateService; |
||
5 | PointedEar | 36 | |
37 | /** |
||
7 | PointedEar | 38 | * Activity that implements currency conversion |
5 | PointedEar | 39 | * |
7 | PointedEar | 40 | * @author pelinux |
5 | PointedEar | 41 | */ |
42 | public class CurrenciesActivity extends Activity |
||
43 | { |
||
17 | PointedEar | 44 | /** |
45 | * String to use to indicate that an exchange rate has never been updated |
||
46 | */ |
||
47 | private static final String TEXT_NEVER = "never"; |
||
48 | |||
49 | /** |
||
50 | * Serialization version id |
||
51 | */ |
||
52 | private static final long serialVersionUID = 1L; |
||
53 | |||
7 | PointedEar | 54 | /* |
8 | PointedEar | 55 | * Constants for mapping value strings |
12 | PointedEar | 56 | * |
57 | * @todo: Use resource IDs |
||
7 | PointedEar | 58 | */ |
12 | PointedEar | 59 | /** |
60 | * Database field/spinner value for Swiss Francs |
||
61 | */ |
||
62 | public static final String VALUE_CHF = "CHF"; //$NON-NLS-1$ |
||
7 | PointedEar | 63 | |
12 | PointedEar | 64 | /** |
65 | * Database field/spinner value for Euros |
||
66 | */ |
||
7 | PointedEar | 67 | |
12 | PointedEar | 68 | public static final String VALUE_EUR = "EUR"; //$NON-NLS-1$ |
69 | |||
70 | /** |
||
71 | * Database field/spinner value for US Dollars |
||
72 | */ |
||
73 | public static final String VALUE_USD = "USD"; //$NON-NLS-1$ |
||
74 | |||
7 | PointedEar | 75 | /* Unit spinners (dropdowns) */ |
76 | private Spinner spinnerUnit1; |
||
77 | private Spinner spinnerUnit2; |
||
17 | PointedEar | 78 | private CurrenciesDatabase database; |
7 | PointedEar | 79 | |
17 | PointedEar | 80 | private HashMap<String, ConversionData> conversionRates; |
12 | PointedEar | 81 | |
17 | PointedEar | 82 | /** |
83 | * Receiver for intent broadcasts, registered in |
||
84 | * {@link CurrenciesActivity#onCreate(Bundle)} |
||
85 | */ |
||
86 | public class UpdateBroadcastReceiver extends BroadcastReceiver |
||
87 | { |
||
88 | /** |
||
89 | * Notification message template |
||
90 | */ |
||
91 | private static final String EXCHANGE_RATES_UPDATED_TO = |
||
92 | " exchange rates updated to "; |
||
16 | PointedEar | 93 | |
17 | PointedEar | 94 | /* |
95 | * (non-Javadoc) |
||
96 | * |
||
97 | * @see android.content.BroadcastReceiver#onReceive(android.content.Context, |
||
98 | * android.content.Intent) |
||
99 | */ |
||
100 | @Override |
||
101 | public void onReceive(Context context, Intent intent) |
||
102 | { |
||
103 | if (intent.getAction().equals(UpdateService.ACTION_UPDATE)) |
||
104 | { |
||
105 | Bundle extras = intent.getExtras(); |
||
106 | DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$ |
||
107 | Notifier.sendMessage(CurrenciesActivity.this, |
||
108 | extras.get(UpdateService.EXTRA_NUM_RATES) |
||
109 | + UpdateBroadcastReceiver.EXCHANGE_RATES_UPDATED_TO |
||
110 | + df.format(extras.get(UpdateService.EXTRA_DATE))); |
||
111 | } |
||
112 | } |
||
113 | } |
||
114 | |||
7 | PointedEar | 115 | /** Called when the activity is first created. */ |
5 | PointedEar | 116 | @Override |
7 | PointedEar | 117 | public void onCreate(Bundle savedInstanceState) |
5 | PointedEar | 118 | { |
119 | super.onCreate(savedInstanceState); |
||
7 | PointedEar | 120 | this.setContentView(R.layout.activity_currencies); |
5 | PointedEar | 121 | |
17 | PointedEar | 122 | UpdateBroadcastReceiver br = new UpdateBroadcastReceiver(); |
123 | this.registerReceiver(br, new IntentFilter(UpdateService.ACTION_UPDATE)); |
||
124 | |||
12 | PointedEar | 125 | /* Set up currency database, and retrieve conversion rates */ |
17 | PointedEar | 126 | this.database = new CurrenciesDatabase(this); |
127 | this.setConversionRates(this.getDatabase().getConversionRates()); |
||
14 | PointedEar | 128 | this.fillTableRates(); |
8 | PointedEar | 129 | |
7 | PointedEar | 130 | final EditText editValue1 = |
131 | (EditText) this.findViewById(R.id.currencies_edit_value1); |
||
132 | final EditText editValue2 = |
||
133 | (EditText) this.findViewById(R.id.currencies_edit_value2); |
||
134 | |||
135 | final OnKeyListener editValue1OnKey = new OnKeyListener() { |
||
136 | @Override |
||
137 | public boolean onKey(View v, int keyCode, KeyEvent event) |
||
138 | { |
||
139 | Editable editable1 = ((EditText) v).getText(); |
||
140 | |||
141 | Double value1; |
||
142 | try |
||
143 | { |
||
144 | value1 = Double.parseDouble(editable1.toString()); |
||
145 | } |
||
146 | catch (NumberFormatException e) |
||
147 | { |
||
148 | value1 = null; |
||
149 | } |
||
150 | |||
151 | String string2 = ""; //$NON-NLS-1$ |
||
152 | if (value1 != null) |
||
153 | { |
||
154 | string2 = CurrenciesActivity.this.getConvertedValue(value1, false); |
||
155 | } |
||
156 | |||
157 | editValue2.setText(string2); |
||
158 | |||
159 | return false; |
||
160 | } |
||
161 | }; |
||
162 | editValue1.setOnKeyListener(editValue1OnKey); |
||
163 | |||
164 | final OnKeyListener editValue2OnKey = new OnKeyListener() { |
||
165 | @Override |
||
166 | public boolean onKey(View v, int keyCode, KeyEvent event) |
||
167 | { |
||
168 | Editable editable2 = ((EditText) v).getText(); |
||
169 | |||
170 | Double value2; |
||
171 | try |
||
172 | { |
||
173 | value2 = Double.parseDouble(editable2.toString()); |
||
174 | } |
||
175 | catch (NumberFormatException e) |
||
176 | { |
||
177 | value2 = null; |
||
178 | } |
||
179 | |||
180 | String string1 = ""; //$NON-NLS-1$ |
||
181 | if (value2 != null) |
||
182 | { |
||
183 | string1 = CurrenciesActivity.this.getConvertedValue(value2, true); |
||
184 | } |
||
185 | |||
186 | editValue1.setText(string1); |
||
187 | |||
188 | return false; |
||
189 | } |
||
190 | }; |
||
191 | editValue2.setOnKeyListener(editValue2OnKey); |
||
192 | |||
193 | this.spinnerUnit1 = |
||
194 | (Spinner) this.findViewById(R.id.currencies_spinner_unit1); |
||
195 | this.spinnerUnit2 = |
||
196 | (Spinner) this.findViewById(R.id.currencies_spinner_unit2); |
||
197 | |||
198 | this.spinnerUnit1.setOnItemSelectedListener(new OnItemSelectedListener() { |
||
199 | @Override |
||
200 | public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, |
||
201 | long arg3) |
||
202 | { |
||
203 | /* Simulate input in second EditText so that first EditText is updated */ |
||
204 | editValue2OnKey.onKey(editValue2, 0, null); |
||
205 | } |
||
206 | |||
207 | @Override |
||
208 | public void onNothingSelected(AdapterView<?> arg0) |
||
209 | { |
||
210 | /* no-op */ |
||
211 | } |
||
212 | }); |
||
213 | |||
214 | this.spinnerUnit2.setSelection(1); |
||
215 | this.spinnerUnit2.setOnItemSelectedListener(new OnItemSelectedListener() { |
||
216 | @Override |
||
217 | public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, |
||
218 | long arg3) |
||
219 | { |
||
220 | /* Simulate input in first EditText so that second EditText is updated */ |
||
221 | editValue1OnKey.onKey(editValue1, 0, null); |
||
222 | } |
||
223 | |||
224 | @Override |
||
225 | public void onNothingSelected(AdapterView<?> arg0) |
||
226 | { |
||
227 | /* no-op */ |
||
228 | } |
||
229 | }); |
||
13 | PointedEar | 230 | |
231 | Button buttonClear = |
||
232 | (Button) this.findViewById(R.id.currencies_button_clear); |
||
233 | buttonClear.setOnClickListener(new OnClickListener() { |
||
234 | |||
235 | @SuppressWarnings("nls") |
||
236 | @Override |
||
237 | public void onClick(View v) |
||
238 | { |
||
239 | editValue1.setText(""); |
||
240 | editValue2.setText(""); |
||
241 | } |
||
242 | }); |
||
5 | PointedEar | 243 | } |
7 | PointedEar | 244 | |
245 | /** |
||
14 | PointedEar | 246 | * Fills the table with currency conversion rates |
247 | */ |
||
17 | PointedEar | 248 | public void fillTableRates() |
14 | PointedEar | 249 | { |
250 | TableLayout tableRates = |
||
251 | (TableLayout) this.findViewById(R.id.currencies_table_rates); |
||
252 | |||
17 | PointedEar | 253 | /* Remove any pre-existing currency rows */ |
254 | while (tableRates.getChildCount() > 3) |
||
14 | PointedEar | 255 | { |
17 | PointedEar | 256 | tableRates.removeViewAt(3); |
257 | } |
||
258 | |||
259 | for (Entry<String, ConversionData> factorEntry : this.getConversionRates() |
||
260 | .entrySet()) |
||
261 | { |
||
16 | PointedEar | 262 | TableRow row = new TableRow(this); |
14 | PointedEar | 263 | |
16 | PointedEar | 264 | TextView columnCurrency1 = new TextView(this); |
265 | columnCurrency1.setText(factorEntry.getKey()); |
||
266 | row.addView(columnCurrency1); |
||
14 | PointedEar | 267 | |
16 | PointedEar | 268 | TextView columnRate = new TextView(this); |
17 | PointedEar | 269 | final ConversionData conversionData = factorEntry.getValue(); |
270 | columnRate.setText(conversionData.getRate().toString()); |
||
16 | PointedEar | 271 | row.addView(columnRate); |
14 | PointedEar | 272 | |
17 | PointedEar | 273 | TextView columnUpdated = new TextView(this); |
274 | Date updated = conversionData.getUpdated(); |
||
275 | if (updated.getTime() > 0) |
||
276 | { |
||
277 | DateFormat df = |
||
278 | DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM); |
||
279 | columnUpdated.setText(df.format(updated)); |
||
280 | } |
||
281 | else |
||
282 | { |
||
283 | columnUpdated.setText(CurrenciesActivity.TEXT_NEVER); |
||
284 | } |
||
285 | |||
286 | row.addView(columnUpdated); |
||
287 | |||
16 | PointedEar | 288 | tableRates.addView(row); |
14 | PointedEar | 289 | } |
290 | } |
||
291 | |||
292 | /** |
||
7 | PointedEar | 293 | * @param value |
294 | * @return |
||
295 | */ |
||
296 | private String getConvertedValue(double value, boolean reverse) |
||
297 | { |
||
298 | int selectedItemPosition1 = this.spinnerUnit1.getSelectedItemPosition(); |
||
299 | int selectedItemPosition2 = this.spinnerUnit2.getSelectedItemPosition(); |
||
8 | PointedEar | 300 | String[] items = |
7 | PointedEar | 301 | this.getResources().getStringArray(R.array.currency_units_values); |
8 | PointedEar | 302 | String selectedItemValue1 = items[selectedItemPosition1]; |
303 | String selectedItemValue2 = items[selectedItemPosition2]; |
||
7 | PointedEar | 304 | |
305 | if (reverse) |
||
306 | { |
||
307 | String tmp = selectedItemValue1; |
||
308 | selectedItemValue1 = selectedItemValue2; |
||
309 | selectedItemValue2 = tmp; |
||
310 | } |
||
311 | |||
312 | Double newValue = value; |
||
313 | |||
16 | PointedEar | 314 | /* |
315 | * NOTE: Had to do it the complicated way because somehow the Android SDK |
||
316 | * won't get it another way |
||
317 | */ |
||
17 | PointedEar | 318 | ConversionData conversionData1 = null; |
319 | Double factorToEuro = 1.0; |
||
16 | PointedEar | 320 | if (selectedItemValue1 != null) |
7 | PointedEar | 321 | { |
17 | PointedEar | 322 | conversionData1 = this.getConversionRates().get(selectedItemValue1); |
323 | if (conversionData1 != null) |
||
324 | { |
||
325 | factorToEuro = conversionData1.getRate(); |
||
326 | } |
||
7 | PointedEar | 327 | } |
328 | |||
17 | PointedEar | 329 | ConversionData conversionData2 = null; |
330 | Double factorFromEuro = 1.0; |
||
16 | PointedEar | 331 | if (selectedItemValue2 != null) |
332 | { |
||
17 | PointedEar | 333 | conversionData2 = this.getConversionRates().get(selectedItemValue2); |
334 | if (conversionData2 != null) |
||
335 | { |
||
336 | factorFromEuro = conversionData2.getRate(); |
||
337 | } |
||
16 | PointedEar | 338 | } |
339 | |||
340 | newValue = newValue / factorToEuro * factorFromEuro; |
||
341 | |||
7 | PointedEar | 342 | return newValue.toString(); |
343 | } |
||
8 | PointedEar | 344 | |
345 | /* |
||
346 | * (non-Javadoc) |
||
347 | * |
||
348 | * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) |
||
349 | */ |
||
12 | PointedEar | 350 | /* |
351 | * (non-Javadoc) |
||
352 | * |
||
353 | * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) |
||
354 | */ |
||
8 | PointedEar | 355 | @Override |
356 | public boolean onCreateOptionsMenu(Menu menu) |
||
357 | { |
||
358 | MenuInflater inflater = this.getMenuInflater(); |
||
359 | inflater.inflate(R.menu.options, menu); |
||
360 | return true; |
||
361 | } |
||
362 | |||
363 | /* |
||
364 | * (non-Javadoc) |
||
365 | * |
||
366 | * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) |
||
367 | */ |
||
368 | @Override |
||
369 | public boolean onOptionsItemSelected(MenuItem item) |
||
370 | { |
||
17 | PointedEar | 371 | /* Handle item selection */ |
15 | PointedEar | 372 | switch (item.getItemId()) |
373 | { |
||
374 | case R.id.item_options_update: |
||
17 | PointedEar | 375 | /* |
376 | * Request the update service to run a thread to fetch the rates from |
||
377 | * the Web (project requirement) |
||
378 | */ |
||
379 | Intent intent = new Intent(this, UpdateService.class); |
||
380 | intent.setAction(UpdateService.ACTION_UPDATE); |
||
16 | PointedEar | 381 | |
17 | PointedEar | 382 | /* |
383 | * FIXME: Not thread-safe! |
||
384 | * Get the activity context from the intent directly instead |
||
385 | */ |
||
386 | UpdateService.setActivityContext(this); |
||
15 | PointedEar | 387 | |
17 | PointedEar | 388 | this.startService(intent); |
16 | PointedEar | 389 | return true; |
390 | |||
15 | PointedEar | 391 | default: |
392 | return super.onOptionsItemSelected(item); |
||
393 | } |
||
8 | PointedEar | 394 | } |
17 | PointedEar | 395 | |
396 | /** |
||
397 | * @return the conversionRates |
||
398 | */ |
||
399 | public HashMap<String, ConversionData> getConversionRates() |
||
400 | { |
||
401 | return this.conversionRates; |
||
402 | } |
||
403 | |||
404 | /** |
||
405 | * @param conversionRates |
||
406 | * the conversionRates to set |
||
407 | */ |
||
408 | public void setConversionRates(HashMap<String, ConversionData> conversionRates) |
||
409 | { |
||
410 | this.conversionRates = conversionRates; |
||
411 | } |
||
412 | |||
413 | /** |
||
414 | * @return the database |
||
415 | */ |
||
416 | public CurrenciesDatabase getDatabase() |
||
417 | { |
||
418 | return this.database; |
||
419 | } |
||
5 | PointedEar | 420 | } |