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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +