From 815b10e70285cc04950bea057131382bc4b42f52 Mon Sep 17 00:00:00 2001 From: rusyamurzin Date: Sat, 16 Mar 2019 00:53:56 +0300 Subject: [PATCH 1/4] AsyncTask without Cache --- app/src/main/AndroidManifest.xml | 3 + .../android_2019/citycam/CityCamActivity.java | 418 +++++++++++++++++- .../android_2019/citycam/webcams/Webcams.java | 28 +- app/src/main/res/layout/activity_city_cam.xml | 52 +++ app/src/main/res/values/strings.xml | 4 + build.gradle | 2 +- 6 files changed, 487 insertions(+), 20 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0be15d1..899dd33 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,9 @@ + + + { + + private CityCamActivity activity; + private Integer progress; + private WebcamsMessage mess; + + DownloadImageTask(CityCamActivity activity) { + this.activity = activity; + } + + void attachActivity(CityCamActivity activity) { + this.activity = activity; + updateView(); + } + + void updateView() { + if (activity != null) { + activity.progressView.setVisibility(progress != 100 ? View.VISIBLE : View.INVISIBLE); + activity.camImageView.setImageBitmap(mess.getBitmap()); + String idText = getResources().getString(R.string.id_cam)+mess.getId(); + activity.idTextView.setText(idText); + String titleText = getResources().getString(R.string.title_cam)+mess.getTitle(); + activity.titleTextView.setText(titleText); + String timezoneText = getResources().getString(R.string.timezone_cam)+mess.getTimezone(); + activity.timezoneTextView.setText(timezoneText); + String countViewsText = getResources().getString(R.string.views_cam)+mess.getViews(); + activity.countViewsTextView.setText(countViewsText); + } + } + + @Override + protected void onPreExecute() { + activity.progressView.setVisibility(View.VISIBLE); + } + + protected WebcamsMessage doInBackground(URL... urls) { + int count = urls.length; + Bitmap bitmap = null; + WebcamsMessage message = null; + URL url = null; + for (int i = 0; i < count; i++) { + try { + message = downloadWebcamsMessageByUrl(urls[i]); + } + catch (IOException e) { + Log.w(TAG, "IO exception in downloadWebcamsMessageByUrl method"); + } + try { + url = new URL(message.getPreview()); + } + catch (MalformedURLException e) { + Log.w(TAG, "MalformedURL exception"); + } + try { + bitmap = downloadImageByUrl(url); + } + catch (IOException e) { + Log.w(TAG, "IO exception in downloadImageByUrl"); + e.printStackTrace(); + } + message.setBitmap(bitmap); + if (isCancelled()) break; + } + mess = message; + return message; + } + + protected void onProgressUpdate(Integer... progress) { + } + + protected void onPostExecute(WebcamsMessage result) { + progress = 100; + updateView(); + } + + private Bitmap downloadImageByUrl(URL url) throws IOException { + InputStream stream = null; + HttpsURLConnection connection = null; + Bitmap result = null; + try { + connection = (HttpsURLConnection) url.openConnection(); + connection.setReadTimeout(3000); + connection.setConnectTimeout(3000); + connection.setRequestMethod("GET"); + connection.setDoInput(true); + connection.connect(); + int responseCode = connection.getResponseCode(); + if (responseCode != HttpsURLConnection.HTTP_OK) { + throw new IOException("HTTP error code: " + responseCode); + } + stream = connection.getInputStream(); + if (stream != null) { + result = BitmapFactory.decodeStream(stream); + } + } finally { + // Close Stream and disconnect HTTPS connection. + if (stream != null) { + stream.close(); + } + if (connection != null) { + connection.disconnect(); + } + } + return result; + } + + private WebcamsMessage downloadWebcamsMessageByUrl(URL url) throws IOException { + InputStream stream = null; + HttpsURLConnection connection = null; + WebcamsMessage result = null; + try { + connection = (HttpsURLConnection) url.openConnection(); + connection.setReadTimeout(3000); + connection.setConnectTimeout(3000); + connection.setRequestMethod("GET"); + connection.setDoInput(true); + //Установка ключа в header запроса + connection.setRequestProperty("X-RapidAPI-Key","17ca55dc03mshdd5146ee8cf5aadp1406f0jsn60a12a5afd36"); + connection.connect(); + int responseCode = connection.getResponseCode(); + if (responseCode != HttpsURLConnection.HTTP_OK) { + throw new IOException("HTTP error code: " + responseCode); + } + stream = connection.getInputStream(); + if (stream != null) { + List messagesList = readJsonStream(stream); + Iterator iter = messagesList.iterator(); + WebcamsMessage message = null; + while (iter.hasNext()) { + message = (WebcamsMessage) iter.next(); + if (!message.status.equals("active")) { + iter.remove(); + } + } + Integer maxRand = messagesList.size(); + Random randomValue = new Random(); + result = messagesList.get(randomValue.nextInt(maxRand)); + } + } finally { + // Close Stream and disconnect HTTPS connection. + if (stream != null) { + stream.close(); + } + if (connection != null) { + connection.disconnect(); + } + } + return result; + } + + public List readJsonStream(InputStream in) throws IOException { + JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); + try { + return readResult(reader); + } finally { + reader.close(); + } + } + + public List readResult(JsonReader reader) throws IOException { + List messages = new ArrayList<>(); + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("result")) { + messages = readWebcams(reader); + } else { + reader.skipValue(); + } + } + reader.endObject(); + return messages; + } + + public List readWebcams(JsonReader reader) throws IOException { + List messages = new ArrayList<>(); + Integer total = -1; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("total")) { + total = reader.nextInt(); + } + else if (name.equals("webcams") && total > 0) { + messages = readArrayCams(reader); + } + else { + reader.skipValue(); + } + } + reader.endObject(); + //Если не найдено ни одной камеры + if (total == 0) { + Log.w(TAG, "There is no camera available"); + finish(); + } + return messages; + } + + public List readArrayCams(JsonReader reader) throws IOException { + List messages = new ArrayList<>(); + + reader.beginArray(); + while (reader.hasNext()) { + messages.add(readWebcamsMessage(reader)); + } + reader.endArray(); + return messages; + } + + public WebcamsMessage readWebcamsMessage(JsonReader reader) throws IOException { + String id = null; + String status = null; + String title = null; + String preview = null; + String timezone = null; + Integer views = -1; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("id")) { + id = reader.nextString(); + } + else if (name.equals("status")) { + status = reader.nextString(); + } + else if (name.equals("title")) { + title = reader.nextString(); + } + else if (name.equals("image")) { + preview = readCurrent(reader); + } + else if (name.equals("location")) { + timezone = readTimeZone(reader); + } + else if (name.equals("statistics")) { + reader.beginObject(); + reader.nextName(); + views = reader.nextInt(); + reader.endObject(); + } + else { + reader.skipValue(); + } + } + reader.endObject(); + return new WebcamsMessage(id, status, title, preview, timezone, views); + } + + + public String readCurrent(JsonReader reader) throws IOException { + String preview = null; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("current")) { + preview = readPreview(reader); + } + else { + reader.skipValue(); + } + } + reader.endObject(); + + return preview; + } + public String readPreview(JsonReader reader) throws IOException { + String preview = null; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("preview")) { + preview = reader.nextString(); + } + else { + reader.skipValue(); + } + } + reader.endObject(); + + return preview; + } + + public String readTimeZone(JsonReader reader) throws IOException { + String timezone = null; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("timezone")) { + timezone = reader.nextString(); + } + else { + reader.skipValue(); + } + } + reader.endObject(); + + return timezone; + } + + //["result"]["webcams"][0]["id"] ("1350096618") + //["result"]["webcams"][0]["status"] if "active" + //["result"]["webcams"][0]["title"] ("St Petersburg: Saint Petersburg − Intermodal Terminal") + //["result"]["webcams"][0]["image"]["current"]["preview"] ("https://images.webcams.travel/preview/1240664025.jpg") + //["result"]["webcams"][0]["location"]["timezone"] ("Europe/Moscow") + //["result"]["webcams"][0]["statistics"]["views"] (28718) + private class WebcamsMessage { + private String id; + private String status; + private String title; + private String preview; + private String timezone; + private Integer views; + private Bitmap bitmap = null; + + public WebcamsMessage(String id, String status, String title, String preview, String timezone, Integer views) { + this.id = id; + this.status = status; + this.title = title; + this.preview = preview; + this.timezone = timezone; + this.views = views; + } + + public String getId() { + return id; + } + + public String getStatus() { + return status; + } + + public String getTitle() { + return title; + } + + public String getPreview() { + return preview; + } + + public String getTimezone() { + return timezone; + } + + public Integer getViews() { + return views; + } + + public Bitmap getBitmap() { + return bitmap; + } + + public void setBitmap(Bitmap bitmap) { + this.bitmap = bitmap; + } + } } private static final String TAG = "CityCam"; diff --git a/app/src/main/java/ru/android_2019/citycam/webcams/Webcams.java b/app/src/main/java/ru/android_2019/citycam/webcams/Webcams.java index 9d732ba..f6cc8fd 100644 --- a/app/src/main/java/ru/android_2019/citycam/webcams/Webcams.java +++ b/app/src/main/java/ru/android_2019/citycam/webcams/Webcams.java @@ -12,19 +12,17 @@ public final class Webcams { // Зарегистрируйтесь на http://ru.webcams.travel/developers/ // и вставьте сюда ваш devid - private static final String DEV_ID = "Ваш devid"; + //https://webcamstravel.p.rapidapi.com/webcams/list/nearby=40.11,12.05,250?lang=en&show=webcams:image,location,statistics - private static final String BASE_URL = "http://api.webcams.travel/rest"; + private static final String BASE_URL = "https://webcamstravel.p.rapidapi.com/webcams/"; - private static final String PARAM_DEVID = "devid"; - private static final String PARAM_METHOD = "method"; - private static final String PARAM_LAT = "lat"; - private static final String PARAM_LON = "lng"; - private static final String PARAM_FORMAT = "format"; + private static final String PARAM_LANG = "lang"; + private static final String LANG = "en"; + private static final String PARAM_SHOW = "show"; + private static final String SHOW = "webcams:image,location,statistics"; - private static final String METHOD_NEARBY = "wct.webcams.list_nearby"; - - private static final String FORMAT_JSON = "json"; + private static final String METHOD_NEARBY = "list/nearby"; + private static final String RADIUS = "50"; /** * Возвращает URL для выполнения запроса Webcams API для получения @@ -32,15 +30,11 @@ public final class Webcams { */ public static URL createNearbyUrl(double latitude, double longitude) throws MalformedURLException { - Uri uri = Uri.parse(BASE_URL).buildUpon() - .appendQueryParameter(PARAM_METHOD, METHOD_NEARBY) - .appendQueryParameter(PARAM_LAT, Double.toString(latitude)) - .appendQueryParameter(PARAM_LON, Double.toString(longitude)) - .appendQueryParameter(PARAM_DEVID, DEV_ID) - .appendQueryParameter(PARAM_FORMAT, FORMAT_JSON) + Uri uri = Uri.parse(BASE_URL+METHOD_NEARBY+"="+Double.toString(latitude)+","+Double.toString(longitude)+","+RADIUS).buildUpon() + .appendQueryParameter(PARAM_LANG, LANG) + .appendQueryParameter(PARAM_SHOW, SHOW) .build(); return new URL(uri.toString()); } - private Webcams() {} } diff --git a/app/src/main/res/layout/activity_city_cam.xml b/app/src/main/res/layout/activity_city_cam.xml index 17fab12..d1a401f 100644 --- a/app/src/main/res/layout/activity_city_cam.xml +++ b/app/src/main/res/layout/activity_city_cam.xml @@ -6,6 +6,7 @@ android:background="#fff"> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 314a66f..965c465 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,7 @@ City Cam + ID камеры : + Место : + Часовой пояс : + Количество просмотров : diff --git a/build.gradle b/build.gradle index 274faf4..cae95e2 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.1' + 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 From a983e58879795d3891b2896bc1b57387d2a1d906 Mon Sep 17 00:00:00 2001 From: rusyamurzin Date: Sat, 16 Mar 2019 12:44:56 +0300 Subject: [PATCH 2/4] add error processing --- .../android_2019/citycam/CityCamActivity.java | 62 ++++++++++++------- .../android_2019/citycam/webcams/Webcams.java | 2 - app/src/main/res/layout/activity_city_cam.xml | 15 ++++- app/src/main/res/values/strings.xml | 1 + 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java b/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java index b4f80f3..dc59031 100644 --- a/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java +++ b/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java @@ -1,6 +1,5 @@ package ru.android_2019.citycam; -import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; @@ -47,6 +46,7 @@ public class CityCamActivity extends AppCompatActivity { private TextView titleTextView; private TextView timezoneTextView; private TextView countViewsTextView; + private TextView errorTextView; private DownloadImageTask downloadTask = null; @@ -67,29 +67,28 @@ protected void onCreate(Bundle savedInstanceState) { titleTextView = (TextView) findViewById(R.id.textTitle); timezoneTextView = (TextView) findViewById(R.id.textTimezone); countViewsTextView = (TextView) findViewById(R.id.textNumViews); + errorTextView = (TextView) findViewById(R.id.textError); getSupportActionBar().setTitle(city.name); + // Здесь должен быть код, инициирующий асинхронную загрузку изображения с веб-камеры + // в выбранном городе if (savedInstanceState != null) { // Пытаемся получить ранее запущенный таск downloadTask = (DownloadImageTask) getLastCustomNonConfigurationInstance(); } if (downloadTask == null) { try{ - downloadTask = new DownloadImageTask(this); - downloadTask.execute(Webcams.createNearbyUrl(city.latitude,city.longitude)); + downloadTask = new DownloadImageTask(this); + downloadTask.execute(Webcams.createNearbyUrl(city.latitude,city.longitude)); } catch (MalformedURLException e){ Log.w(TAG, "Cannot create URL with lat : "+city.latitude+" and lng : "+city.longitude); - finish(); + this.finish(); } } else { // Передаем в ранее запущенный таск текущий объект Activity downloadTask.attachActivity(this); } - - // Здесь должен быть код, инициирующий асинхронную загрузку изображения с веб-камеры - // в выбранном городе - } @Override @@ -102,6 +101,7 @@ private class DownloadImageTask extends AsyncTask 0) { //Если не найдено ни одной камеры if (total == 0) { Log.w(TAG, "There is no camera available"); - finish(); + throw new IOException("There is no camera available"); } return messages; } diff --git a/app/src/main/java/ru/android_2019/citycam/webcams/Webcams.java b/app/src/main/java/ru/android_2019/citycam/webcams/Webcams.java index f6cc8fd..232c26b 100644 --- a/app/src/main/java/ru/android_2019/citycam/webcams/Webcams.java +++ b/app/src/main/java/ru/android_2019/citycam/webcams/Webcams.java @@ -12,8 +12,6 @@ public final class Webcams { // Зарегистрируйтесь на http://ru.webcams.travel/developers/ // и вставьте сюда ваш devid - //https://webcamstravel.p.rapidapi.com/webcams/list/nearby=40.11,12.05,250?lang=en&show=webcams:image,location,statistics - private static final String BASE_URL = "https://webcamstravel.p.rapidapi.com/webcams/"; private static final String PARAM_LANG = "lang"; diff --git a/app/src/main/res/layout/activity_city_cam.xml b/app/src/main/res/layout/activity_city_cam.xml index d1a401f..4f7c3af 100644 --- a/app/src/main/res/layout/activity_city_cam.xml +++ b/app/src/main/res/layout/activity_city_cam.xml @@ -27,6 +27,13 @@ android:layout_height="wrap_content" android:layout_gravity="center" style="@android:style/Widget.Holo.Light.ProgressBar.Large.Inverse"/> + @@ -69,11 +78,13 @@ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 965c465..2ba24a7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,4 +4,5 @@ Место : Часовой пояс : Количество просмотров : + Изображение не найдено From 633f0a96d1926442f267d6897753786547f20907 Mon Sep 17 00:00:00 2001 From: rusyamurzin Date: Sat, 16 Mar 2019 16:03:39 +0300 Subject: [PATCH 3/4] Support LRUCache --- .../android_2019/citycam/CityCamActivity.java | 50 +++++++++++++++---- .../android_2019/citycam/cache/MyCache.java | 26 ++++++++++ 2 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/ru/android_2019/citycam/cache/MyCache.java diff --git a/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java b/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java index dc59031..7db2176 100644 --- a/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java +++ b/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java @@ -24,6 +24,7 @@ import javax.net.ssl.HttpsURLConnection; +import ru.android_2019.citycam.cache.MyCache; import ru.android_2019.citycam.model.City; import ru.android_2019.citycam.webcams.Webcams; @@ -71,19 +72,26 @@ protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().setTitle(city.name); - // Здесь должен быть код, инициирующий асинхронную загрузку изображения с веб-камеры - // в выбранном городе if (savedInstanceState != null) { // Пытаемся получить ранее запущенный таск downloadTask = (DownloadImageTask) getLastCustomNonConfigurationInstance(); } if (downloadTask == null) { - try{ - downloadTask = new DownloadImageTask(this); - downloadTask.execute(Webcams.createNearbyUrl(city.latitude,city.longitude)); - } catch (MalformedURLException e){ - Log.w(TAG, "Cannot create URL with lat : "+city.latitude+" and lng : "+city.longitude); - this.finish(); + downloadTask = new DownloadImageTask(this); + if (MyCache.getInstance().getLru().get(city.name) == null) { + try { + downloadTask.execute(Webcams.createNearbyUrl(city.latitude, city.longitude)); + } catch (MalformedURLException e) { + Log.w(TAG, "Cannot create URL with lat : " + city.latitude + " and lng : " + city.longitude); + this.finish(); + } + } else { + downloadTask.setMess(MyCache.getInstance().getLru().get(city.name)); + downloadTask.setProgress(100); + if (downloadTask.getMess().getBitmap() == null) { + downloadTask.setErrorOccured(true); + } + downloadTask.attachActivity(this); } } else { // Передаем в ранее запущенный таск текущий объект Activity @@ -96,10 +104,11 @@ public Object onRetainCustomNonConfigurationInstance() { return this.downloadTask; } - private class DownloadImageTask extends AsyncTask { + + public class DownloadImageTask extends AsyncTask { private CityCamActivity activity; - private Integer progress; + private Integer progress = 0; private WebcamsMessage mess; private Boolean errorOccured = false; @@ -181,6 +190,9 @@ protected void onProgressUpdate(Integer... progress) { } protected void onPostExecute(WebcamsMessage result) { + if (MyCache.getInstance().getLru().get(city.name) == null && !errorOccured) { + MyCache.getInstance().getLru().put(city.name, mess); + } progress = 100; updateView(); } @@ -421,13 +433,29 @@ public String readTimeZone(JsonReader reader) throws IOException { return timezone; } + public WebcamsMessage getMess() { + return mess; + } + + public void setMess(WebcamsMessage mess) { + this.mess = mess; + } + + public void setProgress(Integer progress) { + this.progress = progress; + } + + public void setErrorOccured(Boolean errorOccured) { + this.errorOccured = errorOccured; + } + //["result"]["webcams"][0]["id"] ("1350096618") //["result"]["webcams"][0]["status"] if "active" //["result"]["webcams"][0]["title"] ("St Petersburg: Saint Petersburg − Intermodal Terminal") //["result"]["webcams"][0]["image"]["current"]["preview"] ("https://images.webcams.travel/preview/1240664025.jpg") //["result"]["webcams"][0]["location"]["timezone"] ("Europe/Moscow") //["result"]["webcams"][0]["statistics"]["views"] (28718) - private class WebcamsMessage { + public class WebcamsMessage { private String id; private String status; private String title; diff --git a/app/src/main/java/ru/android_2019/citycam/cache/MyCache.java b/app/src/main/java/ru/android_2019/citycam/cache/MyCache.java new file mode 100644 index 0000000..1189329 --- /dev/null +++ b/app/src/main/java/ru/android_2019/citycam/cache/MyCache.java @@ -0,0 +1,26 @@ +package ru.android_2019.citycam.cache; + +import android.util.LruCache; +import ru.android_2019.citycam.CityCamActivity; + +public class MyCache { + + private static MyCache instance; + private LruCache lru; + private int cacheSize = 8 * 1024 * 1024; //8 MiB + + private MyCache() { + lru = new LruCache<>(cacheSize); + } + + public static MyCache getInstance() { + if (instance == null) { + instance = new MyCache(); + } + return instance; + } + + public LruCache getLru() { + return lru; + } +} \ No newline at end of file From fd323f9b71be8be1ded326459605205fefc81fc2 Mon Sep 17 00:00:00 2001 From: rusyamurzin Date: Thu, 4 Apr 2019 14:27:38 +0300 Subject: [PATCH 4/4] Code refactoring --- app/build.gradle | 9 +- .../android_2019/citycam/CityCamActivity.java | 235 +----------------- .../android_2019/citycam/cache/MyCache.java | 6 +- .../citycam/reader/ResponseJsonReader.java | 168 +++++++++++++ .../citycam/reader/WebcamsMessage.java | 63 +++++ app/src/main/res/layout/activity_city_cam.xml | 92 ++++--- app/src/main/res/layout/item_city.xml | 2 + app/src/main/res/values/colors.xml | 3 + app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/strings.xml | 8 +- app/src/main/res/values/styles.xml | 2 - 11 files changed, 303 insertions(+), 287 deletions(-) create mode 100644 app/src/main/java/ru/android_2019/citycam/reader/ResponseJsonReader.java create mode 100644 app/src/main/java/ru/android_2019/citycam/reader/WebcamsMessage.java diff --git a/app/build.gradle b/app/build.gradle index 003df39..1d8cd8b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,6 @@ apply plugin: 'com.android.application' android { compileSdkVersion 23 - buildToolsVersion "23.0.1" defaultConfig { applicationId "ru.android_2019.citycam" @@ -20,8 +19,8 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:23.0.1' - compile 'com.android.support:recyclerview-v7:23.0.1' + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'com.android.support:appcompat-v7:23.4.0' + implementation 'com.android.support:recyclerview-v7:23.4.0' } diff --git a/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java b/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java index 7db2176..6c53f49 100644 --- a/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java +++ b/app/src/main/java/ru/android_2019/citycam/CityCamActivity.java @@ -5,7 +5,6 @@ import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; -import android.util.JsonReader; import android.util.Log; import android.view.View; import android.widget.ImageView; @@ -14,10 +13,8 @@ import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Random; @@ -26,6 +23,8 @@ import ru.android_2019.citycam.cache.MyCache; import ru.android_2019.citycam.model.City; +import ru.android_2019.citycam.reader.ResponseJsonReader; +import ru.android_2019.citycam.reader.WebcamsMessage; import ru.android_2019.citycam.webcams.Webcams; /** @@ -105,7 +104,7 @@ public Object onRetainCustomNonConfigurationInstance() { } - public class DownloadImageTask extends AsyncTask { + public class DownloadImageTask extends AsyncTask { private CityCamActivity activity; private Integer progress = 0; @@ -130,14 +129,10 @@ void updateView() { else { activity.errorTextView.setVisibility(View.INVISIBLE); activity.camImageView.setImageBitmap(mess.getBitmap()); - String idText = getResources().getString(R.string.id_cam)+mess.getId(); - activity.idTextView.setText(idText); - String titleText = getResources().getString(R.string.title_cam)+mess.getTitle(); - activity.titleTextView.setText(titleText); - String timezoneText = getResources().getString(R.string.timezone_cam)+mess.getTimezone(); - activity.timezoneTextView.setText(timezoneText); - String countViewsText = getResources().getString(R.string.views_cam)+mess.getViews(); - activity.countViewsTextView.setText(countViewsText); + activity.idTextView.setText(getString(R.string.id_cam, mess.getId())); + activity.titleTextView.setText(getString(R.string.title_cam, mess.getTitle())); + activity.timezoneTextView.setText(getString(R.string.timezone_cam, mess.getTimezone())); + activity.countViewsTextView.setText(getString(R.string.views_cam, mess.getViews())); } } } @@ -247,7 +242,8 @@ private WebcamsMessage downloadWebcamsMessageByUrl(URL url) throws IOException { } stream = connection.getInputStream(); if (stream != null) { - List messagesList = readJsonStream(stream); + ResponseJsonReader reader = new ResponseJsonReader(); + List messagesList = reader.readJsonStream(stream); Iterator iter = messagesList.iterator(); WebcamsMessage message = null; while (iter.hasNext()) { @@ -277,162 +273,6 @@ private WebcamsMessage downloadWebcamsMessageByUrl(URL url) throws IOException { return result; } - public List readJsonStream(InputStream in) throws IOException { - JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); - try { - return readResult(reader); - } finally { - reader.close(); - } - } - - public List readResult(JsonReader reader) throws IOException { - List messages = new ArrayList<>(); - - reader.beginObject(); - while (reader.hasNext()) { - String name = reader.nextName(); - if (name.equals("result")) { - messages = readWebcams(reader); - } else { - reader.skipValue(); - } - } - reader.endObject(); - return messages; - } - - public List readWebcams(JsonReader reader) throws IOException { - List messages = new ArrayList<>(); - Integer total = -1; - - reader.beginObject(); - while (reader.hasNext()) { - String name = reader.nextName(); - if (name.equals("total")) { - total = reader.nextInt(); - } - else if (name.equals("webcams") && total > 0) { - messages = readArrayCams(reader); - } - else { - reader.skipValue(); - } - } - reader.endObject(); - //Если не найдено ни одной камеры - if (total == 0) { - Log.w(TAG, "There is no camera available"); - throw new IOException("There is no camera available"); - } - return messages; - } - - public List readArrayCams(JsonReader reader) throws IOException { - List messages = new ArrayList<>(); - - reader.beginArray(); - while (reader.hasNext()) { - messages.add(readWebcamsMessage(reader)); - } - reader.endArray(); - return messages; - } - - public WebcamsMessage readWebcamsMessage(JsonReader reader) throws IOException { - String id = null; - String status = null; - String title = null; - String preview = null; - String timezone = null; - Integer views = -1; - - reader.beginObject(); - while (reader.hasNext()) { - String name = reader.nextName(); - if (name.equals("id")) { - id = reader.nextString(); - } - else if (name.equals("status")) { - status = reader.nextString(); - } - else if (name.equals("title")) { - title = reader.nextString(); - } - else if (name.equals("image")) { - preview = readCurrent(reader); - } - else if (name.equals("location")) { - timezone = readTimeZone(reader); - } - else if (name.equals("statistics")) { - reader.beginObject(); - reader.nextName(); - views = reader.nextInt(); - reader.endObject(); - } - else { - reader.skipValue(); - } - } - reader.endObject(); - return new WebcamsMessage(id, status, title, preview, timezone, views); - } - - - public String readCurrent(JsonReader reader) throws IOException { - String preview = null; - - reader.beginObject(); - while (reader.hasNext()) { - String name = reader.nextName(); - if (name.equals("current")) { - preview = readPreview(reader); - } - else { - reader.skipValue(); - } - } - reader.endObject(); - - return preview; - } - public String readPreview(JsonReader reader) throws IOException { - String preview = null; - - reader.beginObject(); - while (reader.hasNext()) { - String name = reader.nextName(); - if (name.equals("preview")) { - preview = reader.nextString(); - } - else { - reader.skipValue(); - } - } - reader.endObject(); - - return preview; - } - - public String readTimeZone(JsonReader reader) throws IOException { - String timezone = null; - - reader.beginObject(); - while (reader.hasNext()) { - String name = reader.nextName(); - if (name.equals("timezone")) { - timezone = reader.nextString(); - } - else { - reader.skipValue(); - } - } - reader.endObject(); - - return timezone; - } - public WebcamsMessage getMess() { return mess; } @@ -448,63 +288,6 @@ public void setProgress(Integer progress) { public void setErrorOccured(Boolean errorOccured) { this.errorOccured = errorOccured; } - - //["result"]["webcams"][0]["id"] ("1350096618") - //["result"]["webcams"][0]["status"] if "active" - //["result"]["webcams"][0]["title"] ("St Petersburg: Saint Petersburg − Intermodal Terminal") - //["result"]["webcams"][0]["image"]["current"]["preview"] ("https://images.webcams.travel/preview/1240664025.jpg") - //["result"]["webcams"][0]["location"]["timezone"] ("Europe/Moscow") - //["result"]["webcams"][0]["statistics"]["views"] (28718) - public class WebcamsMessage { - private String id; - private String status; - private String title; - private String preview; - private String timezone; - private Integer views; - private Bitmap bitmap = null; - - public WebcamsMessage(String id, String status, String title, String preview, String timezone, Integer views) { - this.id = id; - this.status = status; - this.title = title; - this.preview = preview; - this.timezone = timezone; - this.views = views; - } - - public String getId() { - return id; - } - - public String getStatus() { - return status; - } - - public String getTitle() { - return title; - } - - public String getPreview() { - return preview; - } - - public String getTimezone() { - return timezone; - } - - public Integer getViews() { - return views; - } - - public Bitmap getBitmap() { - return bitmap; - } - - public void setBitmap(Bitmap bitmap) { - this.bitmap = bitmap; - } - } } private static final String TAG = "CityCam"; diff --git a/app/src/main/java/ru/android_2019/citycam/cache/MyCache.java b/app/src/main/java/ru/android_2019/citycam/cache/MyCache.java index 1189329..226f444 100644 --- a/app/src/main/java/ru/android_2019/citycam/cache/MyCache.java +++ b/app/src/main/java/ru/android_2019/citycam/cache/MyCache.java @@ -1,12 +1,12 @@ package ru.android_2019.citycam.cache; import android.util.LruCache; -import ru.android_2019.citycam.CityCamActivity; +import ru.android_2019.citycam.reader.WebcamsMessage; public class MyCache { private static MyCache instance; - private LruCache lru; + private LruCache lru; private int cacheSize = 8 * 1024 * 1024; //8 MiB private MyCache() { @@ -20,7 +20,7 @@ public static MyCache getInstance() { return instance; } - public LruCache getLru() { + public LruCache getLru() { return lru; } } \ No newline at end of file diff --git a/app/src/main/java/ru/android_2019/citycam/reader/ResponseJsonReader.java b/app/src/main/java/ru/android_2019/citycam/reader/ResponseJsonReader.java new file mode 100644 index 0000000..4b107ce --- /dev/null +++ b/app/src/main/java/ru/android_2019/citycam/reader/ResponseJsonReader.java @@ -0,0 +1,168 @@ +package ru.android_2019.citycam.reader; + +import android.util.JsonReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + + +public class ResponseJsonReader { + private List messages; + + public ResponseJsonReader() { + messages = new ArrayList<>(); + } + + public List readJsonStream(InputStream in) throws IOException { + JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); + try { + readResult(reader); + } finally { + reader.close(); + } + return messages; + } + + private void readResult(JsonReader reader) throws IOException { + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("result")) { + readWebcams(reader); + } else { + reader.skipValue(); + } + } + reader.endObject(); + } + + private void readWebcams(JsonReader reader) throws IOException { + int total = -1; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("total")) { + total = reader.nextInt(); + } + else if (name.equals("webcams") && total > 0) { + readArrayCams(reader); + } + else { + reader.skipValue(); + } + } + reader.endObject(); + //Если не найдено ни одной камеры + if (total == 0) { + throw new IOException("There is no camera available"); + } + } + + private void readArrayCams(JsonReader reader) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + messages.add(readWebcamsMessage(reader)); + } + reader.endArray(); + } + + private WebcamsMessage readWebcamsMessage(JsonReader reader) throws IOException { + String id = null; + String status = null; + String title = null; + String preview = null; + String timezone = null; + int views = -1; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + switch (name) { + case "id": + id = reader.nextString(); + break; + case "status": + status = reader.nextString(); + break; + case "title": + title = reader.nextString(); + break; + case "image": + preview = readCurrent(reader); + break; + case "location": + timezone = readTimeZone(reader); + break; + case "statistics": + reader.beginObject(); + reader.nextName(); + views = reader.nextInt(); + reader.endObject(); + break; + default: + reader.skipValue(); + break; + } + } + reader.endObject(); + return new WebcamsMessage(id, status, title, preview, timezone, views); + } + + + private String readCurrent(JsonReader reader) throws IOException { + String preview = null; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("current")) { + preview = readPreview(reader); + } + else { + reader.skipValue(); + } + } + reader.endObject(); + + return preview; + } + + private String readPreview(JsonReader reader) throws IOException { + String preview = null; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("preview")) { + preview = reader.nextString(); + } + else { + reader.skipValue(); + } + } + reader.endObject(); + + return preview; + } + + private String readTimeZone(JsonReader reader) throws IOException { + String timezone = null; + + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (name.equals("timezone")) { + timezone = reader.nextString(); + } + else { + reader.skipValue(); + } + } + reader.endObject(); + + return timezone; + } +} diff --git a/app/src/main/java/ru/android_2019/citycam/reader/WebcamsMessage.java b/app/src/main/java/ru/android_2019/citycam/reader/WebcamsMessage.java new file mode 100644 index 0000000..858be31 --- /dev/null +++ b/app/src/main/java/ru/android_2019/citycam/reader/WebcamsMessage.java @@ -0,0 +1,63 @@ +package ru.android_2019.citycam.reader; + +import android.graphics.Bitmap; + +/** + * ["result"]["webcams"][0]["id"] ("1350096618") + * ["result"]["webcams"][0]["status"] if "active" + * ["result"]["webcams"][0]["title"] ("St Petersburg: Saint Petersburg − Intermodal Terminal") + * ["result"]["webcams"][0]["image"]["current"]["preview"] ("https://images.webcams.travel/preview/1240664025.jpg") + * ["result"]["webcams"][0]["location"]["timezone"] ("Europe/Moscow") + * ["result"]["webcams"][0]["statistics"]["views"] (28718) + */ + +public class WebcamsMessage { + private String id; + private String status; + private String title; + private String preview; + private String timezone; + private Integer views; + private Bitmap bitmap = null; + + public WebcamsMessage(String id, String status, String title, String preview, String timezone, Integer views) { + this.id = id; + this.status = status; + this.title = title; + this.preview = preview; + this.timezone = timezone; + this.views = views; + } + + public String getId() { + return id; + } + + public String getStatus() { + return status; + } + + public String getTitle() { + return title; + } + + public String getPreview() { + return preview; + } + + public String getTimezone() { + return timezone; + } + + public Integer getViews() { + return views; + } + + public Bitmap getBitmap() { + return bitmap; + } + + public void setBitmap(Bitmap bitmap) { + this.bitmap = bitmap; + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_city_cam.xml b/app/src/main/res/layout/activity_city_cam.xml index 4f7c3af..11f6ebc 100644 --- a/app/src/main/res/layout/activity_city_cam.xml +++ b/app/src/main/res/layout/activity_city_cam.xml @@ -3,7 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="#fff"> + android:background="@color/colorBackground"> + android:background="@color/colorCamBackground"/> - - - + android:orientation="vertical" + > + + + - - - - - + android:orientation="vertical" + android:layout_toRightOf="@id/firstColumn" + android:layout_toEndOf="@id/firstColumn" + android:layout_alignTop="@id/firstColumn" + android:layout_alignBottom="@id/firstColumn" + > + + + - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_city.xml b/app/src/main/res/layout/item_city.xml index d41c949..18d7878 100644 --- a/app/src/main/res/layout/item_city.xml +++ b/app/src/main/res/layout/item_city.xml @@ -6,7 +6,9 @@ android:textAppearance="?android:attr/textAppearanceListItemSmall" android:gravity="center_vertical" android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" + android:paddingStart="?android:attr/listPreferredItemPaddingLeft" android:paddingRight="?android:attr/listPreferredItemPaddingRight" + android:paddingEnd="?android:attr/listPreferredItemPaddingRight" android:minHeight="?android:attr/listPreferredItemHeightLarge" android:focusableInTouchMode="false" android:focusable="true" diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d390c28..f0b091e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,4 +4,7 @@ #303F9F #FF4081 #33FF4081 + #999 + #fff + #96ffc2 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 47c8224..c35af02 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -2,4 +2,6 @@ 16dp 16dp + 160dp + 40dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ba24a7..905ceed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,8 +1,8 @@ City Cam - ID камеры : - Место : - Часовой пояс : - Количество просмотров : + ID камеры : %s + Место : %s + Часовой пояс : %s + Количество просмотров : %d Изображение не найдено diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5885930..7e2a6ae 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,5 +1,4 @@ - -