diff --git a/.gitignore b/.gitignore index f3294d2d3..8d97ad42d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ .DS_Store /build /captures -/library/target \ No newline at end of file +/library/target +/**/*.iml diff --git a/README.md b/README.md index 6d7784b63..01fce3857 100644 --- a/README.md +++ b/README.md @@ -40,27 +40,28 @@ So say we have these chat messages in our Firebase database: We can represent a chat message with this Java class: - public class ChatMessage { - String message; - String name; +```java +public class ChatMessage { + String message; + String name; - public ChatMessage() { - } - - public ChatMessage(String name, String message) { - this.message = message; - this.name = name; - } + public ChatMessage() { + } - public String getMessage() { - return message; - } + public ChatMessage(String name, String message) { + this.message = message; + this.name = name; + } - public String getName() { - return name; - } + public String getMessage() { + return message; } + public String getName() { + return name; + } +} +``` A few things to note here: * the field have the exact same name as the properties in Firebase. This allows Firebase to automatically map the properties to these fields. @@ -70,28 +71,30 @@ A few things to note here: A little-known feature of Firebase for Android is that you can pass an instance of this `ChatMessage` class to `setValue()`: - Firebase ref = new Firebase("https://nanochat.firebaseio.com/"); - ChatMessage msg = new ChatMessage("puf", "Hello FirebaseUI world!"); - ref.push().setValue(msg); - +```java +Firebase ref = new Firebase("https://nanochat.firebaseio.com/"); +ChatMessage msg = new ChatMessage("puf", "Hello FirebaseUI world!"); +ref.push().setValue(msg); +``` The Firebase Android client will read the values from the `msg` and write them into the properties of the new child in the database. Conversely, we can read a `ChatMessage` straight from a `DataSnapshot` in our event handlers: - ref.limitToLast(5).addValueEventListener(new ValueEventListener() { - @Override - public void onDataChange(DataSnapshot snapshot) { - for (DataSnapshot msgSnapshot: snapshot.getChildren()) { - ChatMessage msg = msgSnapshot.getValue(ChatMessage.class); - Log.i("Chat", chat.getName()+": "+chat.getMessage()); - } - } - @Override - public void onCancelled(FirebaseError firebaseError) { - Log.e("Chat", "The read failed: " + firebaseError.getMessage()); +```java +ref.limitToLast(5).addValueEventListener(new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot snapshot) { + for (DataSnapshot msgSnapshot: snapshot.getChildren()) { + ChatMessage msg = msgSnapshot.getValue(ChatMessage.class); + Log.i("Chat", chat.getName()+": "+chat.getMessage()); } - }); - + } + @Override + public void onCancelled(FirebaseError firebaseError) { + Log.e("Chat", "The read failed: " + firebaseError.getMessage()); + } +}); +``` In the above snippet we have a query for the last 5 chat messages. Whenever those change (i.e. when an new message is added) we get the `ChatMessage` objects from the `DataSnapshot` with `getValue(ChatMessage.class)`. The Firebase Android client will then read the properties that it got from the database and map them to the fields of our `ChatMessage` class. @@ -105,53 +108,59 @@ But when we build our app using FirebaseUI, we often won't need to register our We'll assume you've already added a `ListView` to your layout and have looked it up in the `onCreate` method of your activity: - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); +```java +@Override +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); - } + ListView messagesView = (ListView) findViewById(R.id.messages_list); +} +``` #### Set up connection to Firebase First we'll tell Firebase that we intend to use it in this activity and set up a reference to the database of chat message. - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); +```java +@Override +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); + ListView messagesView = (ListView) findViewById(R.id.messages_list); - Firebase.setAndroidContext(this); - Firebase ref = new Firebase("https://nanochat.firebaseio.com"); - } + Firebase.setAndroidContext(this); + Firebase ref = new Firebase("https://nanochat.firebaseio.com"); +} +``` #### Create custom FirebaseListAdapter subclass Next, we need to create a subclass of the `FirebaseListAdapter` with the correct parameters and implement its `populateView` method: - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); +```java +@Override +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); - ListView messagesView = (ListView) findViewById(R.id.messages_list); + ListView messagesView = (ListView) findViewById(R.id.messages_list); - Firebase.setAndroidContext(this); - Firebase ref = new Firebase("https://nanochat.firebaseio.com"); + Firebase.setAndroidContext(this); + Firebase ref = new Firebase("https://nanochat.firebaseio.com"); - mAdapter = new FirebaseListAdapter(this, ChatMessage.class, android.R.layout.two_line_list_item, ref) { - @Override - protected void populateView(View view, ChatMessage chatMessage) { - ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); - ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getMessage()); + mAdapter = new FirebaseListAdapter(this, ChatMessage.class, android.R.layout.two_line_list_item, ref) { + @Override + protected void populateView(View view, ChatMessage chatMessage) { + ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); + ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getMessage()); - } - }; - messagesView.setListAdapter(mAdapter); - } + } + }; + messagesView.setListAdapter(mAdapter); +} +``` In this last snippet we create a subclass of `FirebaseListAdapter`. We tell is that it is of type ``, so that it is a type-safe collection. We also tell it to use @@ -171,51 +180,55 @@ correct `TextView` controls from the `view`. The code is a bit verbose, but hey. Finally, we need to clean up after ourselves. When the activity is destroyed, we need to call `release()` on the `ListAdapter` so that it can stop listening for changes in the Firebase database. - @Override - protected void onDestroy() { - super.onDestroy(); - mAdapter.cleanup(); - } +```java +@Override +protected void onDestroy() { + super.onDestroy(); + mAdapter.cleanup(); +} +``` #### Sending chat messages Remember when we showed how to use the `ChatMessage` class in `setValue()`. We can now use that in our activity to allow sending a message: - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - ListView messagesView = (ListView) findViewById(R.id.messages_list); - - Firebase.setAndroidContext(this); - Firebase ref = new Firebase("https://nanochat.firebaseio.com"); - - mAdapter = new FirebaseListAdapter(this, ChatMessage.class, android.R.layout.two_line_list_item, ref) { - @Override - protected void populateView(View view, ChatMessage chatMessage) { - ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); - ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getMessage()); - } - }; - setListAdapter(mAdapter); - - final EditText mMessage = (EditText) findViewById(R.id.message_text); - findViewById(R.id.send_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mRef.push().setValue(new ChatMessage("puf", mMessage.getText().toString())); - mMessage.setText(""); - } - }); - } +```java +@Override +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); - @Override - protected void onDestroy() { - super.onDestroy(); - mAdapter.cleanup(); - } + ListView messagesView = (ListView) findViewById(R.id.messages_list); + + Firebase.setAndroidContext(this); + Firebase ref = new Firebase("https://nanochat.firebaseio.com"); + + mAdapter = new FirebaseListAdapter(this, ChatMessage.class, android.R.layout.two_line_list_item, ref) { + @Override + protected void populateView(View view, ChatMessage chatMessage) { + ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName()); + ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getMessage()); + } + }; + setListAdapter(mAdapter); + + final EditText mMessage = (EditText) findViewById(R.id.message_text); + findViewById(R.id.send_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mRef.push().setValue(new ChatMessage("puf", mMessage.getText().toString())); + mMessage.setText(""); + } + }); +} + +@Override +protected void onDestroy() { + super.onDestroy(); + mAdapter.cleanup(); +} +``` Et voila: a minimal, yet fully functional, chat app in about 30 lines of code. Not bad, right? @@ -235,16 +248,18 @@ A ViewHolder is similar to container of a ViewGroup that allows simple lookup of If we use the same layout as before (`android.R.layout.two_line_list_item`), there are two `TextView`s in there. We can wrap that in a ViewHolder with: - private static class ChatMessageViewHolder extends RecyclerView.ViewHolder { - TextView messageText; - TextView nameText; +```java +private static class ChatMessageViewHolder extends RecyclerView.ViewHolder { + TextView messageText; + TextView nameText; - public ChatMessageViewHolder(View itemView) { - super(itemView); - nameText = (TextView)itemView.findViewById(android.R.id.text1); - messageText = (TextView) itemView.findViewById(android.R.id.text2); - } + public ChatMessageViewHolder(View itemView) { + super(itemView); + nameText = (TextView)itemView.findViewById(android.R.id.text1); + messageText = (TextView) itemView.findViewById(android.R.id.text2); } +} +``` There's nothing magical going on here; we're just mapping numeric IDs and casts into a nice, type-safe contract. @@ -252,18 +267,20 @@ There's nothing magical going on here; we're just mapping numeric IDs and casts Just like we did for FirebaseListAdapter, we'll create an anonymous subclass for our ChatMessages: - RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler); - recycler.setHasFixedSize(true); - recycler.setLayoutManager(new LinearLayoutManager(this)); - - mAdapter = new FirebaseRecyclerViewAdapter(ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, mRef) { - @Override - public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder, ChatMessage chatMessage) { - chatMessageViewHolder.nameText.setText(chatMessage.getName()); - chatMessageViewHolder.messageText.setText(chatMessage.getMessage()); - } - }; - recycler.setAdapter(mAdapter); +```java +RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler); +recycler.setHasFixedSize(true); +recycler.setLayoutManager(new LinearLayoutManager(this)); + +mAdapter = new FirebaseRecyclerViewAdapter(ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, mRef) { + @Override + public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder, ChatMessage chatMessage) { + chatMessageViewHolder.nameText.setText(chatMessage.getName()); + chatMessageViewHolder.messageText.setText(chatMessage.getMessage()); + } +}; +recycler.setAdapter(mAdapter); +``` Like before, we get a custom RecyclerView populated with data from Firebase by setting the properties to the correct fields. diff --git a/app/build.gradle b/app/build.gradle index 8264f84de..89f30a197 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ android { defaultConfig { applicationId "com.firebase.uidemo" - minSdkVersion 10 + minSdkVersion 15 targetSdkVersion 22 versionCode 1 versionName "1.0" @@ -33,5 +33,6 @@ dependencies { compile 'com.google.android.gms:play-services-plus:8.1.0' compile 'com.firebase:firebase-client-android:2.3.1' compile 'com.android.support:recyclerview-v7:22.2.1' + compile 'com.facebook.android:facebook-android-sdk:4.6.0' compile project(':library') } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8db880dde..95d4b4897 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,27 @@ + + + + + + + + + + + diff --git a/app/src/main/ic_google-web.png b/app/src/main/ic_google-web.png new file mode 100644 index 000000000..78651b193 Binary files /dev/null and b/app/src/main/ic_google-web.png differ diff --git a/app/src/main/java/com/firebase/uidemo/RecyclerViewDemoActivity.java b/app/src/main/java/com/firebase/uidemo/RecyclerViewDemoActivity.java index b0ad6decb..bfef05e06 100644 --- a/app/src/main/java/com/firebase/uidemo/RecyclerViewDemoActivity.java +++ b/app/src/main/java/com/firebase/uidemo/RecyclerViewDemoActivity.java @@ -1,16 +1,24 @@ package com.firebase.uidemo; import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.Shape; 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.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; import android.widget.TextView; import com.firebase.client.AuthData; @@ -19,110 +27,125 @@ import com.firebase.client.Query; import com.firebase.ui.FirebaseLoginBaseActivity; import com.firebase.ui.FirebaseRecyclerViewAdapter; -import com.firebase.ui.com.firebasei.ui.authimpl.SocialProvider; - public class RecyclerViewDemoActivity extends FirebaseLoginBaseActivity { public static String TAG = "FirebaseUI.chat"; private Firebase mRef; + private Query mChatRef; private AuthData mAuthData; + private String name; + private String uid; private Button mSendButton; private EditText mMessageEdit; + private RecyclerView mMessages; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.recycler_view_demo); - final String name = "Android User"; mSendButton = (Button) findViewById(R.id.sendButton); mMessageEdit = (EditText) findViewById(R.id.messageEdit); - final RecyclerView messages = (RecyclerView) findViewById(R.id.messagesList); - messages.setHasFixedSize(true); - messages.setLayoutManager(new LinearLayoutManager(this)); - mRef = new Firebase("https://firebaseui.firebaseio.com/chat"); + mRef = new Firebase("https://firebaseui.firebaseio.com/chat_3"); + mChatRef = mRef.limitToLast(50); mSendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Chat chat = new Chat(name, mMessageEdit.getText().toString()); - mRef.push().setValue(chat, new Firebase.CompletionListener() { - @Override - public void onComplete(FirebaseError firebaseError, Firebase firebase) { - if (firebaseError != null) { - Log.e(TAG, firebaseError.toString()); - } + Chat chat = new Chat(name, mAuthData.getUid(), mMessageEdit.getText().toString()); + mRef.push().setValue(chat, new Firebase.CompletionListener() { + @Override + public void onComplete(FirebaseError firebaseError, Firebase firebase) { + if (firebaseError != null) { + Log.e(TAG, firebaseError.toString()); } - }); - mMessageEdit.setText(""); + } + }); + mMessageEdit.setText(""); } }); - Query recentMessages = mRef.limitToLast(50); - FirebaseRecyclerViewAdapter adapter = new FirebaseRecyclerViewAdapter(Chat.class, android.R.layout.two_line_list_item, ChatHolder.class, recentMessages) { + mMessages = (RecyclerView) findViewById(R.id.messagesList); + + LinearLayoutManager manager = new LinearLayoutManager(this); + manager.setStackFromEnd(true); + + mMessages.setHasFixedSize(true); + mMessages.setLayoutManager(manager); + + + updateChat(); + } + + protected void updateChat() { + FirebaseRecyclerViewAdapter adapter = new FirebaseRecyclerViewAdapter(Chat.class, R.layout.message, ChatHolder.class, mChatRef) { @Override public void populateViewHolder(ChatHolder chatView, Chat chat) { - chatView.textView.setText(chat.getText()); - chatView.textView.setPadding(10, 0, 10, 0); - chatView.nameView.setText(chat.getName()); - chatView.nameView.setPadding(10, 0, 10, 15); - if (chat.getName().equals(name)) { - chatView.textView.setGravity(Gravity.END); - chatView.nameView.setGravity(Gravity.END); - chatView.nameView.setTextColor(Color.parseColor("#8BC34A")); + + chatView.setName(chat.getName()); + chatView.setText(chat.getText()); + + if (mAuthData != null && chat.getUid().equals(mAuthData.getUid())) { + // Is me + chatView.setSender(true); } else { - chatView.nameView.setTextColor(Color.parseColor("#00BCD4")); + chatView.setSender(false); + // Isn't me } } }; - messages.setAdapter(adapter); + mMessages.setAdapter(adapter); } - public static final int LOGIN = Menu.FIRST; - public static final int LOGOUT = LOGIN+1; - @Override public boolean onCreateOptionsMenu(Menu menu) { - menu.add(LOGIN, LOGIN, LOGIN, "Log in"); - menu.add(LOGOUT, LOGOUT, LOGOUT, "Log out"); - - return super.onCreateOptionsMenu(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.chat_login_menu, menu); + return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { - menu.getItem(LOGIN-Menu.FIRST).setVisible(mAuthData == null); - menu.getItem(LOGOUT-Menu.FIRST).setVisible(mAuthData != null); + menu.findItem(R.id.login_menu_item).setVisible(mAuthData == null); + menu.findItem(R.id.logout_menu_item).setVisible(mAuthData != null); mSendButton.setEnabled(mAuthData != null); mMessageEdit.setEnabled(mAuthData != null); - return super.onPrepareOptionsMenu(menu); + + return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case LOGIN: - this.loginWithProvider(SocialProvider.google); + case R.id.login_menu_item: + this.showFirebaseLoginPrompt(); return true; - case LOGOUT: + case R.id.logout_menu_item: this.logout(); return true; } - return super.onOptionsItemSelected(item); } - - - // Start of FirebaseLoginBaseActivity - @Override - public void onFirebaseLogin(AuthData authData) { - Log.i(TAG, "Logged in"); + public void onFirebaseLoginSuccess(AuthData authData) { + Log.i(TAG, "Logged in to " + authData.getProvider().toString()); mAuthData = authData; + + switch (mAuthData.getProvider()) { + case "password": + name = (String) mAuthData.getProviderData().get("email"); + break; + default: + name = (String) mAuthData.getProviderData().get("displayName"); + break; + } + + updateChat(); invalidateOptionsMenu(); } @@ -130,17 +153,19 @@ public void onFirebaseLogin(AuthData authData) { public void onFirebaseLogout() { Log.i(TAG, "Logged out"); mAuthData = null; + name = ""; invalidateOptionsMenu(); + updateChat(); } @Override - public void onFirebaseLoginError(FirebaseError firebaseError) { - Log.e(TAG, firebaseError.toString()); + public void onFirebaseLoginProviderError(FirebaseError firebaseError) { + Log.i(TAG, "Login provider error: " + firebaseError.toString()); } @Override - public void onFirebaseLoginCancel() { - Log.i(TAG, "Login cancelled"); + public void onFirebaseLoginUserError(FirebaseError firebaseError) { + Log.i(TAG, "Login user error: " + firebaseError.toString()); } @Override @@ -148,36 +173,78 @@ public Firebase getFirebaseRef() { return mRef; } - // End of FirebaseLoginBaseActivity - public static class Chat { String name; String text; + String uid; public Chat() { } - public Chat(String name, String message) { + public Chat(String name, String uid, String message) { this.name = name; this.text = message; + this.uid = uid; } public String getName() { return name; } + public String getUid() { + return uid; + } + public String getText() { return text; } } public static class ChatHolder extends RecyclerView.ViewHolder { - TextView nameView, textView; + View mView; public ChatHolder(View itemView) { super(itemView); - nameView = (TextView) itemView.findViewById(android.R.id.text2); - textView = (TextView) itemView.findViewById(android.R.id.text1); + mView = itemView; + } + + public void setSender(Boolean isSender) { + FrameLayout arrow; + + if (isSender) { + arrow = (FrameLayout) mView.findViewById(R.id.left_arrow); + } else { + arrow = (FrameLayout) mView.findViewById(R.id.right_arrow); + + View messageBox = mView.findViewById(R.id.message); + View messageArrow = mView.findViewById(R.id.left_arrow); + +// LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(); +// lp.gravity= Gravity.RIGHT; +// mView.setLayoutParams(lp); + + + + + + //GradientDrawable messageBoxBackground= (GradientDrawable) (messageBox.getBackground()); + //messageBoxBackground.setColor(Color.MAGENTA); + //GradientDrawable messageArrowBackground = (GradientDrawable) (messageArrow.getBackground()); + //messageArrowBackground.setColor(Color.MAGENTA); + + } + + arrow.setVisibility(View.GONE); + } + + public void setName(String name) { + TextView field = (TextView) mView.findViewById(R.id.name_text); + field.setText(name); + } + + public void setText(String text) { + TextView field = (TextView) mView.findViewById(R.id.message_text); + field.setText(text); } } } diff --git a/app/src/main/res/drawable/chat_message_arrow.xml b/app/src/main/res/drawable/chat_message_arrow.xml new file mode 100644 index 000000000..c376a8105 --- /dev/null +++ b/app/src/main/res/drawable/chat_message_arrow.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chat_message_background.xml b/app/src/main/res/drawable/chat_message_background.xml new file mode 100644 index 000000000..62c531c75 --- /dev/null +++ b/app/src/main/res/drawable/chat_message_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/fabric.properties b/app/src/main/res/fabric.properties new file mode 100644 index 000000000..4824fe9f3 --- /dev/null +++ b/app/src/main/res/fabric.properties @@ -0,0 +1,2 @@ +apiSecret=YOUR_BUILD_SECRET +apiKey=YOUR_API_KEY \ No newline at end of file diff --git a/app/src/main/res/layout/message.xml b/app/src/main/res/layout/message.xml new file mode 100644 index 000000000..65cb9abb9 --- /dev/null +++ b/app/src/main/res/layout/message.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recycler_view_demo.xml b/app/src/main/res/layout/recycler_view_demo.xml index 1e8c128c7..5f6707558 100644 --- a/app/src/main/res/layout/recycler_view_demo.xml +++ b/app/src/main/res/layout/recycler_view_demo.xml @@ -13,7 +13,8 @@ android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentTop="true" - android:layout_above="@+id/footer" /> + android:layout_above="@+id/footer" + /> + android:layout_gravity="bottom|right"/> \ No newline at end of file diff --git a/app/src/main/res/menu/chat_login_menu.xml b/app/src/main/res/menu/chat_login_menu.xml new file mode 100644 index 000000000..17f27274e --- /dev/null +++ b/app/src/main/res/menu/chat_login_menu.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 80d6f7ea5..e25a7ed4c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,7 @@ FirebaseUI Chat + + + + diff --git a/build.gradle b/build.gradle index 81ea48a34..a1cadf81c 100644 --- a/build.gradle +++ b/build.gradle @@ -15,5 +15,7 @@ buildscript { allprojects { repositories { jcenter() + mavenCentral() + maven { url 'https://maven.fabric.io/public' } } } diff --git a/library/build.gradle b/library/build.gradle index 8091d0323..f2d2068d0 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -6,7 +6,7 @@ android { buildToolsVersion "22.0.1" defaultConfig { - minSdkVersion 10 + minSdkVersion 15 targetSdkVersion 22 versionCode 1 versionName "0.2.0" @@ -47,5 +47,10 @@ dependencies { compile 'com.android.support:recyclerview-v7:22.2.0' compile 'com.google.android.gms:play-services-identity:8.1.0' compile 'com.google.android.gms:play-services-plus:8.1.0' + compile 'com.facebook.android:facebook-android-sdk:4.6.0' + compile 'org.twitter4j:twitter4j-core:4.0.2' + compile('com.twitter.sdk.android:twitter:1.9.1@aar') { + transitive = true; + } androidTestCompile 'junit:junit:4.12' } diff --git a/library/src/main/ic_google-web.png b/library/src/main/ic_google-web.png new file mode 100644 index 000000000..78651b193 Binary files /dev/null and b/library/src/main/ic_google-web.png differ diff --git a/library/src/main/java/com/firebase/ui/FirebaseArray.java b/library/src/main/java/com/firebase/ui/FirebaseArray.java index 79fad095c..be90de9e3 100644 --- a/library/src/main/java/com/firebase/ui/FirebaseArray.java +++ b/library/src/main/java/com/firebase/ui/FirebaseArray.java @@ -28,10 +28,12 @@ package com.firebase.ui; -import com.firebase.client.*; +import com.firebase.client.ChildEventListener; +import com.firebase.client.DataSnapshot; +import com.firebase.client.FirebaseError; +import com.firebase.client.Query; import java.util.ArrayList; -import java.util.Collections; /** * This class implements an array-like collection on top of a Firebase location. diff --git a/library/src/main/java/com/firebase/ui/FirebaseLoginBaseActivity.java b/library/src/main/java/com/firebase/ui/FirebaseLoginBaseActivity.java index 14c7076b6..00791bc0d 100644 --- a/library/src/main/java/com/firebase/ui/FirebaseLoginBaseActivity.java +++ b/library/src/main/java/com/firebase/ui/FirebaseLoginBaseActivity.java @@ -1,37 +1,42 @@ package com.firebase.ui; -import android.os.Bundle; +import android.content.Intent; import android.support.v7.app.AppCompatActivity; +import android.util.Log; import com.firebase.client.AuthData; import com.firebase.client.Firebase; import com.firebase.client.FirebaseError; -import com.firebase.ui.com.firebasei.ui.authimpl.GoogleAuthHelper; -import com.firebase.ui.com.firebasei.ui.authimpl.SocialProvider; -import com.firebase.ui.com.firebasei.ui.authimpl.TokenAuthHandler; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.io.InputStream; +import com.firebase.ui.auth.FacebookAuthHelper; +import com.firebase.ui.auth.GoogleAuthHelper; +import com.firebase.ui.auth.PasswordAuthHelper; +import com.firebase.ui.auth.SocialProvider; +import com.firebase.ui.auth.TokenAuthHandler; +import com.firebase.ui.auth.TwitterAuthHelper; public abstract class FirebaseLoginBaseActivity extends AppCompatActivity { - private final String LOG_TAG = "FirebaseLoginBaseAct"; + private final String TAG = "FirebaseLoginBaseAct"; private GoogleAuthHelper mGoogleAuthHelper; + private FacebookAuthHelper mFacebookAuthHelper; + private TwitterAuthHelper mTwitterAuthHelper; + private PasswordAuthHelper mPasswordAuthHelper; + + TokenAuthHandler mHandler; + + private FirebaseLoginDialog mDialog; public SocialProvider mChosenProvider; /* Abstract methods for Login Events */ - protected abstract void onFirebaseLogin(AuthData authData); + protected abstract void onFirebaseLoginSuccess(AuthData authData); protected abstract void onFirebaseLogout(); - protected abstract void onFirebaseLoginError(FirebaseError firebaseError); + protected abstract void onFirebaseLoginProviderError(FirebaseError firebaseError); - protected abstract void onFirebaseLoginCancel(); + protected abstract void onFirebaseLoginUserError(FirebaseError firebaseError); /** * Subclasses of this activity must implement this method and return a valid Firebase reference that @@ -50,8 +55,11 @@ public void loginWithProvider(SocialProvider provider) { mGoogleAuthHelper.login(); break; case facebook: + mFacebookAuthHelper.login(); + break; case twitter: - throw new UnsupportedOperationException(); + mTwitterAuthHelper.login(); + break; } mChosenProvider = provider; @@ -63,97 +71,79 @@ public void logout() { mGoogleAuthHelper.logout(); break; case facebook: + mFacebookAuthHelper.logout(); + break; case twitter: - throw new UnsupportedOperationException(); + mFacebookAuthHelper.logout(); + break; } getFirebaseRef().unauth(); } + public void onActivityResult(int requestCode, int resultCode, Intent data) { + // TODO: If someone isn't extending this activity, they need to implement this by hand + if (mDialog.isActive) { + mDialog.onActivityResult(requestCode, resultCode, data); + } else { + mFacebookAuthHelper.mCallbackManager.onActivityResult(requestCode, resultCode, data); + mTwitterAuthHelper.onActivityResult(requestCode, resultCode, data); + } + } + + public void showFirebaseLoginPrompt() { + mDialog.show(getFragmentManager(), ""); + } @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + protected void onStart() { + super.onStart(); - mGoogleAuthHelper = new GoogleAuthHelper(this, new TokenAuthHandler() { + mHandler = new TokenAuthHandler() { @Override - public void onTokenReceived(String token) { - authenticateRefWithProvider(mGoogleAuthHelper.PROVIDER_NAME, token); + public void onSuccess(AuthData data) { + } @Override - public void onCancelled() { - onFirebaseLoginCancel(); + public void onUserError(FirebaseError err) { + onFirebaseLoginUserError(err); } @Override - public void onError(Exception ex) { - // TODO: Raise GMS Dialog Box? + public void onProviderError(FirebaseError err) { + onFirebaseLoginProviderError(err); } - }); - } + }; + + mFacebookAuthHelper = new FacebookAuthHelper(this, getFirebaseRef(), mHandler); + mGoogleAuthHelper = new GoogleAuthHelper(this, getFirebaseRef(), mHandler); + mTwitterAuthHelper = new TwitterAuthHelper(this, getFirebaseRef(), mHandler); + mPasswordAuthHelper = new PasswordAuthHelper(this, getFirebaseRef(), mHandler); + + mDialog = new FirebaseLoginDialog(); + mDialog + .setContext(this) + .setRef(getFirebaseRef()) + .setHandler(mHandler); + + mDialog + .setProviderEnabled(SocialProvider.facebook) + .setProviderEnabled(SocialProvider.google) + .setProviderEnabled(SocialProvider.twitter) + .setProviderEnabled(SocialProvider.password); - @Override - protected void onStart() { - super.onStart(); - // TODO: is there a way to delay this? Or make it on-demand (i.e. make them call `startMonitoringState`)? // TODO: should we remove the authStateListener on `onStop()`? getFirebaseRef().addAuthStateListener(new Firebase.AuthStateListener() { @Override public void onAuthStateChanged(AuthData authData) { if (authData != null) { mChosenProvider = SocialProvider.valueOf(authData.getProvider()); - onFirebaseLogin(authData); + onFirebaseLoginSuccess(authData); + Log.d(TAG, "Auth data changed"); } else { onFirebaseLogout(); } } }); } - - private void authenticateRefWithProvider(String provider, String token) { - getFirebaseRef().authWithOAuthToken(provider, token, new Firebase.AuthResultHandler() { - @Override - public void onAuthenticated(AuthData authData) { - // Do nothing. Auth updates are handled in the AuthStateListener - } - - @Override - public void onAuthenticationError(FirebaseError firebaseError) { - onFirebaseLoginError(firebaseError); - } - }); - - } - - private String getFirebaseUrlFromConfig() { - String firebaseUrl; - try { - - InputStream inputStream = getAssets().open("firebase-config.json"); - - int size = inputStream.available(); - - byte[] buffer = new byte[size]; - - inputStream.read(buffer); - - inputStream.close(); - - String json = new String(buffer, "UTF-8"); - - JSONObject obj = new JSONObject(json); - - firebaseUrl = obj.getString("firebaseUrl"); - - } catch (IOException ex) { - ex.printStackTrace(); - return null; - } catch (JSONException ex) { - ex.printStackTrace(); - return null; - } - - return firebaseUrl; - } - } diff --git a/library/src/main/java/com/firebase/ui/FirebaseLoginDialog.java b/library/src/main/java/com/firebase/ui/FirebaseLoginDialog.java new file mode 100644 index 000000000..18ada217a --- /dev/null +++ b/library/src/main/java/com/firebase/ui/FirebaseLoginDialog.java @@ -0,0 +1,159 @@ +package com.firebase.ui; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +import com.firebase.client.AuthData; +import com.firebase.client.Firebase; +import com.firebase.client.FirebaseError; +import com.firebase.ui.auth.FacebookAuthHelper; +import com.firebase.ui.auth.FirebaseAuthHelper; +import com.firebase.ui.auth.GoogleAuthHelper; +import com.firebase.ui.auth.PasswordAuthHelper; +import com.firebase.ui.auth.SocialProvider; +import com.firebase.ui.auth.TokenAuthHandler; +import com.firebase.ui.auth.TwitterAuthHelper; + +public class FirebaseLoginDialog extends DialogFragment { + + FacebookAuthHelper mFacebookAuthHelper; + TwitterAuthHelper mTwitterAuthHelper; + GoogleAuthHelper mGoogleAuthHelper; + PasswordAuthHelper mPasswordAuthHelper; + + TokenAuthHandler mHandler; + Firebase mRef; + Context mContext; + public Boolean isActive = false; + + public void onStart() { + super.onStart(); + isActive = true; + } + + public void onDestroy() { + super.onDestroy(); + isActive = false; + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mFacebookAuthHelper != null) { + mFacebookAuthHelper.mCallbackManager.onActivityResult(requestCode, resultCode, data); + } + + if (mTwitterAuthHelper != null) { + mTwitterAuthHelper.onActivityResult(requestCode, resultCode, data); + } + } + + View mView; + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + // Get the layout inflater + LayoutInflater inflater = getActivity().getLayoutInflater(); + + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + mView = inflater.inflate(R.layout.fragment_firebase_login, null); + + if (mFacebookAuthHelper != null) showLoginOption(mFacebookAuthHelper, R.id.facebook_button); + else mView.findViewById(R.id.facebook_button).setVisibility(View.GONE); + + if (mGoogleAuthHelper != null) showLoginOption(mGoogleAuthHelper, R.id.google_button); + else mView.findViewById(R.id.google_button).setVisibility(View.GONE); + + if (mTwitterAuthHelper != null) showLoginOption(mTwitterAuthHelper, R.id.twitter_button); + else mView.findViewById(R.id.twitter_button).setVisibility(View.GONE); + + if (mPasswordAuthHelper != null) { + showLoginOption(mPasswordAuthHelper, R.id.password_button); + if (mFacebookAuthHelper == null && mGoogleAuthHelper == null && mTwitterAuthHelper == null) + mView.findViewById(R.id.or_section).setVisibility(View.GONE); + } + else mView.findViewById(R.id.password_section).setVisibility(View.GONE); + + mView.findViewById(R.id.loading_section).setVisibility(View.GONE); + + builder.setView(mView); + return builder.create(); + } + + public FirebaseLoginDialog setRef(Firebase ref) { + mRef = ref; + return this; + } + + public FirebaseLoginDialog setHandler(final TokenAuthHandler handler) { + //TODO: Make this idiomatic? + final DialogFragment self = this; + mHandler = new TokenAuthHandler() { + @Override + public void onSuccess(AuthData auth) { + self.dismiss(); + handler.onSuccess(auth); + } + + @Override + public void onUserError(FirebaseError err) { + handler.onUserError(err); + } + + @Override + public void onProviderError(FirebaseError err) { + handler.onProviderError(err); + } + }; + return this; + } + + public FirebaseLoginDialog setContext(Context context) { + mContext = context; + return this; + } + + public FirebaseLoginDialog setProviderEnabled(SocialProvider provider) { + switch (provider) { + case facebook: + mFacebookAuthHelper = new FacebookAuthHelper(mContext, mRef, mHandler); + break; + case google: + mGoogleAuthHelper = new GoogleAuthHelper(mContext, mRef, mHandler); + break; + case twitter: + mTwitterAuthHelper = new TwitterAuthHelper(mContext, mRef, mHandler); + break; + case password: + mPasswordAuthHelper = new PasswordAuthHelper(mContext, mRef, mHandler); + break; + } + + return this; + } + + private void showLoginOption(final FirebaseAuthHelper helper, int id) { + mView.findViewById(id).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (helper.getProviderName().equals("password")) { + EditText emailText = (EditText) mView.findViewById(R.id.email); + EditText passwordText = (EditText) mView.findViewById(R.id.password); + helper.login(emailText.getText().toString(), passwordText.getText().toString()); + } else { + helper.login(); + } + mView.findViewById(R.id.login_section).setVisibility(View.GONE); + mView.findViewById(R.id.loading_section).setVisibility(View.VISIBLE); + } + }); + + } +} \ No newline at end of file diff --git a/library/src/main/java/com/firebase/ui/auth/FacebookAuthHelper.java b/library/src/main/java/com/firebase/ui/auth/FacebookAuthHelper.java new file mode 100644 index 000000000..15173e168 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/FacebookAuthHelper.java @@ -0,0 +1,107 @@ +package com.firebase.ui.auth; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import com.facebook.AccessToken; +import com.facebook.CallbackManager; +import com.facebook.FacebookCallback; +import com.facebook.FacebookException; +import com.facebook.FacebookSdk; +import com.facebook.login.LoginManager; +import com.facebook.login.LoginResult; +import com.firebase.client.Firebase; +import com.firebase.client.FirebaseError; + +import java.util.Arrays; +import java.util.Collection; + +public class FacebookAuthHelper extends FirebaseAuthHelper { + + private final String LOG_TAG = "FacebookAuthHelper"; + + public static final String PROVIDER_NAME = "facebook"; + + private LoginManager mLoginManager; + public CallbackManager mCallbackManager; + private TokenAuthHandler mHandler; + private Activity mActivity; + private Firebase mRef; + private Boolean isReady = false; + + public FacebookAuthHelper(Context context, Firebase ref, final TokenAuthHandler handler) { + mActivity = (Activity) context; + FacebookSdk.sdkInitialize(context.getApplicationContext()); + + mLoginManager = LoginManager.getInstance(); + mCallbackManager = CallbackManager.Factory.create(); + mHandler = handler; + mRef = ref; + + mLoginManager.registerCallback(mCallbackManager, + new FacebookCallback() { + @Override + public void onSuccess(LoginResult loginResult) { + AccessToken token = loginResult.getAccessToken(); + + FirebaseOAuthToken foToken = new FirebaseOAuthToken( + PROVIDER_NAME, + token.getToken().toString()); + + onFirebaseTokenReceived(foToken, handler); + } + + @Override + public void onCancel() { + mHandler.onUserError(new FirebaseError(0, "User closed login prompt.")); + } + + @Override + public void onError(FacebookException ex) { + mHandler.onProviderError(new FirebaseError(1, ex.toString())); + } + } + ); + + String facebookAppId = ""; + + try { + ApplicationInfo ai = mActivity.getPackageManager().getApplicationInfo(mActivity.getPackageName(), PackageManager.GET_META_DATA); + Bundle bundle = ai.metaData; + facebookAppId = bundle.getString("com.facebook.sdk.ApplicationId"); + } catch (PackageManager.NameNotFoundException e) { + } catch (NullPointerException e) {} + + if (facebookAppId == null) { + mHandler.onProviderError(new FirebaseError(FirebaseStatuses.PROVIDER_ERROR, "Invalid Facebook Application ID, is it set in your AndroidManifest.xml?")); + return; + } + + if (facebookAppId.compareTo("") == 0) { + mHandler.onProviderError(new FirebaseError(FirebaseStatuses.PROVIDER_ERROR, "Invalid Facebook Application ID, is it set in your res/values/strings.xml?")); + return; + } + + isReady = true; + } + + public void login() { + if (isReady) { + Collection permissions = Arrays.asList("public_profile"); + mLoginManager.logInWithReadPermissions(mActivity, permissions); + } + } + + public String getProviderName() { + return PROVIDER_NAME; + } + + public Firebase getFirebaseRef() {return mRef; } + + public void logout() { + mLoginManager.logOut(); + } +} diff --git a/library/src/main/java/com/firebase/ui/auth/FirebaseAuthHelper.java b/library/src/main/java/com/firebase/ui/auth/FirebaseAuthHelper.java new file mode 100644 index 000000000..6a7abebde --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/FirebaseAuthHelper.java @@ -0,0 +1,55 @@ +package com.firebase.ui.auth; + +import android.util.Log; + +import com.firebase.client.AuthData; +import com.firebase.client.Firebase; +import com.firebase.client.FirebaseError; + +import java.util.HashMap; +import java.util.Map; + +public abstract class FirebaseAuthHelper { + public abstract void logout(); + public abstract String getProviderName(); + public abstract Firebase getFirebaseRef(); + + public void login() { + Log.d("FirebaseAuthHelper", "Login() is not supported for provider type " + getProviderName()); + }; + public void login(String email, String password) { + Log.d("FirebaseAuthHelper", "Login(String email, String password) is not supported for provider type " + getProviderName()); + }; + + public void onFirebaseTokenReceived(FirebaseOAuthToken token, TokenAuthHandler handler) { + authenticateRefWithOAuthFirebasetoken(token, handler); + } + + private void authenticateRefWithOAuthFirebasetoken(FirebaseOAuthToken token, final TokenAuthHandler handler) { + Firebase.AuthResultHandler authResultHandler = new Firebase.AuthResultHandler() { + @Override + public void onAuthenticated(AuthData authData) { + // Do nothing. Auth updates are handled in the AuthStateListener + handler.onSuccess(authData); + } + + @Override + public void onAuthenticationError(FirebaseError firebaseError) { + handler.onProviderError(new FirebaseError(0, "Make sure " + getProviderName() + " login is enabled and configured in your Firebase.")); + } + }; + + if (token.mode == FirebaseOAuthToken.SIMPLE) { + // Simple mode is used for Facebook and Google auth + getFirebaseRef().authWithOAuthToken(token.provider, token.token, authResultHandler); + } else if (token.mode == FirebaseOAuthToken.COMPLEX) { + // Complex mode is used for Twitter auth + Map options = new HashMap<>(); + options.put("oauth_token", token.token); + options.put("oauth_token_secret", token.secret); + options.put("user_id", token.uid); + + getFirebaseRef().authWithOAuthToken(token.provider, options, authResultHandler); + } + } +} diff --git a/library/src/main/java/com/firebase/ui/auth/FirebaseOAuthToken.java b/library/src/main/java/com/firebase/ui/auth/FirebaseOAuthToken.java new file mode 100644 index 000000000..eb2316a09 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/FirebaseOAuthToken.java @@ -0,0 +1,26 @@ +package com.firebase.ui.auth; + +public class FirebaseOAuthToken { + public String token; + public String secret; + public String uid; + public String provider; + public int mode; + + public static final int SIMPLE = 1; + public static final int COMPLEX = 2; + + public FirebaseOAuthToken(String provider, String token) { + this.provider = provider; + this.token = token; + this.mode = SIMPLE; + } + + public FirebaseOAuthToken(String provider, String token, String secret, String uid) { + this.provider = provider; + this.token = token; + this.secret = secret; + this.uid = uid; + this.mode = COMPLEX; + } +} diff --git a/library/src/main/java/com/firebase/ui/auth/FirebaseStatuses.java b/library/src/main/java/com/firebase/ui/auth/FirebaseStatuses.java new file mode 100644 index 000000000..5e350f881 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/FirebaseStatuses.java @@ -0,0 +1,12 @@ +package com.firebase.ui.auth; + +/** + * Created by abehaskins on 11/9/15. + */ +public class FirebaseStatuses { + public static final int LOGIN = 0; + public static final int USER_ERROR = 1; + public static final int PROVIDER_ERROR = 2; + public static final int SUCCESS = 3; +} + diff --git a/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/GoogleAuthHelper.java b/library/src/main/java/com/firebase/ui/auth/GoogleAuthHelper.java similarity index 85% rename from library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/GoogleAuthHelper.java rename to library/src/main/java/com/firebase/ui/auth/GoogleAuthHelper.java index f9ec29f88..84fb0d374 100644 --- a/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/GoogleAuthHelper.java +++ b/library/src/main/java/com/firebase/ui/auth/GoogleAuthHelper.java @@ -1,4 +1,4 @@ -package com.firebase.ui.com.firebasei.ui.authimpl; +package com.firebase.ui.auth; import android.app.Activity; import android.content.Context; @@ -6,6 +6,7 @@ import android.os.Bundle; import android.util.Log; +import com.firebase.client.Firebase; import com.google.android.gms.auth.GoogleAuthException; import com.google.android.gms.auth.UserRecoverableAuthException; import com.google.android.gms.common.ConnectionResult; @@ -16,19 +17,17 @@ import java.io.IOException; -/** - * - */ -public class GoogleAuthHelper implements +public class GoogleAuthHelper extends FirebaseAuthHelper implements GoogleApiClient.ConnectionCallbacks, - GoogleApiClient.OnConnectionFailedListener { + GoogleApiClient.OnConnectionFailedListener{ private final String LOG_TAG = "GoogleAuthHelper"; - public final String PROVIDER_NAME = "google"; + public static final String PROVIDER_NAME = "google"; /* User provided callback class */ private TokenAuthHandler mHandler; + private Firebase mRef; /* Request code used to invoke sign in user interactions. */ private static final int RC_GOOGLE_SIGN_IN = 0; @@ -53,7 +52,7 @@ public class GoogleAuthHelper implements private GoogleTokenTask mGoogleTokenTask; - public GoogleAuthHelper(Context context, TokenAuthHandler handler) { + public GoogleAuthHelper(Context context, Firebase ref, TokenAuthHandler handler) { // Builder API mGoogleApiClient = new GoogleApiClient.Builder(context) .addConnectionCallbacks(this) @@ -65,6 +64,8 @@ public GoogleAuthHelper(Context context, TokenAuthHandler handler) { // Activity Context mContext = context; + mRef = ref; + // User defined callbacks mHandler = handler; @@ -73,22 +74,25 @@ public GoogleAuthHelper(Context context, TokenAuthHandler handler) { mGoogleTokenTask = new GoogleTokenTask(mContext, mGoogleApiClient, new GoogleTokenHandler() { @Override public void onTokenReceived(String token) { - mHandler.onTokenReceived(token); + FirebaseOAuthToken foToken = new FirebaseOAuthToken( + PROVIDER_NAME, + token); + onFirebaseTokenReceived(foToken, mHandler); } @Override public void onUserRecoverableAuthException(UserRecoverableAuthException ex) { - mHandler.onError(ex); + //mHandler.onError(ex); } @Override public void onGoogleAuthException(GoogleAuthException ex) { - mHandler.onError(ex); + //mHandler.onError(ex); } @Override public void onIOException(IOException ex) { - mHandler.onError(ex); + //mHandler.onError(ex); } }); } @@ -110,6 +114,8 @@ public void login() { public void logout() { mGoogleApiClient.disconnect(); } + public String getProviderName() { return PROVIDER_NAME; } + public Firebase getFirebaseRef() { return mRef; } @Override public void onConnected(Bundle bundle) { diff --git a/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/GoogleTokenHandler.java b/library/src/main/java/com/firebase/ui/auth/GoogleTokenHandler.java similarity index 88% rename from library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/GoogleTokenHandler.java rename to library/src/main/java/com/firebase/ui/auth/GoogleTokenHandler.java index 5cb24b197..6c965cdbd 100644 --- a/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/GoogleTokenHandler.java +++ b/library/src/main/java/com/firebase/ui/auth/GoogleTokenHandler.java @@ -1,4 +1,4 @@ -package com.firebase.ui.com.firebasei.ui.authimpl; +package com.firebase.ui.auth; import com.google.android.gms.auth.GoogleAuthException; import com.google.android.gms.auth.UserRecoverableAuthException; diff --git a/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/GoogleTokenTask.java b/library/src/main/java/com/firebase/ui/auth/GoogleTokenTask.java similarity index 96% rename from library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/GoogleTokenTask.java rename to library/src/main/java/com/firebase/ui/auth/GoogleTokenTask.java index 9ebb68647..0822f3c23 100644 --- a/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/GoogleTokenTask.java +++ b/library/src/main/java/com/firebase/ui/auth/GoogleTokenTask.java @@ -1,4 +1,4 @@ -package com.firebase.ui.com.firebasei.ui.authimpl; +package com.firebase.ui.auth; import android.content.Context; import android.os.AsyncTask; @@ -11,12 +11,8 @@ import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.plus.Plus; - import java.io.IOException; -/** - * Created by deast on 9/25/15. - */ public class GoogleTokenTask extends AsyncTask { private final String LOG_TAG = "GoogleTokenTask"; diff --git a/library/src/main/java/com/firebase/ui/auth/PasswordAuthHelper.java b/library/src/main/java/com/firebase/ui/auth/PasswordAuthHelper.java new file mode 100644 index 000000000..f4bdccf6f --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/PasswordAuthHelper.java @@ -0,0 +1,42 @@ +package com.firebase.ui.auth; + +import android.app.Activity; +import android.content.Context; + +import com.firebase.client.AuthData; +import com.firebase.client.Firebase; +import com.firebase.client.FirebaseError; + +public class PasswordAuthHelper extends FirebaseAuthHelper { + + private final String LOG_TAG = "PasswordAuthHelper"; + + public final static String PROVIDER_NAME = "password"; + + private TokenAuthHandler mHandler; + private Activity mActivity; + private Firebase mRef; + + public PasswordAuthHelper(Context context, Firebase ref, TokenAuthHandler handler) { + mActivity = (Activity) context; + mRef = ref; + mHandler = handler; + } + + public void login(String email, String password) { + mRef.authWithPassword(email, password, new Firebase.AuthResultHandler() { + @Override + public void onAuthenticated(AuthData authData) { + mHandler.onSuccess(authData); + } + @Override + public void onAuthenticationError(FirebaseError firebaseError) { + mHandler.onUserError(firebaseError); + } + }); + } + + public String getProviderName() { return PROVIDER_NAME; } + public Firebase getFirebaseRef() { return mRef; } + public void logout() {} +} diff --git a/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/SocialProvider.java b/library/src/main/java/com/firebase/ui/auth/SocialProvider.java similarity index 74% rename from library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/SocialProvider.java rename to library/src/main/java/com/firebase/ui/auth/SocialProvider.java index d39c0cf84..ad21a2074 100644 --- a/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/SocialProvider.java +++ b/library/src/main/java/com/firebase/ui/auth/SocialProvider.java @@ -1,8 +1,9 @@ -package com.firebase.ui.com.firebasei.ui.authimpl; +package com.firebase.ui.auth; // Note: these enum values must be lowercase, to ensure they match the casing used by the Firebase Authentication providers public enum SocialProvider { google, facebook, - twitter + twitter, + password } diff --git a/library/src/main/java/com/firebase/ui/auth/TokenAuthHandler.java b/library/src/main/java/com/firebase/ui/auth/TokenAuthHandler.java new file mode 100644 index 000000000..b1b031553 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/TokenAuthHandler.java @@ -0,0 +1,10 @@ +package com.firebase.ui.auth; + +import com.firebase.client.AuthData; +import com.firebase.client.FirebaseError; + +public interface TokenAuthHandler { + void onSuccess(AuthData auth); + void onUserError(FirebaseError err); + void onProviderError(FirebaseError err); +} \ No newline at end of file diff --git a/library/src/main/java/com/firebase/ui/auth/TwitterAuthHelper.java b/library/src/main/java/com/firebase/ui/auth/TwitterAuthHelper.java new file mode 100644 index 000000000..552f2747c --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/TwitterAuthHelper.java @@ -0,0 +1,50 @@ +package com.firebase.ui.auth; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +import com.firebase.client.Firebase; +import com.firebase.client.FirebaseError; + +public class TwitterAuthHelper extends FirebaseAuthHelper { + + public static final String TAG = "TwitterAuthHelper"; + public static final String PROVIDER_NAME = "twitter"; + + private Activity mActivity; + private TokenAuthHandler mHandler; + private Firebase mRef; + + public TwitterAuthHelper(Context context, Firebase ref, TokenAuthHandler handler) { + mActivity = (Activity) context; + mHandler = handler; + mRef = ref; + } + + public void login() { + mActivity.startActivityForResult(new Intent(mActivity, TwitterPromptActivity.class), FirebaseStatuses.LOGIN); + } + + public void logout() { + // We don't store auth state in this handler, so no need to logout + } + + public String getProviderName() { return PROVIDER_NAME; } + public Firebase getFirebaseRef() { return mRef; } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == FirebaseStatuses.SUCCESS) { + FirebaseOAuthToken token = new FirebaseOAuthToken( + PROVIDER_NAME, + data.getStringExtra("oauth_token"), + data.getStringExtra("oauth_token_secret"), + data.getStringExtra("user_id")); + onFirebaseTokenReceived(token, mHandler); + } else if (resultCode == FirebaseStatuses.USER_ERROR) { + mHandler.onUserError(new FirebaseError(0, data.getStringExtra("error"))); + } else if (resultCode == FirebaseStatuses.PROVIDER_ERROR) { + mHandler.onProviderError(new FirebaseError(0, data.getStringExtra("error"))); + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/firebase/ui/auth/TwitterPromptActivity.java b/library/src/main/java/com/firebase/ui/auth/TwitterPromptActivity.java new file mode 100644 index 000000000..07d0271d9 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/TwitterPromptActivity.java @@ -0,0 +1,136 @@ +package com.firebase.ui.auth; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import com.firebase.client.Firebase; + +import twitter4j.Twitter; +import twitter4j.TwitterException; +import twitter4j.TwitterFactory; +import twitter4j.auth.AccessToken; +import twitter4j.auth.RequestToken; +import twitter4j.conf.ConfigurationBuilder; + +public class TwitterPromptActivity extends Activity { + private static final String TAG = TwitterPromptActivity.class.getSimpleName(); + private Twitter mTwitter; + private WebView mTwitterView; + + @Override + public void onBackPressed() { + sendResultError(FirebaseStatuses.USER_ERROR, "User closed login prompt."); + super.onBackPressed(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String twitterKey = ""; + String twitterSecret = ""; + + try { + ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA); + Bundle bundle = ai.metaData; + twitterKey = bundle.getString("com.firebase.ui.TwitterKey"); + twitterSecret = bundle.getString("com.firebase.ui.TwitterSecret"); + } catch (PackageManager.NameNotFoundException e) { + } catch (NullPointerException e) {} + + if (twitterKey == null || twitterSecret == null) { + sendResultError(FirebaseStatuses.PROVIDER_ERROR, "Invalid Twitter key/secret, are they set in your AndroidManifest.xml?"); + return; + } + + if (twitterKey.compareTo("") == 0|| twitterSecret.compareTo("") == 0) { + sendResultError(FirebaseStatuses.PROVIDER_ERROR, "Invalid Twitter key/secret, are they set in your res/values/strings.xml?"); + return; + } + + mTwitter = new TwitterFactory(new ConfigurationBuilder() + .setOAuthConsumerKey(twitterKey) + .setOAuthConsumerSecret(twitterSecret) + .build()).getInstance(); + + // setup ic_twitter webview + mTwitterView = new WebView(this); + mTwitterView.getSettings().setJavaScriptEnabled(true); + + // initialize view + setContentView(mTwitterView); + + // fetch the oauth request token then prompt the user to authorize the application + new AsyncTask() { + @Override + protected RequestToken doInBackground(Void... params) { + RequestToken token = null; + try { + token = mTwitter.getOAuthRequestToken("oauth://cb"); + } catch (TwitterException te) { + sendResultError(FirebaseStatuses.PROVIDER_ERROR, te.toString()); + } + return token; + } + + @Override + protected void onPostExecute(final RequestToken token) { + mTwitterView.setWebViewClient(new WebViewClient() { + @Override + public void onPageFinished(final WebView view, final String url) { + if (url.startsWith("oauth://cb")) { + mTwitterView.destroy(); + if (url.contains("oauth_verifier")) { + getTwitterOAuthTokenAndLogin(token, Uri.parse(url).getQueryParameter("oauth_verifier")); + } else if (url.contains("denied")) { + sendResultError(FirebaseStatuses.USER_ERROR, "User denied access to their account."); + } + } + } + }); + mTwitterView.loadUrl(token.getAuthorizationURL()); + } + }.execute(); + } + + private void getTwitterOAuthTokenAndLogin(final RequestToken requestToken, final String oauthVerifier) { + new AsyncTask() { + @Override + protected AccessToken doInBackground(Void... params) { + AccessToken accessToken = null; + try { + accessToken = mTwitter.getOAuthAccessToken(requestToken, oauthVerifier); + } catch (TwitterException te) { + sendResultError(FirebaseStatuses.PROVIDER_ERROR, te.toString()); + } + return accessToken; + } + + @Override + protected void onPostExecute(AccessToken token) { + Intent resultIntent = new Intent(); + resultIntent.putExtra("oauth_token", token.getToken()); + resultIntent.putExtra("oauth_token_secret", token.getTokenSecret()); + resultIntent.putExtra("user_id", token.getUserId() + ""); + + setResult(FirebaseStatuses.SUCCESS, resultIntent); + finish(); + } + }.execute(); + } + + private void sendResultError(Integer status, String err) { + Intent resultIntent = new Intent(); + resultIntent.putExtra("error", err); + setResult(status, resultIntent); + finish(); + } +} \ No newline at end of file diff --git a/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/TokenAuthHandler.java b/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/TokenAuthHandler.java deleted file mode 100644 index 2e53f0bdc..000000000 --- a/library/src/main/java/com/firebase/ui/com/firebasei/ui/authimpl/TokenAuthHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.firebase.ui.com.firebasei.ui.authimpl; - -/** - * Created by deast on 9/25/15. - */ -public interface TokenAuthHandler { - void onTokenReceived(String token); - void onCancelled(); - void onError(Exception ex); -} \ No newline at end of file diff --git a/library/src/main/res/drawable/facebook_button.xml b/library/src/main/res/drawable/facebook_button.xml new file mode 100644 index 000000000..517905ecf --- /dev/null +++ b/library/src/main/res/drawable/facebook_button.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable/google_button.xml b/library/src/main/res/drawable/google_button.xml new file mode 100644 index 000000000..70623d09c --- /dev/null +++ b/library/src/main/res/drawable/google_button.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable/ic_facebook.xml b/library/src/main/res/drawable/ic_facebook.xml new file mode 100644 index 000000000..4755f532c --- /dev/null +++ b/library/src/main/res/drawable/ic_facebook.xml @@ -0,0 +1,5 @@ + + + + diff --git a/library/src/main/res/drawable/ic_google.xml b/library/src/main/res/drawable/ic_google.xml new file mode 100644 index 000000000..e3c2f08f8 --- /dev/null +++ b/library/src/main/res/drawable/ic_google.xml @@ -0,0 +1,5 @@ + + + + diff --git a/library/src/main/res/drawable/ic_twitter.xml b/library/src/main/res/drawable/ic_twitter.xml new file mode 100644 index 000000000..75fa319f1 --- /dev/null +++ b/library/src/main/res/drawable/ic_twitter.xml @@ -0,0 +1,5 @@ + + + + diff --git a/library/src/main/res/drawable/or_line.xml b/library/src/main/res/drawable/or_line.xml new file mode 100644 index 000000000..7bbd8f212 --- /dev/null +++ b/library/src/main/res/drawable/or_line.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable/password_button.xml b/library/src/main/res/drawable/password_button.xml new file mode 100644 index 000000000..bfbf96c9b --- /dev/null +++ b/library/src/main/res/drawable/password_button.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable/twitter_button.xml b/library/src/main/res/drawable/twitter_button.xml new file mode 100644 index 000000000..c7b9fb1b3 --- /dev/null +++ b/library/src/main/res/drawable/twitter_button.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/layout/fragment_firebase_login.xml b/library/src/main/res/layout/fragment_firebase_login.xml new file mode 100644 index 000000000..df60ff87d --- /dev/null +++ b/library/src/main/res/layout/fragment_firebase_login.xml @@ -0,0 +1,155 @@ + + + + + + + + + + +