Repository: udacity/ShoppingListPlusPlus
Branch: master
Commit: 46d3e25c28d7
Files: 82
Total size: 287.6 KB
Directory structure:
gitextract_41bacwm3/
├── .gitignore
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── keystores/
│ │ ├── debug.jks
│ │ └── myDebugKey.jks
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── udacity/
│ │ └── firebase/
│ │ └── shoppinglistplusplus/
│ │ └── ApplicationTest.java
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── udacity/
│ │ └── firebase/
│ │ └── shoppinglistplusplus/
│ │ ├── ShoppingListApplication.java
│ │ ├── model/
│ │ │ ├── ShoppingList.java
│ │ │ ├── ShoppingListItem.java
│ │ │ └── User.java
│ │ ├── ui/
│ │ │ ├── BaseActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── SettingsActivity.java
│ │ │ ├── activeListDetails/
│ │ │ │ ├── ActiveListDetailsActivity.java
│ │ │ │ ├── ActiveListItemAdapter.java
│ │ │ │ ├── AddListItemDialogFragment.java
│ │ │ │ ├── EditListDialogFragment.java
│ │ │ │ ├── EditListItemNameDialogFragment.java
│ │ │ │ ├── EditListNameDialogFragment.java
│ │ │ │ └── RemoveListDialogFragment.java
│ │ │ ├── activeLists/
│ │ │ │ ├── ActiveListAdapter.java
│ │ │ │ ├── AddListDialogFragment.java
│ │ │ │ └── ShoppingListsFragment.java
│ │ │ ├── login/
│ │ │ │ ├── CreateAccountActivity.java
│ │ │ │ └── LoginActivity.java
│ │ │ ├── meals/
│ │ │ │ ├── AddMealDialogFragment.java
│ │ │ │ └── MealsFragment.java
│ │ │ └── sharing/
│ │ │ ├── AddFriendActivity.java
│ │ │ ├── AutocompleteFriendAdapter.java
│ │ │ ├── FriendAdapter.java
│ │ │ └── ShareListActivity.java
│ │ └── utils/
│ │ ├── Constants.java
│ │ └── Utils.java
│ └── res/
│ ├── layout/
│ │ ├── activity_active_list_details.xml
│ │ ├── activity_add_friend.xml
│ │ ├── activity_create_account.xml
│ │ ├── activity_login.xml
│ │ ├── activity_main.xml
│ │ ├── activity_share_list.xml
│ │ ├── dialog_add_item.xml
│ │ ├── dialog_add_list.xml
│ │ ├── dialog_add_meal.xml
│ │ ├── dialog_edit_item.xml
│ │ ├── dialog_edit_list.xml
│ │ ├── footer_empty.xml
│ │ ├── fragment_meals.xml
│ │ ├── fragment_shopping_lists.xml
│ │ ├── single_active_list.xml
│ │ ├── single_active_list_item.xml
│ │ ├── single_autocomplete_item.xml
│ │ └── single_user_item.xml
│ ├── layout-land/
│ │ ├── activity_create_account.xml
│ │ └── activity_login.xml
│ ├── menu/
│ │ ├── menu_base.xml
│ │ ├── menu_list_details.xml
│ │ └── menu_main.xml
│ ├── values/
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── themes.xml
│ ├── values-sw480dp/
│ │ └── dimens.xml
│ ├── values-sw640dp/
│ │ └── dimens.xml
│ ├── values-v21/
│ │ ├── styles.xml
│ │ └── themes.xml
│ ├── values-w480dp/
│ │ └── dimens.xml
│ ├── values-w820dp/
│ │ └── dimens.xml
│ └── xml/
│ └── preference_screen.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── rules/
│ ├── git.log
│ ├── name_branches_commits.py
│ ├── rules.bolt
│ └── rules.json
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
#built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
# Local configuration file (sdk path, etc)
local.properties
# Windows thumbnail db
Thumbs.db
# OSX files
.DS_Store
# Eclipse project files
.classpath
.project
# Android Studio
*.iml
.idea
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
.gradle
build/
#.idea/vcs.xml
gradle.properties
#don't store google-services.json
app/google-services.json
================================================
FILE: README.md
================================================
ShoppingList++
========
ShoppingList++ is the companion Android app for the Udacity course [Firebase Essentials : Build a Shopping List App](https://www.udacity.com/course/firebase-essentials-for-android--ud009).
The course covers everything you need to know to incorporate the [Firebase](https://www.firebase.com) into an Android App.
For an explanation of how to use this repository, please refer to the [Using ShoppingList++ Github Repository document](https://www.udacity.com/wiki/ud009/navigategithubrepo?nocache) and [The Github Code & Resources document](https://www.udacity.com/course/viewer#!/c-ud009/l-5389267455/e-5498429638/m-5528878616).
##Seeing Errors?

As mistakes in the original repository are fixed, the code will be rebased and re-uploaded here. The good news is if students discover errors or optimizations, the code will be updated. The unfortunate side effect is that this will destroy Github forks and stars as well as require the code to be re-downloaded to get the newest updates. So my humble suggestion is to **NOT** fork or star this repository. Instead, you can bookmark this page. If you are not checking out branches and instead making the app the **thorough way** you can check out the change log to see what was fixed. If you're working through the **fast way** you can re-clone the repository.
##Changelogs
- **1/26/2016** : [Changelog](https://docs.google.com/document/d/1SgBmUu7COQQT5maqKVvIV4Iv0Oyva9-9-YRnpQ88XuY/pub)
- **12/10/2015** : [Changelog](https://docs.google.com/document/d/1A5BSoLyEHkXrcBC50lNXqrl1Rkh0G2nM-h4ER8lKovw/pub)
- **11/30/2015** : Lesson 1 and 2 launch
# Archival Note
This repository is deprecated; therefore, we are going to archive it. However, learners will be able to fork it to their personal Github account but cannot submit PRs to this repository. If you have any issues or suggestions to make, feel free to:
- Utilize the https://knowledge.udacity.com/ forum to seek help on content-specific issues.
- Submit a support ticket along with the link to your forked repository if (learners are) blocked for other reasons. Here are the links for the [retail consumers](https://udacity.zendesk.com/hc/en-us/requests/new) and [enterprise learners](https://udacityenterprise.zendesk.com/hc/en-us/requests/new?ticket_form_id=360000279131).
================================================
FILE: app/.gitignore
================================================
#built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
# Local configuration file (sdk path, etc)
local.properties
# Windows thumbnail db
Thumbs.db
# OSX files
.DS_Store
# Eclipse project files
.classpath
.project
# Android Studio
*.iml
.idea
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
.gradle
build/
#.idea/vcs.xml
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.udacity.firebase.shoppinglistplusplus"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
}
}
/* This for anyone following along with the repo. Since you will have a different
* root URL, this code loads up a value from your gradle.properties file.
*/
buildTypes.each {
it.buildConfigField 'String', 'UNIQUE_FIREBASE_ROOT_URL', UniqueFirebaseRootUrl
}
packagingOptions {
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE-FIREBASE.txt'
exclude 'META-INF/NOTICE'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.android.support:design:23.0.1'
compile 'com.android.support:support-v4:23.0.1'
compile 'com.android.support:cardview-v7:23.0.1'
/* Firebase SDK */
compile 'com.firebase:firebase-client-android:2.4.0'
/* Firebase UI */
compile 'com.firebaseui:firebase-ui:0.2.2'
/* For Google Play Services */
compile 'com.google.android.gms:play-services-safetynet:8.3.0'
compile 'com.google.android.gms:play-services-auth:8.3.0'
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/admin/Library/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 *;
#}
================================================
FILE: app/src/androidTest/java/com/udacity/firebase/shoppinglistplusplus/ApplicationTest.java
================================================
package com.udacity.firebase.shoppinglistplusplus;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ShoppingListApplication.java
================================================
package com.udacity.firebase.shoppinglistplusplus;
import com.firebase.client.Firebase;
/**
* Includes one-time initialization of Firebase related code
*/
public class ShoppingListApplication extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
/* Initialize Firebase */
Firebase.setAndroidContext(this);
/* Enable disk persistence */
Firebase.getDefaultConfig().setPersistenceEnabled(true);
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/model/ShoppingList.java
================================================
package com.udacity.firebase.shoppinglistplusplus.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.firebase.client.ServerValue;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
import java.util.HashMap;
/**
* Defines the data structure for both Active and Archived ShoppingList objects.
*/
public class ShoppingList {
private String listName;
private String owner;
private HashMap timestampLastChanged;
private HashMap timestampCreated;
private HashMap timestampLastChangedReverse;
private HashMap usersShopping;
/**
* Required public constructor
*/
public ShoppingList() {
}
/**
* Use this constructor to create new ShoppingLists.
* Takes shopping list listName and owner. Set's the last
* changed time to what is stored in ServerValue.TIMESTAMP
*
* @param listName
* @param owner
*/
public ShoppingList(String listName, String owner, HashMap timestampCreated) {
this.listName = listName;
this.owner = owner;
this.timestampCreated = timestampCreated;
HashMap timestampNowObject = new HashMap();
timestampNowObject.put(Constants.FIREBASE_PROPERTY_TIMESTAMP, ServerValue.TIMESTAMP);
this.timestampLastChanged = timestampNowObject;
this.timestampLastChangedReverse = null;
this.usersShopping = new HashMap<>();
}
public String getListName() {
return listName;
}
public String getOwner() {
return owner;
}
public HashMap getTimestampLastChanged() {
return timestampLastChanged;
}
public HashMap getTimestampCreated() {
return timestampCreated;
}
public HashMap getTimestampLastChangedReverse() {
return timestampLastChangedReverse;
}
@JsonIgnore
public long getTimestampLastChangedLong() {
return (long) timestampLastChanged.get(Constants.FIREBASE_PROPERTY_TIMESTAMP);
}
@JsonIgnore
public long getTimestampCreatedLong() {
return (long) timestampLastChanged.get(Constants.FIREBASE_PROPERTY_TIMESTAMP);
}
@JsonIgnore
public long getTimestampLastChangedReverseLong() {
return (long) timestampLastChangedReverse.get(Constants.FIREBASE_PROPERTY_TIMESTAMP);
}
public HashMap getUsersShopping() {
return usersShopping;
}
public void setTimestampLastChangedToNow() {
HashMap timestampNowObject = new HashMap();
timestampNowObject.put(Constants.FIREBASE_PROPERTY_TIMESTAMP, ServerValue.TIMESTAMP);
this.timestampLastChanged = timestampNowObject;
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/model/ShoppingListItem.java
================================================
package com.udacity.firebase.shoppinglistplusplus.model;
/**
* Defines the data structure for ShoppingListItem objects.
*/
public class ShoppingListItem {
private String itemName;
private String owner;
private String boughtBy;
private boolean bought;
/**
* Required public constructor
*/
public ShoppingListItem() {
}
/**
* Use this constructor to create new ShoppingListItem.
* Takes shopping list item name and list item owner email as params
*
* @param itemName
* @param owner
*/
public ShoppingListItem(String itemName, String owner) {
this.itemName = itemName;
this.owner = owner;
this.boughtBy = null;
this.bought = false;
}
public String getItemName() { return itemName; }
public String getOwner() {
return owner;
}
public String getBoughtBy() {
return boughtBy;
}
public boolean isBought() {
return bought;
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/model/User.java
================================================
package com.udacity.firebase.shoppinglistplusplus.model;
import java.util.HashMap;
/**
* Defines the data structure for User objects.
*/
public class User {
private String name;
private String email;
private HashMap timestampJoined;
private boolean hasLoggedInWithPassword;
/**
* Required public constructor
*/
public User() {
}
/**
* Use this constructor to create new User.
* Takes user name, email and timestampJoined as params
*
* @param name
* @param email
* @param timestampJoined
*/
public User(String name, String email, HashMap timestampJoined) {
this.name = name;
this.email = email;
this.timestampJoined = timestampJoined;
this.hasLoggedInWithPassword = false;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public HashMap getTimestampJoined() {
return timestampJoined;
}
public boolean isHasLoggedInWithPassword() {
return hasLoggedInWithPassword;
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/BaseActivity.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.LinearLayout;
import com.firebase.client.AuthData;
import com.firebase.client.Firebase;
import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.ui.login.CreateAccountActivity;
import com.udacity.firebase.shoppinglistplusplus.ui.login.LoginActivity;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
/**
* BaseActivity class is used as a base class for all activities in the app
* It implements GoogleApiClient callbacks to enable "Logout" in all activities
* and defines variables that are being shared across all activities
*/
public abstract class BaseActivity extends AppCompatActivity implements
GoogleApiClient.OnConnectionFailedListener {
protected String mProvider, mEncodedEmail;
/* Client used to interact with Google APIs. */
protected GoogleApiClient mGoogleApiClient;
protected Firebase.AuthStateListener mAuthListener;
protected Firebase mFirebaseRef;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* Setup the Google API object to allow Google logins */
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build();
/**
* Build a GoogleApiClient with access to the Google Sign-In API and the
* options specified by gso.
*/
/* Setup the Google API object to allow Google+ logins */
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
/**
* Getting mProvider and mEncodedEmail from SharedPreferences
*/
final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(BaseActivity.this);
/* Get mEncodedEmail and mProvider from SharedPreferences, use null as default value */
mEncodedEmail = sp.getString(Constants.KEY_ENCODED_EMAIL, null);
mProvider = sp.getString(Constants.KEY_PROVIDER, null);
if (!((this instanceof LoginActivity) || (this instanceof CreateAccountActivity))) {
mFirebaseRef = new Firebase(Constants.FIREBASE_URL);
mAuthListener = new Firebase.AuthStateListener() {
@Override
public void onAuthStateChanged(AuthData authData) {
/* The user has been logged out */
if (authData == null) {
/* Clear out shared preferences */
SharedPreferences.Editor spe = sp.edit();
spe.putString(Constants.KEY_ENCODED_EMAIL, null);
spe.putString(Constants.KEY_PROVIDER, null);
takeUserToLoginScreenOnUnAuth();
}
}
};
mFirebaseRef.addAuthStateListener(mAuthListener);
}
}
@Override
public void onDestroy() {
super.onDestroy();
/* Cleanup the AuthStateListener */
if (!((this instanceof LoginActivity) || (this instanceof CreateAccountActivity))) {
mFirebaseRef.removeAuthStateListener(mAuthListener);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
/* Inflate the menu; this adds items to the action bar if it is present. */
getMenuInflater().inflate(R.menu.menu_base, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
super.onBackPressed();
return true;
}
if (id == R.id.action_logout) {
logout();
return true;
}
return super.onOptionsItemSelected(item);
}
protected void initializeBackground(LinearLayout linearLayout) {
/**
* Set different background image for landscape and portrait layouts
*/
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
linearLayout.setBackgroundResource(R.drawable.background_loginscreen_land);
} else {
linearLayout.setBackgroundResource(R.drawable.background_loginscreen);
}
}
/**
* Logs out the user from their current session and starts LoginActivity.
* Also disconnects the mGoogleApiClient if connected and provider is Google
*/
protected void logout() {
/* Logout if mProvider is not null */
if (mProvider != null) {
mFirebaseRef.unauth();
if (mProvider.equals(Constants.GOOGLE_PROVIDER)) {
/* Logout from Google+ */
Auth.GoogleSignInApi.signOut(mGoogleApiClient).setResultCallback(
new ResultCallback() {
@Override
public void onResult(Status status) {
//nothing
}
});
}
}
}
private void takeUserToLoginScreenOnUnAuth() {
/* Move user to LoginActivity, and remove the backstack */
Intent intent = new Intent(BaseActivity.this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/MainActivity.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui;
import android.app.DialogFragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import com.firebase.client.DataSnapshot;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.ValueEventListener;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.User;
import com.udacity.firebase.shoppinglistplusplus.ui.activeLists.AddListDialogFragment;
import com.udacity.firebase.shoppinglistplusplus.ui.activeLists.ShoppingListsFragment;
import com.udacity.firebase.shoppinglistplusplus.ui.meals.AddMealDialogFragment;
import com.udacity.firebase.shoppinglistplusplus.ui.meals.MealsFragment;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
/**
* Represents the home screen of the app which
* has a {@link ViewPager} with {@link ShoppingListsFragment} and {@link MealsFragment}
*/
public class MainActivity extends BaseActivity {
private Firebase mUserRef;
private static final String LOG_TAG = MainActivity.class.getSimpleName();
private ValueEventListener mUserRefListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* Create Firebase references
*/
mUserRef = new Firebase(Constants.FIREBASE_URL_USERS).child(mEncodedEmail);
/**
* Link layout elements from XML and setup the toolbar
*/
initializeScreen();
/**
* Add ValueEventListeners to Firebase references
* to control get data and control behavior and visibility of elements
*/
mUserRefListener = mUserRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
User user = snapshot.getValue(User.class);
/**
* Set the activity title to current user name if user is not null
*/
if (user != null) {
/* Assumes that the first word in the user's name is the user's first name. */
String firstName = user.getName().split("\\s+")[0];
String title = firstName + "'s Lists";
setTitle(title);
}
}
@Override
public void onCancelled(FirebaseError firebaseError) {
Log.e(LOG_TAG,
getString(R.string.log_error_the_read_failed) +
firebaseError.getMessage());
}
});
}
/**
* Override onOptionsItemSelected to use main_menu instead of BaseActivity menu
*
* @param menu
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
/* Inflate the menu; this adds items to the action bar if it is present. */
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
/**
* Override onOptionsItemSelected to add action_settings only to the MainActivity
*
* @param item
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
/**
* Open SettingsActivity with sort options when Sort icon was clicked
*/
if (id == R.id.action_sort) {
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onDestroy() {
super.onDestroy();
mUserRef.removeEventListener(mUserRefListener);
}
/**
* Link layout elements from XML and setup the toolbar
*/
public void initializeScreen() {
ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
Toolbar toolbar = (Toolbar) findViewById(R.id.app_bar);
setSupportActionBar(toolbar);
/**
* Create SectionPagerAdapter, set it as adapter to viewPager with setOffscreenPageLimit(2)
**/
SectionPagerAdapter adapter = new SectionPagerAdapter(getSupportFragmentManager());
viewPager.setOffscreenPageLimit(2);
viewPager.setAdapter(adapter);
/**
* Setup the mTabLayout with view pager
*/
tabLayout.setupWithViewPager(viewPager);
}
/**
* Create an instance of the AddList dialog fragment and show it
*/
public void showAddListDialog(View view) {
/* Create an instance of the dialog fragment and show it */
DialogFragment dialog = AddListDialogFragment.newInstance(mEncodedEmail);
dialog.show(MainActivity.this.getFragmentManager(), "AddListDialogFragment");
}
/**
* Create an instance of the AddMeal dialog fragment and show it
*/
public void showAddMealDialog(View view) {
/* Create an instance of the dialog fragment and show it */
DialogFragment dialog = AddMealDialogFragment.newInstance();
dialog.show(MainActivity.this.getFragmentManager(), "AddMealDialogFragment");
}
/**
* SectionPagerAdapter class that extends FragmentStatePagerAdapter to save fragments state
*/
public class SectionPagerAdapter extends FragmentStatePagerAdapter {
public SectionPagerAdapter(FragmentManager fm) {
super(fm);
}
/**
* Use positions (0 and 1) to find and instantiate fragments with newInstance()
*
* @param position
*/
@Override
public Fragment getItem(int position) {
Fragment fragment = null;
/**
* Set fragment to different fragments depending on position in ViewPager
*/
switch (position) {
case 0:
fragment = ShoppingListsFragment.newInstance(mEncodedEmail);
break;
case 1:
fragment = MealsFragment.newInstance();
break;
default:
fragment = ShoppingListsFragment.newInstance(mEncodedEmail);
break;
}
return fragment;
}
@Override
public int getCount() {
return 2;
}
/**
* Set string resources as titles for each fragment by it's position
*
* @param position
*/
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.pager_title_shopping_lists);
case 1:
default:
return getString(R.string.pager_title_meals);
}
}
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/SettingsActivity.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
/**
* SettingsActivity represents preference screen and functionality
*/
public class SettingsActivity extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.PrefScreenTheme);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new SortPreferenceFragment())
.commit();
}
/**
* This fragment shows the preferences for the first header.
*/
public static class SortPreferenceFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* Load the preferences from an XML resource */
addPreferencesFromResource(R.xml.preference_screen);
/**
* Bind preference summary to value for lists and meals sorting list preferences
*/
bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_name_sort_order_lists)));
}
/**
* When preference is changed, save it's new value to default shared preferences
*
* @param preference
* @param newValue
*/
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
setPreferenceSummary(preference, newValue);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
SharedPreferences.Editor spe = sharedPref.edit();
spe.putString(Constants.KEY_PREF_SORT_ORDER_LISTS, newValue.toString()).apply();
return true;
}
private void bindPreferenceSummaryToValue(Preference preference) {
/* Set the listener to watch for value changes. */
preference.setOnPreferenceChangeListener(this);
/* Trigger the listener immediately with the preference's current value. */
setPreferenceSummary(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
/**
* Sets preference summary to appropriate value
*
* @param preference
* @param value
*/
private void setPreferenceSummary(Preference preference, Object value) {
String stringValue = value.toString();
if (preference instanceof ListPreference) {
ListPreference listPreference = (ListPreference) preference;
int prefIndex = listPreference.findIndexOfValue(stringValue);
if (prefIndex >= 0) {
preference.setSummary(listPreference.getEntries()[prefIndex]);
}
}
}
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/activeListDetails/ActiveListDetailsActivity.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.activeListDetails;
import android.app.Activity;
import android.app.DialogFragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.firebase.client.DataSnapshot;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.ValueEventListener;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingList;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingListItem;
import com.udacity.firebase.shoppinglistplusplus.model.User;
import com.udacity.firebase.shoppinglistplusplus.ui.BaseActivity;
import com.udacity.firebase.shoppinglistplusplus.ui.sharing.ShareListActivity;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
import com.udacity.firebase.shoppinglistplusplus.utils.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* Represents the details screen for the selected shopping list
*/
public class ActiveListDetailsActivity extends BaseActivity {
private static final String LOG_TAG = ActiveListDetailsActivity.class.getSimpleName();
private Firebase mCurrentListRef, mCurrentUserRef, mSharedWithRef;
private ActiveListItemAdapter mActiveListItemAdapter;
private Button mButtonShopping;
private TextView mTextViewPeopleShopping;
private ListView mListView;
private String mListId;
private User mCurrentUser;
/* Stores whether the current user is shopping */
private boolean mShopping = false;
/* Stores whether the current user is the owner */
private boolean mCurrentUserIsOwner = false;
private ShoppingList mShoppingList;
private ValueEventListener mCurrentUserRefListener, mCurrentListRefListener, mSharedWithListener;
private HashMap mSharedWithUsers;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_active_list_details);
/* Get the push ID from the extra passed by ShoppingListFragment */
Intent intent = this.getIntent();
mListId = intent.getStringExtra(Constants.KEY_LIST_ID);
if (mListId == null) {
/* No point in continuing without a valid ID. */
finish();
return;
}
/**
* Create Firebase references
*/
mCurrentListRef = new Firebase(Constants.FIREBASE_URL_USER_LISTS).child(mEncodedEmail).child(mListId);
mCurrentUserRef = new Firebase(Constants.FIREBASE_URL_USERS).child(mEncodedEmail);
mSharedWithRef = new Firebase (Constants.FIREBASE_URL_LISTS_SHARED_WITH).child(mListId);
Firebase listItemsRef = new Firebase(Constants.FIREBASE_URL_SHOPPING_LIST_ITEMS).child(mListId);
/**
* Link layout elements from XML and setup the toolbar
*/
initializeScreen();
/**
* Setup the adapter
*/
mActiveListItemAdapter = new ActiveListItemAdapter(this, ShoppingListItem.class,
R.layout.single_active_list_item, listItemsRef.orderByChild(Constants.FIREBASE_PROPERTY_BOUGHT_BY),
mListId, mEncodedEmail);
/* Create ActiveListItemAdapter and set to listView */
mListView.setAdapter(mActiveListItemAdapter);
/**
* Add ValueEventListeners to Firebase references
* to control get data and control behavior and visibility of elements
*/
/* Save the most up-to-date version of current user in mCurrentUser */
mCurrentUserRefListener = mCurrentUserRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
User currentUser = dataSnapshot.getValue(User.class);
if (currentUser != null) mCurrentUser = currentUser;
else finish();
}
@Override
public void onCancelled(FirebaseError firebaseError) {
Log.e(LOG_TAG,
getString(R.string.log_error_the_read_failed) +
firebaseError.getMessage());
}
});
final Activity thisActivity = this;
/**
* Save the most recent version of current shopping list into mShoppingList instance
* variable an update the UI to match the current list.
*/
mCurrentListRefListener = mCurrentListRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
/**
* Saving the most recent version of current shopping list into mShoppingList if present
* finish() the activity if the list is null (list was removed or unshared by it's owner
* while current user is in the list details activity)
*/
ShoppingList shoppingList = snapshot.getValue(ShoppingList.class);
if (shoppingList == null) {
finish();
/**
* Make sure to call return, otherwise the rest of the method will execute,
* even after calling finish.
*/
return;
}
mShoppingList = shoppingList;
/**
* Pass the shopping list to the adapter if it is not null.
* We do this here because mShoppingList is null when first created.
*/
mActiveListItemAdapter.setShoppingList(mShoppingList);
/* Check if the current user is owner */
mCurrentUserIsOwner = Utils.checkIfOwner(shoppingList, mEncodedEmail);
/* Calling invalidateOptionsMenu causes onCreateOptionsMenu to be called */
invalidateOptionsMenu();
/* Set title appropriately. */
setTitle(shoppingList.getListName());
HashMap usersShopping = mShoppingList.getUsersShopping();
if (usersShopping != null && usersShopping.size() != 0 &&
usersShopping.containsKey(mEncodedEmail)) {
mShopping = true;
mButtonShopping.setText(getString(R.string.button_stop_shopping));
mButtonShopping.setBackgroundColor(ContextCompat.getColor(ActiveListDetailsActivity.this, R.color.dark_grey));
} else {
mButtonShopping.setText(getString(R.string.button_start_shopping));
mButtonShopping.setBackgroundColor(ContextCompat.getColor(ActiveListDetailsActivity.this, R.color.primary_dark));
mShopping = false;
}
setWhosShoppingText(mShoppingList.getUsersShopping());
}
@Override
public void onCancelled(FirebaseError firebaseError) {
Log.e(LOG_TAG,
getString(R.string.log_error_the_read_failed) +
firebaseError.getMessage());
}
});
mSharedWithListener = mSharedWithRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
mSharedWithUsers = new HashMap();
for (DataSnapshot currentUser : dataSnapshot.getChildren()) {
mSharedWithUsers.put(currentUser.getKey(), currentUser.getValue(User.class));
}
mActiveListItemAdapter.setSharedWithUsers(mSharedWithUsers);
}
@Override
public void onCancelled(FirebaseError firebaseError) {
Log.e(LOG_TAG,
getString(R.string.log_error_the_read_failed) +
firebaseError.getMessage());
}
});
/**
* Set up click listeners for interaction.
*/
/* Show edit list item name dialog on listView item long click event */
mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
/* Check that the view is not the empty footer item */
if (view.getId() != R.id.list_view_footer_empty) {
ShoppingListItem shoppingListItem = mActiveListItemAdapter.getItem(position);
if (shoppingListItem != null) {
/*
If the person is the owner and not shopping and the item is not bought, then
they can edit it.
*/
if (shoppingListItem.getOwner().equals(mEncodedEmail) && !mShopping && !shoppingListItem.isBought()) {
String itemName = shoppingListItem.getItemName();
String itemId = mActiveListItemAdapter.getRef(position).getKey();
showEditListItemNameDialog(itemName, itemId);
return true;
}
}
}
return false;
}
});
/* Perform buy/return action on listView item click event if current user is shopping. */
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
/* Check that the view is not the empty footer item */
if (view.getId() != R.id.list_view_footer_empty) {
final ShoppingListItem selectedListItem = mActiveListItemAdapter.getItem(position);
String itemId = mActiveListItemAdapter.getRef(position).getKey();
if (selectedListItem != null) {
/* If current user is shopping */
if (mShopping) {
/* Create map and fill it in with deep path multi write operations list */
HashMap updatedItemBoughtData = new HashMap();
/* Buy selected item if it is NOT already bought */
if (!selectedListItem.isBought()) {
updatedItemBoughtData.put(Constants.FIREBASE_PROPERTY_BOUGHT, true);
updatedItemBoughtData.put(Constants.FIREBASE_PROPERTY_BOUGHT_BY, mEncodedEmail);
} else {
/* Return selected item only if it was bought by current user */
if (selectedListItem.getBoughtBy().equals(mEncodedEmail)) {
updatedItemBoughtData.put(Constants.FIREBASE_PROPERTY_BOUGHT, false);
updatedItemBoughtData.put(Constants.FIREBASE_PROPERTY_BOUGHT_BY, null);
}
}
/* Do update */
Firebase firebaseItemLocation = new Firebase(Constants.FIREBASE_URL_SHOPPING_LIST_ITEMS)
.child(mListId).child(itemId);
firebaseItemLocation.updateChildren(updatedItemBoughtData, new Firebase.CompletionListener() {
@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
if (firebaseError != null) {
Log.d(LOG_TAG, getString(R.string.log_error_updating_data) +
firebaseError.getMessage());
}
}
});
}
}
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
/* Inflate the menu; this adds items to the action bar if it is present. */
getMenuInflater().inflate(R.menu.menu_list_details, menu);
/**
* Get menu items
*/
MenuItem remove = menu.findItem(R.id.action_remove_list);
MenuItem edit = menu.findItem(R.id.action_edit_list_name);
MenuItem share = menu.findItem(R.id.action_share_list);
MenuItem archive = menu.findItem(R.id.action_archive);
/* Only the edit and remove options are implemented */
remove.setVisible(mCurrentUserIsOwner);
edit.setVisible(mCurrentUserIsOwner);
share.setVisible(mCurrentUserIsOwner);
archive.setVisible(false);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
/**
* Show edit list dialog when the edit action is selected
*/
if (id == R.id.action_edit_list_name) {
showEditListNameDialog();
return true;
}
/**
* removeList() when the remove action is selected
*/
if (id == R.id.action_remove_list) {
removeList();
return true;
}
/**
* Eventually we'll add this
*/
if (id == R.id.action_share_list) {
Intent intent = new Intent(ActiveListDetailsActivity.this, ShareListActivity.class);
intent.putExtra(Constants.KEY_LIST_ID, mListId);
/* Starts an active showing the details for the selected list */
startActivity(intent);
return true;
}
/**
* archiveList() when the archive action is selected
*/
if (id == R.id.action_archive) {
archiveList();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Cleanup when the activity is destroyed.
*/
@Override
public void onDestroy() {
super.onDestroy();
mActiveListItemAdapter.cleanup();
mCurrentListRef.removeEventListener(mCurrentListRefListener);
mCurrentUserRef.removeEventListener(mCurrentUserRefListener);
mSharedWithRef.removeEventListener(mSharedWithListener);
}
/**
* Link layout elements from XML and setup the toolbar
*/
private void initializeScreen() {
mListView = (ListView) findViewById(R.id.list_view_shopping_list_items);
mTextViewPeopleShopping = (TextView) findViewById(R.id.text_view_people_shopping);
mButtonShopping = (Button) findViewById(R.id.button_shopping);
Toolbar toolbar = (Toolbar) findViewById(R.id.app_bar);
/* Common toolbar setup */
setSupportActionBar(toolbar);
/* Add back button to the action bar */
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
/* Inflate the footer, set root layout to null*/
View footer = getLayoutInflater().inflate(R.layout.footer_empty, null);
mListView.addFooterView(footer);
}
/**
* Set appropriate text for Start/Stop shopping button and Who's shopping textView
* depending on the current user shopping status
*/
private void setWhosShoppingText(HashMap usersShopping) {
if (usersShopping != null) {
ArrayList usersWhoAreNotYou = new ArrayList<>();
/**
* If at least one user is shopping
* Add userName to the list of users shopping if this user is not current user
*/
for (User user : usersShopping.values()) {
if (user != null && !(user.getEmail().equals(mEncodedEmail))) {
usersWhoAreNotYou.add(user.getName());
}
}
int numberOfUsersShopping = usersShopping.size();
String usersShoppingText;
/**
* If current user is shopping...
* If current user is the only person shopping, set text to "You are shopping"
* If current user and one user are shopping, set text "You and userName are shopping"
* Else set text "You and N others shopping"
*/
if (mShopping) {
switch (numberOfUsersShopping) {
case 1:
usersShoppingText = getString(R.string.text_you_are_shopping);
break;
case 2:
usersShoppingText = String.format(
getString(R.string.text_you_and_other_are_shopping),
usersWhoAreNotYou.get(0));
break;
default:
usersShoppingText = String.format(
getString(R.string.text_you_and_number_are_shopping),
usersWhoAreNotYou.size());
}
/**
* If current user is not shopping..
* If there is only one person shopping, set text to "userName is shopping"
* If there are two users shopping, set text "userName1 and userName2 are shopping"
* Else set text "userName and N others shopping"
*/
} else {
switch (numberOfUsersShopping) {
case 1:
usersShoppingText = String.format(
getString(R.string.text_other_is_shopping),
usersWhoAreNotYou.get(0));
break;
case 2:
usersShoppingText = String.format(
getString(R.string.text_other_and_other_are_shopping),
usersWhoAreNotYou.get(0),
usersWhoAreNotYou.get(1));
break;
default:
usersShoppingText = String.format(
getString(R.string.text_other_and_number_are_shopping),
usersWhoAreNotYou.get(0),
usersWhoAreNotYou.size() - 1);
}
}
mTextViewPeopleShopping.setText(usersShoppingText);
} else {
mTextViewPeopleShopping.setText("");
}
}
/**
* Archive current list when user selects "Archive" menu item
*/
public void archiveList() {
}
/**
* Start AddItemsFromMealActivity to add meal ingredients into the shopping list
* when the user taps on "add meal" fab
*/
public void addMeal(View view) {
}
/**
* Remove current shopping list and its items from all nodes
*/
public void removeList() {
/* Create an instance of the dialog fragment and show it */
DialogFragment dialog = RemoveListDialogFragment.newInstance(mShoppingList, mListId,
mSharedWithUsers);
dialog.show(getFragmentManager(), "RemoveListDialogFragment");
}
/**
* Show the add list item dialog when user taps "Add list item" fab
*/
public void showAddListItemDialog(View view) {
/* Create an instance of the dialog fragment and show it */
DialogFragment dialog = AddListItemDialogFragment.newInstance(mShoppingList, mListId,
mEncodedEmail, mSharedWithUsers);
dialog.show(getFragmentManager(), "AddListItemDialogFragment");
}
/**
* Show edit list name dialog when user selects "Edit list name" menu item
*/
public void showEditListNameDialog() {
/* Create an instance of the dialog fragment and show it */
DialogFragment dialog = EditListNameDialogFragment.newInstance(mShoppingList, mListId,
mEncodedEmail, mSharedWithUsers);
dialog.show(this.getFragmentManager(), "EditListNameDialogFragment");
}
/**
* Show the edit list item name dialog after longClick on the particular item
*
* @param itemName
* @param itemId
*/
public void showEditListItemNameDialog(String itemName, String itemId) {
/* Create an instance of the dialog fragment and show it */
DialogFragment dialog = EditListItemNameDialogFragment.newInstance(mShoppingList, itemName,
itemId, mListId, mEncodedEmail, mSharedWithUsers);
dialog.show(this.getFragmentManager(), "EditListItemNameDialogFragment");
}
/**
* This method is called when user taps "Start/Stop shopping" button
*/
public void toggleShopping(View view) {
/**
* Create map and fill it in with deep path multi write operations list
*/
HashMap updatedUserData = new HashMap();
String propertyToUpdate = Constants.FIREBASE_PROPERTY_USERS_SHOPPING + "/" + mEncodedEmail;
/**
* If current user is already shopping, remove current user from usersShopping map
*/
if (mShopping) {
/* Add the value to update at the specified property for all lists */
Utils.updateMapForAllWithValue(mSharedWithUsers,
mListId, mShoppingList.getOwner(), updatedUserData,
propertyToUpdate, null);
/* Appends the timestamp changes for all lists */
Utils.updateMapWithTimestampLastChanged(mSharedWithUsers,
mListId, mShoppingList.getOwner(), updatedUserData);
/* Do a deep-path update */
mFirebaseRef.updateChildren(updatedUserData, new Firebase.CompletionListener() {
@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
/* Updates the reversed timestamp */
Utils.updateTimestampReversed(firebaseError, LOG_TAG, mListId, mSharedWithUsers,
mShoppingList.getOwner());
}
});
} else {
/**
* If current user is not shopping, create map to represent User model add to usersShopping map
*/
HashMap currentUser = (HashMap)
new ObjectMapper().convertValue(mCurrentUser, Map.class);
/* Add the value to update at the specified property for all lists */
Utils.updateMapForAllWithValue(mSharedWithUsers,
mListId, mShoppingList.getOwner(), updatedUserData, propertyToUpdate, currentUser);
/* Appends the timestamp changes for all lists */
Utils.updateMapWithTimestampLastChanged(mSharedWithUsers,
mListId, mShoppingList.getOwner(), updatedUserData);
/* Do a deep-path update */
mFirebaseRef.updateChildren(updatedUserData, new Firebase.CompletionListener() {
@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
/* Updates the reversed timestamp */
Utils.updateTimestampReversed(firebaseError, LOG_TAG, mListId, mSharedWithUsers,
mShoppingList.getOwner());
}
});
}
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/activeListDetails/ActiveListItemAdapter.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.activeListDetails;
import android.app.Activity;
import android.content.DialogInterface;
import android.graphics.Paint;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
import com.firebase.client.DataSnapshot;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.Query;
import com.firebase.client.ValueEventListener;
import com.firebase.ui.FirebaseListAdapter;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingList;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingListItem;
import com.udacity.firebase.shoppinglistplusplus.model.User;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
import com.udacity.firebase.shoppinglistplusplus.utils.Utils;
import java.util.HashMap;
/**
* Populates list_view_shopping_list_items inside ActiveListDetailsActivity
*/
public class ActiveListItemAdapter extends FirebaseListAdapter {
private ShoppingList mShoppingList;
private String mListId;
private String mEncodedEmail;
private HashMap mSharedWithUsers;
/**
* Public constructor that initializes private instance variables when adapter is created
*/
public ActiveListItemAdapter(Activity activity, Class modelClass, int modelLayout,
Query ref, String listId, String encodedEmail) {
super(activity, modelClass, modelLayout, ref);
this.mActivity = activity;
this.mListId = listId;
this.mEncodedEmail = encodedEmail;
}
/**
* Public method that is used to pass shoppingList object when it is loaded in ValueEventListener
*/
public void setShoppingList(ShoppingList shoppingList) {
this.mShoppingList = shoppingList;
this.notifyDataSetChanged();
}
public void setSharedWithUsers(HashMap sharedWithUsers) {
this.mSharedWithUsers = sharedWithUsers;
this.notifyDataSetChanged();
}
/**
* Protected method that populates the view attached to the adapter (list_view_friends_autocomplete)
* with items inflated from single_active_list_item.xml
* populateView also handles data changes and updates the listView accordingly
*/
@Override
protected void populateView(View view, final ShoppingListItem item, int position) {
ImageButton buttonRemoveItem = (ImageButton) view.findViewById(R.id.button_remove_item);
TextView textViewItemName = (TextView) view.findViewById(R.id.text_view_active_list_item_name);
final TextView textViewBoughtByUser = (TextView) view.findViewById(R.id.text_view_bought_by_user);
TextView textViewBoughtBy = (TextView) view.findViewById(R.id.text_view_bought_by);
String owner = item.getOwner();
textViewItemName.setText(item.getItemName());
setItemAppearanceBaseOnBoughtStatus(owner, textViewBoughtByUser, textViewBoughtBy, buttonRemoveItem,
textViewItemName, item);
/* Gets the id of the item to remove */
final String itemToRemoveId = this.getRef(position).getKey();
/**
* Set the on click listener for "Remove list item" button
*/
buttonRemoveItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(mActivity, R.style.CustomTheme_Dialog)
.setTitle(mActivity.getString(R.string.remove_item_option))
.setMessage(mActivity.getString(R.string.dialog_message_are_you_sure_remove_item))
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
removeItem(itemToRemoveId);
/* Dismiss the dialog */
dialog.dismiss();
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
/* Dismiss the dialog */
dialog.dismiss();
}
})
.setIcon(android.R.drawable.ic_dialog_alert);
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
});
}
private void removeItem(String itemId) {
Firebase firebaseRef = new Firebase(Constants.FIREBASE_URL);
/* Make a map for the removal */
HashMap updatedRemoveItemMap = new HashMap();
/* Remove the item by passing null */
updatedRemoveItemMap.put("/" + Constants.FIREBASE_LOCATION_SHOPPING_LIST_ITEMS + "/"
+ mListId + "/" + itemId, null);
/* Add the updated timestamp */
Utils.updateMapWithTimestampLastChanged(mSharedWithUsers,
mListId, mShoppingList.getOwner(), updatedRemoveItemMap);
/* Do the update */
firebaseRef.updateChildren(updatedRemoveItemMap, new Firebase.CompletionListener() {
@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
Utils.updateTimestampReversed(firebaseError, "ActListItemAdap", mListId,
mSharedWithUsers, mShoppingList.getOwner());
}
});
}
private void setItemAppearanceBaseOnBoughtStatus(String owner, final TextView textViewBoughtByUser,
TextView textViewBoughtBy, ImageButton buttonRemoveItem,
TextView textViewItemName, ShoppingListItem item) {
/**
* If selected item is bought
* Set "Bought by" text to "You" if current user is owner of the list
* Set "Bought by" text to userName if current user is NOT owner of the list
* Set the remove item button invisible if current user is NOT list or item owner
*/
if (item.isBought() && item.getBoughtBy() != null) {
textViewBoughtBy.setVisibility(View.VISIBLE);
textViewBoughtByUser.setVisibility(View.VISIBLE);
buttonRemoveItem.setVisibility(View.INVISIBLE);
/* Add a strike-through */
textViewItemName.setPaintFlags(textViewItemName.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
if (item.getBoughtBy().equals(mEncodedEmail)) {
textViewBoughtByUser.setText(mActivity.getString(R.string.text_you));
} else {
Firebase boughtByUserRef = new Firebase(Constants.FIREBASE_URL_USERS).child(item.getBoughtBy());
/* Get the item's owner's name; use a SingleValueEvent listener for memory efficiency */
boughtByUserRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
if (user != null) {
textViewBoughtByUser.setText(user.getName());
}
}
@Override
public void onCancelled(FirebaseError firebaseError) {
Log.e(mActivity.getClass().getSimpleName(),
mActivity.getString(R.string.log_error_the_read_failed) +
firebaseError.getMessage());
}
});
}
} else {
/**
* If selected item is NOT bought
* Set "Bought by" text to be empty and invisible
* Set the remove item button visible if current user is owner of the list or selected item
*/
/* Remove the strike-through */
textViewItemName.setPaintFlags(textViewItemName.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
textViewBoughtBy.setVisibility(View.INVISIBLE);
textViewBoughtByUser.setVisibility(View.INVISIBLE);
textViewBoughtByUser.setText("");
/**
* If you are the owner of the item or the owner of the list, then the remove icon
* is visible.
*/
if (owner.equals(mEncodedEmail) || (mShoppingList != null && mShoppingList.getOwner().equals(mEncodedEmail))) {
buttonRemoveItem.setVisibility(View.VISIBLE);
} else {
buttonRemoveItem.setVisibility(View.INVISIBLE);
}
}
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/activeListDetails/AddListItemDialogFragment.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.activeListDetails;
import android.app.Dialog;
import android.os.Bundle;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingList;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingListItem;
import com.udacity.firebase.shoppinglistplusplus.model.User;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
import com.udacity.firebase.shoppinglistplusplus.utils.Utils;
import java.util.HashMap;
import java.util.Map;
/**
* Lets user add new list item.
*/
public class AddListItemDialogFragment extends EditListDialogFragment {
/**
* Public static constructor that creates fragment and passes a bundle with data into it when adapter is created
*/
public static AddListItemDialogFragment newInstance(ShoppingList shoppingList, String listId,
String encodedEmail,
HashMap sharedWithUsers) {
AddListItemDialogFragment addListItemDialogFragment = new AddListItemDialogFragment();
Bundle bundle = EditListDialogFragment.newInstanceHelper(shoppingList,
R.layout.dialog_add_item, listId, encodedEmail, sharedWithUsers);
addListItemDialogFragment.setArguments(bundle);
return addListItemDialogFragment;
}
/**
* Initialize instance variables with data from bundle
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
/** {@link EditListDialogFragment#createDialogHelper(int)} is a
* superclass method that creates the dialog
**/
return super.createDialogHelper(R.string.positive_button_add_list_item);
}
/**
* Adds new item to the current shopping list
*/
@Override
protected void doListEdit() {
String mItemName = mEditTextForList.getText().toString();
/**
* Adds list item if the input name is not empty
*/
if (!mItemName.equals("")) {
Firebase firebaseRef = new Firebase(Constants.FIREBASE_URL);
Firebase itemsRef = new Firebase(Constants.FIREBASE_URL_SHOPPING_LIST_ITEMS).child(mListId);
/* Make a map for the item you are adding */
HashMap updatedItemToAddMap = new HashMap();
/* Save push() to maintain same random Id */
Firebase newRef = itemsRef.push();
String itemId = newRef.getKey();
/* Make a POJO for the item and immediately turn it into a HashMap */
ShoppingListItem itemToAddObject = new ShoppingListItem(mItemName, mEncodedEmail);
HashMap itemToAdd =
(HashMap) new ObjectMapper().convertValue(itemToAddObject, Map.class);
/* Add the item to the update map*/
updatedItemToAddMap.put("/" + Constants.FIREBASE_LOCATION_SHOPPING_LIST_ITEMS + "/"
+ mListId + "/" + itemId, itemToAdd);
/* Update affected lists timestamps */
Utils.updateMapWithTimestampLastChanged(mSharedWith,
mListId, mOwner, updatedItemToAddMap);
/* Do the update */
firebaseRef.updateChildren(updatedItemToAddMap, new Firebase.CompletionListener() {
@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
/* Now that we have the timestamp, update the reversed timestamp */
Utils.updateTimestampReversed(firebaseError, "AddListItem", mListId,
mSharedWith, mOwner);
}
});
/**
* Close the dialog fragment when done
*/
AddListItemDialogFragment.this.getDialog().cancel();
}
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/activeListDetails/EditListDialogFragment.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.activeListDetails;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingList;
import com.udacity.firebase.shoppinglistplusplus.model.User;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
import java.util.HashMap;
/**
* Base class for {@link DialogFragment}s involved with editing a shopping list.
*/
public abstract class EditListDialogFragment extends DialogFragment {
String mListId, mOwner, mEncodedEmail;
EditText mEditTextForList;
int mResource;
HashMap mSharedWith;
/**
* Helper method that creates a basic bundle of all of the information needed to change
* values in a shopping list.
*
* @param shoppingList The shopping list that the dialog is editing
* @param resource The xml layout file associated with the dialog
* @param listId The id of the shopping list the dialog is editing
* @param encodedEmail The encoded email of the current user
* @param sharedWithUsers The HashMap containing all users that the current shopping list
* is shared with
* @return The bundle containing all the arguments.
*/
protected static Bundle newInstanceHelper(ShoppingList shoppingList, int resource, String listId,
String encodedEmail, HashMap sharedWithUsers) {
Bundle bundle = new Bundle();
bundle.putSerializable(Constants.KEY_SHARED_WITH_USERS, sharedWithUsers);
bundle.putString(Constants.KEY_LIST_ID, listId);
bundle.putInt(Constants.KEY_LAYOUT_RESOURCE, resource);
bundle.putString(Constants.KEY_LIST_OWNER, shoppingList.getOwner());
bundle.putString(Constants.KEY_ENCODED_EMAIL, encodedEmail);
return bundle;
}
/**
* Initialize instance variables with data from bundle
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSharedWith = (HashMap) getArguments().getSerializable(Constants.KEY_SHARED_WITH_USERS);
mListId = getArguments().getString(Constants.KEY_LIST_ID);
mResource = getArguments().getInt(Constants.KEY_LAYOUT_RESOURCE);
mOwner = getArguments().getString(Constants.KEY_LIST_OWNER);
mEncodedEmail = getArguments().getString(Constants.KEY_ENCODED_EMAIL);
}
/**
* Open the keyboard automatically when the dialog fragment is opened
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
protected Dialog createDialogHelper(int stringResourceForPositiveButton) {
/* Use the Builder class for convenient dialog construction */
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomTheme_Dialog);
/* Get the layout inflater */
LayoutInflater inflater = getActivity().getLayoutInflater();
/* Inflate the layout, set root ViewGroup to null*/
View rootView = inflater.inflate(mResource, null);
mEditTextForList = (EditText) rootView.findViewById(R.id.edit_text_list_dialog);
/**
* Call doListEdit() when user taps "Done" keyboard action
*/
mEditTextForList.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_DONE || keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
doListEdit();
/**
* Close the dialog fragment when done
*/
EditListDialogFragment.this.getDialog().cancel();
}
return true;
}
});
/* Inflate and set the layout for the dialog */
/* Pass null as the parent view because its going in the dialog layout */
builder.setView(rootView)
/* Add action buttons */
.setPositiveButton(stringResourceForPositiveButton, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
doListEdit();
/**
* Close the dialog fragment
*/
EditListDialogFragment.this.getDialog().cancel();
}
})
.setNegativeButton(R.string.negative_button_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
/**
* Close the dialog fragment
*/
EditListDialogFragment.this.getDialog().cancel();
}
});
return builder.create();
}
/**
* Set the EditText text to be the inputted text
* and put the pointer at the end of the input
*
* @param defaultText
*/
protected void helpSetDefaultValueEditText(String defaultText) {
mEditTextForList.setText(defaultText);
mEditTextForList.setSelection(defaultText.length());
}
/**
* Method to be overriden with whatever edit is supposed to happen to the list
*/
protected abstract void doListEdit();
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/activeListDetails/EditListItemNameDialogFragment.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.activeListDetails;
import android.app.Dialog;
import android.os.Bundle;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingList;
import com.udacity.firebase.shoppinglistplusplus.model.User;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
import com.udacity.firebase.shoppinglistplusplus.utils.Utils;
import java.util.HashMap;
/**
* Lets user edit list item name for all copies of the current list
*/
public class EditListItemNameDialogFragment extends EditListDialogFragment {
String mItemName, mItemId;
/**
* Public static constructor that creates fragment and passes a bundle with data into it when adapter is created
*/
public static EditListItemNameDialogFragment newInstance(ShoppingList shoppingList, String itemName,
String itemId, String listId, String encodedEmail,
HashMap sharedWithUsers) {
EditListItemNameDialogFragment editListItemNameDialogFragment = new EditListItemNameDialogFragment();
Bundle bundle = EditListDialogFragment.newInstanceHelper(shoppingList, R.layout.dialog_edit_item,
listId, encodedEmail, sharedWithUsers);
bundle.putString(Constants.KEY_LIST_ITEM_NAME, itemName);
bundle.putString(Constants.KEY_LIST_ITEM_ID, itemId);
editListItemNameDialogFragment.setArguments(bundle);
return editListItemNameDialogFragment;
}
/**
* Initialize instance variables with data from bundle
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mItemName = getArguments().getString(Constants.KEY_LIST_ITEM_NAME);
mItemId = getArguments().getString(Constants.KEY_LIST_ITEM_ID);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
/** {@link EditListDialogFragment#createDialogHelper(int)} is a
* superclass method that creates the dialog
*/
Dialog dialog = super.createDialogHelper(R.string.positive_button_edit_item);
/**
* {@link EditListDialogFragment#helpSetDefaultValueEditText(String)} is a superclass
* method that sets the default text of the TextView
*/
super.helpSetDefaultValueEditText(mItemName);
return dialog;
}
/**
* Change selected list item name to the editText input if it is not empty
*/
protected void doListEdit() {
String nameInput = mEditTextForList.getText().toString();
/**
* Set input text to be the current list item name if it is not empty and is not the
* previous name.
*/
if (!nameInput.equals("") && !nameInput.equals(mItemName)) {
Firebase firebaseRef = new Firebase(Constants.FIREBASE_URL);
/* Make a map for the item you are changing the name of */
HashMap updatedDataItemToEditMap = new HashMap();
/* Add the new name to the update map*/
updatedDataItemToEditMap.put("/" + Constants.FIREBASE_LOCATION_SHOPPING_LIST_ITEMS + "/"
+ mListId + "/" + mItemId + "/" + Constants.FIREBASE_PROPERTY_ITEM_NAME,
nameInput);
/* Update affected lists timestamps */
Utils.updateMapWithTimestampLastChanged(mSharedWith, mListId, mOwner, updatedDataItemToEditMap);
/* Do the update */
firebaseRef.updateChildren(updatedDataItemToEditMap, new Firebase.CompletionListener() {
@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
/* Now that we have the timestamp, update the reversed timestamp */
Utils.updateTimestampReversed(firebaseError, "EditListItem", mListId,
mSharedWith, mOwner);
}
});
}
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/activeListDetails/EditListNameDialogFragment.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.activeListDetails;
import android.app.Dialog;
import android.os.Bundle;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingList;
import com.udacity.firebase.shoppinglistplusplus.model.User;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
import com.udacity.firebase.shoppinglistplusplus.utils.Utils;
import java.util.HashMap;
/**
* Lets user edit the list name for all copies of the current list
*/
public class EditListNameDialogFragment extends EditListDialogFragment {
private static final String LOG_TAG = ActiveListDetailsActivity.class.getSimpleName();
String mListName;
/**
* Public static constructor that creates fragment and passes a bundle with data into it when adapter is created
*/
public static EditListNameDialogFragment newInstance(ShoppingList shoppingList, String listId,
String encodedEmail,
HashMap sharedWithUsers) {
EditListNameDialogFragment editListNameDialogFragment = new EditListNameDialogFragment();
Bundle bundle = EditListDialogFragment.newInstanceHelper(shoppingList,
R.layout.dialog_edit_list, listId, encodedEmail, sharedWithUsers);
bundle.putString(Constants.KEY_LIST_NAME, shoppingList.getListName());
editListNameDialogFragment.setArguments(bundle);
return editListNameDialogFragment;
}
/**
* Initialize instance variables with data from bundle
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mListName = getArguments().getString(Constants.KEY_LIST_NAME);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
/** {@link EditListDialogFragment#createDialogHelper(int)} is a
* superclass method that creates the dialog
**/
Dialog dialog = super.createDialogHelper(R.string.positive_button_edit_item);
/**
* {@link EditListDialogFragment#helpSetDefaultValueEditText(String)} is a superclass
* method that sets the default text of the TextView
*/
helpSetDefaultValueEditText(mListName);
return dialog;
}
/**
* Changes the list name in all copies of the current list
*/
protected void doListEdit() {
final String inputListName = mEditTextForList.getText().toString();
/**
* Check that the user inputted list name is not empty, has changed the original name
* and that the dialog was properly initialized with the current name and id of the list.
*/
if (!inputListName.equals("") && mListName != null &&
mListId != null && !inputListName.equals(mListName)) {
Firebase firebaseRef = new Firebase(Constants.FIREBASE_URL);
/**
* Create map and fill it in with deep path multi write operations list
*/
HashMap updatedListData = new HashMap();
/* Add the value to update at the specified property for all lists */
Utils.updateMapForAllWithValue(mSharedWith, mListId, mOwner, updatedListData,
Constants.FIREBASE_PROPERTY_LIST_NAME, inputListName);
/* Update affected lists timestamps */
Utils.updateMapWithTimestampLastChanged(mSharedWith, mListId, mOwner, updatedListData);
/* Do a deep-path update */
firebaseRef.updateChildren(updatedListData, new Firebase.CompletionListener() {
@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
/* Now that we have the timestamp, update the reversed timestamp */
Utils.updateTimestampReversed(firebaseError, LOG_TAG, mListId,
mSharedWith, mOwner);
}
});
}
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/activeListDetails/RemoveListDialogFragment.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.activeListDetails;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingList;
import com.udacity.firebase.shoppinglistplusplus.model.User;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
import com.udacity.firebase.shoppinglistplusplus.utils.Utils;
import java.util.HashMap;
/**
* Lets the user remove active shopping list
*/
public class RemoveListDialogFragment extends DialogFragment {
String mListId;
String mListOwner;
HashMap mSharedWith;
final static String LOG_TAG = RemoveListDialogFragment.class.getSimpleName();
/**
* Public static constructor that creates fragment and passes a bundle with data into it when adapter is created
*/
public static RemoveListDialogFragment newInstance(ShoppingList shoppingList, String listId,
HashMap sharedWithUsers) {
RemoveListDialogFragment removeListDialogFragment = new RemoveListDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(Constants.KEY_LIST_ID, listId);
bundle.putString(Constants.KEY_LIST_OWNER, shoppingList.getOwner());
bundle.putSerializable(Constants.KEY_SHARED_WITH_USERS, sharedWithUsers);
removeListDialogFragment.setArguments(bundle);
return removeListDialogFragment;
}
/**
* Initialize instance variables with data from bundle
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mListId = getArguments().getString(Constants.KEY_LIST_ID);
mListOwner = getArguments().getString(Constants.KEY_LIST_OWNER);
mSharedWith = (HashMap) getArguments().getSerializable(Constants.KEY_SHARED_WITH_USERS);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomTheme_Dialog)
.setTitle(getActivity().getResources().getString(R.string.action_remove_list))
.setMessage(getString(R.string.dialog_message_are_you_sure_remove_list))
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
removeList();
/* Dismiss the dialog */
dialog.dismiss();
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
/* Dismiss the dialog */
dialog.dismiss();
}
})
.setIcon(android.R.drawable.ic_dialog_alert);
return builder.create();
}
private void removeList() {
Firebase firebaseRef = new Firebase(Constants.FIREBASE_URL);
/**
* Create map and fill it in with deep path multi write operations list
*/
HashMap removeListData = new HashMap();
/* Remove the ShoppingLists from both user lists and active lists */
Utils.updateMapForAllWithValue(mSharedWith, mListId, mListOwner, removeListData, "", null);
/* Remove the associated list items */
removeListData.put("/" + Constants.FIREBASE_LOCATION_SHOPPING_LIST_ITEMS + "/" + mListId,
null);
removeListData.put("/" + Constants.FIREBASE_LOCATION_OWNER_MAPPINGS + "/" + mListId,
null);
/* Do a deep-path update */
firebaseRef.updateChildren(removeListData, new Firebase.CompletionListener() {
@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
if (firebaseError != null) {
Log.e(LOG_TAG, getString(R.string.log_error_updating_data) + firebaseError.getMessage());
}
}
});
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/activeLists/ActiveListAdapter.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.activeLists;
import android.app.Activity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.firebase.client.DataSnapshot;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.Query;
import com.firebase.client.ValueEventListener;
import com.firebase.ui.FirebaseListAdapter;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingList;
import com.udacity.firebase.shoppinglistplusplus.model.User;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
/**
* Populates the list_view_active_lists inside ShoppingListsFragment
*/
public class ActiveListAdapter extends FirebaseListAdapter {
private String mEncodedEmail;
/**
* Public constructor that initializes private instance variables when adapter is created
*/
public ActiveListAdapter(Activity activity, Class modelClass, int modelLayout,
Query ref, String encodedEmail) {
super(activity, modelClass, modelLayout, ref);
this.mEncodedEmail = encodedEmail;
this.mActivity = activity;
}
/**
* Protected method that populates the view attached to the adapter (list_view_active_lists)
* with items inflated from single_active_list.xml
* populateView also handles data changes and updates the listView accordingly
*/
@Override
protected void populateView(View view, ShoppingList list) {
/**
* Grab the needed Textivews and strings
*/
TextView textViewListName = (TextView) view.findViewById(R.id.text_view_list_name);
final TextView textViewCreatedByUser = (TextView) view.findViewById(R.id.text_view_created_by_user);
final TextView textViewUsersShopping = (TextView) view.findViewById(R.id.text_view_people_shopping_count);
String ownerEmail = list.getOwner();
/* Set the list name and owner */
textViewListName.setText(list.getListName());
/**
* Show "1 person is shopping" if one person is shopping
* Show "N people shopping" if two or more users are shopping
* Show nothing if nobody is shopping
*/
if (list.getUsersShopping() != null) {
int usersShopping = list.getUsersShopping().size();
if (usersShopping == 1) {
textViewUsersShopping.setText(String.format(
mActivity.getResources().getString(R.string.person_shopping),
usersShopping));
} else {
textViewUsersShopping.setText(String.format(
mActivity.getResources().getString(R.string.people_shopping),
usersShopping));
}
} else {
/* otherwise show nothing */
textViewUsersShopping.setText("");
}
/**
* Set "Created by" text to "You" if current user is owner of the list
* Set "Created by" text to userName if current user is NOT owner of the list
*/
if (ownerEmail != null) {
if (ownerEmail.equals(mEncodedEmail)) {
textViewCreatedByUser.setText(mActivity.getResources().getString(R.string.text_you));
} else {
Firebase userRef = new Firebase(Constants.FIREBASE_URL_USERS).child(ownerEmail);
/* Get the user's name */
userRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
if (user != null) {
textViewCreatedByUser.setText(user.getName());
}
}
@Override
public void onCancelled(FirebaseError firebaseError) {
Log.e(mActivity.getClass().getSimpleName(),
mActivity.getString(R.string.log_error_the_read_failed) +
firebaseError.getMessage());
}
});
}
}
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/activeLists/AddListDialogFragment.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.activeLists;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.ServerValue;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingList;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
import com.udacity.firebase.shoppinglistplusplus.utils.Utils;
import java.util.HashMap;
import java.util.Map;
/**
* Adds a new shopping list
*/
public class AddListDialogFragment extends DialogFragment {
String mEncodedEmail;
EditText mEditTextListName;
/**
* Public static constructor that creates fragment and
* passes a bundle with data into it when adapter is created
*/
public static AddListDialogFragment newInstance(String encodedEmail) {
AddListDialogFragment addListDialogFragment = new AddListDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(Constants.KEY_ENCODED_EMAIL, encodedEmail);
addListDialogFragment.setArguments(bundle);
return addListDialogFragment;
}
/**
* Initialize instance variables with data from bundle
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mEncodedEmail = getArguments().getString(Constants.KEY_ENCODED_EMAIL);
}
/**
* Open the keyboard automatically when the dialog fragment is opened
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomTheme_Dialog);
// Get the layout inflater
LayoutInflater inflater = getActivity().getLayoutInflater();
View rootView = inflater.inflate(R.layout.dialog_add_list, null);
mEditTextListName = (EditText) rootView.findViewById(R.id.edit_text_list_name);
/**
* Call addShoppingList() when user taps "Done" keyboard action
*/
mEditTextListName.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_DONE || keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
addShoppingList();
}
return true;
}
});
/* Inflate and set the layout for the dialog */
/* Pass null as the parent view because its going in the dialog layout*/
builder.setView(rootView)
/* Add action buttons */
.setPositiveButton(R.string.positive_button_create, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
addShoppingList();
}
});
return builder.create();
}
/**
* Add new active list
*/
public void addShoppingList() {
String userEnteredName = mEditTextListName.getText().toString();
/**
* If EditText input is not empty
*/
if (!userEnteredName.equals("")) {
/**
* Create Firebase references
*/
Firebase userListsRef = new Firebase(Constants.FIREBASE_URL_USER_LISTS).
child(mEncodedEmail);
final Firebase firebaseRef = new Firebase(Constants.FIREBASE_URL);
Firebase newListRef = userListsRef.push();
/* Save listsRef.push() to maintain same random Id */
final String listId = newListRef.getKey();
/* HashMap for data to update */
HashMap updateShoppingListData = new HashMap<>();
/**
* Set raw version of date to the ServerValue.TIMESTAMP value and save into
* timestampCreatedMap
*/
HashMap timestampCreated = new HashMap<>();
timestampCreated.put(Constants.FIREBASE_PROPERTY_TIMESTAMP, ServerValue.TIMESTAMP);
/* Build the shopping list */
ShoppingList newShoppingList = new ShoppingList(userEnteredName, mEncodedEmail,
timestampCreated);
HashMap shoppingListMap = (HashMap)
new ObjectMapper().convertValue(newShoppingList, Map.class);
Utils.updateMapForAllWithValue(null, listId, mEncodedEmail,
updateShoppingListData, "", shoppingListMap);
updateShoppingListData.put("/" + Constants.FIREBASE_LOCATION_OWNER_MAPPINGS + "/" + listId,
mEncodedEmail);
/* Do the update */
firebaseRef.updateChildren(updateShoppingListData, new Firebase.CompletionListener() {
@Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
/* Now that we have the timestamp, update the reversed timestamp */
Utils.updateTimestampReversed(firebaseError, "AddList", listId,
null, mEncodedEmail);
}
});
/* Close the dialog fragment */
AddListDialogFragment.this.getDialog().cancel();
}
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/activeLists/ShoppingListsFragment.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.activeLists;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import com.firebase.client.Firebase;
import com.firebase.client.Query;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.ShoppingList;
import com.udacity.firebase.shoppinglistplusplus.ui.activeListDetails.ActiveListDetailsActivity;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
/**
* A simple {@link Fragment} subclass that shows a list of all shopping lists a user can see.
* Use the {@link ShoppingListsFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class ShoppingListsFragment extends Fragment {
private String mEncodedEmail;
private ActiveListAdapter mActiveListAdapter;
private ListView mListView;
public ShoppingListsFragment() {
/* Required empty public constructor */
}
/**
* Create fragment and pass bundle with data as it's arguments
* Right now there are not arguments...but eventually there will be.
*/
public static ShoppingListsFragment newInstance(String encodedEmail) {
ShoppingListsFragment fragment = new ShoppingListsFragment();
Bundle args = new Bundle();
args.putString(Constants.KEY_ENCODED_EMAIL, encodedEmail);
fragment.setArguments(args);
return fragment;
}
/**
* Initialize instance variables with data from bundle
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mEncodedEmail = getArguments().getString(Constants.KEY_ENCODED_EMAIL);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
/**
* Initialize UI elements
*/
View rootView = inflater.inflate(R.layout.fragment_shopping_lists, container, false);
initializeScreen(rootView);
/**
* Set interactive bits, such as click events and adapters
*/
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
ShoppingList selectedList = mActiveListAdapter.getItem(position);
if (selectedList != null) {
Intent intent = new Intent(getActivity(), ActiveListDetailsActivity.class);
/* Get the list ID using the adapter's get ref method to get the Firebase
* ref and then grab the key.
*/
String listId = mActiveListAdapter.getRef(position).getKey();
intent.putExtra(Constants.KEY_LIST_ID, listId);
/* Starts an active showing the details for the selected list */
startActivity(intent);
}
}
});
return rootView;
}
/**
* Updates the order of mListView onResume to handle sortOrderChanges properly
*/
@Override
public void onResume() {
super.onResume();
final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
String sortOrder = sharedPref.getString(Constants.KEY_PREF_SORT_ORDER_LISTS, Constants.ORDER_BY_KEY);
Query orderedActiveUserListsRef;
Firebase activeListsRef = new Firebase(Constants.FIREBASE_URL_USER_LISTS)
.child(mEncodedEmail);
/**
* Sort active lists by "date created"
* if it's been selected in the SettingsActivity
*/
if (sortOrder.equals(Constants.ORDER_BY_KEY)) {
orderedActiveUserListsRef = activeListsRef.orderByKey();
} else {
/**
* Sort active by lists by name or datelastChanged. Otherwise
* depending on what's been selected in SettingsActivity
*/
orderedActiveUserListsRef = activeListsRef.orderByChild(sortOrder);
}
/**
* Create the adapter with selected sort order
*/
mActiveListAdapter = new ActiveListAdapter(getActivity(), ShoppingList.class,
R.layout.single_active_list, orderedActiveUserListsRef,
mEncodedEmail);
/**
* Set the adapter to the mListView
*/
mListView.setAdapter(mActiveListAdapter);
}
/**
* Cleanup the adapter when activity is paused.
*/
@Override
public void onPause() {
super.onPause();
mActiveListAdapter.cleanup();
}
/**
* Link list view from XML
*/
private void initializeScreen(View rootView) {
mListView = (ListView) rootView.findViewById(R.id.list_view_active_lists);
}
}
================================================
FILE: app/src/main/java/com/udacity/firebase/shoppinglistplusplus/ui/login/CreateAccountActivity.java
================================================
package com.udacity.firebase.shoppinglistplusplus.ui.login;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.firebase.client.AuthData;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.ServerValue;
import com.udacity.firebase.shoppinglistplusplus.R;
import com.udacity.firebase.shoppinglistplusplus.model.User;
import com.udacity.firebase.shoppinglistplusplus.ui.BaseActivity;
import com.udacity.firebase.shoppinglistplusplus.utils.Constants;
import com.udacity.firebase.shoppinglistplusplus.utils.Utils;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
/**
* Represents Sign up screen and functionality of the app
*/
public class CreateAccountActivity extends BaseActivity {
private static final String LOG_TAG = CreateAccountActivity.class.getSimpleName();
private ProgressDialog mAuthProgressDialog;
private Firebase mFirebaseRef;
private EditText mEditTextUsernameCreate, mEditTextEmailCreate;
private String mUserName, mUserEmail, mPassword;
private SecureRandom mRandom = new SecureRandom();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_account);
/**
* Create Firebase references
*/
mFirebaseRef = new Firebase(Constants.FIREBASE_URL);
/**
* Link layout elements from XML and setup the progress dialog
*/
initializeScreen();
}
/**
* Override onCreateOptionsMenu to inflate nothing
*
* @param menu The menu with which nothing will happen
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
/**
* Link layout elements from XML and setup the progress dialog
*/
public void initializeScreen() {
mEditTextUsernameCreate = (EditText) findViewById(R.id.edit_text_username_create);
mEditTextEmailCreate = (EditText) findViewById(R.id.edit_text_email_create);
LinearLayout linearLayoutCreateAccountActivity = (LinearLayout) findViewById(R.id.linear_layout_create_account_activity);
initializeBackground(linearLayoutCreateAccountActivity);
/* Setup the progress dialog that is displayed later when authenticating with Firebase */
mAuthProgressDialog = new ProgressDialog(this);
mAuthProgressDialog.setTitle(getResources().getString(R.string.progress_dialog_loading));
mAuthProgressDialog.setMessage(getResources().getString(R.string.progress_dialog_check_inbox));
mAuthProgressDialog.setCancelable(false);
}
/**
* Open LoginActivity when user taps on "Sign in" textView
*/
public void onSignInPressed(View view) {
Intent intent = new Intent(CreateAccountActivity.this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}
/**
* Create new account using Firebase email/password provider
*/
public void onCreateAccountPressed(View view) {
mUserName = mEditTextUsernameCreate.getText().toString();
mUserEmail = mEditTextEmailCreate.getText().toString().toLowerCase();
mPassword = new BigInteger(130, mRandom).toString(32);
/**
* Check that email and user name are okay
*/
boolean validEmail = isEmailValid(mUserEmail);
boolean validUserName = isUserNameValid(mUserName);
if (!validEmail || !validUserName) return;
/**
* If everything was valid show the progress dialog to indicate that
* account creation has started
*/
mAuthProgressDialog.show();
/**
* Create new user with specified email and password
*/
mFirebaseRef.createUser(mUserEmail, mPassword, new Firebase.ValueResultHandler