MP1_CalebFontenot

This commit is contained in:
2024-01-23 18:03:09 -06:00
parent 4587fe1846
commit 75ab169397
2010 changed files with 48871 additions and 0 deletions

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,31 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion '25.0.2'
defaultConfig {
applicationId "com.bignerdranch.android.locatr"
minSdkVersion 19
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.0'
compile 'com.google.android.gms:play-services-location:9.8.0'
compile 'com.google.android.gms:play-services-maps:9.8.0'
testCompile 'junit:junit:4.12'
}

View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/bphillips/devtools/android-sdk-macosx/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,26 @@
package com.bignerdranch.android.locatr;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.bignerdranch.android.locatr", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,20 @@
<resources>
<!--
TODO: Before you run your application, you need a Google Maps API key.
To get one, follow this link, follow the directions and press "Create" at the end:
https://console.developers.google.com/flows/enableapi?apiid=maps_android_backend&keyType=CLIENT_SIDE_ANDROID&r=85:7C:29:5A:30:94:81:BE:06:18:19:95:EA:68:28:1E:4B:E9:B4:5D%3Bcom.bignerdranch.android.locatr
You can also add your credentials to an existing key, using this line:
85:7C:29:5A:30:94:81:BE:06:18:19:95:EA:68:28:1E:4B:E9:B4:5D;com.bignerdranch.android.locatr
Alternatively, follow the directions here:
https://developers.google.com/maps/documentation/android/start#get-key
Once you have your key (it starts with "AIza"), replace the "google_maps_key"
string in this file.
-->
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false"
>AIzaSyClrnnYZEx0iYmJkIc0K4rdObrXcFkLl-U</string>
</resources>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bignerdranch.android.locatr">
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission
android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".LocatrActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!--
The API key for Google Maps-based APIs is defined as a string resource.
(See the file "res/values/google_maps_api.xml").
Note that the API key is linked to the encryption key used to sign the APK.
You need a different API key for each encryption key, including the release key that is used to
sign the APK for publishing.
You can define the keys for the debug and release targets in src/debug/ and src/release/.
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key"/>
</application>
</manifest>

View File

@@ -0,0 +1,137 @@
package com.bignerdranch.android.locatr;
import android.location.Location;
import android.net.Uri;
import android.util.Log;
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.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class FlickrFetchr {
private static final String TAG = "FlickrFetchr";
private static final String API_KEY = "REPLACE_ME_WITH_A_REAL_KEY";
private static final String FETCH_RECENTS_METHOD = "flickr.photos.getRecent";
private static final String SEARCH_METHOD = "flickr.photos.search";
private static final Uri ENDPOINT = Uri
.parse("https://api.flickr.com/services/rest/")
.buildUpon()
.appendQueryParameter("api_key", API_KEY)
.appendQueryParameter("format", "json")
.appendQueryParameter("nojsoncallback", "1")
.appendQueryParameter("extras", "url_s,geo")
.build();
public byte[] getUrlBytes(String urlSpec) throws IOException {
URL url = new URL(urlSpec);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = connection.getInputStream();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new IOException(connection.getResponseMessage() +
": with " +
urlSpec);
}
int bytesRead = 0;
byte[] buffer = new byte[1024];
while ((bytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
out.close();
return out.toByteArray();
} finally {
connection.disconnect();
}
}
public String getUrlString(String urlSpec) throws IOException {
return new String(getUrlBytes(urlSpec));
}
public List<GalleryItem> fetchRecentPhotos() {
String url = buildUrl(FETCH_RECENTS_METHOD, null);
return downloadGalleryItems(url);
}
public List<GalleryItem> searchPhotos(String query) {
String url = buildUrl(SEARCH_METHOD, query);
return downloadGalleryItems(url);
}
public List<GalleryItem> searchPhotos(Location location) {
String url = buildUrl(location);
return downloadGalleryItems(url);
}
private List<GalleryItem> downloadGalleryItems(String url) {
List<GalleryItem> items = new ArrayList<>();
try {
String jsonString = getUrlString(url);
Log.i(TAG, "Received JSON: " + jsonString);
JSONObject jsonBody = new JSONObject(jsonString);
parseItems(items, jsonBody);
} catch (IOException ioe) {
Log.e(TAG, "Failed to fetch items", ioe);
} catch (JSONException je) {
Log.e(TAG, "Failed to parse JSON", je);
}
return items;
}
private String buildUrl(String method, String query) {
Uri.Builder uriBuilder = ENDPOINT.buildUpon()
.appendQueryParameter("method", method);
if (method.equals(SEARCH_METHOD)) {
uriBuilder.appendQueryParameter("text", query);
}
return uriBuilder.build().toString();
}
private String buildUrl(Location location) {
return ENDPOINT.buildUpon()
.appendQueryParameter("method", SEARCH_METHOD)
.appendQueryParameter("lat", "" + location.getLatitude())
.appendQueryParameter("lon", "" + location.getLongitude())
.build().toString();
}
private void parseItems(List<GalleryItem> items, JSONObject jsonBody)
throws IOException, JSONException {
JSONObject photosJsonObject = jsonBody.getJSONObject("photos");
JSONArray photoJsonArray = photosJsonObject.getJSONArray("photo");
for (int i = 0; i < photoJsonArray.length(); i++) {
JSONObject photoJsonObject = photoJsonArray.getJSONObject(i);
GalleryItem item = new GalleryItem();
item.setId(photoJsonObject.getString("id"));
item.setCaption(photoJsonObject.getString("title"));
if (!photoJsonObject.has("url_s")) {
continue;
}
item.setUrl(photoJsonObject.getString("url_s"));
item.setOwner(photoJsonObject.getString("owner"));
item.setLat(photoJsonObject.getDouble("latitude"));
item.setLon(photoJsonObject.getDouble("longitude"));
items.add(item);
}
}
}

View File

@@ -0,0 +1,73 @@
package com.bignerdranch.android.locatr;
import android.net.Uri;
public class GalleryItem {
private String mCaption;
private String mId;
private String mUrl;
private String mOwner;
private double mLat;
private double mLon;
public String getCaption() {
return mCaption;
}
public void setCaption(String caption) {
mCaption = caption;
}
public String getId() {
return mId;
}
public void setId(String id) {
mId = id;
}
public String getUrl() {
return mUrl;
}
public void setUrl(String url) {
mUrl = url;
}
public String getOwner() {
return mOwner;
}
public void setOwner(String owner) {
mOwner = owner;
}
public Uri getPhotoPageUri() {
return Uri.parse("http://www.flickr.com/photos/")
.buildUpon()
.appendPath(mOwner)
.appendPath(mId)
.build();
}
public double getLat() {
return mLat;
}
public void setLat(double lat) {
mLat = lat;
}
public double getLon() {
return mLon;
}
public void setLon(double lon) {
mLon = lon;
}
@Override
public String toString() {
return mCaption;
}
}

View File

@@ -0,0 +1,42 @@
package com.bignerdranch.android.locatr;
import android.app.Dialog;
import android.content.DialogInterface;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
public class LocatrActivity extends SingleFragmentActivity {
private static final int REQUEST_ERROR = 0;
@Override
protected Fragment createFragment() {
return LocatrFragment.newInstance();
}
@Override
protected void onResume() {
super.onResume();
GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
int errorCode = apiAvailability.isGooglePlayServicesAvailable(this);
if (errorCode != ConnectionResult.SUCCESS) {
Dialog errorDialog = apiAvailability.getErrorDialog(this,
errorCode,
REQUEST_ERROR,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
// Leave if services are unavailable.
finish();
}
});
errorDialog.show();
}
}
}

View File

@@ -0,0 +1,225 @@
package com.bignerdranch.android.locatr;
import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
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.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.MarkerOptions;
import java.io.IOException;
import java.util.List;
public class LocatrFragment extends SupportMapFragment {
private static final String TAG = "LocatrFragment";
private static final String[] LOCATION_PERMISSIONS = new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
};
private static final int REQUEST_LOCATION_PERMISSIONS = 0;
private GoogleApiClient mClient;
private GoogleMap mMap;
private Bitmap mMapImage;
private GalleryItem mMapItem;
private Location mCurrentLocation;
public static LocatrFragment newInstance() {
return new LocatrFragment();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mClient = new GoogleApiClient.Builder(getActivity()).addApi(LocationServices.API)
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(@Nullable Bundle bundle) {
getActivity().invalidateOptionsMenu();
}
@Override
public void onConnectionSuspended(int i) {
}
})
.build();
getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
updateUI();
}
});
}
@Override
public void onStart() {
super.onStart();
getActivity().invalidateOptionsMenu();
mClient.connect();
}
@Override
public void onStop() {
super.onStop();
mClient.disconnect();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_locatr, menu);
MenuItem searchItem = menu.findItem(R.id.action_locate);
searchItem.setEnabled(mClient.isConnected());
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_locate:
if (hasLocationPermission()) {
findImage();
} else {
requestPermissions(LOCATION_PERMISSIONS,
REQUEST_LOCATION_PERMISSIONS);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
switch (requestCode) {
case REQUEST_LOCATION_PERMISSIONS:
if (hasLocationPermission()) {
findImage();
}
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void findImage() {
LocationRequest request = LocationRequest.create();
request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
request.setNumUpdates(1);
request.setInterval(0);
LocationServices.FusedLocationApi
.requestLocationUpdates(mClient, request, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.i(TAG, "Got a fix: " + location);
new SearchTask().execute(location);
}
});
}
private boolean hasLocationPermission() {
int result = ContextCompat
.checkSelfPermission(getActivity(), LOCATION_PERMISSIONS[0]);
return result == PackageManager.PERMISSION_GRANTED;
}
private void updateUI() {
if (mMap == null || mMapImage == null) {
return;
}
LatLng itemPoint = new LatLng(mMapItem.getLat(), mMapItem.getLon());
LatLng myPoint = new LatLng(
mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude());
BitmapDescriptor itemBitmap = BitmapDescriptorFactory.fromBitmap(mMapImage);
MarkerOptions itemMarker = new MarkerOptions()
.position(itemPoint)
.icon(itemBitmap);
MarkerOptions myMarker = new MarkerOptions()
.position(myPoint);
mMap.clear();
mMap.addMarker(itemMarker);
mMap.addMarker(myMarker);
LatLngBounds bounds = new LatLngBounds.Builder()
.include(itemPoint)
.include(myPoint)
.build();
int margin = getResources().getDimensionPixelSize(R.dimen.map_inset_margin);
CameraUpdate update = CameraUpdateFactory.newLatLngBounds(bounds, margin);
mMap.animateCamera(update);
}
private class SearchTask extends AsyncTask<Location,Void,Void> {
private GalleryItem mGalleryItem;
private Bitmap mBitmap;
private Location mLocation;
@Override
protected Void doInBackground(Location... params) {
mLocation = params[0];
FlickrFetchr fetchr = new FlickrFetchr();
List<GalleryItem> items = fetchr.searchPhotos(params[0]);
if (items.size() == 0) {
return null;
}
mGalleryItem = items.get(0);
try {
byte[] bytes = fetchr.getUrlBytes(mGalleryItem.getUrl());
mBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
} catch (IOException ioe) {
Log.i(TAG, "Unable to decode bitmap", ioe);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
mMapImage = mBitmap;
mMapItem = mGalleryItem;
mCurrentLocation = mLocation;
updateUI();
}
}
}

View File

@@ -0,0 +1,28 @@
package com.bignerdranch.android.locatr;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
public abstract class SingleFragmentActivity extends AppCompatActivity {
protected abstract Fragment createFragment();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container);
if (fragment == null) {
fragment = createFragment();
fm.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}
}

View File

@@ -0,0 +1,5 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -0,0 +1,8 @@
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.bignerdranch.android.locatr.MapsActivity"/>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_locatr"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.bignerdranch.android.locatr.LocatrActivity">
<ImageView
android:id="@+id/image"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@android:color/white"
android:layout_centerInParent="true"
/>
</RelativeLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_locate"
android:icon="@android:drawable/ic_menu_compass"
android:title="@string/search"
android:enabled="false"
app:showAsAction="ifRoom"/>
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="map_inset_margin">100dp</dimen>
</resources>

View File

@@ -0,0 +1,6 @@
<resources>
<string name="app_name">Locatr</string>
<string name="search">Find an image near you</string>
<string name="title_activity_maps">Map</string>
</resources>

View File

@@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@@ -0,0 +1,20 @@
<resources>
<!--
TODO: Before you release your application, you need a Google Maps API key.
To do this, you can either add your release key credentials to your existing
key, or create a new key.
Note that this file specifies the API key for the release build target.
If you have previously set up a key for the debug target with the debug signing certificate,
you will also need to set up a key for your release certificate.
Follow the directions here:
https://developers.google.com/maps/documentation/android/signup
Once you have your key (it starts with "AIza"), replace the "google_maps_key"
string in this file.
-->
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">YOUR_KEY_HERE</string>
</resources>

View File

@@ -0,0 +1,17 @@
package com.bignerdranch.android.locatr;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}