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/CONTRIBUTORS b/CONTRIBUTORS index 89d4bf813..c57796de8 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -10,3 +10,6 @@ # Name Frank van Puffelen +Abraham Haskins +David East +Mike McDonald diff --git a/FirebaseUI-Android.iml b/FirebaseUI-Android.iml deleted file mode 100644 index 4e94ae244..000000000 --- a/FirebaseUI-Android.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index dc977e355..364a45524 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,18 @@ # FirebaseUI for Android — UI Bindings for Firebase -FirebaseUI is an open-source library for Android that allows you to quickly connect common UI elements to the [Firebase](https://www.firebase.com) database for data storage, allowing views to be updated in realtime as they change, and providing simple interfaces for common tasks like displaying lists or collections of items. +FirebaseUI is an open-source library for Android that allows you to quickly connect common UI elements to the [Firebase](https://www.firebase.com) database for data storage, allowing views to be updated in realtime as they change, providing simple interfaces for common tasks like displaying lists or collections of items, and displaying prompts for user authentication. A compatible FirebaseUI client is also available for [iOS](https://github.com/firebase/firebaseui-ios). -## Using the library in your Android app +## Table of Content -To use the FirebaseUI library in our project, we need to do a few things: +1. [Installation](#installation) +2. [Using FirebaseUI for Authentication](#using-firebaseui-for-authentication) +3. [Using FirebaseUI to Populate a ListView](#using-firebaseui-to-populate-a-listview) +4. [Using FirebaseUI to Populate a RecyclerView](#using-firebaseui-to-populate-a-recyclerview) +5. [Contributing](#contributing) -1. Add the library to the list of dependencies of our project -2. Create a class to represent the properties of our objects, as they are stored into the database -3. Create a custom list adapter to map from Firebase to Android - -The FirebaseUI library is most prominent in step 3. But first we have to add it to our project. - -### Adding the library to your project +## Installation If your Android app already uses Firebase, you have added a dependency to the Firebase SDK to your dependencies. In this step we'll add the FirebaseUI library as another dependency. @@ -26,10 +24,193 @@ In this step we'll add the FirebaseUI library as another dependency. You can also add the library dependency directly to your app's gradle.build file: -![Added to gradle.build](doc-images/5-gradle-dependency-added.png "Added to gradle.build") +``` +dependencies { + compile 'com.firebaseui:firebase-ui:0.2.2' +} +``` After the project is synchronized, we're ready to start using Firebase functionality in our app. +## Using FirebaseUI for Authentication + +FirebaseUI has a built-in dialog that you can use pop up to allow your users to log in. + +![FirebaseUI Login Dialog](doc-images/mdialog.png "FirebaseUI Login Dialog") + +To use FirebaseUI to authenticate users we need to do a few things: + +1. Add SDK dependencies +2. Add Facebook/Twitter/Google keys to strings.xml +3. Change our AndroidManifest.xml +4. Inherit from FirebaseLoginBaseActivity +5. Enable authentication providers +6. Call showFirebaseLoginDialog(); + +We'll go into each of these steps below. + +### Add SDK dependencies + +Since FirebaseUI depends on the SDKs of various providers, we'll need to include those in our depedencies as well. + +``` +dependencies { + ... + compile 'com.facebook.android:facebook-android-sdk:4.6.0' + compile 'com.google.android.gms:play-services-auth:8.3.0' + compile 'org.twitter4j:twitter4j-core:4.0.2' +} +``` + +### Add Facebook/Twitter/Google keys to strings.xml + +Open your `res/values/strings.xml` file and add the following lines, replacing `[VALUE]` with your key. + +Keep in mind, these are all optional. You only have to provide values for the providers you plan to use. + + [`FirebaseLoginBaseActivity.java`](library/src/main/java/com/firebase/ui/auth/core/FirebaseLoginBaseActivity.java). + +```xml +[VALUE] +[VALUE] +[VALUE] +[VALUE] +``` + +### Change our AndroidManifest.xml + +Open your `manifests/AndroidManifest.xml` file. This will allow Android to recognize the various activities that FirebaseUI exposes. + +First though, double check that you've requested the `INTERNET` permission in your `` tag. + +```xml + +``` + +If you're using Twitter authentication, add the following to your `` tag. + +```xml + + + + +``` + +If you're using Facebook authentication, add the following to your `` tag. + +```xml + + + +``` + +If you're using Google authentication, add the following to your `` tag. + +```xml + + + ``` + +**Note:** If you're using Google Sign-in you'll also need to ensure that your `google-services.json` file is created +and placed in your app folder. + +### Inherit from FirebaseLoginBaseActivity + +Now we get to the juicy bits. Open your `MainActivity` and change your activity to extend `FirebaseLoginBaseActivity` + +```java +public class MainActivity extends FirebaseLoginBaseActivity { + ... +} +``` + +This activity should implement a few methods so your application can react to changing authentication state. + +```java +public class MainActivity extends FirebaseLoginBaseActivity { + ... + @Override + public Firebase getFirebaseRef() { + // TODO: Return your Firebase ref + } + + @Override + public void onFirebaseLoggedIn(AuthData authData) { + // TODO: Handle successful login + } + + @Override + public void onFirebaseLoggedOut() { + // TODO: Handle logout + } + + @Override + public void onFirebaseLoginProviderError(FirebaseLoginError firebaseError) { + // TODO: Handle an error from the authentication provider + } + + @Override + public void onFirebaseLoginUserError(FirebaseLoginError firebaseError) { + // TODO: Handle an error from the user + } +} +``` + +### Enable Authentication Providers + +Now that our activity is set up, we can enable authentication providers. The FirebaseUI login prompt will only display providers you enable here, so don't worry if you don't want to use them all! + +```java +public class MainActivity extends FirebaseLoginBaseActivity { + ... + @Override + protected void onStart() { + super.onStart(); + // All providers are optional! Remove any you don't want. + setEnabledAuthProvider(SocialProvider.facebook); + setEnabledAuthProvider(SocialProvider.twitter); + setEnabledAuthProvider(SocialProvider.google); + setEnabledAuthProvider(SocialProvider.password); + } +``` + + +### Call showFirebaseLoginDialog(); + +You're now ready to display the login dialog! + +Simply call `showFirebaseLoginPrompt()` from within your activity and you'll see the dialog! + +For example, if we had a mLoginButton, we could display the dialog when the user clicks the button. +```java +mLoginButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showFirebaseLoginPrompt(); + } +}); +``` + + +## Using FirebaseUI to Populate a ListView + +To use the FirebaseUI to display Firebase data, we need to do a few things: + +1. Create a class to represent the properties of our objects, as they are stored into the database +2. Create a custom list adapter to map from Firebase to Android + ### Creating a model class In your app, create a class that represents the data from Firebase that you want to show in the ListView. @@ -40,27 +221,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 +252,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. @@ -99,59 +283,63 @@ then read the properties that it got from the database and map them to the field But when we build our app using FirebaseUI, we often won't need to register our own EventListener. The `FirebaseListAdapter` takes care of that for us. -### Subclassing the FirebaseListAdapter - -#### Look up the ListView +### Find the ListView 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 +### Connect 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 +### 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 @@ -166,63 +354,67 @@ It passes us the `ChatMessage` and a `View`, which is an instance of the `androi we specified in the constructor. So what we do in our subclass is map the fields from `chatMessage` to the correct `TextView` controls from the `view`. The code is a bit verbose, but hey... that's Java and Android for you. -#### Clean up when the activity is destroyed +### Clean up When the Activity is Destroyed 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 +### Send 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"); -Et voila: a minimal, yet fully functional, chat app in about 30 lines of code. Not bad, right? + 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); -## Using a RecyclerView + 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(); +} +``` + +You're done! You now have a minimal, yet fully functional, chat app in about 30 lines of code. Not bad, right? + +## Using FirebaseUI to Populate a RecyclerView RecyclerView is the new preferred way to handle potentially long lists of items. Since Firebase collections -can contain many items, there is an `FirebaseRecyclerViewAdapter` too. Here's how you use it: +can contain many items, there is an `FirebaseRecyclerAdapter` too. Here's how you use it: 1. Create a custom ViewHolder class 2. Create a custom subclass FirebaseListAdapter @@ -235,50 +427,54 @@ 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. -### Create a custom FirebaseRecyclerAdapter +### Create a custom FirebaseListAdapter 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)); +```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); +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. -## Installing locally +## Contributing -You can get the latest released version of FirebaseUI from Maven Central. +### Installing locally -Alternatively, you can download the latest release from the Releases tab on the Github repo and install it into your local Maven repository with: +We are still working on deploying FirebaseUI to Maven Central. In the meantime, you can download the +latest release from the Releases tab on the Github repo and install it into your local Maven repository +with: mvn install:install-file -Dfile=/path/to/library-debug.aar -DgroupId=com.firebase -DartifactId=firebase-ui -Dversion=0.1.0 -Dpackaging=aar - -## Deployment - -### To get the build server ready to build FirebaseUI-Android +### Deployment +To get the build server ready to build FirebaseUI-Android * Install a JDK (if it's not installed yet): * `sudo apt-get install default-jdk` @@ -311,7 +507,7 @@ Alternatively, you can download the latest release from the Releases tab on the sonatypeUsername=YourSonatypeJiraUsername sonatypePassword=YourSonatypeJiraPassword -### to build a release +## to build a release * build the project in Android Studio or with Gradle * this generates the main binary: `library/build/outputs/aar/library-debug.aar` @@ -320,30 +516,20 @@ Alternatively, you can download the latest release from the Releases tab on the * this generates the javadoc: `library/build/outputs/library-javadoc.jar` -### to deploy a build to your local maven repo - -``` -mvn install:install-file -Dfile=/path/to/library-debug.aar -DgroupId=com.firebaseui -DartifactId=firebase-ui -Dversion=0.1.0 -Dpackaging=aar -``` - -Don't forget to update the path and the version number in the command. - -### to tag a release on Github +## to tag a release on Github * ensure that all your changes are on master and that your local build is on master * ensure that the correct version number is in both `library/build.gradle` and `library/pom.xml` -### to deploy a release to Maven Central +## to deploy a release to Maven Central * log onto the build box * checkout and update the master branch * `./release.sh` to build the library and update maven * close/release the repository from sonatype -## Contributing to FirebaseUI - -### Contributor License Agreements +## Contributor License Agreements We'd love to accept your sample apps and patches! Before we can take them, we have to jump a couple of legal hurdles. @@ -362,7 +548,7 @@ Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. -### Contribution Process +## Contribution Process 1. Submit an issue describing your proposed change to the repo in question. 1. The repo owner will respond to your issue promptly. diff --git a/app/app.iml b/app/app.iml deleted file mode 100644 index eaee690ac..000000000 --- a/app/app.iml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 12e3781e8..d1b57835f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'com.google.gms.google-services' android { compileSdkVersion 22 @@ -6,7 +7,7 @@ android { defaultConfig { applicationId "com.firebase.uidemo" - minSdkVersion 10 + minSdkVersion 16 targetSdkVersion 22 versionCode 1 versionName "1.0" @@ -28,8 +29,9 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.2.0' + compile 'com.google.android.gms:play-services-auth:8.3.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 09e3aab0b..9175ab527 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,32 @@ + + + + + + + + + + + + + + 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 3d2552e26..3d420fd8e 100644 --- a/app/src/main/java/com/firebase/uidemo/RecyclerViewDemoActivity.java +++ b/app/src/main/java/com/firebase/uidemo/RecyclerViewDemoActivity.java @@ -1,102 +1,246 @@ package com.firebase.uidemo; -import android.graphics.Color; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; 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.RelativeLayout; import android.widget.TextView; +import com.firebase.client.AuthData; import com.firebase.client.Firebase; import com.firebase.client.FirebaseError; -import com.firebase.ui.FirebaseRecyclerViewAdapter; +import com.firebase.client.Query; +import com.firebase.ui.auth.core.FirebaseLoginBaseActivity; +import com.firebase.ui.FirebaseRecyclerAdapter; +import com.firebase.ui.auth.core.FirebaseLoginError; +import com.firebase.ui.auth.core.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; + private FirebaseRecyclerAdapter mRecycleViewAdapter; - -public class RecyclerViewDemoActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.recycler_view_demo); - final Firebase ref = new Firebase("https://firebaseui.firebaseio.com/chat"); + mSendButton = (Button) findViewById(R.id.sendButton); + mMessageEdit = (EditText) findViewById(R.id.messageEdit); - final String name = "Android User"; - final Button sendButton = (Button) findViewById(R.id.sendButton); - final EditText messageEdit = (EditText) findViewById(R.id.messageEdit); - final RecyclerView messages = (RecyclerView) findViewById(R.id.messagesList); - messages.setHasFixedSize(true); - messages.setLayoutManager(new LinearLayoutManager(this)); + mSendButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showFirebaseLoginPrompt(); + } + }); - sendButton.setOnClickListener(new View.OnClickListener() { + 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, messageEdit.getText().toString()); - ref.push().setValue(chat, new Firebase.CompletionListener() { - @Override - public void onComplete(FirebaseError firebaseError, Firebase firebase) { - if (firebaseError != null) { - Log.e("FirebaseUI.chat", 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()); } - }); - messageEdit.setText(""); + } + }); + mMessageEdit.setText(""); } }); - FirebaseRecyclerViewAdapter adapter = new FirebaseRecyclerViewAdapter(Chat.class, android.R.layout.two_line_list_item, ChatHolder.class, ref) { + mMessages = (RecyclerView) findViewById(R.id.messagesList); + + LinearLayoutManager manager = new LinearLayoutManager(this); + manager.setReverseLayout(false); + + mMessages.setHasFixedSize(false); + mMessages.setLayoutManager(manager); + + mRecycleViewAdapter = new FirebaseRecyclerAdapter(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())) { + chatView.setIsSender(true); } else { - chatView.nameView.setTextColor(Color.parseColor("#00BCD4")); + chatView.setIsSender(false); } } }; - messages.setAdapter(adapter); + mMessages.setAdapter(mRecycleViewAdapter); } + @Override + protected void onStart() { + super.onStart(); +// setEnabledAuthProvider(SocialProvider.facebook); +// setEnabledAuthProvider(SocialProvider.twitter); +// setEnabledAuthProvider(SocialProvider.google); + setEnabledAuthProvider(SocialProvider.password); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.chat_login_menu, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + 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 true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.login_menu_item: + this.showFirebaseLoginPrompt(); + return true; + case R.id.logout_menu_item: + this.logout(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onFirebaseLoggedIn(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; + } + + invalidateOptionsMenu(); + mRecycleViewAdapter.notifyDataSetChanged(); + } + + @Override + public void onFirebaseLoggedOut() { + Log.i(TAG, "Logged out"); + mAuthData = null; + name = ""; + invalidateOptionsMenu(); + mRecycleViewAdapter.notifyDataSetChanged(); + } + + @Override + public void onFirebaseLoginProviderError(FirebaseLoginError firebaseError) { + Log.i(TAG, "Login provider error: " + firebaseError.toString()); + } + + @Override + public void onFirebaseLoginUserError(FirebaseLoginError firebaseError) { + resetFirebaseLoginDialog(); + Log.i(TAG, "Login user error: " + firebaseError.toString()); + } + + @Override + public Firebase getFirebaseRef() { + return mRef; + } 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 setIsSender(Boolean isSender) { + FrameLayout left_arrow = (FrameLayout) mView.findViewById(R.id.left_arrow); + FrameLayout right_arrow = (FrameLayout) mView.findViewById(R.id.right_arrow); + RelativeLayout messageContainer = (RelativeLayout) mView.findViewById(R.id.message_container); + LinearLayout message = (LinearLayout) mView.findViewById(R.id.message); + + + if (isSender) { + left_arrow.setVisibility(View.GONE); + right_arrow.setVisibility(View.VISIBLE); + messageContainer.setGravity(Gravity.RIGHT); + } else { + left_arrow.setVisibility(View.VISIBLE); + right_arrow.setVisibility(View.GONE); + messageContainer.setGravity(Gravity.LEFT); + } + } + + 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..5bda81872 --- /dev/null +++ b/app/src/main/res/layout/message.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + \ 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 9902fc9ac..7df9fa7d9 100644 --- a/app/src/main/res/layout/recycler_view_demo.xml +++ b/app/src/main/res/layout/recycler_view_demo.xml @@ -7,13 +7,13 @@ tools:context=".RecyclerViewDemoActivity"> + android:layout_above="@+id/footer" /> + android:id="@+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 9405f3fd1..9e3388ae7 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:1.2.3' - + classpath 'com.google.gms:google-services:1.5.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -15,5 +15,6 @@ buildscript { allprojects { repositories { jcenter() + mavenCentral() } } diff --git a/doc-images/mdialog.png b/doc-images/mdialog.png new file mode 100644 index 000000000..bc71d54f2 Binary files /dev/null and b/doc-images/mdialog.png differ diff --git a/library/build.gradle b/library/build.gradle index 94ca68629..d185a2986 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,14 +1,15 @@ apply plugin: 'com.android.library' +apply plugin: 'com.google.gms.google-services' android { compileSdkVersion 22 buildToolsVersion "22.0.1" defaultConfig { - minSdkVersion 10 + minSdkVersion 16 targetSdkVersion 22 versionCode 1 - versionName "0.2.2" + versionName "0.3.0" } buildTypes { release { @@ -44,5 +45,8 @@ dependencies { compile 'com.android.support:appcompat-v7:22.2.0' compile 'com.firebase:firebase-client-android:2.3.1' compile 'com.android.support:recyclerview-v7:22.2.0' + compile 'com.google.android.gms:play-services-auth:8.3.0' + compile 'com.facebook.android:facebook-android-sdk:4.6.0' + compile 'org.twitter4j:twitter4j-core:4.0.2' androidTestCompile 'junit:junit:4.12' } diff --git a/library/google-services.json b/library/google-services.json new file mode 100644 index 000000000..e8e5fc2fd --- /dev/null +++ b/library/google-services.json @@ -0,0 +1,41 @@ +{ + "project_info": { + "project_id": "", + "project_number": "", + "name": "" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "", + "client_id": "", + "client_type": 1, + "android_client_info": { + "package_name": "" + } + }, + "oauth_client": [ + { + "client_id": "", + "client_type": 1, + "android_info": { + "package_name": "", + "certificate_hash": "" + } + } + ], + "api_key": [ + { + "current_key": "" + } + ], + "services": { + + } + } + ], + "client_info": [ + + ], + "ARTIFACT_VERSION": "1" +} diff --git a/library/library.iml b/library/library.iml deleted file mode 100644 index 4946a11d0..000000000 --- a/library/library.iml +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/library/pom.xml b/library/pom.xml index 94fb0e88f..bf36e0053 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -6,7 +6,7 @@ FirebaseUI-Android FirebaseUI library for Android applications https://github.com/firebase/FirebaseUI-Android - 0.2.2 + 0.3.0 aar scm:git@github.com/firebase/FirebaseUI-Android diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index 69af9e67b..c7c992473 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -1,9 +1,4 @@ - - - - - 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/FirebaseListAdapter.java b/library/src/main/java/com/firebase/ui/FirebaseListAdapter.java index 269798135..1d214aefb 100644 --- a/library/src/main/java/com/firebase/ui/FirebaseListAdapter.java +++ b/library/src/main/java/com/firebase/ui/FirebaseListAdapter.java @@ -162,6 +162,7 @@ protected void populateView(View v, T model, int position) { * @param v The view to populate * @param model The object containing the data used to populate the view */ + @Deprecated protected void populateView(View v, T model) { } diff --git a/library/src/main/java/com/firebase/ui/FirebaseRecyclerViewAdapter.java b/library/src/main/java/com/firebase/ui/FirebaseRecyclerAdapter.java similarity index 92% rename from library/src/main/java/com/firebase/ui/FirebaseRecyclerViewAdapter.java rename to library/src/main/java/com/firebase/ui/FirebaseRecyclerAdapter.java index 4d8e7b6bb..4706279db 100644 --- a/library/src/main/java/com/firebase/ui/FirebaseRecyclerViewAdapter.java +++ b/library/src/main/java/com/firebase/ui/FirebaseRecyclerAdapter.java @@ -60,14 +60,14 @@ * } * } * - * FirebaseRecyclerViewAdapter adapter; + * FirebaseRecyclerAdapter adapter; * ref = new Firebase("https://.firebaseio.com"); * * RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler); * recycler.setHasFixedSize(true); * recycler.setLayoutManager(new LinearLayoutManager(this)); * - * adapter = new FirebaseRecyclerViewAdapter(ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, mRef) { + * adapter = new FirebaseRecyclerAdapter(ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, mRef) { * public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder, ChatMessage chatMessage) { * chatMessageViewHolder.nameText.setText(chatMessage.getName()); * chatMessageViewHolder.messageText.setText(chatMessage.getMessage()); @@ -80,7 +80,7 @@ * @param The Java class that maps to the type of objects stored in the Firebase location. * @param The ViewHolder class that contains the Views in the layout that is shown for each object. */ -public abstract class FirebaseRecyclerViewAdapter extends RecyclerView.Adapter { +public abstract class FirebaseRecyclerAdapter extends RecyclerView.Adapter { Class mModelClass; protected int mModelLayout; @@ -95,7 +95,7 @@ public abstract class FirebaseRecyclerViewAdapterlimit(), startAt(), and endAt() */ - public FirebaseRecyclerViewAdapter(Class modelClass, int modelLayout, Class viewHolderClass, Query ref) { + public FirebaseRecyclerAdapter(Class modelClass, int modelLayout, Class viewHolderClass, Query ref) { mModelClass = modelClass; mModelLayout = modelLayout; mViewHolderClass = viewHolderClass; @@ -132,7 +132,7 @@ public void onChanged(EventType type, int index, int oldIndex) { * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, using some * combination of limit(), startAt(), and endAt() */ - public FirebaseRecyclerViewAdapter(Class modelClass, int modelLayout, Class viewHolderClass, Firebase ref) { + public FirebaseRecyclerAdapter(Class modelClass, int modelLayout, Class viewHolderClass, Firebase ref) { this(modelClass, modelLayout, viewHolderClass, (Query) ref); } @@ -186,7 +186,7 @@ public void onBindViewHolder(VH viewHolder, int position) { * this class. The third argument is the item's position in the list. *

* Your implementation should populate the view using the data contained in the model. - * You should implement either this method or the other FirebaseRecyclerViewAdapter#populateViewHolder(VH, Object) method + * You should implement either this method or the other FirebaseRecyclerAdapter#populateViewHolder(VH, Object) method * but not both. * * @param viewHolder The view to populate @@ -199,7 +199,7 @@ protected void populateViewHolder(VH viewHolder, T model, int position) { /** * This is a backwards compatible version of populateViewHolder. *

- * You should implement either this method or the other FirebaseRecyclerViewAdapter#populateViewHolder(VH, T, int) method + * You should implement either this method or the other FirebaseRecyclerAdapter#populateViewHolder(VH, T, int) method * but not both. * * @see FirebaseListAdapter#populateView(View, Object, int) @@ -207,6 +207,7 @@ protected void populateViewHolder(VH viewHolder, T model, int position) { * @param viewHolder The view to populate * @param model The object containing the data used to populate the view */ + @Deprecated protected void populateViewHolder(VH viewHolder, T model) { }; diff --git a/library/src/main/java/com/firebase/ui/auth/core/FirebaseAuthProvider.java b/library/src/main/java/com/firebase/ui/auth/core/FirebaseAuthProvider.java new file mode 100644 index 000000000..7db7eaeec --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/core/FirebaseAuthProvider.java @@ -0,0 +1,55 @@ +package com.firebase.ui.auth.core; + +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 FirebaseAuthProvider { + public abstract void logout(); + public abstract String getProviderName(); + public abstract Firebase getFirebaseRef(); + public abstract SocialProvider getProviderType(); + + public void login() { + Log.d("FirebaseAuthProvider", "Login() is not supported for provider type " + getProviderName()); + }; + public void login(String email, String password) { + Log.d("FirebaseAuthProvider", "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) { + handler.onSuccess(authData); + } + + @Override + public void onAuthenticationError(FirebaseError firebaseError) { + handler.onProviderError(new FirebaseLoginError(FirebaseResponse.PROVIDER_NOT_ENABLED, "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/core/FirebaseLoginBaseActivity.java b/library/src/main/java/com/firebase/ui/auth/core/FirebaseLoginBaseActivity.java new file mode 100644 index 000000000..f7c9ed8da --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/core/FirebaseLoginBaseActivity.java @@ -0,0 +1,138 @@ +package com.firebase.ui.auth.core; + +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; + +import com.firebase.client.AuthData; +import com.firebase.client.Firebase; + +public abstract class FirebaseLoginBaseActivity extends AppCompatActivity { + + private final String TAG = "FirebaseLoginBaseAct"; + + private Firebase.AuthStateListener mAuthStateListener; + private FirebaseLoginDialog mDialog; + private TokenAuthHandler mHandler; + + /** + * Subclasses of this activity may implement this method to handle when a user logs in. + * + * @return void + */ + protected abstract void onFirebaseLoggedIn(AuthData authData); + + /** + * Subclasses of this activity may implement this method to handle when a user logs out. + * + * @return void + */ + protected abstract void onFirebaseLoggedOut(); + + /** + * Subclasses of this activity may implement this method to handle any potential provider errors + * like OAuth or other internal errors. + * + * @return void + */ + protected abstract void onFirebaseLoginProviderError(FirebaseLoginError firebaseError); + + /** + * Subclasses of this activity may implement this method to handle any potential user errors + * like entering an incorrect password or closing the login dialog. + * + * @return void + */ + protected abstract void onFirebaseLoginUserError(FirebaseLoginError firebaseError); + + /** + * Subclasses of this activity must implement this method and return a valid Firebase reference that + * can be used to call authentication related methods on. + * + * @return a Firebase reference that can be used to call authentication related methods on + */ + protected abstract Firebase getFirebaseRef(); + + public void logout() { + mDialog.logout(); + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + mDialog.onActivityResult(requestCode, resultCode, data); + } + + public void showFirebaseLoginPrompt() { + mDialog.show(getFragmentManager(), ""); + } + + public void dismissFirebaseLoginPrompt() { + mDialog.dismiss(); + } + + public void resetFirebaseLoginDialog() { + mDialog.reset(); + } + + public void setEnabledAuthProvider(SocialProvider provider) { + mDialog.setEnabledProvider(provider); + } + + @Override + protected void onStart() { + super.onStart(); + + mHandler = new TokenAuthHandler() { + @Override + public void onSuccess(AuthData data) { + /* onFirebaseLoginSuccess is called by the AuthStateListener below */ + } + + @Override + public void onUserError(FirebaseLoginError err) { + onFirebaseLoginUserError(err); + } + + @Override + public void onProviderError(FirebaseLoginError err) { + onFirebaseLoginProviderError(err); + } + }; + + mAuthStateListener = new Firebase.AuthStateListener() { + @Override + public void onAuthStateChanged(AuthData authData) { + if (authData != null) { + onFirebaseLoggedIn(authData); + } else { + onFirebaseLoggedOut(); + } + } + }; + + mDialog = new FirebaseLoginDialog(); + mDialog + .setContext(this) + .setRef(getFirebaseRef()) + .setHandler(mHandler); + + getFirebaseRef().addAuthStateListener(mAuthStateListener); + } + + protected void onStop() { + super.onStop(); + getFirebaseRef().removeAuthStateListener(mAuthStateListener); + mDialog.cleanUp(); + } + + @Override + protected void onPause() { + super.onPause(); + mDialog.cleanUp(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mDialog.cleanUp(); + } +} diff --git a/library/src/main/java/com/firebase/ui/auth/core/FirebaseLoginDialog.java b/library/src/main/java/com/firebase/ui/auth/core/FirebaseLoginDialog.java new file mode 100644 index 000000000..e19f7494e --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/core/FirebaseLoginDialog.java @@ -0,0 +1,189 @@ +package com.firebase.ui.auth.core; + +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.ui.R; +import com.firebase.ui.auth.facebook.FacebookAuthProvider; +import com.firebase.ui.auth.google.GoogleAuthProvider; +import com.firebase.ui.auth.password.PasswordAuthProvider; +import com.firebase.ui.auth.twitter.TwitterAuthProvider; + +public class FirebaseLoginDialog extends DialogFragment { + + FacebookAuthProvider mFacebookAuthProvider; + TwitterAuthProvider mTwitterAuthProvider; + GoogleAuthProvider mGoogleAuthProvider; + PasswordAuthProvider mPasswordAuthProvider; + TokenAuthHandler mHandler; + SocialProvider mActiveProvider; + Firebase mRef; + Context mContext; + View mView; + + /* + We need to be extra aggressive about building / destroying mGoogleauthProviders so we don't + end up with two clients connected at the same time. + */ + + public void onStop() { + super.onStop(); + cleanUp(); + } + + public void onDestroy() { + super.onDestroy(); + cleanUp(); + } + + @Override + public void onPause() { + super.onPause(); + cleanUp(); + } + + public void cleanUp() { + if (mGoogleAuthProvider != null) mGoogleAuthProvider.cleanUp(); + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mFacebookAuthProvider != null) { + mFacebookAuthProvider.mCallbackManager.onActivityResult(requestCode, resultCode, data); + } + + if (mTwitterAuthProvider != null) { + mTwitterAuthProvider.onActivityResult(requestCode, resultCode, data); + } + + if (mGoogleAuthProvider != null) { + mGoogleAuthProvider.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + LayoutInflater inflater = getActivity().getLayoutInflater(); + + mView = inflater.inflate(R.layout.fragment_firebase_login, null); + + if (mFacebookAuthProvider != null) showLoginOption(mFacebookAuthProvider, R.id.facebook_button); + else mView.findViewById(R.id.facebook_button).setVisibility(View.GONE); + + if (mGoogleAuthProvider != null) showLoginOption(mGoogleAuthProvider, R.id.google_button); + else mView.findViewById(R.id.google_button).setVisibility(View.GONE); + + if (mTwitterAuthProvider != null) showLoginOption(mTwitterAuthProvider, R.id.twitter_button); + else mView.findViewById(R.id.twitter_button).setVisibility(View.GONE); + + if (mPasswordAuthProvider != null) { + showLoginOption(mPasswordAuthProvider, R.id.password_button); + if (mFacebookAuthProvider == null && mGoogleAuthProvider == null && mTwitterAuthProvider == 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 setContext(Context context) { + mContext = context; + return this; + } + + public void reset() { + mView.findViewById(R.id.login_section).setVisibility(View.VISIBLE); + mView.findViewById(R.id.loading_section).setVisibility(View.GONE); + } + + public void logout() { + if (mTwitterAuthProvider != null) mTwitterAuthProvider.logout(); + if (mFacebookAuthProvider != null) mFacebookAuthProvider.logout(); + if (mGoogleAuthProvider != null) mGoogleAuthProvider.logout(); + if (mPasswordAuthProvider != null) mPasswordAuthProvider.logout(); + mRef.unauth(); + } + + public FirebaseLoginDialog setHandler(final TokenAuthHandler handler) { + mHandler = new TokenAuthHandler() { + @Override + public void onSuccess(AuthData auth) { + dismiss(); + handler.onSuccess(auth); + } + + @Override + public void onUserError(FirebaseLoginError err) { + handler.onUserError(err); + } + + @Override + public void onProviderError(FirebaseLoginError err) { + handler.onProviderError(err); + } + }; + return this; + } + + public FirebaseLoginDialog setEnabledProvider(SocialProvider provider) { + switch (provider) { + case facebook: + if (mFacebookAuthProvider == null) + mFacebookAuthProvider = new FacebookAuthProvider(mContext, mRef, mHandler); + break; + case google: + if (mGoogleAuthProvider == null) + mGoogleAuthProvider = new GoogleAuthProvider(mContext, mRef, mHandler); + break; + case twitter: + if (mTwitterAuthProvider == null) + mTwitterAuthProvider = new TwitterAuthProvider(mContext, mRef, mHandler); + break; + case password: + if (mPasswordAuthProvider == null) + mPasswordAuthProvider = new PasswordAuthProvider(mContext, mRef, mHandler); + break; + } + + return this; + } + + private void showLoginOption(final FirebaseAuthProvider helper, int id) { + mView.findViewById(id).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (helper.getProviderType() == SocialProvider.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()); + + passwordText.setText(""); + } else { + helper.login(); + } + mActiveProvider = helper.getProviderType(); + 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/core/FirebaseLoginError.java b/library/src/main/java/com/firebase/ui/auth/core/FirebaseLoginError.java new file mode 100644 index 000000000..7646fa014 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/core/FirebaseLoginError.java @@ -0,0 +1,16 @@ +package com.firebase.ui.auth.core; + +public class FirebaseLoginError { + public String message; + public FirebaseResponse error; + + public FirebaseLoginError(FirebaseResponse error, String message) { + this.message = message; + this.error = error; + } + + public String toString() { + return error.toString() + ": " + message; + } + +} diff --git a/library/src/main/java/com/firebase/ui/auth/core/FirebaseOAuthToken.java b/library/src/main/java/com/firebase/ui/auth/core/FirebaseOAuthToken.java new file mode 100644 index 000000000..351d2f20f --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/core/FirebaseOAuthToken.java @@ -0,0 +1,26 @@ +package com.firebase.ui.auth.core; + +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/core/FirebaseResponse.java b/library/src/main/java/com/firebase/ui/auth/core/FirebaseResponse.java new file mode 100644 index 000000000..e365d2844 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/core/FirebaseResponse.java @@ -0,0 +1,12 @@ +package com.firebase.ui.auth.core; + +public enum FirebaseResponse { + WRONG_CREDENTIALS, + PROVIDER_NOT_ENABLED, + LOGIN_CANCELLED, + MISC_PROVIDER_ERROR, + INVALID_PROVIDER_APP_ID, + INVALID_PROVIDER_APP_KEY, + MISSING_PROVIDER_APP_ID, + MISSING_PROVIDER_APP_KEY +} \ No newline at end of file diff --git a/library/src/main/java/com/firebase/ui/auth/core/SocialProvider.java b/library/src/main/java/com/firebase/ui/auth/core/SocialProvider.java new file mode 100644 index 000000000..d79ddc3f0 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/core/SocialProvider.java @@ -0,0 +1,9 @@ +package com.firebase.ui.auth.core; + +// 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, + password +} diff --git a/library/src/main/java/com/firebase/ui/auth/core/TokenAuthHandler.java b/library/src/main/java/com/firebase/ui/auth/core/TokenAuthHandler.java new file mode 100644 index 000000000..23e96982c --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/core/TokenAuthHandler.java @@ -0,0 +1,10 @@ +package com.firebase.ui.auth.core; + +import com.firebase.client.AuthData; +import com.firebase.client.FirebaseError; + +public interface TokenAuthHandler { + void onSuccess(AuthData auth); + void onUserError(FirebaseLoginError err); + void onProviderError(FirebaseLoginError err); +} \ No newline at end of file diff --git a/library/src/main/java/com/firebase/ui/auth/facebook/FacebookAuthProvider.java b/library/src/main/java/com/firebase/ui/auth/facebook/FacebookAuthProvider.java new file mode 100644 index 000000000..0283339cb --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/facebook/FacebookAuthProvider.java @@ -0,0 +1,114 @@ +package com.firebase.ui.auth.facebook; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +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.ui.auth.core.FirebaseAuthProvider; +import com.firebase.ui.auth.core.FirebaseResponse; +import com.firebase.ui.auth.core.FirebaseLoginError; +import com.firebase.ui.auth.core.FirebaseOAuthToken; +import com.firebase.ui.auth.core.SocialProvider; +import com.firebase.ui.auth.core.TokenAuthHandler; + +import java.util.Arrays; +import java.util.Collection; + +public class FacebookAuthProvider extends FirebaseAuthProvider { + + public static final String PROVIDER_NAME = "facebook"; + public static final SocialProvider PROVIDER_TYPE = SocialProvider.facebook; + private final String TAG = "FacebookAuthProvider"; + public CallbackManager mCallbackManager; + private LoginManager mLoginManager; + private TokenAuthHandler mHandler; + private Activity mActivity; + private Firebase mRef; + private Boolean isReady = false; + + public FacebookAuthProvider(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 FirebaseLoginError(FirebaseResponse.LOGIN_CANCELLED, "User closed login dialog.")); + } + + @Override + public void onError(FacebookException ex) { + mHandler.onProviderError(new FirebaseLoginError(FirebaseResponse.MISC_PROVIDER_ERROR, 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 FirebaseLoginError(FirebaseResponse.MISSING_PROVIDER_APP_ID, "Missing Facebook Application ID, is it set in your AndroidManifest.xml?")); + return; + } + + if (facebookAppId.compareTo("") == 0) { + mHandler.onProviderError(new FirebaseLoginError(FirebaseResponse.INVALID_PROVIDER_APP_ID, "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 void onActivityResult(int requestCode, int resultCode, Intent data) { + mCallbackManager.onActivityResult(requestCode, resultCode, data); + } + + public String getProviderName() { return PROVIDER_NAME; } + public SocialProvider getProviderType() { return PROVIDER_TYPE; }; + public Firebase getFirebaseRef() {return mRef; } + + public void logout() { + mLoginManager.logOut(); + } +} diff --git a/library/src/main/java/com/firebase/ui/auth/google/GoogleActions.java b/library/src/main/java/com/firebase/ui/auth/google/GoogleActions.java new file mode 100644 index 000000000..6d2cfaf21 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/google/GoogleActions.java @@ -0,0 +1,7 @@ +package com.firebase.ui.auth.google; + +public class GoogleActions { + private static int base = 9000; + public static int SIGN_IN = base+0; + public static int SIGN_OUT = base+1; +} diff --git a/library/src/main/java/com/firebase/ui/auth/google/GoogleAuthProvider.java b/library/src/main/java/com/firebase/ui/auth/google/GoogleAuthProvider.java new file mode 100644 index 000000000..c46a5cf30 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/google/GoogleAuthProvider.java @@ -0,0 +1,182 @@ +package com.firebase.ui.auth.google; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.util.Log; + +import com.firebase.client.Firebase; +import com.firebase.ui.auth.core.FirebaseAuthProvider; +import com.firebase.ui.auth.core.FirebaseLoginError; +import com.firebase.ui.auth.core.FirebaseOAuthToken; +import com.firebase.ui.auth.core.FirebaseResponse; +import com.firebase.ui.auth.core.SocialProvider; +import com.firebase.ui.auth.core.TokenAuthHandler; +import com.google.android.gms.auth.api.Auth; +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import com.google.android.gms.auth.api.signin.GoogleSignInResult; +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; + +public class GoogleAuthProvider extends FirebaseAuthProvider implements + GoogleApiClient.OnConnectionFailedListener, + GoogleApiClient.ConnectionCallbacks, + GoogleOAuthTaskHandler { + + public final static String PROVIDER_NAME = "google"; + public static final SocialProvider PROVIDER_TYPE = SocialProvider.google; + private final String TAG = "GoogleAuthProvider"; + private GoogleApiClient mGoogleApiClient; + private TokenAuthHandler mHandler; + private Activity mActivity; + private Firebase mRef; + private Integer onConnectedAction; + + public GoogleAuthProvider(Context context, Firebase ref, TokenAuthHandler handler) { + mActivity = (Activity) context; + mRef = ref; + mHandler = handler; + + String googleClientId = ""; + + try { + ApplicationInfo ai = mActivity.getPackageManager().getApplicationInfo(mActivity.getPackageName(), PackageManager.GET_META_DATA); + Bundle bundle = ai.metaData; + googleClientId = bundle.getString("com.firebase.ui.GoogleClientId"); + } catch (PackageManager.NameNotFoundException e) { + } catch (NullPointerException e) {} + + if (googleClientId == null) { + FirebaseLoginError error = new FirebaseLoginError( + FirebaseResponse.MISSING_PROVIDER_APP_KEY, + "Missing Google client ID, is it set in your AndroidManifest.xml?"); + mHandler.onProviderError(error); + return; + } + + if (googleClientId.compareTo("") == 0) { + FirebaseLoginError error = new FirebaseLoginError( + FirebaseResponse.INVALID_PROVIDER_APP_KEY, + "Invalid Google client ID, is it set in your res/values/strings.xml?"); + mHandler.onProviderError(error); + return; + } + + GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(googleClientId) + .requestEmail() + .build(); + + mGoogleApiClient = new GoogleApiClient.Builder(mActivity) + .enableAutoManage((FragmentActivity) mActivity, this) + .addApi(Auth.GOOGLE_SIGN_IN_API, gso) + .build(); + + mGoogleApiClient.connect(); + + } + + public String getProviderName() { return PROVIDER_NAME; } + public Firebase getFirebaseRef() { return mRef; } + public SocialProvider getProviderType() { return PROVIDER_TYPE; }; + + @Override + public void onConnected(Bundle bundle) { + if (onConnectedAction == GoogleActions.SIGN_IN) { + login(); + } else if (onConnectedAction == GoogleActions.SIGN_OUT) { + logout(); + } + + onConnectedAction = 0; + } + + @Override + public void onConnectionSuspended(int i) {} + + public void logout() { + if (mGoogleApiClient.isConnected()) { + Auth.GoogleSignInApi.signOut(mGoogleApiClient).setResultCallback( + new ResultCallback() { + @Override + public void onResult(Status status) { + revokeAccess(); + } + }); + } else { + onConnectedAction = GoogleActions.SIGN_OUT; + } + } + + public void login() { + if (mGoogleApiClient.isConnected()) { + Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); + mActivity.startActivityForResult(signInIntent, GoogleActions.SIGN_IN); + } else { + onConnectedAction = GoogleActions.SIGN_IN; + } + } + private void revokeAccess() { + Auth.GoogleSignInApi.revokeAccess(mGoogleApiClient).setResultCallback( + new ResultCallback() { + @Override + public void onResult(Status status) { + + } + }); + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == GoogleActions.SIGN_IN && resultCode == -1) { + GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data); + handleSignInResult(result); + } + + if (requestCode == GoogleActions.SIGN_IN && resultCode == 0) { + Log.d(TAG, data.getExtras().keySet().toString()); + mHandler.onUserError(new FirebaseLoginError(FirebaseResponse.LOGIN_CANCELLED, "User closed login dialog.")); + } + } + + private void handleSignInResult(GoogleSignInResult result) { + if (result.isSuccess()) { + GoogleSignInAccount acct = result.getSignInAccount(); + acct.getServerAuthCode(); + GoogleOAuthTask googleOAuthTask = new GoogleOAuthTask(); + googleOAuthTask.setContext(mActivity); + googleOAuthTask.setHandler(this); + googleOAuthTask.execute(acct.getEmail()); + } + } + + + public void onOAuthSuccess(String OAuthToken) { + FirebaseOAuthToken token = new FirebaseOAuthToken( + PROVIDER_NAME, + OAuthToken); + onFirebaseTokenReceived(token, mHandler); + } + + public void onOAuthFailure(FirebaseLoginError firebaseError) { + mHandler.onProviderError(firebaseError); + } + + public void cleanUp() { + if (mGoogleApiClient != null) { + mGoogleApiClient.disconnect(); + mGoogleApiClient.stopAutoManage((FragmentActivity) mActivity); + } + } + + @Override + public void onConnectionFailed(ConnectionResult connectionResult) { + FirebaseLoginError error = new FirebaseLoginError(FirebaseResponse.MISC_PROVIDER_ERROR, connectionResult.toString()); + mHandler.onProviderError(error);} +} diff --git a/library/src/main/java/com/firebase/ui/auth/google/GoogleOAuthTask.java b/library/src/main/java/com/firebase/ui/auth/google/GoogleOAuthTask.java new file mode 100644 index 000000000..cab15b609 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/google/GoogleOAuthTask.java @@ -0,0 +1,46 @@ +package com.firebase.ui.auth.google; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; + +import com.firebase.ui.auth.core.FirebaseLoginError; +import com.firebase.ui.auth.core.FirebaseResponse; +import com.google.android.gms.auth.GoogleAuthException; +import com.google.android.gms.auth.GoogleAuthUtil; +import com.google.android.gms.auth.UserRecoverableAuthException; + +class GoogleOAuthTask extends AsyncTask { + private Context mContext; + private GoogleOAuthTaskHandler mHandler; + + protected String doInBackground(String... emails) { + String token = ""; + + try { + token = GoogleAuthUtil.getToken(mContext, emails[0], "oauth2:profile email"); + } catch (UserRecoverableAuthException e) { + + } catch (GoogleAuthException e) { + + } catch (java.io.IOException e) { + + } + if (!token.equals("")) return token; + else return ""; + } + + public void setContext(Context context) { + mContext = context; + } + public void setHandler(GoogleOAuthTaskHandler handler) { mHandler = handler; } + + protected void onPostExecute(String token) { + if (token.equals("")) { + mHandler.onOAuthFailure(new FirebaseLoginError(FirebaseResponse.MISC_PROVIDER_ERROR, "Fetching OAuth token from Google failed")); + } else { + mHandler.onOAuthSuccess(token); + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/firebase/ui/auth/google/GoogleOAuthTaskHandler.java b/library/src/main/java/com/firebase/ui/auth/google/GoogleOAuthTaskHandler.java new file mode 100644 index 000000000..4f85104e7 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/google/GoogleOAuthTaskHandler.java @@ -0,0 +1,8 @@ +package com.firebase.ui.auth.google; + +import com.firebase.ui.auth.core.FirebaseLoginError; + +interface GoogleOAuthTaskHandler { + public void onOAuthSuccess(String token); + public void onOAuthFailure(FirebaseLoginError firebaseError); +} diff --git a/library/src/main/java/com/firebase/ui/auth/password/PasswordAuthProvider.java b/library/src/main/java/com/firebase/ui/auth/password/PasswordAuthProvider.java new file mode 100644 index 000000000..72cfdeac1 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/password/PasswordAuthProvider.java @@ -0,0 +1,49 @@ +package com.firebase.ui.auth.password; + +import android.app.Activity; +import android.content.Context; + +import com.firebase.client.AuthData; +import com.firebase.client.Firebase; +import com.firebase.client.FirebaseError; +import com.firebase.ui.auth.core.FirebaseAuthProvider; +import com.firebase.ui.auth.core.FirebaseResponse; +import com.firebase.ui.auth.core.FirebaseLoginError; +import com.firebase.ui.auth.core.SocialProvider; +import com.firebase.ui.auth.core.TokenAuthHandler; + +public class PasswordAuthProvider extends FirebaseAuthProvider { + + private final String LOG_TAG = "PasswordAuthProvider"; + + public final static String PROVIDER_NAME = "password"; + public final static SocialProvider PROVIDER_TYPE = SocialProvider.password; + + private TokenAuthHandler mHandler; + private Activity mActivity; + private Firebase mRef; + + public PasswordAuthProvider(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(new FirebaseLoginError(FirebaseResponse.MISC_PROVIDER_ERROR, firebaseError.toString())); + } + }); + } + + public String getProviderName() { return PROVIDER_NAME; } + public Firebase getFirebaseRef() { return mRef; } + public SocialProvider getProviderType() { return PROVIDER_TYPE; }; + public void logout() {} +} diff --git a/library/src/main/java/com/firebase/ui/auth/twitter/TwitterActions.java b/library/src/main/java/com/firebase/ui/auth/twitter/TwitterActions.java new file mode 100644 index 000000000..3f969e188 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/twitter/TwitterActions.java @@ -0,0 +1,9 @@ +package com.firebase.ui.auth.twitter; + +public class TwitterActions { + private static int base = 2000; + public static int REQUEST = base+0; + public static int SUCCESS = base+1; + public static int PROVIDER_ERROR = base+2; + public static int USER_ERROR = base+3; +} diff --git a/library/src/main/java/com/firebase/ui/auth/twitter/TwitterAuthProvider.java b/library/src/main/java/com/firebase/ui/auth/twitter/TwitterAuthProvider.java new file mode 100644 index 000000000..f938d0114 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/twitter/TwitterAuthProvider.java @@ -0,0 +1,59 @@ +package com.firebase.ui.auth.twitter; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +import com.firebase.client.Firebase; +import com.firebase.ui.auth.core.FirebaseAuthProvider; +import com.firebase.ui.auth.core.FirebaseLoginError; +import com.firebase.ui.auth.core.FirebaseOAuthToken; +import com.firebase.ui.auth.core.FirebaseResponse; +import com.firebase.ui.auth.core.SocialProvider; +import com.firebase.ui.auth.core.TokenAuthHandler; + +public class TwitterAuthProvider extends FirebaseAuthProvider { + + public static final String TAG = "TwitterAuthProvider"; + public static final String PROVIDER_NAME = "twitter"; + public static final SocialProvider PROVIDER_TYPE = SocialProvider.twitter; + + private Activity mActivity; + private TokenAuthHandler mHandler; + private Firebase mRef; + + public TwitterAuthProvider(Context context, Firebase ref, TokenAuthHandler handler) { + mActivity = (Activity) context; + mHandler = handler; + mRef = ref; + } + + public void login() { + mActivity.startActivityForResult(new Intent(mActivity, TwitterPromptActivity.class), TwitterActions.REQUEST); + } + + 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 SocialProvider getProviderType() { return PROVIDER_TYPE; }; + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == TwitterActions.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 == TwitterActions.USER_ERROR) { + FirebaseResponse error = FirebaseResponse.values()[data.getIntExtra("code", 0)]; + mHandler.onUserError(new FirebaseLoginError(error, data.getStringExtra("error"))); + } else if (resultCode == TwitterActions.PROVIDER_ERROR) { + FirebaseResponse error = FirebaseResponse.values()[data.getIntExtra("code", 0)]; + mHandler.onProviderError(new FirebaseLoginError(error, data.getStringExtra("error"))); + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/firebase/ui/auth/twitter/TwitterPromptActivity.java b/library/src/main/java/com/firebase/ui/auth/twitter/TwitterPromptActivity.java new file mode 100644 index 000000000..f48179d40 --- /dev/null +++ b/library/src/main/java/com/firebase/ui/auth/twitter/TwitterPromptActivity.java @@ -0,0 +1,136 @@ +package com.firebase.ui.auth.twitter; + +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.webkit.WebView; +import android.webkit.WebViewClient; + +import com.firebase.ui.auth.core.FirebaseResponse; + +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(TwitterActions.USER_ERROR, FirebaseResponse.LOGIN_CANCELLED.ordinal(), "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(TwitterActions.PROVIDER_ERROR, FirebaseResponse.MISSING_PROVIDER_APP_KEY.ordinal(), "Missing Twitter key/secret, are they set in your AndroidManifest.xml?"); + return; + } + + if (twitterKey.compareTo("") == 0|| twitterSecret.compareTo("") == 0) { + sendResultError(TwitterActions.PROVIDER_ERROR, FirebaseResponse.INVALID_PROVIDER_APP_KEY.ordinal(), "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(TwitterActions.PROVIDER_ERROR, FirebaseResponse.MISC_PROVIDER_ERROR.ordinal(), 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(TwitterActions.USER_ERROR, FirebaseResponse.LOGIN_CANCELLED.ordinal(), "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(TwitterActions.PROVIDER_ERROR, FirebaseResponse.MISC_PROVIDER_ERROR.ordinal(), 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(TwitterActions.SUCCESS, resultIntent); + finish(); + } + }.execute(); + } + + private void sendResultError(Integer status, int errCode, String err) { + Intent resultIntent = new Intent(); + resultIntent.putExtra("error", err); + resultIntent.putExtra("code", errCode); + setResult(status, resultIntent); + finish(); + } +} \ 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..d564b2dc3 --- /dev/null +++ b/library/src/main/res/layout/fragment_firebase_login.xml @@ -0,0 +1,156 @@ + + + + + + + + + + +