diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml deleted file mode 100644 index 8b8b96b..0000000 --- a/.idea/assetWizardSettings.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index 9601e05..0dcfb72 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/.idea/misc.xml b/.idea/misc.xml index 99202cc..cc04cd3 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,27 +5,37 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 7fbdb17..d98c658 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,15 +1,17 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' android { - compileSdkVersion 27 - buildToolsVersion "27.0.3" + compileSdkVersion 28 + buildToolsVersion '28.0.3' defaultConfig { applicationId "razorbacktransit.arcu.razorbacktransit" - minSdkVersion 19 - targetSdkVersion 27 + minSdkVersion 21 + targetSdkVersion 28 versionCode 17 versionName "4.2.1" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -23,23 +25,49 @@ android { // signingConfig signingConfigs.config } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { + def lifecycle_version = "2.0.0" implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.google.firebase:firebase-messaging:17.3.0' - androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { + implementation 'com.google.firebase:firebase-core:16.0.5' + implementation 'com.google.firebase:firebase-messaging:17.3.4' + androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' }) - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.github.barteksc:android-pdf-viewer:2.8.1' - implementation 'com.android.support:design:27.1.1' - implementation 'com.android.support.constraint:constraint-layout:1.1.2' - implementation 'com.android.support:support-v4:27.1.1' - implementation 'com.google.firebase:firebase-core:15.0.0' - implementation 'com.google.android.gms:play-services-maps:15.0.0' - implementation 'com.squareup.okhttp3:okhttp:3.10.0' testImplementation 'junit:junit:4.12' + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'com.github.barteksc:android-pdf-viewer:2.8.1' + implementation 'com.google.android.material:material:1.1.0-alpha02' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'com.google.android.gms:play-services-maps:16.0.0' + implementation 'com.squareup.okhttp3:okhttp:3.12.0' + implementation 'com.squareup.retrofit2:retrofit:2.5.0' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0' + implementation 'com.squareup.retrofit2:converter-moshi:2.4.0' + implementation 'com.squareup.moshi:moshi-kotlin:1.8.0' + implementation 'io.reactivex.rxjava2:rxjava:2.2.6' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' + implementation 'com.jakewharton.rxbinding3:rxbinding-core:3.0.0-alpha1' + implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' + implementation 'com.jakewharton:butterknife:9.0.0' + kapt 'com.jakewharton:butterknife-compiler:9.0.0' + implementation 'androidx.core:core-ktx:1.0.1' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "android.arch.lifecycle:extensions:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + implementation 'com.github.bumptech.glide:glide:4.8.0' + kapt 'com.github.bumptech.glide:compiler:4.8.0' + } apply plugin: 'com.google.gms.google-services' +apply plugin: 'kotlin-android' +repositories { + mavenCentral() +} diff --git a/app/src/androidTest/java/razorbacktransit/arcu/razorbacktransit/ExampleInstrumentedTest.java b/app/src/androidTest/java/razorbacktransit/arcu/razorbacktransit/ExampleInstrumentedTest.java index ca016e5..d09c403 100644 --- a/app/src/androidTest/java/razorbacktransit/arcu/razorbacktransit/ExampleInstrumentedTest.java +++ b/app/src/androidTest/java/razorbacktransit/arcu/razorbacktransit/ExampleInstrumentedTest.java @@ -1,8 +1,8 @@ package razorbacktransit.arcu.razorbacktransit; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -18,7 +18,7 @@ public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { - // Context of the app under test. + // Context of the applivation under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("razorbacktransit.arcu.razorbacktransit", appContext.getPackageName()); diff --git a/app/src/debug/res/values/google_maps_api.xml b/app/src/debug/res/values/google_maps_api.xml index b669533..7072663 100644 --- a/app/src/debug/res/values/google_maps_api.xml +++ b/app/src/debug/res/values/google_maps_api.xml @@ -21,6 +21,6 @@ string in this file. --> - AIzaSyCw6Ph5uz-2fnVzVMFDIZs0B8aBXONywOk + AIzaSyCJ_t16aFkINz-nsI1T2BMoP6EPrz1iM80 diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/LiveMapFragment.java b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/LiveMapFragment.java deleted file mode 100644 index 5ad1dd1..0000000 --- a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/LiveMapFragment.java +++ /dev/null @@ -1,643 +0,0 @@ -package razorbacktransit.arcu.razorbacktransit; - -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.support.v4.app.Fragment; -import android.util.Base64; -import android.util.DisplayMetrics; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.google.android.gms.maps.CameraUpdate; -import com.google.android.gms.maps.CameraUpdateFactory; -import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.OnMapReadyCallback; -import com.google.android.gms.maps.SupportMapFragment; -import com.google.android.gms.maps.UiSettings; -import com.google.android.gms.maps.model.BitmapDescriptor; -import com.google.android.gms.maps.model.BitmapDescriptorFactory; -import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.LatLngBounds; -import com.google.android.gms.maps.model.Marker; -import com.google.android.gms.maps.model.MarkerOptions; -import com.google.android.gms.maps.model.Polyline; -import com.google.android.gms.maps.model.PolylineOptions; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; - -public class LiveMapFragment extends Fragment implements OnMapReadyCallback, GoogleMap.OnMarkerClickListener { - - private OnFragmentInteractionListener mListener; - private GoogleMap googleMap; - private SharedPreferences sharedPreferences; - private SharedPreferences.Editor editor; - - private final OkHttpClient client = new OkHttpClient(); - private final List busMarkers = new ArrayList<>(); - private final HashMap stopMarkerHashMap = new HashMap<>(); - private List routeIDsForBusses = new ArrayList<>(); - private Timer busTimer; - - private int stopImageWidth; - private int stopImageHeight; - private int busImageWidth; - private int busImageHeight; - - public LiveMapFragment() { - // Required empty public constructor - } - - public static LiveMapFragment newInstance(String param1, String param2) { - return new LiveMapFragment(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - try { - sharedPreferences = getActivity().getPreferences(Context.MODE_PRIVATE); - } catch (NullPointerException e) { - e.printStackTrace(); - } - - editor = sharedPreferences.edit(); - - final int widthPixels = getActivity().getResources().getDisplayMetrics().widthPixels; - - stopImageWidth = (int) (widthPixels * 0.02638888889); - stopImageHeight = (int) (widthPixels * 0.02638888889); - - busImageWidth = (int) (widthPixels * 0.05833333333); - busImageHeight = (int) (widthPixels * 0.05833333333 * 1.6153846154); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - View view = inflater.inflate(R.layout.fragment_maps, container, false); - SupportMapFragment mapFragment = (SupportMapFragment) this.getChildFragmentManager() - .findFragmentById(R.id.map); - mapFragment.getMapAsync(this); - - return view; - } - - @Override - public void onResume() { - super.onResume(); - - if (this.googleMap != null) { - this.googleMap.clear(); - } - - try { - loadRoutes(); - loadBusses(); - } catch (Exception e) { - e.printStackTrace(); - } - - busTimer = new Timer(); - busTimer.schedule(new TimerTask() { - @Override - public void run() { - try { - loadBusses(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }, 5000, 5000); - } - - @Override - public void onPause() { - super.onPause(); - - editor.apply(); - busTimer.cancel(); - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (context instanceof OnFragmentInteractionListener) { - mListener = (OnFragmentInteractionListener) context; - } else { - throw new RuntimeException(context.toString() - + " must implement OnFragmentInteractionListener"); - } - } - - @Override - public void onDetach() { - super.onDetach(); - mListener = null; - } - - @Override - public void onMapReady(GoogleMap googleMap) { - this.googleMap = googleMap; - - this.googleMap.moveCamera(CameraUpdateFactory.newLatLng(new LatLng(36.09, -94.1785))); - this.googleMap.moveCamera(CameraUpdateFactory.zoomTo(12.0f)); - this.googleMap.setMinZoomPreference(10); - this.googleMap.setOnMarkerClickListener(this); - UiSettings uiSettings = this.googleMap.getUiSettings(); - uiSettings.setRotateGesturesEnabled(true); - uiSettings.setTiltGesturesEnabled(false); - - } - - public void loadRoutes() throws Exception { - - Request request = new Request.Builder() - .url(getString(R.string.route_url)) - .build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - if (!response.isSuccessful()) - throw new IOException("Unexpected code " + response); - - String responseText = responseBody.string(); - responseText = responseText.substring(7, responseText.length() - 2); - - try { - final JSONArray jsonArray = new JSONArray(responseText); - - Runnable myRunnable = new Runnable() { - @Override - public void run() { - - List routeIDs = new ArrayList<>(); - for (int i = 0; i < jsonArray.length(); i++) { - - try { - final JSONObject jsonObject = jsonArray.getJSONObject(i); - if (jsonObject.getString("inService").equals("1")) { - - List points = new ArrayList<>(); - String[] coordinates = jsonObject.getString("shape").split(","); - - if (coordinates.length > 1) { - for (String coordinate : coordinates) { - String[] latlong = coordinate.split(" "); - points.add(new LatLng( - Double.parseDouble(latlong[0]), - Double.parseDouble(latlong[1]))); - } - final Polyline polyline = googleMap.addPolyline(new PolylineOptions() - .color((int) Long.parseLong(("99" + jsonObject.getString("color") - .substring(1)), 16))); - polyline.setPoints(points); - } - if (!routeIDs.contains(jsonObject.getString("id"))) { - routeIDs.add(jsonObject.getString("id")); - } - } - } catch (JSONException e) { - e.printStackTrace(); - } - } - try { - routeIDsForBusses = new ArrayList<>(routeIDs); - loadBusses(); - loadStops(routeIDs); - } catch (Exception e) { - e.printStackTrace(); - } - } - }; - new Handler(Looper.getMainLooper()).post(myRunnable); - - } catch (JSONException e) { - e.printStackTrace(); - } - } - } - }); - } - - private String buildStopURL(List routeIDs) { - String stopString = getString(R.string.stop_url); - for (String id : routeIDs) { - stopString = stopString.concat("-" + id); - } - return stopString; - } - - private String buildStopImageULR(String id, List routeIDs) { - String urlString = "https://campusdata.uark.edu/api/stopimages?stopId=" + id + "&routeIds=undefined"; - for (String routeID : routeIDs) { - urlString = urlString.concat("-" + routeID); - } - return urlString; - } - - public void loadStops(final List routeIDs) throws Exception { - - Request request = new Request.Builder() - .url(buildStopURL(routeIDs)) - .build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - if (!response.isSuccessful()) - throw new IOException("Unexpected code " + response); - - String responseText = responseBody.string(); - responseText = responseText.substring(10, responseText.length() - 2); - - try { - final JSONArray jsonArray = new JSONArray(responseText); - stopMarkerHashMap.clear(); - - if (getActivity() == null) { - return; - } - - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - - for (int i = 0; i < jsonArray.length(); i++) { - - try { - final JSONObject jsonObject = jsonArray.getJSONObject(i); - - final Marker marker = googleMap.addMarker(new MarkerOptions() - .flat(true) - .alpha(0) - .snippet(jsonObject.getString("name")) - .title("Next Arrival: ...") - .position(new LatLng( - Double.parseDouble(jsonObject.getString("latitude")), - Double.parseDouble(jsonObject.getString("longitude"))))); - - stopMarkerHashMap.put(marker, jsonObject.getString("id")); - String encodedImage = sharedPreferences.getString(jsonObject.getString("id") + "1", ""); - - if (!encodedImage.equals("")) { - byte[] bytes = Base64.decode(encodedImage, Base64.DEFAULT); - final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); - final BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory. - fromBitmap(bitmap); - marker.setIcon(bitmapDescriptor); - marker.setAlpha(1); - } - - Request iconRequest = new Request - .Builder() - .url(buildStopImageULR(jsonObject.getString("id"), routeIDs)) - .build(); - - client.newCall(iconRequest).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - if (!response.isSuccessful()) - throw new IOException("Unexpected code " + response); - - InputStream inputStream = response.body().byteStream(); - final Bitmap bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeStream(inputStream), stopImageWidth, stopImageHeight, true); - final BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory. - fromBitmap(bitmap); - - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); - byte[] bytes = byteArrayOutputStream.toByteArray(); - - editor.putString(jsonObject.getString("id") + "1", Base64.encodeToString(bytes, Base64.DEFAULT)); - - if (getActivity() == null) { - return; - } - - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - try { - marker.setIcon(bitmapDescriptor); - marker.setAlpha(1); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - } - }); - } catch (JSONException e) { - e.printStackTrace(); - } - } - }); - - } catch (JSONException e) { - e.printStackTrace(); - } - - LatLngBounds.Builder builder = new LatLngBounds.Builder(); - - for (Marker marker : stopMarkerHashMap.keySet().toArray(new Marker[]{})) { - builder.include(marker.getPosition()); - } - LatLngBounds bounds = builder.build(); - int padding = busImageHeight; // offset from edges of the map in pixels - CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, padding); - googleMap.animateCamera(cu); - } - } - - }); - } catch (JSONException e) { - e.printStackTrace(); - } - } catch (NullPointerException e) { - e.printStackTrace(); - } - } - }); - } - - public String getBusImageKey(String heading, String color) { - Double x = Double.parseDouble(heading); - return color + Integer.toString(((x.intValue() + 29) / 30) * 30); - } - - private String buildBusURL() { - String part1 = "https://campusdata.uark.edu/api/buses?callback=jQuery18002674589609856972_1510069338014&routeIds=undefined"; - String part2 = "&_=1510069339459"; - for (String id : routeIDsForBusses) { - part1 = part1.concat("-" + id); - } - part1 = part1.concat(part2); - return part1; - } - - public void loadBusses() throws Exception { - - Request request = new Request.Builder() - .url(buildBusURL()) - .build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - if (!response.isSuccessful()) - throw new IOException("Unexpected code " + response); - - String responseText = responseBody.string(); - responseText = responseText.substring(41, responseText.length() - 2); - - try { - final JSONArray jsonArray = new JSONArray(responseText); - - if (getActivity() == null) { - return; - } - - getActivity().runOnUiThread(new Runnable() { - - @Override - public void run() { - - List oldMarkers = new ArrayList<>(busMarkers); - busMarkers.clear(); - - for (int i = 0; i < jsonArray.length(); i++) { - - try { - final JSONObject jsonObject = jsonArray.getJSONObject(i); - - - - final Marker marker = googleMap.addMarker(new MarkerOptions() - .position(new LatLng( - Double.parseDouble(jsonObject.getString("latitude")), - Double.parseDouble(jsonObject.getString("longitude")))) - .flat(true) - .alpha(0) - .title(jsonObject.getString("routeName"))); - - busMarkers.add(marker); - final String key = getBusImageKey(jsonObject.getString("heading"), jsonObject.getString("color")); - final String encodedImage = sharedPreferences.getString(key, ""); - - if (!encodedImage.equals("")) { - - byte[] bytes = Base64.decode(encodedImage, Base64.DEFAULT); - final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); - final BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory. - fromBitmap(bitmap); - marker.setIcon(bitmapDescriptor); - marker.setAlpha(1); - - } else { - - Request iconRequest = new Request - .Builder() - .url("https://campusdata.uark.edu/api/busimages?color=" + jsonObject.getString("color").substring(1) + "&heading=" + jsonObject.getString("heading")) - .build(); - - client.newCall(iconRequest).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - if (!response.isSuccessful()) - throw new IOException("Unexpected code " + response); - - InputStream inputStream = response.body().byteStream(); - final Bitmap bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeStream(inputStream), busImageWidth, busImageHeight, true); - final BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory. - fromBitmap(bitmap); - - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); - byte[] bytes = byteArrayOutputStream.toByteArray(); - - editor.putString(key, Base64.encodeToString(bytes, Base64.DEFAULT)); - editor.apply(); - - if (getActivity() == null) { - return; - } - - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - if (marker.isVisible()) { - try { - marker.setIcon(bitmapDescriptor); - marker.setAlpha(1); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - } - } - }); - } - } - }); - - } - } catch (JSONException e) { - e.printStackTrace(); - } - } - for (Marker temp : oldMarkers) { - try { - temp.remove(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - } - } - }); - } catch (JSONException e) { - e.printStackTrace(); - } - } catch (NullPointerException e) { - e.printStackTrace(); - } - } - }); - } - - @Override - public boolean onMarkerClick(final Marker marker) { - - if (!stopMarkerHashMap.containsKey(marker)) { - return false; - } - - marker.setTitle("Next Arrival: Loading..."); - marker.showInfoWindow(); - - final String url = "https://campusdata.uark.edu/api/routes?callback=jQuery18004251280482585251_1507605405541&stopId=" + stopMarkerHashMap.get(marker) + "&_=1507605550296"; - - final Request request = new Request.Builder() - .url(url) - .build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - try (ResponseBody responseBody = response.body()) { - if (!response.isSuccessful()) - throw new IOException("Unexpected code " + response); - - String responseText = responseBody.string(); - responseText = responseText.substring(41, responseText.length() - 2); - try { - - final JSONArray jsonArray = new JSONArray(responseText); - - if (getActivity() == null) { - return; - } - - getActivity().runOnUiThread(new Runnable() { - - @Override - public void run() { - - for (int i = 0; i < jsonArray.length(); i++) { - - try { - JSONObject jsonObject = jsonArray.getJSONObject(i); - - final String nextArrival = jsonObject.getString("nextArrival"); - - if (!nextArrival.equals("...") && !nextArrival.equals("null")) { - - marker.setTitle("Next Arival: " + nextArrival); - marker.showInfoWindow(); - - return; - } - - } catch (JSONException e) { - e.printStackTrace(); - } - } - marker.setTitle("Next Arrival: None"); - marker.showInfoWindow(); - } - }); - } catch (JSONException e) { - e.printStackTrace(); - } - } catch (NullPointerException e) { - e.printStackTrace(); - } - } - }); - return false; - } - - public interface OnFragmentInteractionListener { - // TODO: Update argument type and name - void onFragmentInteraction(Uri uri); - } -} diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/LiveMapFragment.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/LiveMapFragment.kt new file mode 100644 index 0000000..cbbf9e9 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/LiveMapFragment.kt @@ -0,0 +1,163 @@ +package razorbacktransit.arcu.razorbacktransit + +import android.content.Context +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.MainThread +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProviders +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.PolylineOptions +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import razorbacktransit.arcu.razorbacktransit.model.bus.Bus +import razorbacktransit.arcu.razorbacktransit.model.route.Route +import razorbacktransit.arcu.razorbacktransit.network.LiveMapViewModel +import razorbacktransit.arcu.razorbacktransit.network.SubmitUiModel +import razorbacktransit.arcu.razorbacktransit.utils.clearMarkers + + +class LiveMapFragment: Fragment(), OnMapReadyCallback, GoogleMap.OnMarkerClickListener +{ + private var mListener: OnFragmentInteractionListener? = null + private var googleMap: GoogleMap? = null + + private var busMarkers = arrayListOf() + private val stopMarkers = arrayListOf() + + private lateinit var disposable: Disposable + private lateinit var viewModel: LiveMapViewModel + + override fun onCreate(savedInstanceState: Bundle?) + { + super.onCreate(savedInstanceState) + viewModel = ViewModelProviders.of(this).get(LiveMapViewModel::class.java) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? + { + + val view = inflater.inflate(R.layout.fragment_maps, container, false) + val mapFragment = this.childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? + mapFragment!!.getMapAsync(this) + + return view + } + + override fun onResume() + { + super.onResume() + + disposable = viewModel.observeAll + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + when(it) + { + is SubmitUiModel.SubmitRoute -> updateRoutes(it.route) + is SubmitUiModel.SubmitBuses -> updateBusses(it.bus) + is SubmitUiModel.SubmitStop -> updateStops(it.stop.icon!!) + } + } + + if (googleMap != null) + { + busMarkers.clearMarkers() + googleMap?.clear() + } + } + + override fun onAttach(context: Context?) + { + super.onAttach(context) + if (context is OnFragmentInteractionListener) + { + mListener = context + } + else + { + throw RuntimeException(context!!.toString() + " must implement OnFragmentInteractionListener") + } + } + + override fun onDetach() + { + super.onDetach() + mListener = null + disposable.dispose() + } + + override fun onMapReady(map: GoogleMap) + { + googleMap = map + googleMap?.apply { + moveCamera(CameraUpdateFactory.newLatLng(LatLng(36.09, -94.1785))) + moveCamera(CameraUpdateFactory.zoomTo(12.0f)) + setMinZoomPreference(10f) + setOnMarkerClickListener(this@LiveMapFragment) + uiSettings.isRotateGesturesEnabled = true + uiSettings.isTiltGesturesEnabled = false + } + } + + @MainThread + private fun updateRoutes(routes: List) + { + Log.d("DEBUGGING", "updateRoutes()") + for (route in routes) + { + if (route.coordinates.size > 1) + { + val polylineOptions = PolylineOptions().color(route.color) + val polyline = googleMap!!.addPolyline(polylineOptions) + polyline.points = route.coordinates + } + } + } + + @MainThread + private fun updateBusses(buses: List) + { + Log.d("DEBUGGING", "updateBusses()") + val markers = ArrayList() + for(bus in buses) + { + markers += googleMap!!.addMarker(bus.icon).apply { alpha = 1f } + } + busMarkers.clearMarkers() + busMarkers = markers + } + + @MainThread + private fun updateStops(stop: MarkerOptions) + { + Log.d("DEBUGGING", "updateStops()") + stopMarkers += googleMap!!.addMarker( stop ).apply { alpha = 1f } + } + + override fun onMarkerClick(marker: Marker): Boolean = false + + interface OnFragmentInteractionListener + { + // TODO: Update argument type and name + fun onFragmentInteraction(uri: Uri) + } + + companion object + { + + fun newInstance(param1: String, param2: String): LiveMapFragment + { + return LiveMapFragment() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MainActivity.java b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MainActivity.java index d30914d..11be525 100644 --- a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MainActivity.java +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MainActivity.java @@ -2,18 +2,21 @@ import android.net.Uri; import android.os.Bundle; -import android.support.design.widget.NavigationView; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.view.GravityCompat; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import com.google.android.material.navigation.NavigationView; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; +import androidx.core.view.GravityCompat; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import android.view.MenuItem; import com.google.firebase.analytics.FirebaseAnalytics; +import butterknife.BindView; +import butterknife.ButterKnife; + public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, LiveMapFragment.OnFragmentInteractionListener, @@ -21,10 +24,12 @@ public class MainActivity extends AppCompatActivity SchedulesFragment.OnFragmentInteractionListener, RoutesFragment.OnFragmentInteractionListener, ViewScheduleFragment.OnFragmentInteractionListener, - ViewRouteFragment.OnFragmentInteractionListener { + ViewRouteFragment.OnFragmentInteractionListener +{ - int lastMenuItemId = 10; - private NavigationView navigationView; + @BindView(R.id.nav_view) NavigationView navigationView; + @BindView(R.id.toolbar) Toolbar toolbar; + @BindView(R.id.drawer_layout) DrawerLayout drawer; private LiveMapFragment liveMapFragment; private SchedulesFragment schedulesFragment; private RoutesFragment routesFragment; @@ -33,6 +38,7 @@ public class MainActivity extends AppCompatActivity private ViewRouteFragment viewRouteFragment; private FirebaseAnalytics mFirebaseAnalytics; private final FragmentManager fragmentManager = getSupportFragmentManager(); + int lastMenuItemId = 10; @Override protected void onCreate(Bundle savedInstanceState) { @@ -41,16 +47,14 @@ protected void onCreate(Bundle savedInstanceState) { mFirebaseAnalytics = FirebaseAnalytics.getInstance(this); setTheme(R.style.AppTheme_NoActionBar); setContentView(R.layout.activity_main); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + ButterKnife.bind(this); setSupportActionBar(toolbar); - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.setDrawerListener(toggle); toggle.syncState(); - navigationView = (NavigationView) findViewById(R.id.nav_view); navigationView.setNavigationItemSelectedListener(this); liveMapFragment = new LiveMapFragment(); @@ -70,7 +74,6 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onBackPressed() { - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { @@ -88,7 +91,6 @@ public boolean onNavigationItemSelected(MenuItem item) { if (lastMenuItemId == navigationView.getMenu().getItem(1).getItemId() || lastMenuItemId == navigationView.getMenu().getItem(2).getItemId()) { lastMenuItemId = id; } else { - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } @@ -213,7 +215,6 @@ public boolean onNavigationItemSelected(MenuItem item) { } } - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } @@ -250,7 +251,7 @@ public void onFragmentInteraction(ViewRouteFragment fragment, String title) { FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); Bundle bundle = new Bundle(); bundle.putString(FirebaseAnalytics.Param.ITEM_ID, title); - bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, "Route Viewed"); + bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, "BusRoute Viewed"); mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle); fragmentTransaction.add(R.id.container, viewRouteFragment).addToBackStack(viewRouteFragment.getClass().getSimpleName()); diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MyFirebaseInstanceIDService.java b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MyFirebaseInstanceIDService.java index 8c57e42..f38e7f7 100644 --- a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MyFirebaseInstanceIDService.java +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MyFirebaseInstanceIDService.java @@ -46,7 +46,7 @@ public void onTokenRefresh() { // If you want to send messages to this application instance or // manage this apps subscriptions on the server side, send the - // Instance ID token to your app server. + // Instance ID token to your applivation server. sendRegistrationToServer(refreshedToken); } // [END refresh_token] @@ -60,6 +60,6 @@ public void onTokenRefresh() { * @param token The new token. */ private void sendRegistrationToServer(String token) { - // TODO: Implement this method to send token to your app server. + // TODO: Implement this method to send token to your applivation server. } } \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MyFirebaseMessagingService.java b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MyFirebaseMessagingService.java index edd56cb..5006584 100644 --- a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MyFirebaseMessagingService.java +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/MyFirebaseMessagingService.java @@ -26,26 +26,23 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.RingtoneManager; import android.net.Uri; -import android.support.v4.app.NotificationCompat; +import androidx.core.app.NotificationCompat; import android.util.Log; import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; public class MyFirebaseMessagingService extends FirebaseMessagingService { private static final String TAG = "MyAndroidFCMService"; @Override public void onMessageReceived(RemoteMessage remoteMessage) { - //Log data to Log Cat + //Log busRoutes to Log Cat Log.d(TAG, "From: " + remoteMessage.getFrom()); Log.d(TAG, "Notification Message Body: " + remoteMessage.getNotification().getBody()); //create notification diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/PDFFactory.java b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/PDFFactory.java index 3cc07f2..90da45f 100644 --- a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/PDFFactory.java +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/PDFFactory.java @@ -14,7 +14,7 @@ public class PDFFactory { private PDF PURPLE44 = new PDF("Purple 44", "purple_44_schedule"); private PDF RED26 = new PDF("Red 26", "red_26_schedule"); private PDF REMOTEEXPRESS = new PDF("Remote Express 48", "remoteexpress_48_schedule"); - private PDF ROUTE13 = new PDF("Route 13", "route_13_schedule"); + private PDF ROUTE13 = new PDF("BusRoute 13", "route_13_schedule"); private PDF TAN35 = new PDF("Tan 35", "tan_35_schedule"); private PDF YELLOW12 = new PDF("Yellow 12", "yellow_12_schedule"); private PDF GRAY21 = new PDF("Gray 21", "gray_21_schedule"); @@ -34,7 +34,7 @@ public class PDFFactory { private PDF PURPLE44_ROUTE = new PDF("Purple 44", "purple_44_route"); private PDF RED26_ROUTE = new PDF("Red 26", "red_26_route"); private PDF REMOTEEXPRESS_ROUTE = new PDF("Remote Express 48", "remoteexpress_48_route"); - private PDF ROUTE13_ROUTE = new PDF("Route 13", "route_13_route"); + private PDF ROUTE13_ROUTE = new PDF("BusRoute 13", "route_13_route"); private PDF TAN35_ROUTE = new PDF("Tan 35", "tan_35_route"); private PDF YELLOW12_ROUTE = new PDF("Yellow 12", "yellow_12_route"); private PDF GRAY21_ROUTE = new PDF("Gray 21", "gray_21_route"); diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ParkingMapFragment.java b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ParkingMapFragment.java index ab10756..cdcaa02 100644 --- a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ParkingMapFragment.java +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ParkingMapFragment.java @@ -3,7 +3,7 @@ import android.content.Context; import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -12,6 +12,9 @@ import java.io.InputStream; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; /** @@ -24,9 +27,9 @@ */ public class ParkingMapFragment extends Fragment { - private PDFView pdfView; + @BindView(R.id.pdfView) PDFView pdfView; + private Unbinder unbinder; private Boolean loaded; - private OnFragmentInteractionListener mListener; public ParkingMapFragment() { @@ -53,8 +56,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_parking_map, container, false); - - pdfView = (PDFView)view.findViewById(R.id.pdfView); + unbinder = ButterKnife.bind(this, view); InputStream inputStream = getResources().openRawResource( getResources().getIdentifier("parkmap", @@ -97,6 +99,12 @@ public void onDetach() { mListener = null; } + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + /** * This interface must be implemented by activities that contain this * fragment to allow an interaction in this fragment to be communicated diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/RoutesFragment.java b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/RoutesFragment.java index 38fd7d1..3fe2eb1 100644 --- a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/RoutesFragment.java +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/RoutesFragment.java @@ -2,8 +2,8 @@ import android.content.Context; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.ListFragment; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.ListFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/SchedulesFragment.java b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/SchedulesFragment.java index eaefd4a..ff648ad 100644 --- a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/SchedulesFragment.java +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/SchedulesFragment.java @@ -2,8 +2,8 @@ import android.content.Context; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.ListFragment; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.ListFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ViewRouteFragment.java b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ViewRouteFragment.java index d924195..694b097 100644 --- a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ViewRouteFragment.java +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ViewRouteFragment.java @@ -3,7 +3,7 @@ import android.content.Context; import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -12,6 +12,9 @@ import java.io.InputStream; +import butterknife.BindView; +import butterknife.ButterKnife; + /** * A simple {@link Fragment} subclass. @@ -27,7 +30,7 @@ public class ViewRouteFragment extends Fragment { private static final String ARG_PARAM1 = "filepath"; private String mParam1; - private PDFView pdfView; + @BindView(R.id.pdfView) PDFView pdfView; private OnFragmentInteractionListener mListener; @@ -56,7 +59,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_view_route, container, false); - pdfView = (PDFView)view.findViewById(R.id.pdfView); + ButterKnife.bind(this, view); InputStream inputStream = getResources().openRawResource( getResources().getIdentifier(mParam1, diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ViewScheduleFragment.java b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ViewScheduleFragment.java index f82999d..a763e71 100644 --- a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ViewScheduleFragment.java +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/ViewScheduleFragment.java @@ -3,7 +3,7 @@ import android.content.Context; import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -13,6 +13,9 @@ import java.io.InputStream; +import butterknife.BindView; +import butterknife.ButterKnife; + /** * A simple {@link Fragment} subclass. @@ -24,11 +27,9 @@ */ public class ViewScheduleFragment extends Fragment { + @BindView(R.id.pdfView) PDFView pdfView; private static final String ARG_PARAM1 = "filepath"; - private String mParam1; - private PDFView pdfView; - private OnFragmentInteractionListener mListener; public ViewScheduleFragment() { @@ -57,7 +58,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_view_schedule, container, false); - pdfView = (PDFView)view.findViewById(R.id.pdfView); + ButterKnife.bind(this, view); InputStream inputStream = getResources().openRawResource( getResources().getIdentifier(mParam1, diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/bus/Bus.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/bus/Bus.kt new file mode 100644 index 0000000..21d75f2 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/bus/Bus.kt @@ -0,0 +1,27 @@ +package razorbacktransit.arcu.razorbacktransit.model.bus + +import com.google.android.gms.maps.model.BitmapDescriptor +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.MarkerOptions + +data class Bus(val id: String, + val fleet: String, + val name: String, + val description: String, + val zonarId: String, + val gpsId: String, + val coordinates: LatLng, + val speed: Float, + val heading: Int, + val power: Boolean, + val date: String, + val color: String?, + val routeName: String?, + val routeId: String?, + val distance: Double?, + val nextStop: String?, + val nextArrival: String?) +{ + @Transient var icon: MarkerOptions? = null + @Transient var ids: String? = null +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/bus/BusJson.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/bus/BusJson.kt new file mode 100644 index 0000000..7b1bbc0 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/bus/BusJson.kt @@ -0,0 +1,45 @@ +package razorbacktransit.arcu.razorbacktransit.model.bus + +import com.google.android.gms.maps.model.LatLng +import com.squareup.moshi.Json +import kotlin.math.roundToInt + +data class BusJson(@Json(name = "id") val id: String, + @Json(name = "fleet") val fleet: String, + @Json(name = "name") val name: String, + @Json(name = "description") val description: String, + @Json(name = "zonarId") val zonarId: String, + @Json(name = "gpsId") val gpsId: String, + @Json(name = "latitude") val latitude: Double, + @Json(name = "longitude") val longitude: Double, + @Json(name = "speed") val speed: Float, + @Json(name = "heading") val heading: Float, + @Json(name = "power") val power: Boolean, + @Json(name = "date") val date: String, + @Json(name = "color") val color: String?, + @Json(name = "routeName") val routeName: String?, + @Json(name = "routeId") val routeId: String?, + @Json(name = "distance") val distance: Double?, + @Json(name = "nextStop") val nextStop: String?, + @Json(name = "nextArrival") val nextArrival: String?) +{ + fun toBus() = Bus( + id = id, + fleet = fleet, + name = name, + description = description, + zonarId = zonarId, + gpsId = gpsId, + coordinates = LatLng(latitude, longitude), + speed = speed, + heading = heading.roundToInt(), + power = power, + date = date, + color = color, + routeName = routeName, + routeId = routeId, + distance = distance, + nextStop = nextStop, + nextArrival = nextArrival + ) +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/bus/BusJsonAdapter.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/bus/BusJsonAdapter.kt new file mode 100644 index 0000000..4d38954 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/bus/BusJsonAdapter.kt @@ -0,0 +1,8 @@ +package razorbacktransit.arcu.razorbacktransit.model.bus + +import com.squareup.moshi.FromJson + +class BusJsonAdapter +{ + @FromJson fun busFromJson(busJson: BusJson): Bus = busJson.toBus() +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/route/Route.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/route/Route.kt new file mode 100644 index 0000000..2ee4aa8 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/route/Route.kt @@ -0,0 +1,16 @@ +package razorbacktransit.arcu.razorbacktransit.model.route + +import com.google.android.gms.maps.model.LatLng + +data class Route(val id: Int, + val name: String, + val description: String, + val color: Int, + val coordinates: List, + val status: Int, + val inService: Boolean, + val pdfUrl: String?, + val nextArrival: String?, + val length: Float, + val departureStop: Int, + val nextDeparture: String?) \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/route/RouteJson.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/route/RouteJson.kt new file mode 100644 index 0000000..e6a0a57 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/route/RouteJson.kt @@ -0,0 +1,51 @@ +package razorbacktransit.arcu.razorbacktransit.model.route + +import android.graphics.Color +import com.google.android.gms.maps.model.LatLng +import com.squareup.moshi.Json + +data class RouteJson(@field:Json(name = "id") val id: Int, + @field:Json(name = "name") val name: String, + @field:Json(name = "description") val description: String, + @field:Json(name = "color") val color: String, + @field:Json(name = "shape") val shape: String, + @field:Json(name = "status") val status: Int, + @field:Json(name = "inService") val inService: Int, + @field:Json(name = "url") val pdfUrl: String?, + @field:Json(name = "nextArrival") val nextArrival: String?, + @field:Json(name = "length") val length: Float, + @field:Json(name = "departureStop") val departureStop: Int, + @field:Json(name = "nextDeparture") val nextDeparture: String?) +{ + fun toRoute(): Route + { + val coords = parseCoordinates( this.shape ) + return Route( + id = id, + name = name, + description = description, + color = Color.parseColor( color ), + coordinates = coords, + status = status, + inService = inService == 1, + pdfUrl = pdfUrl, + nextArrival = nextArrival, + length = length, + departureStop = departureStop, + nextDeparture = nextDeparture + ) + } + + private fun parseCoordinates( shape: String ): List + { + val pairs = shape.split(",") + val list = arrayListOf() + for(pair in pairs) + { + val latLongPair = pair.split(" ") + list.add( LatLng( latLongPair[0].toDouble(), latLongPair[1].toDouble() ) ) + + } + return list + } +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/route/RouteJsonAdapter.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/route/RouteJsonAdapter.kt new file mode 100644 index 0000000..83b8f46 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/route/RouteJsonAdapter.kt @@ -0,0 +1,8 @@ +package razorbacktransit.arcu.razorbacktransit.model.route + +import com.squareup.moshi.FromJson + +class RouteJsonAdapter +{ + @FromJson fun routeFromJson( routeJson: RouteJson ): Route = routeJson.toRoute() +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/stop/Stop.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/stop/Stop.kt new file mode 100644 index 0000000..b4d46e9 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/stop/Stop.kt @@ -0,0 +1,16 @@ +package razorbacktransit.arcu.razorbacktransit.model.stop + +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.MarkerOptions + +class Stop(val id: String, + val name: String, + val description: String, + val coordinates: LatLng, + val order: Int, + val distance: String?, + val nextArrival: String?) +{ + @Transient var icon: MarkerOptions? = null + @Transient var routeIds: String? = null +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/stop/StopJson.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/stop/StopJson.kt new file mode 100644 index 0000000..21c9fab --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/stop/StopJson.kt @@ -0,0 +1,24 @@ +package razorbacktransit.arcu.razorbacktransit.model.stop + +import com.google.android.gms.maps.model.LatLng +import com.squareup.moshi.Json + +data class StopJson(@Json(name = "id") val id: String, + @Json(name = "name") val name: String, + @Json(name = "description") val description: String, + @Json(name = "latitude") val latitude: String, + @Json(name = "longitude") val longitude: String, + @Json(name = "order") val order: Int, + @Json(name = "distance") val distance: String?, + @Json(name = "nextArrival") val nextArrival: String?) +{ + fun toStop() = Stop( + id = id, + name = name, + description = description, + coordinates = LatLng(latitude.toDouble(), longitude.toDouble()), + order = order, + distance = distance, + nextArrival = nextArrival + ) +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/stop/StopJsonAdapter.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/stop/StopJsonAdapter.kt new file mode 100644 index 0000000..c1c6aba --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/model/stop/StopJsonAdapter.kt @@ -0,0 +1,8 @@ +package razorbacktransit.arcu.razorbacktransit.model.stop + +import com.squareup.moshi.FromJson + +class StopJsonAdapter +{ + @FromJson fun stopFromJson(stopJson: StopJson): Stop = stopJson.toStop() +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/CampusService.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/CampusService.kt new file mode 100644 index 0000000..52b0d76 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/CampusService.kt @@ -0,0 +1,29 @@ +package razorbacktransit.arcu.razorbacktransit.network + +import io.reactivex.Flowable +import okhttp3.ResponseBody +import razorbacktransit.arcu.razorbacktransit.model.bus.Bus +import razorbacktransit.arcu.razorbacktransit.model.route.Route +import razorbacktransit.arcu.razorbacktransit.model.stop.Stop +import retrofit2.http.GET +import retrofit2.http.Query +import retrofit2.http.QueryMap + +// Base +// https://campusdata.uark.edu/api/ +interface CampusService +{ + @GET("routes") + fun getRoutes(): Flowable> + + @GET("buses") + fun getBuses(@Query("routeIds") ids: String ): Flowable> + + @GET("stops") + fun getStops(@Query("routeIds") ids: String ): Flowable> +} +// Buildings +// https://campusdata.uark.edu/api/buildings?callback=Buildings + +// Routes +// https://campusdata.uark.edu/api/routes?callback=Routes \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/LiveMapViewModel.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/LiveMapViewModel.kt new file mode 100644 index 0000000..0049805 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/LiveMapViewModel.kt @@ -0,0 +1,49 @@ +package razorbacktransit.arcu.razorbacktransit.network + +import android.app.Application +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import io.reactivex.Flowable +import io.reactivex.schedulers.Schedulers +import java.util.concurrent.TimeUnit + +class LiveMapViewModel(application: Application): AndroidViewModel(application) +{ + private val applicationContext = application.applicationContext + private val routeNetworking = RouteNetworking(applicationContext) + private val updateBusesNotification = Flowable.interval(5, TimeUnit.SECONDS, Schedulers.io()) + .map { Unit } + .share() + + private val routes: Flowable = updateBusesNotification + .compose(routeNetworking.routesEndpoint) + .map { + if(it is NetworkState.Success.Routes) + { + it.busRoutes = it.busRoutes.filter{ route -> listOf(221, 223, 227).contains(route.id) } + } + return@map it + } + .share() + + private val routesUi: Flowable = routes + .ofType(NetworkState.Success.Routes::class.java) + .map { return@map SubmitUiModel.SubmitRoute(it.busRoutes) } + .distinctUntilChanged() + + private val buses: Flowable = routes + .ofType(NetworkState.Success.Routes::class.java) + .compose(routeNetworking.buses) + .distinctUntilChanged() + + private val stops: Flowable = routes + .ofType(NetworkState.Success.Routes::class.java) + .compose(routeNetworking.stops) + .distinctUntilChanged { old, new -> old.stops != new.stops } + .flatMapIterable { it.stops } + .map { SubmitUiModel.SubmitStop(it) } + + val observeAll: Flowable = Flowable.merge(routesUi, buses, stops) + .onBackpressureDrop() + .share() +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/NetworkState.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/NetworkState.kt new file mode 100644 index 0000000..5a66b33 --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/NetworkState.kt @@ -0,0 +1,17 @@ +package razorbacktransit.arcu.razorbacktransit.network + +import razorbacktransit.arcu.razorbacktransit.model.bus.Bus +import razorbacktransit.arcu.razorbacktransit.model.route.Route +import razorbacktransit.arcu.razorbacktransit.model.stop.Stop + +sealed class NetworkState +{ + class InTransit(val message: String = ""): NetworkState() + sealed class Success: NetworkState() + { + class Routes(var busRoutes: List): Success() + class Buses(val buses: List): Success() + class Stops(val stops: List): Success() + } + class Failure(val t: Throwable): NetworkState() +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/NetworkUtils.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/NetworkUtils.kt new file mode 100644 index 0000000..7f589eb --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/NetworkUtils.kt @@ -0,0 +1,64 @@ +package razorbacktransit.arcu.razorbacktransit.network + +import android.util.Log +import io.reactivex.Flowable +import okhttp3.HttpUrl +import razorbacktransit.arcu.razorbacktransit.model.route.Route + +fun getBusImageUrl(color: String, heading: String): String = HttpUrl.Builder() + .scheme("https") + .host("campusdata.uark.edu") + .addPathSegment("api") + .addPathSegment("busimages") + .addQueryParameter("color", color.replace("#", "")) + .addQueryParameter("heading", heading) + .build() + .uri() + .toASCIIString() + +fun getStopImageUrl(stopId: String, routeIds: String): String = HttpUrl.Builder() + .scheme("https") + .host("campusdata.uark.edu") + .addPathSegment("api") + .addPathSegment("stopimages") + .addQueryParameter("stopId", stopId) + .addQueryParameter("routeIds", routeIds) + .build() + .uri() + .toASCIIString() + +fun Flowable>.buildBusIdsString(): Flowable +{ + return this.map { it.map { route -> route.id } } + .filter { it.isNotEmpty() } + .map { ids: List -> + var idString = "" + for (id in ids) + { + idString += "$id-" + } + if (idString[idString.lastIndex] == '-') + { + return@map idString.substring(0, idString.lastIndex) + } + return@map idString + } +} + +fun Flowable>.buildStopIdsString(): Flowable +{ + return this.map { it.map { route -> route.id } } + .filter { it.isNotEmpty() } + .map { ids: List -> + var idString = "" + for (id in ids) + { + idString += "$id-" + } + if (idString[idString.lastIndex] == '-') + { + return@map idString.substring(0, idString.lastIndex) + } + return@map idString + } +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/RouteNetworking.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/RouteNetworking.kt new file mode 100644 index 0000000..9b239da --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/RouteNetworking.kt @@ -0,0 +1,143 @@ +package razorbacktransit.arcu.razorbacktransit.network + +import android.content.Context +import com.bumptech.glide.Glide +import com.google.android.gms.maps.model.MarkerOptions +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import io.reactivex.Flowable +import io.reactivex.FlowableTransformer +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import razorbacktransit.arcu.razorbacktransit.model.bus.Bus +import razorbacktransit.arcu.razorbacktransit.model.bus.BusJsonAdapter +import razorbacktransit.arcu.razorbacktransit.model.route.Route +import razorbacktransit.arcu.razorbacktransit.model.route.RouteJsonAdapter +import razorbacktransit.arcu.razorbacktransit.model.stop.Stop +import razorbacktransit.arcu.razorbacktransit.model.stop.StopJsonAdapter +import razorbacktransit.arcu.razorbacktransit.utils.toBitMapDescriptor +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.moshi.MoshiConverterFactory + +class RouteNetworking(private val applicationContext: Context) +{ + private val widthPixels: Int = applicationContext.resources.displayMetrics.widthPixels + + private val moshiAdapter = Moshi.Builder() + .add(RouteJsonAdapter()) + .add(BusJsonAdapter()) + .add(StopJsonAdapter()) + .add(KotlinJsonAdapterFactory()) + .build() + + private val campusAPI: CampusService = Retrofit.Builder() + .baseUrl("https://campusdata.uark.edu/api/") + .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) + .addConverterFactory(MoshiConverterFactory.create(moshiAdapter).asLenient()) + .build() + .create(CampusService::class.java) + + val routesEndpoint: FlowableTransformer = FlowableTransformer { + it.observeOn(Schedulers.io()) + .flatMap { + campusAPI.getRoutes() + .map { routes: List -> NetworkState.Success.Routes(routes) } + .onErrorReturn { t -> NetworkState.Failure(t) } + .observeOn(AndroidSchedulers.mainThread()) + .startWith(NetworkState.InTransit()) + } + } + + val buses: FlowableTransformer = FlowableTransformer { + it.compose(busesEndpoint) + .ofType(NetworkState.Success.Buses::class.java) + .compose(busImagesEndpoint) + } + + private val busesEndpoint: FlowableTransformer = FlowableTransformer { + it.map { it.busRoutes } + .buildBusIdsString() + .flatMap { busIds: String -> + campusAPI.getBuses(busIds) + .map { busses: List -> NetworkState.Success.Buses(busses) } + .onErrorReturn { t -> NetworkState.Failure(t) } + .observeOn(AndroidSchedulers.mainThread()) + .startWith(NetworkState.InTransit()) + } + } + + private val busImagesEndpoint: FlowableTransformer = FlowableTransformer { + it.observeOn( Schedulers.computation() ) + .map { it.buses } + .observeOn( Schedulers.io() ) + // Load the images into the busses + .flatMap { busses: List -> + Flowable.fromIterable( busses ) + .map { + it.apply { + if(it.color != null) + { + val busImageWidth = (widthPixels * 0.05833333333).toInt() + val busImageHeight = (widthPixels.toDouble() * 0.05833333333 * 1.6153846154).toInt() + val myIcon = Glide.with(applicationContext).load(getBusImageUrl(it.color, heading.toString())).submit().get().toBitMapDescriptor(busImageWidth, busImageHeight) + it.icon = MarkerOptions() + .title( it.routeName ) + .position( it.coordinates ) + .icon( myIcon ) + .flat(true) + .alpha(0f) + } + } + } + .observeOn(Schedulers.computation()) + .toList().toFlowable() + .map { buses -> return@map SubmitUiModel.SubmitBuses(buses) } + } + } + + val stops: FlowableTransformer = FlowableTransformer { + it.compose(loadStops) + .ofType(NetworkState.Success.Stops::class.java) + .compose(stopImages) + } + + private val loadStops: FlowableTransformer = FlowableTransformer { + it.map { it.busRoutes } + .buildStopIdsString() + .observeOn( Schedulers.io() ) + .flatMap { ids: String -> + campusAPI.getStops( ids ) + .map { stops: List -> NetworkState.Success.Stops( stops.onEach { it.routeIds = ids } ) } + .onErrorReturn{ t -> NetworkState.Failure( t ) } + .observeOn( AndroidSchedulers.mainThread() ) + .startWith( NetworkState.InTransit() ) + } + } + + private val stopImages: FlowableTransformer = FlowableTransformer { + it.observeOn(Schedulers.computation()) + .map { it.stops } + .observeOn( Schedulers.computation() ) + .switchMap {stops: List -> + Flowable.fromIterable( stops ) + .map { + val stopImageWidth = (widthPixels * 0.02638888889).toInt() + val stopImageHeight = (widthPixels * 0.02638888889).toInt() + val myIcon = Glide.with(applicationContext).load(getStopImageUrl(it.id, it.routeIds!!)).submit().get().toBitMapDescriptor(stopImageWidth, stopImageHeight) + it.icon = MarkerOptions() + .snippet( it.name ) + .title( it.nextArrival ) + .position( it.coordinates ) + .icon( myIcon ) + .flat(true) + .alpha(0f) + return@map it + } + .toList() + .toFlowable() + .map { return@map SubmitUiModel.SubmitStops(it) } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/SubmitUiModel.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/SubmitUiModel.kt new file mode 100644 index 0000000..fc9d71d --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/network/SubmitUiModel.kt @@ -0,0 +1,14 @@ +package razorbacktransit.arcu.razorbacktransit.network + +import razorbacktransit.arcu.razorbacktransit.model.bus.Bus +import razorbacktransit.arcu.razorbacktransit.model.route.Route +import razorbacktransit.arcu.razorbacktransit.model.stop.Stop + +sealed class SubmitUiModel +{ + class SubmitBuses(val bus: List): SubmitUiModel() + class SubmitStops(val stops: List): SubmitUiModel() + class SubmitStop(val stop: Stop): SubmitUiModel() + class SubmitRoute(val route: List): SubmitUiModel() + class InProgress(): SubmitUiModel() +} \ No newline at end of file diff --git a/app/src/main/java/razorbacktransit/arcu/razorbacktransit/utils/Utils.kt b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/utils/Utils.kt new file mode 100644 index 0000000..c4c4bdc --- /dev/null +++ b/app/src/main/java/razorbacktransit/arcu/razorbacktransit/utils/Utils.kt @@ -0,0 +1,42 @@ +package razorbacktransit.arcu.razorbacktransit.utils + +import android.graphics.drawable.Drawable +import android.util.Log +import androidx.core.graphics.drawable.toBitmap +import com.google.android.gms.maps.model.BitmapDescriptor +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.Marker +import io.reactivex.Flowable +import razorbacktransit.arcu.razorbacktransit.model.route.Route +import razorbacktransit.arcu.razorbacktransit.network.NetworkState + +fun Flowable.logNetworkState( methodName: String ): Flowable +{ + return this.doOnNext { + when(it) + { + is NetworkState.Failure -> + { + Log.d("NETWORKDEBUGGING", "$methodName: FAILURE, ${it.t.localizedMessage}") + it.t.printStackTrace() + } + is NetworkState.InTransit -> Log.d("NETWORKDEBUGGING", "$methodName: IN TRANSIT") + is NetworkState.Success -> Log.d("NETWORKDEBUGGING", "$methodName: SUCCESS") + } + } + +} + +fun ArrayList.clearMarkers() +{ + for(marker in this) + { + marker.remove() + } + this.clear() +} + +fun Drawable.toBitMapDescriptor(width: Int, height: Int): BitmapDescriptor +{ + return BitmapDescriptorFactory.fromBitmap(this.toBitmap(width, height)) +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a61d8a6..8e69e22 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - - + diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml index 6517f69..7132181 100644 --- a/app/src/main/res/layout/app_bar_main.xml +++ b/app/src/main/res/layout/app_bar_main.xml @@ -1,26 +1,26 @@ - - - - + - + diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index 3f65978..5e8fe3c 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -1,5 +1,5 @@ - - + diff --git a/build.gradle b/build.gradle index 558fdf8..870de58 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,18 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.11' repositories { jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' + classpath 'com.android.tools.build:gradle:3.3.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files classpath 'com.google.gms:google-services:4.0.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle.properties b/gradle.properties index aac7c9b..dfd42d6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,8 +9,12 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. +android.enableJetifier=true +android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m +android.jetifier.blacklist = butterknife-compiler + # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8f43cb3..e7b257e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jun 19 16:57:27 CDT 2018 +#Tue Jan 15 00:51:33 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip