diff --git a/app/build.gradle b/app/build.gradle
index 946c4f1..4eab8f9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -6,10 +6,11 @@ android {
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.example.mobilelab"
- minSdkVersion 21
+ minSdkVersion 26
targetSdkVersion 29
versionCode 1
versionName "1.0"
+ multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -22,6 +23,18 @@ android {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
+ packagingOptions {
+ exclude 'project.properties'
+ exclude 'META-INF/INDEX.LIST'
+ exclude 'META-INF/DEPENDENCIES'
+ exclude 'META-INF/LICENSE'
+ exclude 'META-INF/LICENSE.txt'
+ exclude 'META-INF/license.txt'
+ exclude 'META-INF/NOTICE'
+ exclude 'META-INF/NOTICE.txt'
+ exclude 'META-INF/notice.txt'
+ exclude 'META-INF/ASL2.0'
+ }
}
dependencies {
@@ -33,6 +46,7 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
- implementation 'com.google.firebase:firebase-auth:19.1.0'
+ implementation 'com.google.firebase:firebase-auth:19.2.0'
implementation 'com.google.android.material:material:1.0.0'
+ implementation 'com.google.cloud:google-cloud-storage:1.101.0'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8234400..75a6350 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -25,6 +25,7 @@
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/mobilelab/Good.java b/app/src/main/java/com/example/mobilelab/Good.java
index e3d39f7..5bc6bd0 100644
--- a/app/src/main/java/com/example/mobilelab/Good.java
+++ b/app/src/main/java/com/example/mobilelab/Good.java
@@ -3,9 +3,11 @@
import com.google.gson.annotations.SerializedName;
import java.text.DateFormat;
+import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
+import java.util.Objects;
public class Good {
@@ -28,6 +30,21 @@ public Good(String title, String place, float price, String img, long date) {
this.date = date;
}
+ public Good(String title, String place, String date, String price, String img) {
+ this.title = title;
+ this.place = place;
+ this.price = Float.parseFloat(price);
+ this.img = img;
+ final String pattern = "dd/MM/yyyy";
+ SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.US);
+ try {
+ Date parsedDate = format.parse(date);
+ this.date = Objects.requireNonNull(parsedDate).getTime();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ }
+
public String getTitle() {
return title;
}
@@ -41,7 +58,7 @@ public String getPrice() {
}
public String getImg() {
- return String.format("%s%s", "http://bowling-iot.pp.ua", img);
+ return img;
}
public String getDate() {
diff --git a/app/src/main/java/com/example/mobilelab/GoodsService.java b/app/src/main/java/com/example/mobilelab/GoodsService.java
index f0e5dcc..2b08a70 100644
--- a/app/src/main/java/com/example/mobilelab/GoodsService.java
+++ b/app/src/main/java/com/example/mobilelab/GoodsService.java
@@ -3,9 +3,14 @@
import java.util.List;
import retrofit2.Call;
+import retrofit2.http.Body;
import retrofit2.http.GET;
+import retrofit2.http.POST;
public interface GoodsService {
@GET("goods/all")
Call> listGoods();
+
+ @POST("goods_android/")
+ Call addGood(@Body Good good);
}
diff --git a/app/src/main/java/com/example/mobilelab/ListFragment.java b/app/src/main/java/com/example/mobilelab/ListFragment.java
index c9f1571..c083bc5 100644
--- a/app/src/main/java/com/example/mobilelab/ListFragment.java
+++ b/app/src/main/java/com/example/mobilelab/ListFragment.java
@@ -8,6 +8,7 @@
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -57,6 +58,9 @@ private void initFields(final View view) {
DividerItemDecoration itemDecor = new DividerItemDecoration(Objects.requireNonNull(getActivity()), VERTICAL);
recyclerView.addItemDecoration(itemDecor);
recyclerView.setAdapter(new GoodsAdapter(main.getContext(), new ArrayList<>(), R.layout.list_item_good));
+ view.findViewById(R.id.new_item_button).setOnClickListener(v -> {
+ startActivity(new Intent(view.getContext(), NewItemActivity.class));
+ });
}
private void initOnRefresh(final View view) {
diff --git a/app/src/main/java/com/example/mobilelab/NewItemActivity.java b/app/src/main/java/com/example/mobilelab/NewItemActivity.java
new file mode 100644
index 0000000..b2ae430
--- /dev/null
+++ b/app/src/main/java/com/example/mobilelab/NewItemActivity.java
@@ -0,0 +1,231 @@
+package com.example.mobilelab;
+
+import android.app.DatePickerDialog;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.WindowManager;
+import android.webkit.MimeTypeMap;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.android.material.snackbar.Snackbar;
+import com.google.android.material.textfield.TextInputLayout;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.BlobInfo;
+import com.google.cloud.storage.Bucket;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.squareup.picasso.Picasso;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.Objects;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public class NewItemActivity extends AppCompatActivity {
+ private static final int IMAGE_REQUEST = 1;
+ private TextInputLayout titleField;
+ private TextInputLayout placeField;
+ private TextInputLayout dateField;
+ private TextInputLayout priceField;
+ private DatePickerDialog picker;
+ private ProgressBar progressBar;
+ private String imgDownloadLink;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_new_item);
+ Objects.requireNonNull(getSupportActionBar()).setTitle(getString(R.string.new_item));
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ initFields();
+ }
+
+ private void initFields() {
+ titleField = findViewById(R.id.title_wrapper);
+ placeField = findViewById(R.id.place_wrapper);
+ dateField = findViewById(R.id.date_wrapper);
+ progressBar = findViewById(R.id.progress_bar);
+ priceField = findViewById(R.id.price_wrapper);
+ findViewById(R.id.new_image_button).setOnClickListener(v -> openImage());
+ initButton();
+ initDatePicker();
+ }
+
+ private void initButton() {
+ findViewById(R.id.btn_create_item).setOnClickListener(v -> {
+ final String title = Objects.requireNonNull(titleField.getEditText()).getText().toString();
+ final String name = Objects.requireNonNull(placeField.getEditText()).getText().toString();
+ final String date = Objects.requireNonNull(dateField.getEditText()).getText().toString();
+ final String price = Objects.requireNonNull(priceField.getEditText()).getText().toString();
+ addItem(title, name, date, price, imgDownloadLink);
+ });
+ }
+
+ private void initDatePicker() {
+ final EditText date = dateField.getEditText();
+ Objects.requireNonNull(date).setOnClickListener(v -> {
+ final Calendar calendar = Calendar.getInstance();
+ int day = calendar.get(Calendar.DAY_OF_MONTH);
+ int month = calendar.get(Calendar.MONTH);
+ int year = calendar.get(Calendar.YEAR);
+ picker = new DatePickerDialog(this, (view, year1, monthOfYear, dayOfMonth) ->
+ date.setText(String.format(getString(R.string.date_format),
+ dayOfMonth, monthOfYear + 1, year1)), year, month, day);
+ picker.show();
+ });
+ }
+
+ private void addItem(final String title, final String place, final String date, final String price, final String imgPath) {
+ if (!validate(title, place, date, price)) {
+ return;
+ }
+ final GoodsService service = getApplicationEx().getApiService();
+ Good good = new Good(title, place, date, price, imgPath);
+ Call call = service.addGood(good);
+ progressBar.setVisibility(View.VISIBLE);
+ call.enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response response) {
+ progressBar.setVisibility(View.INVISIBLE);
+ if (response.isSuccessful()) {
+ openMainActivity();
+ }
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ progressBar.setVisibility(View.INVISIBLE);
+ Snackbar.make(findViewById(R.id.item_view), R.string.post_failed, Snackbar.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private void openImage() {
+ final Intent intent = new Intent();
+ intent.setType("image/*");
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ startActivityForResult(intent, IMAGE_REQUEST);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {
+ new UploadImageTask().execute(data.getData());
+ }
+ }
+
+ private void showImage(final String photoUrl) {
+ final int TARGET_WIDTH = 200;
+ final int TARGET_HEIGHT = 200;
+ if (!photoUrl.isEmpty()) {
+ Picasso.get().load(photoUrl).resize(TARGET_WIDTH, TARGET_HEIGHT).into((ImageView) findViewById(R.id.item_image));
+ }
+ }
+
+ private String getFileExtension(Uri uri) {
+ ContentResolver contentResolver = Objects.requireNonNull(this).getContentResolver();
+ MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
+ return mimeTypeMap.getExtensionFromMimeType(contentResolver.getType(uri));
+ }
+
+ public boolean validate(final String title, final String place, final String date, final String price) {
+ boolean valid = true;
+ if (Utils.validateString(title)) {
+ titleField.setError(null);
+ } else {
+ titleField.setError(getString(R.string.title_error));
+ valid = false;
+ }
+
+ if (Utils.validateString(place)) {
+ placeField.setError(null);
+ } else {
+ placeField.setError(getString(R.string.place_error));
+ valid = false;
+ }
+
+ if (!date.isEmpty()) {
+ dateField.setError(null);
+ } else {
+ dateField.setError(getString(R.string.date_error));
+ valid = false;
+ }
+
+ if (Utils.validatePrice(price)) {
+ priceField.setError(null);
+ } else {
+ priceField.setError(getString(R.string.price_error));
+ valid = false;
+ }
+
+ return valid;
+ }
+
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ openMainActivity();
+ return true;
+ }
+
+ private void openMainActivity() {
+ final Intent myIntent = new Intent(getApplicationContext(), MainActivity.class);
+ startActivityForResult(myIntent, 0);
+ }
+
+ private App getApplicationEx() {
+ return ((App) Objects.requireNonNull(this.getApplication()));
+ }
+
+ private class UploadImageTask extends AsyncTask {
+ protected void onPreExecute() {
+ progressBar.setVisibility(View.VISIBLE);
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+ }
+
+ protected Void doInBackground(Uri... blobName) {
+ ServiceAccountCredentials credentials;
+ try {
+ credentials = ServiceAccountCredentials.fromStream(getResources().openRawResource(R.raw.bowlingsite));
+ Storage storage = StorageOptions.newBuilder().setProjectId("bowlingsite")
+ .setCredentials(credentials)
+ .build()
+ .getService();
+ final String bucketName = "www.bowling-iot.pp.ua";
+ final Bucket bucket = storage.get(bucketName);
+ final String imgPath = "img/" + blobName[0].getLastPathSegment() + "." + getFileExtension(blobName[0]);
+ final BlobId blobId = BlobId.of(bucket.getName(), imgPath);
+ final InputStream imageStream = getContentResolver().openInputStream(blobName[0]);
+ final BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("image/jpeg").build();
+ final Blob blob = storage.create(blobInfo, imageStream);
+ imgDownloadLink = blob.getMediaLink();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ showImage(imgDownloadLink);
+ progressBar.setVisibility(View.INVISIBLE);
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/mobilelab/ProfileFragment.java b/app/src/main/java/com/example/mobilelab/ProfileFragment.java
index 8cf29a7..63528ef 100644
--- a/app/src/main/java/com/example/mobilelab/ProfileFragment.java
+++ b/app/src/main/java/com/example/mobilelab/ProfileFragment.java
@@ -8,6 +8,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.text.InputType;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -139,7 +140,7 @@ private void showEmailUpdateDialog() {
private void updateName() {
final String name = newName.getText().toString();
- if (Utils.validateName(name)) {
+ if (Utils.validateString(name)) {
performNameUpdate(name);
} else {
newName.setError(getString(R.string.name_error));
@@ -150,6 +151,7 @@ private void performNameUpdate(String name) {
newName.setError(null);
final UserProfileChangeRequest profileUpdates = new UserProfileChangeRequest.Builder()
.setDisplayName(name).build();
+ this.name.setText(name);
fuser.updateProfile(profileUpdates)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
diff --git a/app/src/main/java/com/example/mobilelab/SignUpActivity.java b/app/src/main/java/com/example/mobilelab/SignUpActivity.java
index ba56c24..4c14bab 100644
--- a/app/src/main/java/com/example/mobilelab/SignUpActivity.java
+++ b/app/src/main/java/com/example/mobilelab/SignUpActivity.java
@@ -111,7 +111,7 @@ public boolean validate(final String email, final String name, final String phon
valid = false;
}
- if (Utils.validateName(name)) {
+ if (Utils.validateString(name)) {
nameField.setError(null);
} else {
nameField.setError(getString(R.string.name_error));
diff --git a/app/src/main/java/com/example/mobilelab/Utils.java b/app/src/main/java/com/example/mobilelab/Utils.java
index de80c15..004e3d9 100644
--- a/app/src/main/java/com/example/mobilelab/Utils.java
+++ b/app/src/main/java/com/example/mobilelab/Utils.java
@@ -1,5 +1,7 @@
package com.example.mobilelab;
+import android.text.TextUtils;
+
class Utils {
static boolean validateEmail(String email) {
return !email.isEmpty() && android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches();
@@ -13,8 +15,11 @@ static boolean validatePhone(String phone) {
return !phone.isEmpty() && !android.util.Patterns.PHONE.matcher(phone).matches();
}
- static boolean validateName(String name) {
- return name.matches("^[A-Za-z]+$");
+ static boolean validateString(String name) {
+ return name.matches("^[A-Za-z\\s*]+");
}
+ static boolean validatePrice(String price) {
+ return !price.isEmpty() && TextUtils.isDigitsOnly(price);
+ }
}
diff --git a/app/src/main/res/drawable/ic_add_black_24dp.xml b/app/src/main/res/drawable/ic_add_black_24dp.xml
new file mode 100644
index 0000000..e3979cd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_black_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index a7f9666..65b104e 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,31 +1,45 @@
+ tools:context=".MainActivity">
+
-
+
+
+
+
+ android:visibility="invisible" />
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_new_item.xml b/app/src/main/res/layout/activity_new_item.xml
new file mode 100644
index 0000000..5736cfd
--- /dev/null
+++ b/app/src/main/res/layout/activity_new_item.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/raw/bowlingsite.json b/app/src/main/res/raw/bowlingsite.json
new file mode 100644
index 0000000..2a31a7d
--- /dev/null
+++ b/app/src/main/res/raw/bowlingsite.json
@@ -0,0 +1,12 @@
+{
+ "type": "service_account",
+ "project_id": "bowlingsite",
+ "private_key_id": "99d773d3b104e876cced8dca8094cefdba23f653",
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDKm1B9ZSffUZUP\nYXPZx7OjrwDQd9R2ZhUt98bV308Lv6gYyYc5YIo+qsnRPvjgGW2BgjkdDBylsXvO\nGpZgyJAfoT51YVx+a0d8YpK9+2BFPmqSNyOIMgFDweo9B1AzNPR3pWdK9ifeWR9q\nbDf3y9UiyC6mngM30QPA0dBNH4pJk/rWTiaSdOPPra6M70vaE8TF6yUERlOj68b5\nAeXezf+c5vrUg1myIMWkG5caehYp4Y+AhraWjw0nwqP219AHRpfU3YIWCwyDZr2/\nKZMx0pA6TljO5bE4uasHKcD/A+/opMPzRWK2VSiYWI5jKfZEKF14Bsh7mgEJxHXS\n8JpVZT9tAgMBAAECggEAF4YdfBW24a+1/CVvP1G3ypE66bmEDmQvYNk0OahWI/HS\niWA6xl9uZbTMPnKOkGx7Ya5WRcheeiGHk6hUHfQjgbylxRizFc2MbQV/7rWFWbzT\nwFrNEhbmPTECaqgS+IMVQGAQxGwcuG0GIAPlIq0Et+dgvUE68+vdgPGLeyzmkEC8\niWjBuUHo4f9qMuRH12bgE+2XyXtRINEF/TG3iQYhLk9WHU+HkJCTQzkjv32drQTL\nHJnJ4/XNsTaEaHWB1LtDFFzBvClnRpjjgdak/Kv6USBGjNWaJRCcXxOi1zLchV5y\n+mt3xwRfz5B1jV0rFWOJinQDVcsnMmOCvOvOW4uoSQKBgQDwAxWcc4rJ7ThGAWmY\nXw+rbhyrC8XpcC5Uf7O3c+DK1T2kiS0K53fBt0G29o9z0Tpinp6FkHqKmlw6Voc2\nUyC27hAfEzm8PDwowi/z350VjdI8ARtHgG03/R5ilMZcn3/po3VPkn7632zRe033\nqdSkQ0q9B2EZTHdIaVMnKCoH6QKBgQDYGluqwz2QuWDr4ut0qZRUDp5InRBrz//d\nkeIerm0ej4KiD/gAYgIVUS/eYu9Y1vNsYz5Ts5sE3nTZkzJjLUCOOjwUBxxkAFd7\n24DbeRoI+zT57/VtNGzd5peymJPLrk9s0NYmLr2IRFAIcNbBoYOkai74Beoyd9is\nx6NYTPtM5QKBgDeuP4FAPN6Drh5vjXPP+e6naPc1kACMCfOIfMT+mRP1TIuzV0PE\nV0AVp6UupjEURRtD+Tq7PfKRxPuzv0KXHmvR5uOOrkJBJyO9iSpmoiax9Cf/ibiY\nzr3Cdx4frbUnO6GUTk7biaHh23jtxjTCrZY+FkSsRZZ86t9dr/DRnI6hAoGARN3Z\neoivlPBOn7131Xy5JTRnYrKS6hil7GEyEx747TVEpwq5uL2C9nIh9BfJOKF/f3n9\nAcWJ0DpoOHH9K7ffC7QpMyS0+nVVa8YewVJNZrFPR5sdtkhvrSNwEl0nNFB/RSkk\nJZTdl5BVhOsYtSoYfovMHgQchyrEJQOV3Wzi5LUCgYEAnDHI0xsfCSddOnqFwnP0\nb6Trq0HV+fi2CHtGnaIspDx2oLzf7AL29ZECS9DDc0qSMy1nFAg/wnfVpFjCBMBw\ngDnX9seZHY7suw817GSx+g6YygrwTqKSyNvZ4St/18a5GRvlawJIh9VzQWlgnEl7\nWUW8Nprk+tMbz5IkjcvFjKs=\n-----END PRIVATE KEY-----\n",
+ "client_email": "bowlingsite@appspot.gserviceaccount.com",
+ "client_id": "116677193359201066626",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/bowlingsite%40appspot.gserviceaccount.com"
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3b7c2e4..8587860 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -9,11 +9,13 @@
Phone
Logo
Create Account
+ Create Item
Welcome!
Hello World! I\'m blank fragment
Sign Out
Authentication failed
Can\'t fetch data from server
+ Can\'t post data to server
No internet connection
Please check your email and password
Enter a valid email
@@ -25,7 +27,7 @@
\$
Place of purchase: %1$s
image
- Title:
+ Title:
Place of purchase:
Description:
Price:
@@ -48,4 +50,11 @@
Confirm
Name updated
Good\'s details
+ Add new good
+ Image of the item
+ Please provide a valid title
+ Please type in a valid place
+ This does not look like a date
+ Please fill in a valid price
+ %d/%d/%d