From 6c273bf8166e2d3c58affe3ae8b6ab8977b9da57 Mon Sep 17 00:00:00 2001 From: Jack Shendrikov Date: Wed, 10 Feb 2021 23:39:49 +0200 Subject: [PATCH] Add Lab 3 (Book) --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 12 ++ .../ua/kpi/comsys/io8227/jackshen/Book.java | 67 ++++++ .../comsys/io8227/jackshen/BookActivity.java | 123 +++++++++++ .../comsys/io8227/jackshen/BookAdapter.java | 119 ++++++++++ .../io8227/jackshen/BookJSONParser.java | 204 ++++++++++++++++++ .../comsys/io8227/jackshen/BookLoader.java | 47 ++++ .../comsys/io8227/jackshen/MainActivity.java | 23 +- app/src/main/res/drawable/noimage.png | Bin 0 -> 5830 bytes app/src/main/res/layout/activity_book.xml | 46 ++++ app/src/main/res/layout/activity_main.xml | 39 ++-- app/src/main/res/layout/book_list_item.xml | 105 +++++++++ app/src/main/res/values/colors.xml | 1 + 13 files changed, 758 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/ua/kpi/comsys/io8227/jackshen/Book.java create mode 100644 app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookActivity.java create mode 100644 app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookAdapter.java create mode 100644 app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookJSONParser.java create mode 100644 app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookLoader.java create mode 100644 app/src/main/res/drawable/noimage.png create mode 100644 app/src/main/res/layout/activity_book.xml create mode 100644 app/src/main/res/layout/book_list_item.xml diff --git a/app/build.gradle b/app/build.gradle index fb17597..cf0e993 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,6 +24,7 @@ repositories { } dependencies { + implementation 'com.android.support:support-annotations:28.0.0' implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ab5cdae..7600201 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/ua/kpi/comsys/io8227/jackshen/Book.java b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/Book.java new file mode 100644 index 0000000..34ecd81 --- /dev/null +++ b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/Book.java @@ -0,0 +1,67 @@ +package ua.kpi.comsys.io8227.jackshen; + +/** An {@link Book} object contains information related to a single book. */ + +class Book { + + /** Title of the book */ + private String mTitle; + + /** Subtitle of the book */ + private String mSubtitle; + + /** ISBN of the book*/ + private String mIsbn; + + /** Retail price of the book */ + private String mPrice; + + /** Cover of the book */ + private String mImageUrl; + + /** + * Create book object + * + * @param title - title of the book + * @param subtitle - subtitle of the book + * @param isbn - isbn number of the book + * @param price - retail price of the book + * @param imageUrl - the URL to find cover of the book + */ + Book(String title, String subtitle, String isbn, String price, String imageUrl) { + this.mTitle = title; + this.mSubtitle = subtitle; + this.mIsbn = isbn; + this.mPrice = price; + this.mImageUrl = imageUrl; + } + + /** + * Return the title information of the book + * + * @return the title of the book + */ + String getTitle() { + return mTitle; + } + + /** Return the subtitle of the book */ + String getSubtitle() { + return mSubtitle; + } + + /** Return the ISBN number of the book */ + String getISBN() { + return mIsbn; + } + + /** Return the retail price of the book */ + String getPrice() { + return mPrice; + } + + /** Return the URL to find cover of the book */ + String getImageUrl() { + return mImageUrl; + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookActivity.java b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookActivity.java new file mode 100644 index 0000000..dbdbedb --- /dev/null +++ b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookActivity.java @@ -0,0 +1,123 @@ +package ua.kpi.comsys.io8227.jackshen; + +import android.app.LoaderManager; +import android.content.Context; +import android.content.Loader; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AppCompatActivity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + + +public class BookActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks> { + + /** URL for book data */ + private static final String REQUEST_URL = "https://api.jsonbin.io/b/6023ffbd87173a3d2f5b37e9"; + + /** + * Constant value for the book loader ID. + * We can choose any int, it`s really needed for multiple loaders + */ + private static final int BOOK_LOADER_ID = 1; + + /** Adapter for the list of books */ + private BookAdapter mAdapter; + + /** TextView that is displayed when the list is empty */ + private TextView mEmptyTextView; + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + @Override + protected void onCreate(Bundle savedInstanceState) { + // Initialize activity on main thread. + // Bundle holds previous state when re-initialized + super.onCreate(savedInstanceState); + + // Inflate the activity's UI + setContentView(R.layout.activity_book); + + // Find a reference to the {@link ListView} in the layout + ListView bookListView = findViewById(R.id.list); + + // Set empty view when there is no data + mEmptyTextView = findViewById(R.id.empty_view); + bookListView.setEmptyView(mEmptyTextView); + + // Create a new adapter that takes an empty list of books as input + mAdapter = new BookAdapter(this, new ArrayList()); + + // Set the adapter on the {@link ListView} so the list can be populated in UI + bookListView.setAdapter(mAdapter); + + // Get a reference to the ConnectivityManager to check state of network connectivity + ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + + // Get details on the currently active default data network + NetworkInfo networkInfo = Objects.requireNonNull(connMgr).getActiveNetworkInfo(); + + // If there is a network connection -> get data + if (networkInfo != null && networkInfo.isConnected()) { + // Get a reference to the LoaderManager, in order to interact with loaders. + LoaderManager loaderManager = getLoaderManager(); + + // Get the loader initialized. Go through the above specified int ID constant + // and pass the bundle to null. + loaderManager.initLoader(BOOK_LOADER_ID, null, this); + } else { + + // Otherwise, display error and hide loading indicator so error message will be visible + View loadingIndicator = findViewById(R.id.progress_bar); + loadingIndicator.setVisibility(View.GONE); + + // Update empty state with no connection error message + mEmptyTextView.setText("No internet connection."); + } + } + + @Override + public Loader> onCreateLoader(int i, Bundle bundle) { + // Create a new loader for the given URL + Uri baseUri = Uri.parse(REQUEST_URL); + + return new BookLoader(this, baseUri.toString()); + } + + /** + * The loader requests and parses information downloader from the internet on a background + * thread pool, keeping the UI thread unblocked + */ + @Override + public void onLoadFinished(Loader> loader, List books) { + + // Hide progress bar + View loadingIndicator = findViewById(R.id.progress_bar); + loadingIndicator.setVisibility(View.GONE); + + // Set empty state text to display "No books to display." + mEmptyTextView.setText("No books to display."); + + // Clear the adapter of previous data + mAdapter.clear(); + + // Add valid list of books to the adapter + if (books != null && !books.isEmpty()) + mAdapter.addAll(books); + } + + @Override + public void onLoaderReset(Loader> loader) { + // Clear existing data on adapter as loader is reset + mAdapter.clear(); + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookAdapter.java b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookAdapter.java new file mode 100644 index 0000000..53a6643 --- /dev/null +++ b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookAdapter.java @@ -0,0 +1,119 @@ +package ua.kpi.comsys.io8227.jackshen; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.AsyncTask; +import android.os.Build; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import java.io.InputStream; +import java.util.List; +import java.util.Objects; + + +/** + * Class {@link BookAdapter} is used to create a list item layout for each book + * in the data source (a list of {@link Book} objects). + * + * An adapter view such as ListView will be provided with these + * list item layouts to be presented to the user. + */ + +public class BookAdapter extends ArrayAdapter { + + /** + * Constructs a new {@link BookAdapter}. + * + * @param context of the app + * @param books - list of books + */ + + BookAdapter(Context context, List books) { + super(context, 0, books); + } + + /** + * Returns the view of the list item that shows information about the book in the book list. + */ + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + // Check if there is an existing convertView that we can reuse, otherwise, + // if convertView is null, then inflate a new list item layout. + View listItemView = convertView; + if (listItemView == null) { + listItemView = LayoutInflater.from(getContext()).inflate(R.layout.book_list_item, parent, false); + } + + // Find the book at the given position in the list of books + Book currentBook = getItem(position); + + // Find the TextView with view ID titleBook + TextView titleView = listItemView.findViewById(R.id.titleBook); + // Find the TextView with view ID subtitleBook + TextView subtitleView = listItemView.findViewById(R.id.subtitleBook); + // Find the TextView with view ID isbnBook + TextView isbnView = listItemView.findViewById(R.id.isbnBook); + // Find the TextView with view ID priceBook + TextView priceView = listItemView.findViewById(R.id.priceBook); + // Find the ImageView with view ID imageBook + ImageView imageView = listItemView.findViewById(R.id.imageBook); + + // Display the title of the current book in that TextView + titleView.setText(Objects.requireNonNull(currentBook).getTitle()); + // Display the subtitle of the current book in that TextView + subtitleView.setText(currentBook.getSubtitle()); + // Display the ISBN of the current book in that TextView + isbnView.setText(currentBook.getISBN()); + // Display the price of the current book in that TextView + priceView.setText(currentBook.getPrice()); + + // Display image in the ImageView widget + if (currentBook.getImageUrl().equals("")) { + imageView.setImageResource(R.drawable.noimage); + } else { + new DownloadImage(imageView).execute(currentBook.getImageUrl()); + } + + return listItemView; + } + + /** Class to download an image from URL */ + private class DownloadImage extends AsyncTask { + final ImageView bmImage; + + DownloadImage(ImageView bmImage) { + this.bmImage = bmImage; + } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + protected Bitmap doInBackground(String... urls) { + String urlDisplay = urls[0]; + Bitmap mIcon = null; + try { + InputStream in = new java.net.URL(urlDisplay).openStream(); + mIcon = BitmapFactory.decodeStream(in); + } catch (Exception e) { + Log.e("Error", Objects.requireNonNull(e.getMessage())); + e.printStackTrace(); + } + return mIcon; + } + + protected void onPostExecute(Bitmap result) { + bmImage.setImageBitmap(result); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookJSONParser.java b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookJSONParser.java new file mode 100644 index 0000000..538e8d4 --- /dev/null +++ b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookJSONParser.java @@ -0,0 +1,204 @@ +package ua.kpi.comsys.io8227.jackshen; + +import android.text.TextUtils; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +/** Helper methods to request and retrieve book data from a specified JSON */ +final class BookJSONParser { + + /** Tag for the log messages */ + private static final String LOG_MSG = BookJSONParser.class.getSimpleName(); + + /** + * We are creating a private constructor because no one else should create + * the {@link BookJSONParser} object. + */ + private BookJSONParser() { + } + + /** + * Query given JSON and return a list of {@link String} objects. + * + * @param requestUrl - our URL as a {@link String} object + * @return the list of {@link Book}s + */ + static List getBookData(String requestUrl) { + URL url = makeUrl(requestUrl); + + // Perform HTTP request to the URL and receive a JSON response back + String jsonResponse = null; + try { + jsonResponse = makeHttpRequest(url); + } catch (IOException err) { + Log.e(LOG_MSG, "Problem making the HTTP request.", err); + } + + // Extract relevant fields from the JSON response and create a list of {@link Book}s + // Then return the list of {@link Book}s + return extractDataFromJson(jsonResponse); + } + + /** Returns new URL object from the given string URL. */ + private static URL makeUrl(String strUrl) { + // Initialize an empty {@link URL} object to hold the parsed URL from the strUrl + URL url = null; + + // Parse valid URL from param strUrl and handle Malformed urls + try { + url = new URL(strUrl); + } catch (MalformedURLException err) { + Log.e(LOG_MSG, "Problem building the URL!", err); + } + + // Return valid URL + return url; + } + + /** Make an HTTP request to the given URL and return a String as the response. */ + private static String makeHttpRequest(URL url) throws IOException { + // Initialize variable to hold the parsed JSON response + String jsonResponse = ""; + + // Return response if URL is null + if (url == null) { + return jsonResponse; + } + + // Initialize HTTP connection object + HttpURLConnection connection = null; + + // Initialize {@link InputStream} to hold response from request + InputStream inputStream = null; + try { + // Establish connection to the URL + connection = (HttpURLConnection) url.openConnection(); + + // Set request type + connection.setRequestMethod("GET"); + + // Setting how long to wait on the request (in milliseconds) + connection.setReadTimeout(10000); + connection.setConnectTimeout(15000); + + // Establish connection to the URL + connection.connect(); + + // Check for successful connection + if (connection.getResponseCode() == 200) { + inputStream = connection.getInputStream(); + jsonResponse = readFromStream(inputStream); + } else { + Log.e(LOG_MSG, "Error response code: " + connection.getResponseCode()); + } + } catch (IOException err) { + Log.e(LOG_MSG, "Problem retrieving the book JSON results.", err); + } finally { + if (connection != null) { + // Disconnect the connection after successfully making the HTTP request + connection.disconnect(); + } + + if (inputStream != null) { + // Close the stream after successfully parsing the request + // This also can throw an IOException + inputStream.close(); + } + } + + // Return JSON as a {@link String} + return jsonResponse; + } + + /** + * Convert the {@link InputStream} into a String which contains the whole JSON response. + */ + private static String readFromStream(InputStream inputStream) throws IOException { + StringBuilder output = new StringBuilder(); + if (inputStream != null) { + // Decode the bits + InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8")); + + // Buffer the decoded characters + BufferedReader reader = new BufferedReader(inputStreamReader); + + // Store a line of characters from the BufferedReader + String line = reader.readLine(); + + // If not end of buffered input stream, read next line and add to output + while (line != null) { + output.append(line); + line = reader.readLine(); + } + } + + // Convert chars sequence from the builder into a string and return + return output.toString(); + } + + private static List extractDataFromJson(String bookJSON) { + // Exit if no data was returned from the HTTP request + if (TextUtils.isEmpty(bookJSON)) + return null; + + // Initialize list of strings to hold the extracted books + List books = new ArrayList<>(); + + try { + // Create JSON object from response + JSONObject rawJSONResponse = new JSONObject(bookJSON); + + // Extract the array that holds the books + JSONArray bookArray = rawJSONResponse.getJSONArray("books"); + + for (int i = 0; i < bookArray.length(); i++) { + // Get the current book + JSONObject currentBook = bookArray.getJSONObject(i); + + // Get the book's title + String title = currentBook.getString("title"); + + // Get the book's subtitle + String subtitle = currentBook.getString("subtitle"); + + // Get the book's ISBN number + String isbn13 = currentBook.getString("isbn13"); + + // Get the book's price + String price = currentBook.getString("price"); + + // Get the URL of book's cover + String imageUrl = currentBook.getString("image"); + + // Create a new {@link Book} object with the title, subtitle, isbn13, price + // and imageUrl from the JSON response. + Book book = new Book(title, subtitle, isbn13, price, imageUrl); + + // Add the new {@link Book} to the list of books. + books.add(book); + } + + } catch (JSONException e) { + // Catch the exception from the 'try' block and print a log message + Log.e("BookJSONParser", "Problem parsing the book JSON results", e); + } + + // Return the list of books + return books; + } + +} \ No newline at end of file diff --git a/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookLoader.java b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookLoader.java new file mode 100644 index 0000000..79fb6fb --- /dev/null +++ b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/BookLoader.java @@ -0,0 +1,47 @@ +package ua.kpi.comsys.io8227.jackshen; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import java.util.List; + + +/** Using AsyncTask to load a list of books by network request to a certain URL. */ +public class BookLoader extends AsyncTaskLoader> { + + /** Query URL **/ + final private String mUrl; + + + /** + * Constructs a new {@link BookLoader}. + * + * @param context of the activity + * @param url to load data from + */ + BookLoader(Context context, String url) { + super(context); + mUrl = url; + } + + + /** Forcing the loader to make an HTTP request and start downloading the required data */ + @Override + protected void onStartLoading() { + forceLoad(); + } + + /** + * This method is called in a background thread and takes care of the + * generating new data from the given JSON file + */ + @Override + public List loadInBackground() { + // Check for valid string url + if (mUrl == null) { + return null; + } + // Perform the network request, parse the response, and extract a list of books. + return BookJSONParser.getBookData(mUrl); + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/kpi/comsys/io8227/jackshen/MainActivity.java b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/MainActivity.java index 215ab1b..c6c7111 100644 --- a/app/src/main/java/ua/kpi/comsys/io8227/jackshen/MainActivity.java +++ b/app/src/main/java/ua/kpi/comsys/io8227/jackshen/MainActivity.java @@ -3,12 +3,10 @@ import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.view.View; import android.view.Window; import android.view.WindowManager; -import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; @@ -81,14 +79,19 @@ public void onSwipeLeft() { }); - Button button = findViewById(R.id.open_portfolio); - button.setOnClickListener(new View.OnClickListener(){ - @Override - public void onClick(View view) { - Intent openSite = new Intent(Intent.ACTION_VIEW, Uri.parse("https://jackshen.herokuapp.com/")); - startActivity(openSite); - } - }); +// Button button = findViewById(R.id.open_portfolio); +// button.setOnClickListener(new View.OnClickListener(){ +// @Override +// public void onClick(View view) { +// Intent openSite = new Intent(Intent.ACTION_VIEW, Uri.parse("https://jackshen.herokuapp.com/")); +// startActivity(openSite); +// } +// }); + } + + public void openBooks(View v) { + Intent intent = new Intent(this, BookActivity.class); + startActivity(intent); } public void openInfograph(View v) { diff --git a/app/src/main/res/drawable/noimage.png b/app/src/main/res/drawable/noimage.png new file mode 100644 index 0000000000000000000000000000000000000000..bda4bcb0f98fcf8602b423a36e368ff750fd3d1d GIT binary patch literal 5830 zcmZ`-WmprA^WVtPj2r`L7|lp&2aLgx1`&{Egh+QN4mLoL7>%Q(BnB#}gh-4oDG5cT zB~(&TRDOP6{$Ko`d+t8Z=k7kwJ@@8bT#AL6Arn269smF^p^cDM{}A(^gQ)(+m3^m= z|A5L{*Hjk(Xv$>x?MD5N3wjt?nF0VY;s5{^4*>l7m%@Gq0K(w_z@94rpiu|_a0eB4 zT5A0(P`F<=L;^08GbdjEV{}1A_Mrd(1N(mt1Q3fM|B|#}Xj22)Eed*0s0^(dMil_y z>_8)RZK77b6h{*r2k#AETm4pe$Gf#_*4T$di0%p&A&6&$`02xeF6cC;PfIlH8zNI~ z!(=p{W|MTV)mDf*j8k*#lawEy09eO8Ei*Tzk1JWXUyzOt*lFHMdes)%^P^Mqes}4k zt0bo{*%Y$(U)FeipDZf<`tikm@5?xC>B1Wi2~>sXNNP5H-w6Hg3Db%9VhA=cp`cGo z+od(f&5f8OLwUt#i{Zs4GM$d(%!rVG7 zUo&khDJ!Lf6uVpa?|Wz6yeL^&Cx8r)NTkoD>N7pP*eh&otfe=*I>!HyrD=^4gOg$t z?%3c?xAn^{WC=t-9^(h)rO@nSKENZZR?$k1w@KqnLwkm^?vJ<_X6g@%J>HgTwfZq* zO-8eX3)meYZDK#+XW{e0F+fWl$51i$Pyog_BloEIIgCwNtzR-VNTfIa@I0cKW1iAb zFJ0LhtHx>c(4)avy981iEEuiI_J#R+FkExl*OaOJZ|-nG{lI@?Nt{`O2dNpwdy;0C z7I@CHg94M`e`{omoGjrZTVEQKvCO?ARYj>J7J@Nf_$b>v?0*p{AY zlF+Sf4oaXY6eR%8a!xD9qutD~YIFC3*yX+$4tbvXZb&LaJZHqiF%seAEdaP~HEjtLB}b$`3nJBWqa-r>tK~5zclpXYVUUoXdTm zQY&x2zm=x#q9=HG=j}h{`c5bM@+*%CE|cu`W#ycci}l6nY&Nz!bQ~C;1$}$0y3{zSI5V!!NR#!0>?e zy%}5WGG^Z}kEIC%f45{wZq&DVfiRS&XXc#r358|0h&zIxu3l$PY}8^(is|fLXEZia z-Pa z`jGUy*`#ZOvb(^ttf!n5C|@7ArFD|s>3znN4jOAvDq&T{Yz``e%uMf)-4XFQ*`!aE42 z{95yqh@4K#27(pq^=O%$@RZ2fJDJHY-&H#+UcTBR~UO#bE(|mflSkb%)McwO8>HQ(Xe- z;k6AhxY*#!*9M7n)|>`b_%w@hTsJje4#{fk#pm=8gb@ymh3h>|%(C)LfkV;|K0>03 z{6r&zikRE}S7YteH8c1qJD1y@9REE;tv-b3%F-R}6>g8IzO{zYEU*vGo(s#)`lQak zX~3wPZ4`lTWSAf`qhMKVLU@ExN(1R}5Q3bja8qp-{1_uB-3&b|>eBg1N^dA+GSm}4 zXB6l(zLzn6MMxnnN37rzRAE)$z8$E*RCyBt00R-^R8Vm**m`f zHPoQ++OHex&`yuY+Njoik;@*tHwRdz{C#x1at(PC58-3Hou5-gM$QoY#uJm$r1gZ2 zIUq194o<1!#|Zj(J;60dg(a_!Tntcnr<&M;p~UGZi9c%!S= z1lecxI07n2tmNRXY{GSw8C;W^G-_vGcsbKpuo;Qt)ZbRvq2KKES&d z;g5`n;ENP1S-TqGu^_{!6NeLD;FGb>sCi$r@jRU07F$eosXtYNyXRhA3AZyc`W_w)`<<;hJ zgLeHYwzd7O$#XB@;)xS~4Jw1bxm77uc(W$0g{Z^7rQlmU7!%WxoNPZJMwe5xcDIx) zWX@E^&!s3}&^vEh7{oSd-73Y+`f4ofEjm!-VMUuvjIXU7d<>|kH&tEMbx!}z9!=kO z$KL0~Ag>(?KOi+VyyM%fHLC9;O8>I>Ap|%j_UYPBmN;E`r?*_G!WnC`W-xW} zC@k2Kk^f{QMElN2DDZXi8i@2Vt9_`UQ7_F;WyYHL_@0{d_>bLC>c@~ZLb#KMOwAiM zCox(@h}3x$MA<;uU(KdAd%&V!OcbY|#c_KKzo|Y^*_}m=NSBIs9P^m z&JCMZ9qiohCA8>e-#KT~*ADSn!%8YuddpY7GgxVhQ>SE9&`bZCj}`ihM)vdKio+z-3hN&pS>SUe74#Af(&>o0?n)Ea&G>)Jxa#Z9 z3AWP7#nt!+-d1m%i5Q->qSah@X`)zeO<`&C14TREsVYbjV#A78RhQx&;L&t5T29R= zf6yn!(jJ&61ubY`x-~|H;QGiWH3RZ3b_LlXxYeKk$+n+V<#kYP$UpMRhzo5MSUK4` zZ0WQXGO`pj=QW?x5)+lV>aMHSuRT8!Bz>dDbVMtldVI*?nkR44S@$v&_bmwYGJ7A| zqJFRiWVQs(o*u>6ZX60`+RMcY4alC(K*igk?X<+QKuw@Jl@D<_Ah9qQz> z!n5I$Ivb~LV>u#t1tma7X_pTP^ExbKSwE}_zCMH0+EM`iNNVID(# zd=&9jZ+`P?xBKCsYm-p_r}Z^K5IRf5b0BbzRf%@G{s{)_9j!un@SOD3-t61ZC-(*)7ODDO%pZ8OvIAm63r|x?>#F~84luGyS3};N<8zIB&nAP~!+)9T&Rn+pX zIN2O6_X21on;&{Iec44;#K-X?v$oG78iLyaNd|Nq7BzSC`I=-J7HHhVZEZU?2s(yF zK5dl&YvCf#50Faz@^p`eg}>XGLfx(=sBT;K31PTz&IGV81@{}VHsge&pFY3)eH1*r z(7YDBH}-Xs-v~zP7dXhy3JRxuP?365{+n`wfj_&}7>&@XpD?l|n{isi!soj}+GFx6 zQbKfid0MPE(U9yM(L_U=%;CxBs5wjh-e-8S-R~X{w&0F6XTXTL=xg(T?|MZX=^S_f zyG;f?f?{AjLXXm?tQ#ST0Yz6Gh?rjcAxc}9@{ zEbZ%am6)3|f(&LKq}o^G07<*O9~FMJ97aH?UjQ;?;)FSw>a%=7vQ>!<7$t2PPg++H z&pIGMS^6-Z?E-$_@gU&o4u6v5v5EUfutm%E+Pm*l;WB2(Fq)0dko+-b2Ke2c)^mof z51&_vycNMCa|X0LcOCimV=GY@Qln^>W53zkH(u%ZZ2$}`v+WfEL3QM?r!{2;*q&Vc zP8i6Yd07QU_?Safn9!PLA}SDTxm#hFq~?BRjdQBSVyE9e=jE*|SHfmd`O0$6m8Z6w z03wHUZIgg$X*DF`c^Wkz2BgcK4Sbx_k_Az|C5T41wVOlu2Fmq%~pXKRk6io{6%~uN~vD8RpPq2s5BJ2rUw%_8ks>sSw8mrm|1hz zFCmAKL(iHL@5EWLobLD31$2k&FSfQNj*8ING<3ooe98>a;dqDC>Lch7g-S<*g$t=r z6N>XYgfYvMpfDD&u3h9tkju)&Q7IWA;#aoKT5(YkpGz0gsp<6!cU|d*YDcf2+7#WC z&@kN4K53j^W7i{BU&c+RlFwCvovMLMNkuEs#FmL;1WNv@J){`_Ea=L8gHh5pi`Xip zgbiygY+I~vBlA1~B~+=_YQ)WJ=#oUABN3%NqdZ~D%wqY5yG2Rv zssiCa$R=E}-CC$wJqn#KJe}pnT^W@wi|`X?Rs#9K`b7o}!r*TEgH6qm&*PaeFtG%O z>Ah*_gH7rQD{CJw&Kzp2I0(BF{4&9VuY#p4E(18O){Ax?pqjoVOTsN4JbrklHO!ig z^=HiL)3?hMg19M7o9Y)R47JXx>oY`M-SgWpJ>#V|f)3GlNrVvS&07cyer`(ApWR>v z->o-z#vl~RsR~1CeL6#GV`|goBgb70TM%3?Rb}p*(;%fqv(Q4sXRf4)1`!p+mvXeUZpxsvf+g1$W<2-Nd0m1Z^ zkw|<%6ws$_w@KdF)q3HTjIM`WBEs7B3XK8xL%-JsDSNpzQ@3T%-Fpw~4C#)RV#d@5 zksM+qq7sGV8h1hl>q5xsV5^CsMasNm&Yu*1lv_jC+q`+vG4m%!AC$G8C}6qh(-l$V z*~Ub!EKtADj~-vyXks`@&=aHUxehPad;JHUI8*oPgByhhUU>-rq#|UH6gFHn8?Mqa zx@wA$=pU+cz$wt+W}xW`Q2#UqF;{E3nCQE znDkvKp`bUcA_np4WEhSZ;z=`5}-fg107;`dJm@3 zlDg}HzX`f1T+yr>3tO$!G^&!<6o2_E?YBc1P4Z_qK{1SVmdDZd?mMX+(O;aS>sr%6 zCsmaQ;Va8$a=zYxp)>@=5ST|OQEZBa+cV-)8!gEF2K~KIOU@^RY+yWdD`ILQ@RJw7 zYU4iGUQ%>YshUC@`P#6qdPnPcgHMGCa@2(I9xXq3^mBKK5s~^{q?p+}X8ubSS=TIT zfZF!OY4LP6kq>b2bZ~HB*o7_JheloJ1;Y6;S@+{DqPRZMM1-oKoYzceomjIt0)NZI zEuW-Yzt|DPz)p3rF)~iSscgG%S6paMyhQ>9rySOTAEzh=JRS3J%=)Z7g#RWN#Hwm4 z`oQA+WUIy{vMI_=%S~z{bRbIvl#ye<8z&s=OwU4#>xG%7p6Y!|$l{89jbl%f$>Y}i z4&qt*Z1R%#JnaJ{&N!lKNrxK|Q>S>TcMtY0Lc5Id_N?vlz2B + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3ab363e..9462c0c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -100,18 +100,18 @@ android:paddingLeft="32dp" android:paddingRight="32dp"> - + + + + + + + + + + + +