From 054e0c9c2b15ad678d717bb4694449a7fb36933f Mon Sep 17 00:00:00 2001 From: Julia Luo Date: Thu, 2 Mar 2017 23:34:24 -0800 Subject: [PATCH] Proj4 --- .gitignore | 9 + .idea/compiler.xml | 22 ++ .idea/copyright/profiles_settings.xml | 3 + .idea/encodings.xml | 6 + .idea/gradle.xml | 18 ++ .idea/misc.xml | 64 +++++ .idea/modules.xml | 9 + .idea/runConfigurations.xml | 12 + .idea/vcs.xml | 6 + app/.gitignore | 1 + app/build.gradle | 45 +++ app/google-services.json | 55 ++++ app/proguard-rules.pro | 17 ++ .../mdbsocials/ExampleInstrumentedTest.java | 26 ++ app/src/main/AndroidManifest.xml | 45 +++ .../www/mdbsocials/DetailedSocial.java | 81 ++++++ .../www/mdbsocials/DetailsActivity.java | 262 ++++++++++++++++++ .../www/mdbsocials/FeedActivity.java | 186 +++++++++++++ .../juliazluo/www/mdbsocials/FeedAdapter.java | 90 ++++++ .../www/mdbsocials/LoginActivity.java | 85 ++++++ .../www/mdbsocials/NewSocialActivity.java | 228 +++++++++++++++ .../www/mdbsocials/PopupAdapter.java | 67 +++++ .../www/mdbsocials/SignupActivity.java | 224 +++++++++++++++ .../com/juliazluo/www/mdbsocials/Social.java | 78 ++++++ .../com/juliazluo/www/mdbsocials/User.java | 34 +++ .../com/juliazluo/www/mdbsocials/Utils.java | 212 ++++++++++++++ .../main/res/drawable/ic_add_white_36dp.xml | 9 + .../main/res/drawable/ic_group_black_24dp.xml | 4 + .../main/res/drawable/ic_star_accent_24dp.xml | 9 + app/src/main/res/layout/activity_details.xml | 128 +++++++++ app/src/main/res/layout/activity_feed.xml | 36 +++ app/src/main/res/layout/activity_login.xml | 120 ++++++++ .../main/res/layout/activity_new_social.xml | 105 +++++++ app/src/main/res/layout/activity_signup.xml | 155 +++++++++++ app/src/main/res/layout/content_feed.xml | 21 ++ app/src/main/res/layout/feed_item.xml | 75 +++++ app/src/main/res/layout/interested_popup.xml | 27 ++ app/src/main/res/layout/popup_item.xml | 39 +++ app/src/main/res/menu/menu.xml | 10 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes app/src/main/res/values-w820dp/dimens.xml | 6 + app/src/main/res/values/colors.xml | 8 + app/src/main/res/values/dimens.xml | 6 + app/src/main/res/values/strings.xml | 28 ++ app/src/main/res/values/styles.xml | 20 ++ app/src/main/res/xml/file_paths.xml | 4 + .../www/mdbsocials/ExampleUnitTest.java | 17 ++ build.gradle | 24 ++ gradle.properties | 17 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 +++++++++++ gradlew.bat | 90 ++++++ settings.gradle | 1 + 58 files changed, 3010 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 .idea/vcs.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/google-services.json create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/juliazluo/www/mdbsocials/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/DetailedSocial.java create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/DetailsActivity.java create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/FeedActivity.java create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/FeedAdapter.java create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/LoginActivity.java create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/NewSocialActivity.java create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/PopupAdapter.java create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/SignupActivity.java create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/Social.java create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/User.java create mode 100644 app/src/main/java/com/juliazluo/www/mdbsocials/Utils.java create mode 100644 app/src/main/res/drawable/ic_add_white_36dp.xml create mode 100644 app/src/main/res/drawable/ic_group_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_star_accent_24dp.xml create mode 100644 app/src/main/res/layout/activity_details.xml create mode 100644 app/src/main/res/layout/activity_feed.xml create mode 100644 app/src/main/res/layout/activity_login.xml create mode 100644 app/src/main/res/layout/activity_new_social.xml create mode 100644 app/src/main/res/layout/activity_signup.xml create mode 100644 app/src/main/res/layout/content_feed.xml create mode 100644 app/src/main/res/layout/feed_item.xml create mode 100644 app/src/main/res/layout/interested_popup.xml create mode 100644 app/src/main/res/layout/popup_item.xml create mode 100644 app/src/main/res/menu/menu.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/values-w820dp/dimens.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/xml/file_paths.xml create mode 100644 app/src/test/java/com/juliazluo/www/mdbsocials/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..22c7d6e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f5e9010 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..b635629 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,45 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.2" + defaultConfig { + applicationId "com.juliazluo.www.mdbsocials" + minSdkVersion 14 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:25.1.1' + compile 'com.google.firebase:firebase-auth:10.0.1' + compile 'com.google.firebase:firebase-core:10.0.1' + compile 'com.android.support:design:25.1.1' + compile 'com.android.support:recyclerview-v7:25.1.1' + compile 'com.android.support:cardview-v7:25.1.1' + compile 'com.google.firebase:firebase-database:10.0.1' + compile 'com.google.firebase:firebase-storage:10.0.1' + testCompile 'junit:junit:4.12' + compile 'com.android.support.constraint:constraint-layout:1.0.0-beta5' + compile 'com.github.bumptech.glide:glide:3.5.2' + compile 'com.android.support:support-v4:25.0.0' +} + + + + + +apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..9909a87 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,55 @@ +{ + "project_info": { + "project_number": "169942198807", + "firebase_url": "https://mdbsocials-e7639.firebaseio.com", + "project_id": "mdbsocials-e7639", + "storage_bucket": "mdbsocials-e7639.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:169942198807:android:22dfb5876576d185", + "android_client_info": { + "package_name": "com.juliazluo.www.mdbsocials" + } + }, + "oauth_client": [ + { + "client_id": "169942198807-05jkokp3ereb7jfkdtpu3hm5vadomdg4.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.juliazluo.www.mdbsocials", + "certificate_hash": "F7CC9337433CDE3989DBB81795A709DB656C6ABF" + } + }, + { + "client_id": "169942198807-1k930tvvqd9sd5ei0uhsolg5qktgii07.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAb__JFWpZHzua2-Q34Zamh51MW3Ahfqac" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 2, + "other_platform_oauth_client": [ + { + "client_id": "169942198807-1k930tvvqd9sd5ei0uhsolg5qktgii07.apps.googleusercontent.com", + "client_type": 3 + } + ] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..ff2266e --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\julia\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/com/juliazluo/www/mdbsocials/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/juliazluo/www/mdbsocials/ExampleInstrumentedTest.java new file mode 100644 index 0000000..7cf7dec --- /dev/null +++ b/app/src/androidTest/java/com/juliazluo/www/mdbsocials/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.juliazluo.www.mdbsocials; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.juliazluo.www.mdbsocials", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b034a23 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/DetailedSocial.java b/app/src/main/java/com/juliazluo/www/mdbsocials/DetailedSocial.java new file mode 100644 index 0000000..eab2b22 --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/DetailedSocial.java @@ -0,0 +1,81 @@ +package com.juliazluo.www.mdbsocials; + +/** + * Created by julia on 2017-03-02. + */ + +public class DetailedSocial { + + private String id, name, email, imageName, description, date; + private int numRSVP; + + public DetailedSocial(String id, String name, String email, String imageName, String description, + String date, int numRSVP) { + this.id = id; + this.name = name; + this.email = email; + this.imageName = imageName; + this.description = description; + this.date = date; + this.numRSVP = numRSVP; + } + + public DetailedSocial() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getImageName() { + return imageName; + } + + public void setImageName(String imageName) { + this.imageName = imageName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public int getNumRSVP() { + return numRSVP; + } + + public void setNumRSVP(int numRSVP) { + this.numRSVP = numRSVP; + } +} diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/DetailsActivity.java b/app/src/main/java/com/juliazluo/www/mdbsocials/DetailsActivity.java new file mode 100644 index 0000000..6c0d1a4 --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/DetailsActivity.java @@ -0,0 +1,262 @@ +package com.juliazluo.www.mdbsocials; + +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.PopupWindow; +import android.widget.TextView; + +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.auth.UserInfo; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; +import com.google.firebase.storage.FirebaseStorage; +import com.google.firebase.storage.StorageReference; + +import java.util.ArrayList; + +public class DetailsActivity extends AppCompatActivity implements View.OnClickListener { + + private static final String CLASS_NAME = "DetailsActivity"; + private DatabaseReference detailsRef, socialsListRef; + private StorageReference storageRef; + private FirebaseAuth.AuthStateListener mAuthListener; + private static FirebaseAuth mAuth; + private FirebaseUser firebaseUser; + private TextView name, email, date, description; + private Button numInterestedBtn, interestedBtn; + private ImageView imageView; + private String id, displayName; + private Uri profileUri; + private int numInterested; + private PopupWindow popupWindow; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_details); + + //Initiate activity components + detailsRef = FirebaseDatabase.getInstance().getReference("/socialDetails"); + socialsListRef = FirebaseDatabase.getInstance().getReference("/socialsList"); + storageRef = FirebaseStorage.getInstance().getReference(); + name = (TextView) findViewById(R.id.name_detail); + email = (TextView) findViewById(R.id.email_detail); + date = (TextView) findViewById(R.id.date_detail); + description = (TextView) findViewById(R.id.description_detail); + numInterestedBtn = (Button) findViewById(R.id.num_interested_btn); + interestedBtn = (Button) findViewById(R.id.interested_btn); + imageView = (ImageView) findViewById(R.id.image_detail); + numInterestedBtn.setOnClickListener(this); + interestedBtn.setOnClickListener(this); + mAuth = FirebaseAuth.getInstance(); + + //Initiate user authentication listener + mAuthListener = new FirebaseAuth.AuthStateListener() { + @Override + public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { + FirebaseUser user = firebaseAuth.getCurrentUser(); + if (user != null) { + // User is signed in + firebaseUser = user; + + //Retrieve the current user's name and profile image URI + displayName = user.getDisplayName(); + profileUri = user.getPhotoUrl(); + + for (UserInfo userInfo : user.getProviderData()) { + if (displayName == null && userInfo.getDisplayName() != null) { + displayName = userInfo.getDisplayName(); + } + if (profileUri == null && userInfo.getPhotoUrl() != null) { + profileUri = userInfo.getPhotoUrl(); + } + } + Log.i(CLASS_NAME, "onAuthStateChanged:signed_in:" + user.getUid()); + } else { + // User is signed out + Log.i(CLASS_NAME, "onAuthStateChanged:signed_out"); + Intent intent = new Intent(getApplicationContext(), LoginActivity.class); + startActivity(intent); + } + } + }; + } + + @Override + public void onStop() { + super.onStop(); + if (mAuthListener != null) { + mAuth.removeAuthStateListener(mAuthListener); + } + firebaseUser = null; + } + + @Override + protected void onStart() { + super.onStart(); + mAuth.addAuthStateListener(mAuthListener); + + //Retrieve the id and image name of the social + Intent intent = getIntent(); + id = intent.getStringExtra(FeedAdapter.SOCIAL_ID); + String imageName = intent.getStringExtra(FeedAdapter.IMAGE_NAME); + + detailsRef.addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + //Retrieve the detailed social from database and update activity components based on the information + DetailedSocial social = dataSnapshot.child(id).child("social").getValue(DetailedSocial.class); + name.setText(social.getName()); + email.setText("Host: " + social.getEmail()); + description.setText(social.getDescription()); + date.setText("Date: " + social.getDate()); + numInterested = social.getNumRSVP(); + numInterestedBtn.setText(numInterested + " Interested"); + + if (!dataSnapshot.child(id).child("usersRSVP").hasChild(firebaseUser.getUid())) { + interestedBtn.setText("Interested"); + } else { + interestedBtn.setText("Not interested"); + } + } + + @Override + public void onCancelled(DatabaseError error) { + // Failed to read value + Log.i(CLASS_NAME, "Failed to read value.", error.toException()); + } + }); + + //Load the social image into image view + Utils.loadImage(CLASS_NAME, imageName, this, imageView, 300); + } + + /** + * Increment the number of users interested in the social + */ + private void incrementInterested() { + if (firebaseUser != null) { + //Add user to RSVP list in database + User user = new User(displayName, profileUri.toString()); + detailsRef.child(id).child("usersRSVP").child(firebaseUser.getUid()).setValue(user); + } + + //Increment the number of interested users + numInterested += 1; + updateInterested(); + interestedBtn.setText("Not interested"); + } + + /** + * Decrement the number of users interested in the social + */ + private void decrementInterested() { + if (firebaseUser != null) { + //Remove user from RSVP list + detailsRef.child(id).child("usersRSVP").child(firebaseUser.getUid()).removeValue(); + } + + //Decrement the number of interested users + numInterested -= 1; + updateInterested(); + interestedBtn.setText("Interested"); + } + + /** + * Update Firebase database and button to reflect change in number of interested users + */ + private void updateInterested() { + detailsRef.child(id).child("social").child("numRSVP").setValue(numInterested); + socialsListRef.child(id).child("social").child("numRSVP").setValue(numInterested); + numInterestedBtn.setText(numInterested + " Interested"); + } + + /** + * Display the popup with recycler view of interested users + */ + private void showPopup() { + //Initialize popup and recyclerview for the popup + final View popupView = LayoutInflater.from(this).inflate(R.layout.interested_popup, null); + popupWindow = new PopupWindow(popupView, 1000, ViewGroup.LayoutParams.WRAP_CONTENT); + RecyclerView recyclerView = (RecyclerView) popupView.findViewById(R.id.interested_recycler); + final ArrayList users = new ArrayList<>(); + final PopupAdapter adapter = new PopupAdapter(this, users); + + detailsRef.addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + TextView textView = (TextView) popupView.findViewById(R.id.popup_text); + + if (dataSnapshot.child(id).child("usersRSVP").hasChildren()) { + for (DataSnapshot snapshot : dataSnapshot.child(id).child("usersRSVP").getChildren()) { + //Add user to the list of interested users + User user = snapshot.getValue(User.class); + users.add(user); + } + adapter.notifyDataSetChanged(); + + //Effectively remove the textView meant for no users interested + textView.setText(""); + textView.setHeight(0); + } else { + //Display that no users are interested + ((TextView) popupView.findViewById(R.id.popup_text)).setText("No users interested yet"); + ((TextView) popupView.findViewById(R.id.popup_text)).setHeight(120); + } + } + + @Override + public void onCancelled(DatabaseError error) { + // Failed to read value + Log.i(CLASS_NAME, "Failed to read value.", error.toException()); + } + }); + + //Finalize adapter + recyclerView.setAdapter(adapter); + RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext()); + recyclerView.setLayoutManager(mLayoutManager); + + //Finalize and display popup + ((Button) popupView.findViewById(R.id.popup_exit_btn)).setOnClickListener(this); + popupWindow.showAtLocation(popupView, Gravity.CENTER, 0, 0); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.num_interested_btn: + showPopup(); + break; + case R.id.interested_btn: + //Check if user clicked "Interested" or "Not interested" and act accordingly + if (interestedBtn.getText().toString() == "Interested") { + incrementInterested(); + } else { + decrementInterested(); + } + break; + case R.id.popup_exit_btn: + popupWindow.dismiss(); + break; + + } + } +} diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/FeedActivity.java b/app/src/main/java/com/juliazluo/www/mdbsocials/FeedActivity.java new file mode 100644 index 0000000..34f0b2e --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/FeedActivity.java @@ -0,0 +1,186 @@ +package com.juliazluo.www.mdbsocials; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.database.ChildEventListener; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; + +public class FeedActivity extends AppCompatActivity { + + private static final String CLASS_NAME = "FeedActivity"; + private ArrayList socials; + private FeedAdapter adapter; + private DatabaseReference ref = FirebaseDatabase.getInstance().getReference("/socialsList"); + private FirebaseAuth.AuthStateListener mAuthListener; + private HashMap socialHashMap; + private static FirebaseAuth mAuth; + private boolean rememberMe; //Whether to log user out on exit + protected static boolean leavingApp = true; //Whether user is leaving app or just this screen + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_feed); + Toolbar myToolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(myToolbar); + + //Initiate activity components + FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(getApplicationContext(), NewSocialActivity.class); + leavingApp = false; + startActivity(intent); + } + }); + mAuth = FirebaseAuth.getInstance(); + socials = new ArrayList<>(); + socialHashMap = new HashMap<>(); + adapter = new FeedAdapter(this, socials); + RecyclerView recyclerAdapter = (RecyclerView) findViewById(R.id.feed_recycler); + recyclerAdapter.setLayoutManager(new LinearLayoutManager(this)); + recyclerAdapter.setAdapter(adapter); + + //Initiate user authentication listener + mAuthListener = new FirebaseAuth.AuthStateListener() { + @Override + public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { + FirebaseUser user = firebaseAuth.getCurrentUser(); + if (user != null) { + // User is signed in + Log.i(CLASS_NAME, "onAuthStateChanged:signed_in:" + user.getUid()); + } else { + // If user is signed out, go back to login screen + Log.i(CLASS_NAME, "onAuthStateChanged:signed_out"); + Intent intent = new Intent(getApplicationContext(), LoginActivity.class); + startActivity(intent); + } + } + }; + + //Listen for changes to the children of the socials list node + ref.addChildEventListener(new ChildEventListener() { + + @Override + public void onChildAdded(DataSnapshot snapshot, String s) { + if (snapshot.child("social") != null && snapshot.child("timestamp").getValue() != null) { + //Retrieve social from database and display on recycler view + String id = snapshot.getKey(); + Social social = snapshot.child("social").getValue(Social.class); + if (!socialHashMap.containsKey(id)) { + socialHashMap.put(id, social); + } + socials.add(social); + Collections.sort(socials); + adapter.notifyDataSetChanged(); + } + } + + @Override + public void onChildChanged(DataSnapshot snapshot, String s) { + //Retrieve number of interested users and timestamp from database + String id = snapshot.getKey(); + int numRSVP = (int) (long) snapshot.child("social").child("numRSVP").getValue(Long.class); + long timestamp = snapshot.child("timestamp").getValue(Long.class); + Social social; + + if (socialHashMap.containsKey(id)) { + //Altering existing social + social = socialHashMap.get(id); + } else { + //New social added + social = snapshot.child("social").getValue(Social.class); + socialHashMap.put(id, social); + } + + //Update the changed social's number of interested users and timestamp + social.setTimestamp(timestamp); + social.setNumRSVP(numRSVP); + adapter.notifyDataSetChanged(); + } + + @Override + public void onChildRemoved(DataSnapshot dataSnapshot) { + + } + + @Override + public void onChildMoved(DataSnapshot dataSnapshot, String s) { + + } + + @Override + public void onCancelled(DatabaseError databaseError) { + + } + }); + } + + @Override + public void onStart() { + super.onStart(); + mAuth.addAuthStateListener(mAuthListener); + Intent intent = getIntent(); + + //Change remember me to reflect what was checked in login activity, if applicable + rememberMe = intent.getBooleanExtra(LoginActivity.REMEMBER_ME, rememberMe); + leavingApp = true; //Default as true + } + + @Override + public void onStop() { + super.onStop(); + if (mAuthListener != null) { + mAuth.removeAuthStateListener(mAuthListener); + } + + //If user does not want to be remembered and user is leaving app + if (leavingApp && !rememberMe) { + mAuth.signOut(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + //Initiate the menu + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case R.id.log_out: + //If user clicked log out + mAuth.signOut(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + +} diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/FeedAdapter.java b/app/src/main/java/com/juliazluo/www/mdbsocials/FeedAdapter.java new file mode 100644 index 0000000..5d250b7 --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/FeedAdapter.java @@ -0,0 +1,90 @@ +package com.juliazluo.www.mdbsocials; + +/** + * Created by julia on 2017-02-19. + */ + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.ArrayList; + +/** + * Created by Julia Luo on 2/17/2017. + */ + +public class FeedAdapter extends RecyclerView.Adapter { + + protected static final String SOCIAL_ID = "SocialID"; + protected static final String IMAGE_NAME = "ImageName"; + private static final String CLASS_NAME = "FeedAdapter"; + private Context context; + private ArrayList data; + + public FeedAdapter(Context context, ArrayList data) { + this.context = context; + this.data = data; + } + + @Override + public CustomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.feed_item, parent, false); + return new CustomViewHolder(view); + } + + + @Override + public void onBindViewHolder(final CustomViewHolder holder, int position) { + //Display the social's information onto the ViewHolder + final Social social = data.get(position); + holder.nameText.setText(social.getName()); + holder.emailText.setText("Host: " + social.getEmail()); + holder.attendingText.setText(social.getNumRSVP() + ""); + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //Proceed to details activity with the ID and image name of the clicked social + Intent intent = new Intent(context, DetailsActivity.class); + intent.putExtra(SOCIAL_ID, social.getId()); + intent.putExtra(IMAGE_NAME, social.getImageName()); + FeedActivity.leavingApp = false; + context.startActivity(intent); + } + }); + + //Load the social image into image view + Utils.loadImage(CLASS_NAME, social.getImageName(), context, holder.image, 120); + } + + + @Override + public int getItemCount() { + return data.size(); + } + + /** + * A card displayed in the RecyclerView + */ + class CustomViewHolder extends RecyclerView.ViewHolder { + TextView nameText, emailText, attendingText; + ImageView image; + + public CustomViewHolder(View view) { + super(view); + + //Initiate components on the ViewHolder (list item view) + this.nameText = (TextView) view.findViewById(R.id.feed_name); + this.emailText = (TextView) view.findViewById(R.id.feed_email); + this.attendingText = (TextView) view.findViewById(R.id.feed_attending); + this.image = (ImageView) view.findViewById(R.id.feed_image); + } + } +} + diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/LoginActivity.java b/app/src/main/java/com/juliazluo/www/mdbsocials/LoginActivity.java new file mode 100644 index 0000000..48967bb --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/LoginActivity.java @@ -0,0 +1,85 @@ +package com.juliazluo.www.mdbsocials; + +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.auth.AuthResult; +import com.google.firebase.auth.FirebaseAuth; + +public class LoginActivity extends AppCompatActivity implements View.OnClickListener { + + private static final String CLASS_NAME = "LoginActivity"; + protected static final String REMEMBER_ME = "RememberMe"; + private static FirebaseAuth mAuth; + private FirebaseAuth.AuthStateListener mAuthListener; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + + //Initiate activity components + mAuth = FirebaseAuth.getInstance(); + ((Button) findViewById(R.id.login_btn)).setOnClickListener(this); + ((TextView) findViewById(R.id.to_signup)).setOnClickListener(this); + } + + private void attemptLogin() { + //Retrieve inputted email and password + String email = ((EditText) findViewById(R.id.email_login)).getText().toString(); + String password = ((EditText) findViewById(R.id.password_login)).getText().toString(); + + if (!email.equals("") && !password.equals("")) { + //Attempt to sign user in with Firebase authentication + mAuth.signInWithEmailAndPassword(email, password) + .addOnCompleteListener(this, new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + Log.d(CLASS_NAME, "signInWithEmail:onComplete:" + task.isSuccessful()); + + if (!task.isSuccessful()) { + //Notify user that authentication failed + Log.w(CLASS_NAME, "signInWithEmail:failed", task.getException()); + Toast.makeText(LoginActivity.this, "Incorrect email or password, please try again", + Toast.LENGTH_SHORT).show(); + } else { + //Proceed to feed activity, passing on whether user wants to be remembered + Intent intent = new Intent(getApplicationContext(), FeedActivity.class); + boolean rememberMe = ((CheckBox) findViewById(R.id.remember_me)).isChecked(); + intent.putExtra(REMEMBER_ME, rememberMe); + startActivity(intent); + } + } + }); + } else { + //Email or password is empty + Toast.makeText(LoginActivity.this, "Please enter an email and password", + Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.login_btn: + attemptLogin(); + break; + case R.id.to_signup: + //Proceed to sign up screen + Intent intent = new Intent(getApplicationContext(), SignupActivity.class); + startActivity(intent); + break; + } + } +} diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/NewSocialActivity.java b/app/src/main/java/com/juliazluo/www/mdbsocials/NewSocialActivity.java new file mode 100644 index 0000000..4481540 --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/NewSocialActivity.java @@ -0,0 +1,228 @@ +package com.juliazluo.www.mdbsocials; + +import android.app.DatePickerDialog; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.DatePicker; +import android.widget.EditText; +import android.widget.Toast; + +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ServerValue; +import com.google.firebase.storage.FirebaseStorage; +import com.google.firebase.storage.OnProgressListener; +import com.google.firebase.storage.StorageReference; +import com.google.firebase.storage.UploadTask; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +public class NewSocialActivity extends AppCompatActivity implements View.OnClickListener { + + private static final int REQUEST_IMAGE_CAPTURE = 1; + private static final int REQUEST_GALLERY = 2; + private static final String CLASS_NAME = "NewSocialActivity"; + private Uri dataIn; + private StorageReference storageRef; + private DatabaseReference socialsListRef, socialDetailsRef; + private EditText name, description, date; + private static FirebaseAuth mAuth; + private FirebaseAuth.AuthStateListener mAuthListener; + private FirebaseUser user; + private Calendar calendar; + private DatePickerDialog.OnDateSetListener dateSetListener; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_new_social); + + //Initiate activity components + storageRef = FirebaseStorage.getInstance().getReference(); + socialsListRef = FirebaseDatabase.getInstance().getReference("/socialsList"); + socialDetailsRef = FirebaseDatabase.getInstance().getReference("/socialDetails"); + mAuth = FirebaseAuth.getInstance(); + ((Button) findViewById(R.id.add_image_btn)).setOnClickListener(this); + ((Button) findViewById(R.id.add_new_btn)).setOnClickListener(this); + name = (EditText) findViewById(R.id.name_new); + description = (EditText) findViewById(R.id.description_new); + date = (EditText) findViewById(R.id.date_new); + date.setOnClickListener(this); + + //Initiate user authentication listener + mAuthListener = new FirebaseAuth.AuthStateListener() { + @Override + public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { + user = firebaseAuth.getCurrentUser(); + if (user != null) { + // User is signed in + Log.d("User", "onAuthStateChanged:signed_in:" + user.getUid()); + } else { + // User is signed out + Log.d("User", "onAuthStateChanged:signed_out"); + } + } + }; + + //Initiate listener for when user sets the date + dateSetListener = new DatePickerDialog.OnDateSetListener() { + + @Override + public void onDateSet(DatePicker view, int year, int monthOfYear, + int dayOfMonth) { + //Set calendar to store the user's selected date + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, monthOfYear); + calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateLabel(); + } + }; + } + + /** + * Update the date text field to display the user's chosen date + */ + private void updateLabel() { + String dateFormat = "MM/dd/yy"; + SimpleDateFormat sdf = new SimpleDateFormat(dateFormat, Locale.US); + date.setText(sdf.format(calendar.getTime())); + } + + @Override + public void onStart() { + super.onStart(); + mAuth.addAuthStateListener(mAuthListener); + + //Initially calendar is set to today's date + calendar = Calendar.getInstance(TimeZone.getDefault()); + updateLabel(); + } + + @Override + public void onStop() { + super.onStop(); + if (mAuthListener != null) { + mAuth.removeAuthStateListener(mAuthListener); + } + + //Remove currently selected image, if any + dataIn = null; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + //Store image URI returned by camera or gallery intent + dataIn = Utils.getImageURI(requestCode, resultCode, data, REQUEST_IMAGE_CAPTURE, RESULT_OK, + REQUEST_GALLERY); + } + + /** + * Attempt to create a new social based on the information inputted by the user + */ + public void submit() { + //Retrieve name, description, and date of sical + final String nameTxt = name.getText().toString(); + final String descTxt = description.getText().toString(); + final String dateTxt = date.getText().toString(); + + //Generate random ID in database + final String id = socialsListRef.push().getKey(); + final String imageName = id + ".png"; + + //Create Social and DetailedSocial objects based on inputted information + final Social newSocial = new Social(id, nameTxt, user.getEmail(), 0, imageName, 0); + final DetailedSocial newDetailedSocial = new DetailedSocial(id, nameTxt, user.getEmail(), + imageName, descTxt, dateTxt, 0); + + if (nameTxt.equals("")) { + Toast.makeText(NewSocialActivity.this, "Social name cannot be empty", Toast.LENGTH_SHORT).show(); + } else if (dataIn != null) { + //Start upload of chosen image to the Firebase storage + storageRef.child(id + ".png").putFile(dataIn) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { + //Push data to database + pushToDatabase(id, newSocial, newDetailedSocial); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + // Handle unsuccessful uploads + Log.i(CLASS_NAME, "failed upload " + exception.getMessage()); + Toast.makeText(NewSocialActivity.this, "Image upload failed", + Toast.LENGTH_SHORT).show(); + } + }) + .addOnProgressListener(new OnProgressListener() { + @Override + public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { + //Display progress of upload to user + int progress = (int) ((100.0 * taskSnapshot.getBytesTransferred()) + / taskSnapshot.getTotalByteCount()); + Toast.makeText(NewSocialActivity.this, "Photo upload is " + progress + "% done", + Toast.LENGTH_SHORT).show(); + } + }); + + } else { + //Set image URI to default image and push data to database + newSocial.setImageName("default_icon.png"); + newDetailedSocial.setImageName("default_icon.png"); + pushToDatabase(id, newSocial, newDetailedSocial); + } + } + + /** + * Stores Social and DetailedSocial objects into Firebase database + * + * @param id + * @param social + * @param detailedSocial + */ + private void pushToDatabase(String id, Social social, DetailedSocial detailedSocial) { + //Push data to socials list ref node + socialsListRef.child(id).child("social").setValue(social); + socialsListRef.child(id).child("timestamp").setValue(ServerValue.TIMESTAMP); + + //Push data to social details ref node + socialDetailsRef.child(id).child("social").setValue(detailedSocial); + + //Return to feed activity + Intent intent = new Intent(getApplicationContext(), FeedActivity.class); + startActivity(intent); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.add_image_btn: + //Open up image options menu and start either camera or gallery intent + Utils.selectImageOption(this, REQUEST_GALLERY, REQUEST_IMAGE_CAPTURE, CLASS_NAME); + break; + case R.id.add_new_btn: + submit(); + break; + case R.id.date_new: + //Display date picker + new DatePickerDialog(NewSocialActivity.this, dateSetListener, calendar + .get(Calendar.YEAR), calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH)).show(); + break; + } + } +} diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/PopupAdapter.java b/app/src/main/java/com/juliazluo/www/mdbsocials/PopupAdapter.java new file mode 100644 index 0000000..9dd8d04 --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/PopupAdapter.java @@ -0,0 +1,67 @@ +package com.juliazluo.www.mdbsocials; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.ArrayList; + +/** + * Created by julia on 2017-02-23. + */ + +public class PopupAdapter extends RecyclerView.Adapter { + + private Context context; + private ArrayList data; + + public PopupAdapter(Context context, ArrayList data) { + this.context = context; + this.data = data; + } + + + @Override + public CustomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.popup_item, parent, false); + return new CustomViewHolder(view); + } + + + @Override + public void onBindViewHolder(final CustomViewHolder holder, int position) { + //Display user information + User user = data.get(position); + holder.nameText.setText(user.getName()); + + //Load the user profile picture into image view + new Utils.DownloadFilesTask(context, holder.image, 100).execute(user.getImageURI()); + } + + + @Override + public int getItemCount() { + return data.size(); + } + + /** + * A card displayed in the RecyclerView + */ + class CustomViewHolder extends RecyclerView.ViewHolder { + TextView nameText; + ImageView image; + + public CustomViewHolder(View view) { + super(view); + + //Initiate components on the ViewHolder (list item view) + this.nameText = (TextView) view.findViewById(R.id.popup_name); + this.image = (ImageView) view.findViewById(R.id.popup_image); + } + } +} diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/SignupActivity.java b/app/src/main/java/com/juliazluo/www/mdbsocials/SignupActivity.java new file mode 100644 index 0000000..653fabf --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/SignupActivity.java @@ -0,0 +1,224 @@ +package com.juliazluo.www.mdbsocials; + +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.auth.AuthResult; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.auth.UserProfileChangeRequest; +import com.google.firebase.storage.FirebaseStorage; +import com.google.firebase.storage.OnProgressListener; +import com.google.firebase.storage.StorageReference; +import com.google.firebase.storage.UploadTask; + +import org.w3c.dom.Text; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import static android.provider.CalendarContract.CalendarCache.URI; + +public class SignupActivity extends AppCompatActivity implements View.OnClickListener { + + private static final String CLASS_NAME = "SignupActivity"; + private static final int REQUEST_GALLERY = 2; + private static final int REQUEST_IMAGE_CAPTURE = 1; + private static final String DEFAULT_IMAGE = "https://firebasestorage.googleapis.com/v0/b/" + + "mdbsocials-e7639.appspot.com/o/default_member.png?alt=media&token=023caaae-d694-469a" + + "-961d-402c29f425c8"; + private static FirebaseAuth mAuth; + private FirebaseAuth.AuthStateListener mAuthListener; + private StorageReference storageRef; + private TextView nameText; + private String userImageUri; + private Uri dataIn; + private static int userNum = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_signup); + + //Initiate activity components + mAuth = FirebaseAuth.getInstance(); + storageRef = FirebaseStorage.getInstance().getReference(); + nameText = (TextView) findViewById(R.id.name_signup); + ((Button) findViewById(R.id.profile_pic_btn)).setOnClickListener(this); + ((Button) findViewById(R.id.signup_btn)).setOnClickListener(this); + ((TextView) findViewById(R.id.to_login)).setOnClickListener(this); + + //Initiate user authentication listener + mAuthListener = new FirebaseAuth.AuthStateListener() { + @Override + public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { + FirebaseUser user = firebaseAuth.getCurrentUser(); + if (user != null) { + // User is signed in + Log.i(CLASS_NAME, "onAuthStateChanged:signed_in:" + user.getUid()); + + //Update user profile with name and profile picture URI + UserProfileChangeRequest profileUpdates = new UserProfileChangeRequest.Builder() + .setDisplayName(nameText.getText().toString()) + .setPhotoUri(Uri.parse(userImageUri)) + .build(); + user.updateProfile(profileUpdates).addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + //Log out user due to bug in Firebase about accessing name and profile image right after registration + mAuth.signOut(); + Toast.makeText(SignupActivity.this, "Success! Please log in with your new account", + Toast.LENGTH_SHORT).show(); + Intent intent = new Intent(getApplicationContext(), LoginActivity.class); + startActivity(intent); + } + } + }); + } else { + // User is signed out + Log.i("User", "onAuthStateChanged:signed_out"); + } + } + }; + } + + @Override + public void onStart() { + super.onStart(); + mAuth.addAuthStateListener(mAuthListener); + } + + @Override + public void onStop() { + super.onStop(); + if (mAuthListener != null) { + mAuth.removeAuthStateListener(mAuthListener); + } + + //Remove currently selected image, if any + dataIn = null; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + //Store image URI returned by camera or gallery intent + dataIn = Utils.getImageURI(requestCode, resultCode, data, REQUEST_IMAGE_CAPTURE, RESULT_OK, + REQUEST_GALLERY); + } + + /** + * Attempt to upload the current image file to Firebase storage + */ + private void attemptUpload() { + //Generate unique image name for user + String location = "user" + userNum + ".png"; + userNum += 1; + + if (dataIn != null) { + //Start upload of chosen image to the Firebase storage + storageRef.child(location).putFile(dataIn) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { + //Store image URI and attempt to sign up the user + userImageUri = taskSnapshot.getDownloadUrl().toString(); + attemptSignup(); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + //Handle unsuccessful uploads + Log.i(CLASS_NAME, "Failed upload " + exception.getMessage()); + Toast.makeText(SignupActivity.this, "Image upload failed", + Toast.LENGTH_SHORT).show(); + } + }) + .addOnProgressListener(new OnProgressListener() { + @Override + public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { + //Display progress of upload to user + int progress = (int) ((100.0 * taskSnapshot.getBytesTransferred()) / + taskSnapshot.getTotalByteCount()); + Toast.makeText(SignupActivity.this, "Photo upload is " + progress + "% done", + Toast.LENGTH_SHORT).show(); + } + }); + + } else { + //Set image URI to default image and attempt to sign up the user + userImageUri = DEFAULT_IMAGE; + attemptSignup(); + } + } + + /** + * Attempt to register the user into Firebase authentication + */ + private void attemptSignup() { + //Retrieve the user's input + String email = ((EditText) findViewById(R.id.email_signup)).getText().toString(); + String password = ((EditText) findViewById(R.id.password_signup)).getText().toString(); + String confirm = ((EditText) findViewById(R.id.confirm_signup)).getText().toString(); + + if (!email.equals("") && !password.equals("") && confirm.equals(password)) { + //Use Firebase authentication to create new user with given email and password + mAuth.createUserWithEmailAndPassword(email, password) + .addOnCompleteListener(this, new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + Log.i(CLASS_NAME, "createUserWithEmail:onComplete:" + task.isSuccessful()); + + if (!task.isSuccessful()) { + //Notify user that authentication failed + Toast.makeText(SignupActivity.this, "Authentication failed, please " + + "try again", + Toast.LENGTH_SHORT).show(); + } + } + }); + } else if (!confirm.equals(password)) { + //Passwords do not match + Toast.makeText(SignupActivity.this, "The two passwords do not match", + Toast.LENGTH_SHORT).show(); + } else { + //Email or password is blank + Toast.makeText(SignupActivity.this, "Your email or password cannot be blank", + Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.profile_pic_btn: + //Open up image options menu and start either camera or gallery intent + Utils.selectImageOption(this, REQUEST_GALLERY, REQUEST_IMAGE_CAPTURE, CLASS_NAME); + break; + case R.id.signup_btn: + attemptUpload(); + break; + case R.id.to_login: + //Proceed to login screen + Intent intent = new Intent(getApplicationContext(), LoginActivity.class); + startActivity(intent); + break; + } + } +} diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/Social.java b/app/src/main/java/com/juliazluo/www/mdbsocials/Social.java new file mode 100644 index 0000000..dfa4e52 --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/Social.java @@ -0,0 +1,78 @@ +package com.juliazluo.www.mdbsocials; + +/** + * Created by julia on 2017-02-19. + */ + +public class Social implements Comparable { + + private String id, name, email, imageName; + private long timestamp; + private int numRSVP; + + public Social(String id, String name, String email, int numRSVP, String imageName, long timestamp) { + this.id = id; + this.name = name; + this.email = email; + this.numRSVP = numRSVP; + this.imageName = imageName; + this.timestamp = timestamp; + } + + public Social() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getImageName() { + return imageName; + } + + public void setImageName(String imageName) { + this.imageName = imageName; + } + + public int getNumRSVP() { + return numRSVP; + } + + public void setNumRSVP(int numRSVP) { + this.numRSVP = numRSVP; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public int compareTo(Social other) { + //Compare socials by their timestamp + return (int) (other.timestamp - this.timestamp); + } +} diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/User.java b/app/src/main/java/com/juliazluo/www/mdbsocials/User.java new file mode 100644 index 0000000..b06ca24 --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/User.java @@ -0,0 +1,34 @@ +package com.juliazluo.www.mdbsocials; + +/** + * Created by julia on 2017-02-23. + */ + +public class User { + + private String name, imageURI; + + public User(String name, String imageURI) { + this.name = name; + this.imageURI = imageURI; + } + + public User() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getImageURI() { + return imageURI; + } + + public void setImageURI(String imageURI) { + this.imageURI = imageURI; + } +} diff --git a/app/src/main/java/com/juliazluo/www/mdbsocials/Utils.java b/app/src/main/java/com/juliazluo/www/mdbsocials/Utils.java new file mode 100644 index 0000000..3b249af --- /dev/null +++ b/app/src/main/java/com/juliazluo/www/mdbsocials/Utils.java @@ -0,0 +1,212 @@ +package com.juliazluo.www.mdbsocials; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Environment; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.v4.content.FileProvider; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.widget.ImageView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.auth.AuthResult; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.storage.FirebaseStorage; +import com.google.firebase.storage.StorageReference; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Created by julia on 2017-02-28. + */ + +public class Utils { + + private static String mCurrentPhotoPath = "gallery"; //Stores current photo path + + /** + * Display alert dialog allowing the user to either take a picture or select an image from gallery + * + * @param activity Activity accessing this method + * @param requestGalleryCode Request code for gallery access + * @param requestCameraCode Request code for image capture from camera + * @param className Name of the class accessing this method (for logs) + */ + public static void selectImageOption(final Activity activity, final int requestGalleryCode, + final int requestCameraCode, final String className) { + + mCurrentPhotoPath = "gallery"; //Starts as "gallery" - indicates gallery option was selected + final CharSequence[] items = {"Take Photo", "Choose from Library", + "Cancel"}; + + //Initiate alert dialog + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle("Add Image"); + builder.setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int item) { + if (items[item].equals("Take Photo")) { + //Update current photo path to be the new photo that was taken + mCurrentPhotoPath = dispatchTakePictureIntent(className, activity, requestCameraCode); + } else if (items[item].equals("Choose from Library")) { + //Start intent to pick image from gallery + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + activity.startActivityForResult(intent, requestGalleryCode); + } else if (items[item].equals("Cancel")) { + dialog.dismiss(); //Dismiss dialog + } + } + }); + builder.show(); + } + + /** + * Create new image file location on phone and populate that file with photo from camera intent + * + * @param className Name of the class accessing this method (for logs) + * @param activity Activity accessing this method + * @param requestCameraCode Request code for image capture from camera + * @return String of the path to the newly created image file + */ + private static String dispatchTakePictureIntent(final String className, Activity activity, + final int requestCameraCode) { + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + // Ensure that there's a camera activity to handle the intent + String photoPath = "error"; + if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) { + // Create the File where the photo should go + File photoFile = null; + try { + // Create an image file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String imageFileName = "JPEG_" + timeStamp + "_"; + File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES); + File image = File.createTempFile( + imageFileName, /* prefix */ + ".png", /* suffix */ + storageDir /* directory */ + ); + photoPath = image.getAbsolutePath(); //Retrieve path of new image file + photoFile = image; + } catch (IOException ex) { + // Error occurred while creating the File + Log.i(className, "Error occurred while creating photo file"); + } + // Continue only if the File was successfully created + if (photoFile != null) { + //Start intent to take photo with camera, storing the new photo in the new image file location + Uri photoURI = FileProvider.getUriForFile(activity, + "com.example.android.fileprovider", + photoFile); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); + activity.startActivityForResult(takePictureIntent, requestCameraCode); + } + } + return photoPath; + } + + /** + * Return the image URI of the current image either selected from gallery or taken by camera + * + * @param requestCode Request code of the returned intent + * @param resultCode Result code of the returned intent + * @param data Returned intent from gallery or camera + * @param REQUEST_IMAGE_CAPTURE Request code for image capture from camera + * @param RESULT_OK Result code for successful intent + * @param REQUEST_GALLERY Request code for gallery access + * @return URI of the image specified by the intent + */ + public static Uri getImageURI(int requestCode, int resultCode, Intent data, final int REQUEST_IMAGE_CAPTURE, + final int RESULT_OK, final int REQUEST_GALLERY) { + if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) { + //Retrieve file at current photo path + File f = new File(Utils.mCurrentPhotoPath); + return Uri.fromFile(f); + } else if (resultCode == RESULT_OK && requestCode == REQUEST_GALLERY) { + //Return the URI from gallery intent + return data.getData(); + } + return null; + } + + /** + * AsyncTask that downloads an image into a bitmap using Glide + */ + public static class DownloadFilesTask extends AsyncTask { + + private Context context; + private ImageView imageView; + private int dimension; + + public DownloadFilesTask(Context context, ImageView imageView, int dimension) { + this.context = context; + this.imageView = imageView; + this.dimension = dimension; + } + + protected Bitmap doInBackground(String... strings) { + //Download image into a bitmap using Glide (Aneesh said this was okay) and return it + try { + return Glide. + with(context). + load(strings[0]). + asBitmap(). + into(dimension, dimension). + get(); + } catch (Exception e) { + return null; + } + } + + protected void onProgressUpdate(Void... progress) { + } + + protected void onPostExecute(Bitmap result) { + //Set the image of the image view to the returned bitmap + imageView.setImageBitmap(result); + } + } + + /** + * Retrieve specified image from Firebase storage and load it into an image view + * + * @param className Name of the class accessing this method (for logs) + * @param imageName Name of the image (specifies location in storage) + * @param context Context of the activity loading this image + * @param imageView Image view to load image into + * @param dimension Dimension that the image should be (px) + */ + public static void loadImage(final String className, final String imageName, final Context context, + final ImageView imageView, final int dimension) { + StorageReference storageRef = FirebaseStorage.getInstance().getReference(); + storageRef.child(imageName).getDownloadUrl().addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Uri uri) { + //Create new asynctask to load image + new DownloadFilesTask(context, imageView, dimension).execute(uri.toString()); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + Log.i(className, "Couldn't find image file to load"); + } + }); + } +} diff --git a/app/src/main/res/drawable/ic_add_white_36dp.xml b/app/src/main/res/drawable/ic_add_white_36dp.xml new file mode 100644 index 0000000..3f184da --- /dev/null +++ b/app/src/main/res/drawable/ic_add_white_36dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_group_black_24dp.xml b/app/src/main/res/drawable/ic_group_black_24dp.xml new file mode 100644 index 0000000..eea2486 --- /dev/null +++ b/app/src/main/res/drawable/ic_group_black_24dp.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_star_accent_24dp.xml b/app/src/main/res/drawable/ic_star_accent_24dp.xml new file mode 100644 index 0000000..b615bf6 --- /dev/null +++ b/app/src/main/res/drawable/ic_star_accent_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml new file mode 100644 index 0000000..86a41bc --- /dev/null +++ b/app/src/main/res/layout/activity_details.xml @@ -0,0 +1,128 @@ + + + + +