Basic checking and alerting should work

master
Vitaliy Filippov 2016-02-24 02:17:44 +03:00
parent 9382315ab1
commit eb565c7773
16 changed files with 2149 additions and 33 deletions

View File

@ -2,16 +2,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ru.vfilippov.electronreminder" >
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET" />
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" >

View File

@ -8,7 +8,6 @@ import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.Toast;
import org.xmlpull.v1.XmlPullParserException;
@ -43,7 +42,7 @@ public class ElectronReminderService extends IntentService
super("ElectronReminderService");
}
public static final String TAG = "Electron Reminder";
public static final String TAG = "ElectronReminderService";
private String token = null;
private NotificationManager mNotificationManager;
@ -71,7 +70,8 @@ public class ElectronReminderService extends IntentService
}
else if (action == "load_stations")
{
int cityId = intent.getIntExtra("city_id", 0);
String cityId = intent.getStringExtra("cityId");
initToken();
is = downloadUrl("http://mobile.rasp.yandex.net/export/suburban/city/"+cityId+"/stations?uuid="+token);
File file = new File(getFilesDir(), "stations_"+cityId+".xml");
fs = new FileOutputStream(file);
@ -81,9 +81,11 @@ public class ElectronReminderService extends IntentService
}
else if (action == "load_trips")
{
int from = intent.getIntExtra("from", 0);
int to = intent.getIntExtra("to", 0);
String from = intent.getStringExtra("from");
String to = intent.getStringExtra("to");
String date = intent.getStringExtra("date");
if (date == null)
date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
getTrips(from, to, date, false);
Intent i = new Intent("trips_loaded");
LocalBroadcastManager.getInstance(this).sendBroadcast(i);
@ -102,8 +104,8 @@ public class ElectronReminderService extends IntentService
SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd");
for (SimpleXmlNode reminder: remindersRoot.childrenByName.get("reminder"))
{
int from = Integer.valueOf(reminder.attributes.get("from"));
int to = Integer.valueOf(reminder.attributes.get("to"));
String from = reminder.attributes.get("from");
String to = reminder.attributes.get("to");
String time = reminder.attributes.get("time");
String date;
// FIXME Check for different date?
@ -132,11 +134,13 @@ public class ElectronReminderService extends IntentService
}
catch(IOException e)
{
Toast.makeText(getApplicationContext(), "Network error: "+e.getMessage(), Toast.LENGTH_LONG).show();
e.printStackTrace();
//Toast.makeText(getApplicationContext(), "Network error: "+e.getMessage(), Toast.LENGTH_LONG).show();
}
catch (XmlPullParserException e)
{
Toast.makeText(getApplicationContext(), "Error parsing XML: "+e.getMessage(), Toast.LENGTH_LONG).show();
e.printStackTrace();
//Toast.makeText(getApplicationContext(), "Error parsing XML: "+e.getMessage(), Toast.LENGTH_LONG).show();
}
finally
{
@ -155,7 +159,7 @@ public class ElectronReminderService extends IntentService
RecheckAlarmReceiver.completeWakefulIntent(intent);
}
private List<SimpleXmlNode> getTrips(int from, int to, String date, boolean decode) throws IOException, XmlPullParserException
private List<SimpleXmlNode> getTrips(String from, String to, String date, boolean decode) throws IOException, XmlPullParserException
{
if (token == null)
initToken();
@ -207,7 +211,7 @@ public class ElectronReminderService extends IntentService
}
// Post a notification indicating suburban changes
private void sendNotification(int notificationId, int from, int to, String fromName, String toName, String time)
private void sendNotification(int notificationId, String from, String to, String fromName, String toName, String time)
{
mNotificationManager = (NotificationManager)
this.getSystemService(Context.NOTIFICATION_SERVICE);

View File

@ -1,32 +1,222 @@
package ru.vfilippov.electronreminder;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.util.Xml;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.ListAdapter;
import android.widget.ListView;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import simple.SimpleXml;
import simple.SimpleXmlNode;
public class MainActivity extends AppCompatActivity
{
List<Trip> trips;
Map<String,SimpleXmlNode> reminders;
ArrayAdapter<String> stationsAdapter, citiesAdapter;
List<String> stations;
Map<String,String> stationIds;
String curCity, curFrom, curTo, curFromName, curToName;
private final String TAG = "ElectronReminder";
private AlertDialog citySelectDialog = null;
private RecheckAlarmReceiver alarmer = new RecheckAlarmReceiver();
private BroadcastReceiver srvReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
// put here whaterver you want your activity to do with the intent received
if (intent.getAction() == "cities_loaded")
loadCitiesFile();
else if (intent.getAction() == "stations_loaded")
loadStationsFile();
else if (intent.getAction() == "trips_loaded")
loadTripsFile();
}
};
private boolean loadCitiesFile()
{
SimpleXmlNode n = SimpleXml.readXmlFile(getFilesDir(), "suburban_cities.xml");
if (n != null)
{
if (citiesAdapter != null)
citiesAdapter.notifyDataSetChanged();
return true;
}
return false;
}
private void loadCities()
{
if (!loadCitiesFile())
{
Intent intent = new Intent(this, ElectronReminderService.class);
intent.setAction("load_cities");
intent.putExtra("cityId", curCity);
startService(intent);
}
}
private boolean loadStationsFile()
{
SimpleXmlNode n = SimpleXml.readXmlFile(getFilesDir(), "stations_"+curCity+".xml");
if (n != null)
{
List<SimpleXmlNode> sts = n.childrenByName.get("station");
stations.clear();
stationIds.clear();
int i = 0;
for (SimpleXmlNode s: sts)
{
stations.add(s.attributes.get("title"));
stationIds.put(s.attributes.get("title"), s.attributes.get("esr"));
i++;
}
stationsAdapter.notifyDataSetChanged();
return true;
}
return false;
}
private void loadStations()
{
if (curCity == null)
return;
if (!loadStationsFile())
{
Intent intent = new Intent(this, ElectronReminderService.class);
intent.setAction("load_stations");
intent.putExtra("cityId", curCity);
startService(intent);
}
}
private void loadRemindersFile()
{
SimpleXmlNode n = SimpleXml.readXmlFile(getFilesDir(), "reminders.xml");
reminders = null;
if (n != null)
{
reminders.clear();
for (SimpleXmlNode r: n.childrenByName.get("reminder"))
{
String key = r.attributes.get("from")+"|"+r.attributes.get("to")+"|"+r.attributes.get("time");
reminders.put(key, r);
}
}
}
private boolean loadTripsFile()
{
SimpleXmlNode xmltrips = SimpleXml.readXmlFile(getFilesDir(), "trips_"+curFrom+"_"+curTo+".xml");
if (xmltrips != null)
{
trips.clear();
for (SimpleXmlNode s: xmltrips.childrenByName.get("segment"))
{
Trip t = new Trip();
t.arrival = s.attributes.get("arrival").substring(11);
t.departure = s.attributes.get("departure").substring(11);
t.title = s.attributes.get("title");
t.reminderOn = reminders != null && reminders.containsKey(curFrom+"|"+curTo+"|"+t.departure);
trips.add(t);
}
ListView v = (ListView)findViewById(R.id.tripList);
((TripItemAdapter)v.getAdapter()).notifyDataSetChanged();
return true;
}
return false;
}
private void loadTrips()
{
if (curFrom == null || curTo == null)
return;
if (!loadTripsFile())
{
Intent intent = new Intent(this, ElectronReminderService.class);
intent.setAction("load_trips");
intent.putExtra("from", curFrom);
intent.putExtra("to", curTo);
startService(intent);
}
}
private void saveReminders()
{
File f = new File(getFilesDir(), "reminders.xml");
FileOutputStream fs = null;
try
{
fs = new FileOutputStream(f);
SimpleXmlNode n = new SimpleXmlNode();
n.name = "reminders";
for (SimpleXmlNode r: reminders.values())
n.appendChild(r);
SimpleXml.write(n, fs);
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if (fs != null)
fs.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
if (reminders.size() > 0)
alarmer.setAlarm(this);
else
alarmer.cancelAlarm(this);
}
protected void onResume()
{
super.onResume();
IntentFilter flt = new IntentFilter("cities_loaded");
flt.addAction("stations_loaded");
flt.addAction("trips_loaded");
flt.addAction("cities_loaded");
LocalBroadcastManager.getInstance(this).registerReceiver(srvReceiver, flt);
}
@ -41,6 +231,99 @@ public class MainActivity extends AppCompatActivity
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences sp = getSharedPreferences("prefs", MODE_PRIVATE);
curCity = sp.getString("cityId", "24461"); // Default is Moscow
reminders = new HashMap<String, SimpleXmlNode>();
trips = new ArrayList<Trip>();
ListView v = (ListView)findViewById(R.id.tripList);
v.setAdapter(new TripItemAdapter(this, trips));
v.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l)
{
Trip t = trips.get(i);
String key = curFrom+"|"+curTo+"|"+trips.get(i).departure;
if (t.reminderOn)
reminders.remove(key);
else
{
SimpleXmlNode n = new SimpleXmlNode();
n.name = "reminder";
n.attributes.put("from", curFrom);
n.attributes.put("to", curTo);
n.attributes.put("time", t.departure);
n.attributes.put("fromName", curFromName);
n.attributes.put("toName", curToName);
reminders.put(key, n);
}
t.reminderOn = !t.reminderOn;
((TripItemAdapter)adapterView.getAdapter()).notifyDataSetChanged();
saveReminders();
}
});
stations = new ArrayList<String>();
stationIds = new HashMap<String,String>();
stationsAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, stations);
AutoCompleteTextView stFrom = (AutoCompleteTextView)findViewById(R.id.fromView);
stFrom.setAdapter(stationsAdapter);
stFrom.addTextChangedListener(new TextWatcher()
{
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)
{
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2)
{
String id = stationIds.get(charSequence.toString());
if (id != null)
{
curFrom = id;
curFromName = charSequence.toString();
loadTrips();
}
}
@Override
public void afterTextChanged(Editable editable)
{
}
});
AutoCompleteTextView stTo = (AutoCompleteTextView)findViewById(R.id.toView);
stTo.setAdapter(stationsAdapter);
stTo.addTextChangedListener(new TextWatcher()
{
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)
{
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2)
{
String id = stationIds.get(charSequence.toString());
if (id != null)
{
curTo = id;
curToName = charSequence.toString();
loadTrips();
}
}
@Override
public void afterTextChanged(Editable editable)
{
}
});
loadStations();
}
@Override
@ -51,6 +334,34 @@ public class MainActivity extends AppCompatActivity
return true;
}
private void changeCity()
{
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(new ContextThemeWrapper(this, android.R.style.Theme_Black));
loadCities();
ArrayList<String> cities = new ArrayList<String>();
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line, cities);
LayoutInflater inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View searchView = inflater.inflate(R.layout.city_autocomplete, null);
final AutoCompleteTextView cityName = (AutoCompleteTextView)searchView.findViewById(R.id.city_name);
cityName.setAdapter(adapter);
alertDialogBuilder.setView(searchView);
alertDialogBuilder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int i)
{
dialog.dismiss();
}
});
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
@ -59,12 +370,18 @@ public class MainActivity extends AppCompatActivity
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings)
if (id == R.id.action_change_city)
{
//Intent intent = new Intent(this, SettingsActivity.class);
//startActivity(intent);
return true;
}
else if (id == R.id.action_check_all)
{
Intent intent = new Intent(this, ElectronReminderService.class);
intent.setAction("refresh_all");
startService(intent);
}
return super.onOptionsItemSelected(item);
}

View File

@ -46,7 +46,7 @@ public class RecheckAlarmReceiver extends WakefulBroadcastReceiver
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
// Set the alarm's trigger time to 8:30 a.m.
// FIXME Remove hardcoded time
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 30);

View File

@ -0,0 +1,7 @@
package ru.vfilippov.electronreminder;
public class Trip
{
public String title, departure, arrival;
public boolean reminderOn;
}

View File

@ -0,0 +1,78 @@
package ru.vfilippov.electronreminder;
import android.content.Context;
import android.content.Intent;
import android.text.Layout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import simple.SimpleXmlNode;
public class TripItemAdapter extends BaseAdapter
{
static class ViewHolder
{
protected TextView time, arrival, title;
protected ImageView reminder;
}
Context context;
LayoutInflater inflater;
List<Trip> list;
public TripItemAdapter(Context context, List<Trip> trips)
{
this.context = context;
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
list = trips;
}
@Override
public int getCount()
{
return list.size();
}
@Override
public Object getItem(int i)
{
return list.get(i);
}
@Override
public long getItemId(int i)
{
return i;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = null;
if (convertView == null)
{
view = inflater.inflate(R.layout.tripitem, null);
final ViewHolder h = new ViewHolder();
h.time = (TextView) view.findViewById(R.id.time);
h.arrival = (TextView) view.findViewById(R.id.arrival);
h.title = (TextView) view.findViewById(R.id.title);
h.reminder = (ImageView) view.findViewById(R.id.reminder);
view.setTag(h);
}
else
view = convertView;
Trip m = list.get(position);
ViewHolder h = (ViewHolder) view.getTag();
h.reminder.setImageResource(m.reminderOn ? R.drawable.fav_on : R.drawable.fav);
h.time.setText(m.departure);
h.title.setText(m.title);
h.arrival.setText(m.arrival);
return view;
}
}

View File

@ -4,11 +4,17 @@ import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import ru.vfilippov.electronreminder.Trip;
public class SimpleXml
{
public static SimpleXmlNode parse(InputStream in) throws IOException, XmlPullParserException
@ -43,10 +49,65 @@ public class SimpleXml
*/
}
protected static void writeNode(SimpleXmlNode n, XmlSerializer s) throws IOException
{
s.startTag("", n.name);
for (String key: n.attributes.keySet())
s.attribute("", key, n.attributes.get(key));
if (n.value != null && n.value.length() > 0)
s.text(n.value);
for (SimpleXmlNode child: n.children)
writeNode(child, s);
s.endTag("", n.name);
}
public static void write(SimpleXmlNode n, OutputStream out) throws IOException
{
XmlSerializer s = Xml.newSerializer();
s.setOutput(out, "utf-8");
s.startDocument("utf-8", true);
writeNode(n, s);
s.endDocument();
}
public static SimpleXmlNode readXmlFile(File dir, String name)
{
File file = new File(dir, name);
SimpleXmlNode n = null;
if (file.exists())
{
FileInputStream is = null;
try
{
is = new FileInputStream(file);
n = SimpleXml.parse(is);
}
catch (IOException e)
{
e.printStackTrace();
}
catch (XmlPullParserException e)
{
e.printStackTrace();
}
finally
{
try
{
is.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
return n;
}
public static SimpleXmlNode readFeed(XmlPullParser parser) throws IOException, XmlPullParserException
{
ArrayList<SimpleXmlNode> stack = new ArrayList<SimpleXmlNode>();
while (parser.next() != XmlPullParser.START_TAG) {}
while (parser.next() != XmlPullParser.END_DOCUMENT)
{
if (parser.getEventType() == XmlPullParser.START_TAG)
@ -72,7 +133,10 @@ public class SimpleXml
return stack.get(0);
}
else if (parser.getEventType() == XmlPullParser.TEXT)
stack.get(stack.size() - 1).value += parser.getText();
{
SimpleXmlNode n = stack.get(stack.size() - 1);
n.value = (n.value == null ? parser.getText() : n.value+parser.getText());
}
}
return null;
}

View File

@ -9,7 +9,7 @@
<ListView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/listView"
android:id="@+id/tripList"
android:layout_centerHorizontal="true"
android:layout_below="@+id/tableLayout"/>
@ -35,11 +35,14 @@
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/autoCompleteTextView"
android:id="@+id/fromView"
android:layout_column="1"
android:editable="true"
android:enabled="true"
android:layout_weight="1"/>
android:layout_weight="1"
android:linksClickable="false"
android:lines="1"
android:singleLine="true"/>
</TableRow>
<TableRow
@ -56,11 +59,15 @@
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/autoCompleteTextView2"
android:id="@+id/toView"
android:layout_column="1"
android:editable="true"
android:enabled="true"
android:layout_weight="1"/>
android:layout_weight="1"
android:longClickable="false"
android:linksClickable="false"
android:lines="1"
android:singleLine="true"/>
</TableRow>
</TableLayout>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoCompleteTextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/city_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10" />

View File

@ -1,15 +1,16 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:minHeight="30dip">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_width="50dip"
android:layout_height="match_parent"
android:touchscreenBlocksFocus="true"
android:measureWithLargestChild="true"
android:layout_weight="1">
>
<TextView
android:layout_width="wrap_content"
@ -38,8 +39,15 @@
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_width="30dip"
android:layout_height="match_parent"
android:layout_weight="1">
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/reminder"
android:layout_gravity="center"
android:src="@drawable/fav"/>
</LinearLayout>
</LinearLayout>

View File

@ -2,6 +2,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="ru.vfilippov.electronreminder.MainActivity">
<item android:id="@+id/action_settings" android:title="@string/action_settings"
<item android:id="@+id/action_change_city" android:title="@string/action_change_city"
android:orderInCategory="100" app:showAsAction="never" />
<item android:id="@+id/action_check_all" android:title="@string/action_check_all"
android:orderInCategory="101" app:showAsAction="never" />
</menu>

View File

@ -1,7 +1,8 @@
<resources>
<string name="app_name">Electron Reminder</string>
<string name="title_activity_main">Electron Reminder</string>
<string name="action_settings">Settings</string>
<string name="action_change_city">Change City</string>
<string name="action_check_all">Check All</string>
<string name="changes_detected">Suburban Changes Detected</string>
<string name="changes_details">Timetable changes: %1$s to %2$s, %3$s</string>
</resources>

2
example/startup.xml Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<startup><app cur_app_version="100" min_app_version="100"/><uuid>5dba382d820dba83478026e95ac689dd</uuid></startup>

File diff suppressed because one or more lines are too long

1615
example/suburban_cities.xml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long