Rev 17 | 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 | { |
||
18 | PointedEar | 105 | CurrenciesActivity.this.fillTableRates(); |
106 | |||
17 | PointedEar | 107 | Bundle extras = intent.getExtras(); |
108 | DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$ |
||
109 | Notifier.sendMessage(CurrenciesActivity.this, |
||
110 | extras.get(UpdateService.EXTRA_NUM_RATES) |
||
111 | + UpdateBroadcastReceiver.EXCHANGE_RATES_UPDATED_TO |
||
112 | + df.format(extras.get(UpdateService.EXTRA_DATE))); |
||
113 | } |
||
114 | } |
||
115 | } |
||
116 | |||
7 | PointedEar | 117 | /** Called when the activity is first created. */ |
5 | PointedEar | 118 | @Override |
7 | PointedEar | 119 | public void onCreate(Bundle savedInstanceState) |
5 | PointedEar | 120 | { |
121 | super.onCreate(savedInstanceState); |
||
7 | PointedEar | 122 | this.setContentView(R.layout.activity_currencies); |
5 | PointedEar | 123 | |
17 | PointedEar | 124 | UpdateBroadcastReceiver br = new UpdateBroadcastReceiver(); |
125 | this.registerReceiver(br, new IntentFilter(UpdateService.ACTION_UPDATE)); |
||
126 | |||
12 | PointedEar | 127 | /* Set up currency database, and retrieve conversion rates */ |
17 | PointedEar | 128 | this.database = new CurrenciesDatabase(this); |
129 | this.setConversionRates(this.getDatabase().getConversionRates()); |
||
14 | PointedEar | 130 | this.fillTableRates(); |
8 | PointedEar | 131 | |
7 | PointedEar | 132 | final EditText editValue1 = |
133 | (EditText) this.findViewById(R.id.currencies_edit_value1); |
||
134 | final EditText editValue2 = |
||
135 | (EditText) this.findViewById(R.id.currencies_edit_value2); |
||
136 | |||
137 | final OnKeyListener editValue1OnKey = new OnKeyListener() { |
||
138 | @Override |
||
139 | public boolean onKey(View v, int keyCode, KeyEvent event) |
||
140 | { |
||
141 | Editable editable1 = ((EditText) v).getText(); |
||
142 | |||
143 | Double value1; |
||
144 | try |
||
145 | { |
||
146 | value1 = Double.parseDouble(editable1.toString()); |
||
147 | } |
||
148 | catch (NumberFormatException e) |
||
149 | { |
||
150 | value1 = null; |
||
151 | } |
||
152 | |||
153 | String string2 = ""; //$NON-NLS-1$ |
||
154 | if (value1 != null) |
||
155 | { |
||
156 | string2 = CurrenciesActivity.this.getConvertedValue(value1, false); |
||
157 | } |
||
158 | |||
159 | editValue2.setText(string2); |
||
160 | |||
161 | return false; |
||
162 | } |
||
163 | }; |
||
164 | editValue1.setOnKeyListener(editValue1OnKey); |
||
165 | |||
166 | final OnKeyListener editValue2OnKey = new OnKeyListener() { |
||
167 | @Override |
||
168 | public boolean onKey(View v, int keyCode, KeyEvent event) |
||
169 | { |
||
170 | Editable editable2 = ((EditText) v).getText(); |
||
171 | |||
172 | Double value2; |
||
173 | try |
||
174 | { |
||
175 | value2 = Double.parseDouble(editable2.toString()); |
||
176 | } |
||
177 | catch (NumberFormatException e) |
||
178 | { |
||
179 | value2 = null; |
||
180 | } |
||
181 | |||
182 | String string1 = ""; //$NON-NLS-1$ |
||
183 | if (value2 != null) |
||
184 | { |
||
185 | string1 = CurrenciesActivity.this.getConvertedValue(value2, true); |
||
186 | } |
||
187 | |||
188 | editValue1.setText(string1); |
||
189 | |||
190 | return false; |
||
191 | } |
||
192 | }; |
||
193 | editValue2.setOnKeyListener(editValue2OnKey); |
||
194 | |||
195 | this.spinnerUnit1 = |
||
196 | (Spinner) this.findViewById(R.id.currencies_spinner_unit1); |
||
197 | this.spinnerUnit2 = |
||
198 | (Spinner) this.findViewById(R.id.currencies_spinner_unit2); |
||
199 | |||
200 | this.spinnerUnit1.setOnItemSelectedListener(new OnItemSelectedListener() { |
||
201 | @Override |
||
202 | public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, |
||
203 | long arg3) |
||
204 | { |
||
205 | /* Simulate input in second EditText so that first EditText is updated */ |
||
206 | editValue2OnKey.onKey(editValue2, 0, null); |
||
207 | } |
||
208 | |||
209 | @Override |
||
210 | public void onNothingSelected(AdapterView<?> arg0) |
||
211 | { |
||
212 | /* no-op */ |
||
213 | } |
||
214 | }); |
||
215 | |||
216 | this.spinnerUnit2.setSelection(1); |
||
217 | this.spinnerUnit2.setOnItemSelectedListener(new OnItemSelectedListener() { |
||
218 | @Override |
||
219 | public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, |
||
220 | long arg3) |
||
221 | { |
||
222 | /* Simulate input in first EditText so that second EditText is updated */ |
||
223 | editValue1OnKey.onKey(editValue1, 0, null); |
||
224 | } |
||
225 | |||
226 | @Override |
||
227 | public void onNothingSelected(AdapterView<?> arg0) |
||
228 | { |
||
229 | /* no-op */ |
||
230 | } |
||
231 | }); |
||
13 | PointedEar | 232 | |
233 | Button buttonClear = |
||
234 | (Button) this.findViewById(R.id.currencies_button_clear); |
||
235 | buttonClear.setOnClickListener(new OnClickListener() { |
||
236 | |||
237 | @SuppressWarnings("nls") |
||
238 | @Override |
||
239 | public void onClick(View v) |
||
240 | { |
||
241 | editValue1.setText(""); |
||
242 | editValue2.setText(""); |
||
243 | } |
||
244 | }); |
||
5 | PointedEar | 245 | } |
7 | PointedEar | 246 | |
247 | /** |
||
14 | PointedEar | 248 | * Fills the table with currency conversion rates |
249 | */ |
||
17 | PointedEar | 250 | public void fillTableRates() |
14 | PointedEar | 251 | { |
252 | TableLayout tableRates = |
||
253 | (TableLayout) this.findViewById(R.id.currencies_table_rates); |
||
254 | |||
17 | PointedEar | 255 | /* Remove any pre-existing currency rows */ |
256 | while (tableRates.getChildCount() > 3) |
||
14 | PointedEar | 257 | { |
17 | PointedEar | 258 | tableRates.removeViewAt(3); |
259 | } |
||
260 | |||
261 | for (Entry<String, ConversionData> factorEntry : this.getConversionRates() |
||
262 | .entrySet()) |
||
263 | { |
||
16 | PointedEar | 264 | TableRow row = new TableRow(this); |
14 | PointedEar | 265 | |
16 | PointedEar | 266 | TextView columnCurrency1 = new TextView(this); |
267 | columnCurrency1.setText(factorEntry.getKey()); |
||
268 | row.addView(columnCurrency1); |
||
14 | PointedEar | 269 | |
16 | PointedEar | 270 | TextView columnRate = new TextView(this); |
17 | PointedEar | 271 | final ConversionData conversionData = factorEntry.getValue(); |
272 | columnRate.setText(conversionData.getRate().toString()); |
||
16 | PointedEar | 273 | row.addView(columnRate); |
14 | PointedEar | 274 | |
17 | PointedEar | 275 | TextView columnUpdated = new TextView(this); |
276 | Date updated = conversionData.getUpdated(); |
||
277 | if (updated.getTime() > 0) |
||
278 | { |
||
279 | DateFormat df = |
||
280 | DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM); |
||
281 | columnUpdated.setText(df.format(updated)); |
||
282 | } |
||
283 | else |
||
284 | { |
||
285 | columnUpdated.setText(CurrenciesActivity.TEXT_NEVER); |
||
286 | } |
||
287 | |||
288 | row.addView(columnUpdated); |
||
289 | |||
16 | PointedEar | 290 | tableRates.addView(row); |
14 | PointedEar | 291 | } |
292 | } |
||
293 | |||
294 | /** |
||
7 | PointedEar | 295 | * @param value |
296 | * @return |
||
297 | */ |
||
298 | private String getConvertedValue(double value, boolean reverse) |
||
299 | { |
||
300 | int selectedItemPosition1 = this.spinnerUnit1.getSelectedItemPosition(); |
||
301 | int selectedItemPosition2 = this.spinnerUnit2.getSelectedItemPosition(); |
||
8 | PointedEar | 302 | String[] items = |
7 | PointedEar | 303 | this.getResources().getStringArray(R.array.currency_units_values); |
8 | PointedEar | 304 | String selectedItemValue1 = items[selectedItemPosition1]; |
305 | String selectedItemValue2 = items[selectedItemPosition2]; |
||
7 | PointedEar | 306 | |
307 | if (reverse) |
||
308 | { |
||
309 | String tmp = selectedItemValue1; |
||
310 | selectedItemValue1 = selectedItemValue2; |
||
311 | selectedItemValue2 = tmp; |
||
312 | } |
||
313 | |||
314 | Double newValue = value; |
||
315 | |||
16 | PointedEar | 316 | /* |
317 | * NOTE: Had to do it the complicated way because somehow the Android SDK |
||
318 | * won't get it another way |
||
319 | */ |
||
17 | PointedEar | 320 | ConversionData conversionData1 = null; |
321 | Double factorToEuro = 1.0; |
||
16 | PointedEar | 322 | if (selectedItemValue1 != null) |
7 | PointedEar | 323 | { |
17 | PointedEar | 324 | conversionData1 = this.getConversionRates().get(selectedItemValue1); |
325 | if (conversionData1 != null) |
||
326 | { |
||
327 | factorToEuro = conversionData1.getRate(); |
||
328 | } |
||
7 | PointedEar | 329 | } |
330 | |||
17 | PointedEar | 331 | ConversionData conversionData2 = null; |
332 | Double factorFromEuro = 1.0; |
||
16 | PointedEar | 333 | if (selectedItemValue2 != null) |
334 | { |
||
17 | PointedEar | 335 | conversionData2 = this.getConversionRates().get(selectedItemValue2); |
336 | if (conversionData2 != null) |
||
337 | { |
||
338 | factorFromEuro = conversionData2.getRate(); |
||
339 | } |
||
16 | PointedEar | 340 | } |
341 | |||
342 | newValue = newValue / factorToEuro * factorFromEuro; |
||
343 | |||
7 | PointedEar | 344 | return newValue.toString(); |
345 | } |
||
8 | PointedEar | 346 | |
347 | /* |
||
348 | * (non-Javadoc) |
||
349 | * |
||
350 | * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) |
||
351 | */ |
||
12 | PointedEar | 352 | /* |
353 | * (non-Javadoc) |
||
354 | * |
||
355 | * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) |
||
356 | */ |
||
8 | PointedEar | 357 | @Override |
358 | public boolean onCreateOptionsMenu(Menu menu) |
||
359 | { |
||
360 | MenuInflater inflater = this.getMenuInflater(); |
||
361 | inflater.inflate(R.menu.options, menu); |
||
362 | return true; |
||
363 | } |
||
364 | |||
365 | /* |
||
366 | * (non-Javadoc) |
||
367 | * |
||
368 | * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) |
||
369 | */ |
||
370 | @Override |
||
371 | public boolean onOptionsItemSelected(MenuItem item) |
||
372 | { |
||
17 | PointedEar | 373 | /* Handle item selection */ |
15 | PointedEar | 374 | switch (item.getItemId()) |
375 | { |
||
376 | case R.id.item_options_update: |
||
17 | PointedEar | 377 | /* |
378 | * Request the update service to run a thread to fetch the rates from |
||
379 | * the Web (project requirement) |
||
380 | */ |
||
381 | Intent intent = new Intent(this, UpdateService.class); |
||
382 | intent.setAction(UpdateService.ACTION_UPDATE); |
||
16 | PointedEar | 383 | |
17 | PointedEar | 384 | /* |
385 | * FIXME: Not thread-safe! |
||
386 | * Get the activity context from the intent directly instead |
||
387 | */ |
||
388 | UpdateService.setActivityContext(this); |
||
15 | PointedEar | 389 | |
17 | PointedEar | 390 | this.startService(intent); |
16 | PointedEar | 391 | return true; |
392 | |||
15 | PointedEar | 393 | default: |
394 | return super.onOptionsItemSelected(item); |
||
395 | } |
||
8 | PointedEar | 396 | } |
17 | PointedEar | 397 | |
398 | /** |
||
399 | * @return the conversionRates |
||
400 | */ |
||
401 | public HashMap<String, ConversionData> getConversionRates() |
||
402 | { |
||
403 | return this.conversionRates; |
||
404 | } |
||
405 | |||
406 | /** |
||
407 | * @param conversionRates |
||
408 | * the conversionRates to set |
||
409 | */ |
||
410 | public void setConversionRates(HashMap<String, ConversionData> conversionRates) |
||
411 | { |
||
412 | this.conversionRates = conversionRates; |
||
413 | } |
||
414 | |||
415 | /** |
||
416 | * @return the database |
||
417 | */ |
||
418 | public CurrenciesDatabase getDatabase() |
||
419 | { |
||
420 | return this.database; |
||
421 | } |
||
5 | PointedEar | 422 | } |