├── .idea
├── .name
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── vcs.xml
├── inspectionProfiles
│ ├── profiles_settings.xml
│ └── Project_Default.xml
├── modules.xml
├── runConfigurations.xml
├── compiler.xml
├── gradle.xml
└── misc.xml
├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable
│ │ │ │ ├── bg_message_item_sent.xml
│ │ │ │ ├── bg_message_item_received.xml
│ │ │ │ ├── bg_button_circle.xml
│ │ │ │ ├── bg_button_circle_colour_picker.xml
│ │ │ │ ├── ic_send_light.xml
│ │ │ │ ├── ic_insert_photo_grey.xml
│ │ │ │ ├── ic_brush_light.xml
│ │ │ │ ├── ic_avatar_circle_dark.xml
│ │ │ │ ├── ic_avatar_circle_light.xml
│ │ │ │ ├── ic_loop_light.xml
│ │ │ │ ├── ic_insert_emoticon_grey.xml
│ │ │ │ └── bg_button.xml
│ │ │ ├── values-v21
│ │ │ │ └── styles.xml
│ │ │ ├── menu
│ │ │ │ └── menu_main.xml
│ │ │ ├── values
│ │ │ │ ├── styles.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── strings.xml
│ │ │ └── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── message_user_item.xml
│ │ │ │ ├── message_item_received.xml
│ │ │ │ ├── message_item_sent.xml
│ │ │ │ ├── content_activity_main.xml
│ │ │ │ └── activity_chat.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── danielcswain
│ │ │ │ └── nearbychat
│ │ │ │ ├── Users
│ │ │ │ ├── UserViewHolder.java
│ │ │ │ ├── UserObject.java
│ │ │ │ └── UserAdapter.java
│ │ │ │ ├── Messages
│ │ │ │ ├── MessageViewHolder.java
│ │ │ │ ├── MessageDialog.java
│ │ │ │ ├── MessageObject.java
│ │ │ │ └── MessageAdapter.java
│ │ │ │ ├── Tasks
│ │ │ │ └── ImageCompressAsyncTask.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── ChatActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── danielcswain
│ │ │ └── nearbychat
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── danielcswain
│ │ └── nearbychat
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── LICENSE.md
├── README.md
├── gradlew.bat
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | Nearby Chat
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulternate/nearby-chat/HEAD/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulternate/nearby-chat/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulternate/nearby-chat/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulternate/nearby-chat/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulternate/nearby-chat/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulternate/nearby-chat/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulternate/nearby-chat/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | gradle.properties
10 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_message_item_sent.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_message_item_received.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_button_circle.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
8 |
9 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_button_circle_colour_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
8 |
9 |
12 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_send_light.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/danielcswain/nearbychat/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/danielcswain/nearbychat/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_insert_photo_grey.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_brush_light.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_avatar_circle_dark.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_avatar_circle_light.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_loop_light.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_insert_emoticon_grey.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/ulternate/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Daniel Swain
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/danielcswain/nearbychat/Users/UserViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat.Users;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.View;
5 | import android.widget.ImageView;
6 | import android.widget.TextView;
7 |
8 | import com.danielcswain.nearbychat.R;
9 |
10 | /**
11 | * Created by ulternate on 9/08/2016.
12 | *
13 | * Custom view holder extending the RecyclerView.ViewHolder class to support the custom user layout
14 | * to show the current users
15 | */
16 | public class UserViewHolder extends RecyclerView.ViewHolder {
17 |
18 | public ImageView userAvatar;
19 | public TextView toolTipTarget;
20 |
21 | /**
22 | * The ViewHolder constructor that holds the views for the RecyclerView Adapter
23 | * @param itemView a view representing a single layout item in the RecyclerView (i.e. a user icon)
24 | */
25 | public UserViewHolder(View itemView) {
26 | super(itemView);
27 | // The avatar imageView for the user
28 | this.userAvatar = (ImageView) itemView.findViewById(R.id.user_avatar);
29 | // The target for the ToolTip
30 | this.toolTipTarget = (TextView) itemView.findViewById(R.id.user_avatar_tooltip_target);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/message_user_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
16 |
17 |
20 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/danielcswain/nearbychat/Messages/MessageViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat.Messages;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.View;
5 | import android.widget.ImageView;
6 | import android.widget.TextView;
7 |
8 | import com.danielcswain.nearbychat.R;
9 |
10 | /**
11 | * Created by ulternate on 3/08/2016.
12 | *
13 | * Custom view holder extending the RecyclerView.ViewHolder class to support the custom message layout
14 | */
15 | public class MessageViewHolder extends RecyclerView.ViewHolder {
16 |
17 | public TextView usernameTV;
18 | public TextView messageBodyTV;
19 | public ImageView userAvatarIV;
20 | public ImageView messageBodyIV;
21 |
22 | /**
23 | * The ViewHolder constructor that holds the views for the RecyclerView Adapter
24 | * @param itemView a view representing a single layout item in the RecyclerView (i.e. a message row)
25 | */
26 | public MessageViewHolder(View itemView) {
27 | super(itemView);
28 |
29 | this.usernameTV = (TextView) itemView.findViewById(R.id.username);
30 | this.messageBodyTV = (TextView) itemView.findViewById(R.id.message_body);
31 | this.userAvatarIV = (ImageView) itemView.findViewById(R.id.user_avatar);
32 | this.messageBodyIV = (ImageView) itemView.findViewById(R.id.message_body_image);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 4dp
3 | 4dp
4 | 8dp
5 | 8dp
6 | 16dp
7 | 16dp
8 | 24dp
9 | 24dp
10 | 32dp
11 | 32dp
12 | 48dp
13 | 48dp
14 | 18sp
15 | 16sp
16 | -6dp
17 | 8dp
18 | 4dp
19 | 110dp
20 | 160dp
21 | 40dp
22 | 32dp
23 | 28sp
24 |
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nearby-chat
2 | Chat application using Google's Nearby Messages API
3 |
4 | Chat with people nearby using a combination of bluetooth and ultrasonic audio.
5 | Notes:
6 | - All messages are visible to anyone who is in the chat room.
7 | - Once you leave a chat your messages are unpublished and will dissapear (like Snapchat).
8 | - You may hear your phone click with older phones (that's the ultrasonic audio broadcasting to the chat).
9 | - If you take a bit of time when picking an image to send your old messages may get unpublished as you technically leave the chat window.
10 | - Messages may have their order jumbled or changed randomly if you leave and return from the chat quickly.
11 |
12 | ## App store beta link
13 |
14 | You can grab the latest beta apk from the Google Play Store at the following link:
15 |
16 | https://play.google.com/apps/testing/com.danielcswain.nearbychat
17 |
18 | ## Building in Android Studio
19 |
20 | 1. Import project into Android Studio.
21 | 2. Follow the steps at https://developers.google.com/nearby/messages/android/get-started to get your own api key.
22 | 3. Create a `gradle.properties` file in your root directory to store this API key.
23 | 4. Store the API key in the above file with the `NEARBY_API_KEY_DEBUG` or `NEARBY_API_KEY_RELEASE` variable name depending on your release flavour.
24 | (note, apks built with different keys won't be able to communicate with each other).
25 | 5. The api key is retreived during the build step for your release flavour, see `build.gradle` for more info (https://github.com/ulternate/nearby-chat/blob/master/app/build.gradle)
26 | 6. Don't store this key in your own repo ;)
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
14 |
15 |
16 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | -
6 |
7 |
11 |
14 |
16 |
21 |
22 |
23 |
24 | -
25 |
26 |
30 |
33 |
35 |
40 |
41 |
42 |
43 | -
44 |
45 |
49 |
52 |
54 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "24.0.1"
6 |
7 | defaultConfig {
8 | applicationId "com.danielcswain.nearbychat"
9 | minSdkVersion 19
10 | targetSdkVersion 24
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | // Build string resource containing Nearby API Key for release
19 | resValue("string", "NEARBY_API_KEY", "\"" + getReleaseApiKey() + "\"")
20 | }
21 |
22 | debug{
23 | // Build string resource containing Nearby API Key for debugging
24 | resValue("string", "NEARBY_API_KEY", "\"" + getDebugApiKey() + "\"")
25 | }
26 | }
27 | }
28 |
29 | // Repository for Supernova-Emoji library and ImagePicker
30 | repositories {
31 | maven { url "https://dl.bintray.com/hani-momanii/maven"}
32 | maven { url "https://jitpack.io" }
33 | }
34 |
35 | //Get the Nearby API Key (Debug key) from the gradle.properties file (not checked in)
36 | def getDebugApiKey(){
37 | def Properties properties = new Properties()
38 | properties.load(new FileInputStream(new File('gradle.properties')))
39 | return properties['NEARBY_API_KEY_DEBUG']
40 | }
41 |
42 | def getReleaseApiKey(){
43 | def Properties properties = new Properties()
44 | properties.load(new FileInputStream(new File('gradle.properties')))
45 | return properties['NEARBY_API_KEY_RELEASE']
46 | }
47 |
48 | dependencies {
49 | compile fileTree(dir: 'libs', include: ['*.jar'])
50 | testCompile 'junit:junit:4.12'
51 | compile 'com.android.support:appcompat-v7:24.1.1'
52 | compile 'com.android.support:design:24.1.1'
53 | compile 'com.android.support:recyclerview-v7:24.1.1'
54 | compile 'com.google.code.gson:gson:2.4'
55 | compile 'com.thebluealliance:spectrum:0.5.0'
56 | compile 'com.tomergoldst.android:tooltips:1.0.2'
57 | compile 'hani.momanii.supernova_emoji_library:supernova-emoji-library:0.0.2'
58 | compile 'com.github.nguyenhoanglam:ImagePicker:1.0.2'
59 | compile 'com.google.android.gms:play-services-nearby:9.2.1'
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/danielcswain/nearbychat/Messages/MessageDialog.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat.Messages;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.app.DialogFragment;
6 | import android.content.DialogInterface;
7 | import android.os.Bundle;
8 |
9 | import com.danielcswain.nearbychat.ChatActivity;
10 | import com.danielcswain.nearbychat.MainActivity;
11 | import com.danielcswain.nearbychat.R;
12 |
13 | /**
14 | * Created by ulternate on 11/08/2016.
15 | *
16 | * A custom Dialog to notify the user that the messages they send are ephemeral and will disappear
17 | * when they leave the chat. This dialog shows each time the user enters the chat unless the user
18 | * has clicked don't show again
19 | */
20 | public class MessageDialog extends DialogFragment {
21 |
22 | /**
23 | * Create the dialog and set the positive and negative actions
24 | */
25 | @Override
26 | public Dialog onCreateDialog(Bundle savedInstanceState) {
27 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
28 | builder.setMessage(R.string.dialog_messages_ephemeral_body)
29 | .setPositiveButton(R.string.dialog_messages_ephemeral_positive_action, new DialogInterface.OnClickListener() {
30 | @Override
31 | public void onClick(DialogInterface dialogInterface, int i) {
32 | // Do nothing the count is iterated in onDismiss
33 | }
34 | })
35 | .setNegativeButton(R.string.dialog_messages_ephemeral_negative_action, new DialogInterface.OnClickListener() {
36 | @Override
37 | public void onClick(DialogInterface dialogInterface, int i) {
38 | // Set the dialog to not show again
39 | MainActivity.sharedPreferences.edit().putBoolean(ChatActivity.DIALOG_DISMISSED_KEY, true).apply();
40 | }
41 | });
42 | return builder.create();
43 | }
44 |
45 | /**
46 | * Iterate the dialog displayed counter by 1 when dismissed by either of the buttons
47 | * Store the count value in the shared preferences
48 | */
49 | @Override
50 | public void onDismiss(DialogInterface dialog) {
51 | super.onDismiss(dialog);
52 | int counter = MainActivity.sharedPreferences.getInt(ChatActivity.DIALOG_COUNTER_KEY, 1) + 1;
53 | MainActivity.sharedPreferences.edit().putInt(ChatActivity.DIALOG_COUNTER_KEY, counter).apply();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #c2185b
7 | #f06292
8 | #0d47a1
9 | #cfd8dc
10 | #EEEEEE
11 | #e0e0e0
12 | #ffffff
13 | #212121
14 |
15 | #f44336
16 | #FF4081
17 | #9C27B0
18 | #673AB7
19 | #3F51B5
20 | #2196F3
21 | #03A9F4
22 | #00BCD4
23 | #009688
24 | #4CAF50
25 | #8BC34A
26 | #CDDC39
27 | #FFEB3B
28 | #FFC107
29 | #FF9800
30 | #FF5722
31 | #795548
32 | #9E9E9E
33 | #607D8B
34 | #ffffff
35 |
36 |
37 | - @color/md_red_500
38 | - @color/md_pink_500
39 | - @color/md_purple_500
40 | - @color/md_deep_purple_500
41 | - @color/md_indigo_500
42 | - @color/md_blue_500
43 | - @color/md_light_blue_500
44 | - @color/md_cyan_500
45 | - @color/md_teal_500
46 | - @color/md_green_500
47 | - @color/md_light_green_500
48 | - @color/md_lime_500
49 | - @color/md_yellow_500
50 | - @color/md_amber_500
51 | - @color/md_orange_500
52 | - @color/md_deep_orange_500
53 | - @color/md_brown_500
54 | - @color/md_grey_500
55 | - @color/md_blue_grey_500
56 | - @color/md_white
57 |
58 |
59 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/danielcswain/nearbychat/Users/UserObject.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat.Users;
2 |
3 | import com.google.android.gms.nearby.messages.Message;
4 | import com.google.gson.Gson;
5 |
6 | import java.nio.charset.Charset;
7 | import java.util.Objects;
8 |
9 | /**
10 | * Created by ulternate on 8/08/2016.
11 | *
12 | * Custom Object representing a single user object
13 | */
14 | public class UserObject {
15 |
16 | public static final String MESSAGE_TYPE = "Greeting";
17 |
18 | private static final Gson sGson = new Gson();
19 | private String mUsername;
20 | private String mAvatarColour;
21 |
22 | /**
23 | * Constructor for initiating a single UserObject
24 | * @param username the username of the user
25 | * @param avatarColour the colour of the user's avatar
26 | */
27 | public UserObject(String username, String avatarColour){
28 | this.mUsername = username;
29 | this.mAvatarColour = avatarColour;
30 | }
31 |
32 | /**
33 | * Create a Nearby Message using the UserObject
34 | * @param userObject the UserObject being sent
35 | * @return a Message with the UserObject as the package (converted to a byte[])
36 | */
37 | public static Message newNearbyMessage(UserObject userObject){
38 | return new Message(sGson.toJson(userObject).getBytes(Charset.forName("UTF-8")), MESSAGE_TYPE);
39 | }
40 |
41 | /**
42 | * Retrieve a userObject from the Nearby Message's content
43 | * @param message the Nearby Message containing the userObject as a byte[]
44 | * @return a UserObject using Gson to convert from JSON to an instance of the UserObject class
45 | */
46 | public static UserObject fromNearbyMessage(Message message){
47 | String nearbyMessageString = new String(message.getContent()).trim();
48 | return sGson.fromJson(
49 | (new String(nearbyMessageString.getBytes(Charset.forName("UTF-8")))),
50 | UserObject.class);
51 | }
52 |
53 | /**
54 | * Custom implementation to test for equality through user object content, not if they refer to the same
55 | * object in memory. Used by Adapter and ArrayList methods.
56 | */
57 | @Override
58 | public boolean equals(Object obj) {
59 | boolean match = false;
60 | if (obj != null && obj instanceof UserObject){
61 | if (Objects.equals(((UserObject) obj).mUsername, this.mUsername) &&
62 | Objects.equals(((UserObject) obj).mAvatarColour, this.mAvatarColour)){
63 | match = true;
64 | }
65 | }
66 | return match;
67 | }
68 |
69 | public String getUsername() {
70 | return mUsername;
71 | }
72 |
73 | public String getAvatarColour() {
74 | return mAvatarColour;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/message_item_received.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
17 |
18 |
19 |
31 |
32 |
33 |
46 |
47 |
48 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/message_item_sent.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
18 |
19 |
20 |
32 |
33 |
34 |
48 |
49 |
50 |
63 |
64 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Android
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Nearby Chat
3 | Settings
4 | Connected successfully.
5 | Could not publish message.
6 | Could not subscribe to chat.
7 | Username
8 | Generate Username
9 | Enter Chat
10 | Select Avatar Colour
11 | Chat with people nearby you.
12 | Please enter or generate a username before joining the chat
13 | Please enter a message and try again
14 | Type a message
15 | Send Message
16 | Your Avatar
17 | Sender\'s Avatar
18 | User\'s Avatar
19 | OK
20 | The messages you send in this chat aren\'t persistent and will disappear shortly after you leave the app, or turn off your screen
21 | Don\'t Show Again
22 | Choose emoticon
23 | Message Image
24 | Add image
25 | Send %1$s
26 | Chosen image too large to send as a nearby message. Messages must be less than 100kb
27 | Sending message in the background. Larger images will take longer to be sent
28 |
29 |
30 | - Armadillo
31 | - Buffalo
32 | - Cheetah
33 | - Dingo
34 | - Eagle
35 | - Ferret
36 | - Giraffe
37 | - Hippo
38 | - Iguana
39 | - Jaguar
40 | - Kangaroo
41 | - Llama
42 | - Monkey
43 | - Newt
44 | - Orangutan
45 | - Penguin
46 | - Quail
47 | - Rabbit
48 | - Snake
49 | - Tiger
50 | - Urial
51 | - Vulture
52 | - Wolf
53 | - Xerus
54 | - Yak
55 | - Zebra
56 |
57 |
58 |
59 | - Angry
60 | - Happy
61 | - Sad
62 | - Clever
63 | - Clean
64 | - Fancy
65 | - Obedient
66 | - Brave
67 | - Bewildered
68 | - Clumsy
69 | - Tiny
70 | - Large
71 | - Deafening
72 | - Quiet
73 | - Melodic
74 | - Whispering
75 | - Cold
76 | - Warm
77 |
78 |
79 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
24 |
25 |
26 |
38 |
39 |
40 |
52 |
53 |
54 |
64 |
65 |
66 |
77 |
78 |
79 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_chat.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
21 |
22 |
23 |
32 |
33 |
34 |
42 |
43 |
44 |
53 |
54 |
55 |
64 |
65 |
66 |
78 |
79 |
80 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/src/main/java/com/danielcswain/nearbychat/Tasks/ImageCompressAsyncTask.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat.Tasks;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.BitmapFactory;
5 | import android.os.AsyncTask;
6 | import android.util.Base64;
7 | import android.util.Log;
8 |
9 | import com.danielcswain.nearbychat.ChatActivity;
10 | import com.danielcswain.nearbychat.MainActivity;
11 | import com.danielcswain.nearbychat.Messages.MessageObject;
12 | import com.danielcswain.nearbychat.R;
13 |
14 | import java.io.ByteArrayOutputStream;
15 | import java.io.IOException;
16 |
17 | /**
18 | * Created by ulternate on 12/08/2016.
19 | *
20 | * AsyncTask to compress the selected Image and return the message as a base64 encoded string
21 | * ready to be sent as a Nearby Message
22 | */
23 | public class ImageCompressAsyncTask extends AsyncTask {
24 |
25 | public static final String TRUE = "TRUE";
26 | public static final String FALSE = "FALSE";
27 |
28 | private static final int MAX_IMAGE_SIZE = 70000;
29 | private String mUsername;
30 | private String mAvatarColour;
31 | private boolean mFromUser;
32 |
33 | /**
34 | * The AsyncTask to compress the image and return an encoded Base64 string representation to be able
35 | * to send the image
36 | * @param strings the parameters needed to send the message and compress the image
37 | * in order they are: filePath, sender's username, sender's avatar colour and whether from the user or not.
38 | * @return the encoded string
39 | */
40 | @Override
41 | protected String doInBackground(String... strings) {
42 | if (strings.length == 4) {
43 | int streamLength = MAX_IMAGE_SIZE;
44 | int compressionQuality = 55;
45 |
46 | // Get the message info from the strings
47 | String mFilePath = strings[0];
48 | mUsername = strings[1];
49 | mAvatarColour = strings[2];
50 | mFromUser = !strings[3].equals(FALSE);
51 |
52 | // Decode a bitmap from the file path string
53 | Bitmap bitmap = BitmapFactory.decodeFile(mFilePath);
54 | String bitmapString = "";
55 |
56 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
57 |
58 | // Keep compressing the image until it is smaller than the MAX_IMAGE_SIZE
59 | while (streamLength >= MAX_IMAGE_SIZE && compressionQuality > 5) {
60 | // To avoid an out of memory error, flush and refresh the output stream
61 | try {
62 | byteArrayOutputStream.flush();
63 | byteArrayOutputStream.reset();
64 | } catch (IOException e) {
65 | e.printStackTrace();
66 | }
67 | // Reduce the compression quality
68 | compressionQuality -= 5;
69 | Log.d("compressionQuality", "cQ = " + compressionQuality);
70 | // Compress the image and update the streamLength
71 | bitmap.compress(Bitmap.CompressFormat.WEBP, compressionQuality, byteArrayOutputStream);
72 | streamLength = byteArrayOutputStream.size();
73 | Log.d("streamLength", "streamLength = " + streamLength);
74 | }
75 |
76 | // Only set the bitmapString if the streamLength is less than the max message size
77 | if (streamLength < MAX_IMAGE_SIZE) {
78 | bitmapString = Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.NO_WRAP);
79 | }
80 |
81 | // Recycle the bitmap as we're finished with it to free up memory
82 | bitmap.recycle();
83 |
84 | return bitmapString;
85 | } else {
86 | return null;
87 | }
88 | }
89 |
90 | /**
91 | * Show the image on the chat board once it's been compressed
92 | * @param s the encoded compressed image
93 | */
94 | @Override
95 | protected void onPostExecute(String s) {
96 | if (!s.isEmpty()) {
97 | // The compression completed and returned a non-empty string, publish on the chat board
98 | ChatActivity.publishMessage(new MessageObject(mUsername, s, MessageObject.MESSAGE_CONTENT_IMAGE, mAvatarColour, mFromUser));
99 | } else {
100 | // The compression couldn't make the image small enough, it was too large to begin with. Notify the user
101 | ChatActivity.showSnackbar(MainActivity.mainContext.getString(R.string.error_image_to_large));
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/app/src/main/java/com/danielcswain/nearbychat/Messages/MessageObject.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat.Messages;
2 |
3 | import com.google.android.gms.nearby.messages.Message;
4 | import com.google.gson.Gson;
5 |
6 | import java.nio.charset.Charset;
7 | import java.util.Objects;
8 | import java.util.UUID;
9 |
10 | /**
11 | * Created by ulternate on 3/08/2016.
12 | *
13 | * Custom Object representing a single message object
14 | */
15 | public class MessageObject {
16 |
17 | public static final String MESSAGE_TYPE = "Message";
18 | public static final String MESSAGE_CONTENT_TEXT = "text";
19 | public static final String MESSAGE_CONTENT_IMAGE = "image";
20 |
21 | private static final Gson sGson = new Gson();
22 | private String mUsername;
23 | private String mMessageBody;
24 | private String mAvatarColour;
25 | private boolean mFromUser;
26 | private UUID mMessageUuid;
27 | private String mMessageContent;
28 |
29 | /**
30 | * Constructor for initialising a single MessageObject.
31 | * @param username the username of the user sending the message
32 | * @param messageBody the message text
33 | * @param messageContent the type of content being sent in the message body (image or text)
34 | * @param avatarColour the colour of the user's avatar
35 | * @param fromUser boolean used to determine which layout to use, either the message_item_received or sent.
36 | */
37 | public MessageObject(String username, String messageBody, String messageContent, String avatarColour, boolean fromUser){
38 | this.mUsername = username;
39 | this.mMessageBody = messageBody;
40 | this.mMessageContent = messageContent;
41 | this.mAvatarColour = avatarColour;
42 | this.mFromUser = fromUser;
43 | this.mMessageUuid = UUID.randomUUID();
44 | }
45 |
46 | /**
47 | * Create a Nearby Message using the messageObject
48 | * @param messageObject the messageObject being sent
49 | * @return a Message with the messageObject as the package (converted to a byte[])
50 | */
51 | public static Message newNearbyMessage(MessageObject messageObject){
52 | return new Message(sGson.toJson(messageObject).getBytes(Charset.forName("UTF-8")), MESSAGE_TYPE);
53 | }
54 |
55 | /**
56 | * Retrieve a messageObject from the Nearby Message's content
57 | * @param message the Nearby Message containing the messageObject as a byte[]
58 | * @return a MessageObject using Gson to convert from JSON to an instance of the MessageObject class
59 | */
60 | public static MessageObject fromNearbyMessage(Message message){
61 | String nearbyMessageString = new String(message.getContent()).trim();
62 | return sGson.fromJson(
63 | (new String(nearbyMessageString.getBytes(Charset.forName("UTF-8")))),
64 | MessageObject.class);
65 | }
66 |
67 | /**
68 | * Custom implementation to test for equality through message content, not if they refer to the same
69 | * object in memory. Used by ListAdapter.contains method.
70 | */
71 | @Override
72 | public boolean equals(Object obj) {
73 | boolean match = false;
74 | if (obj != null && obj instanceof MessageObject){
75 | if (Objects.equals(((MessageObject) obj).mUsername, this.mUsername) &&
76 | Objects.equals(((MessageObject) obj).mMessageBody, this.mMessageBody) &&
77 | Objects.equals(((MessageObject) obj).mMessageContent, this.mMessageContent) &&
78 | Objects.equals(((MessageObject) obj).mAvatarColour, this.mAvatarColour) &&
79 | Objects.equals(((MessageObject) obj).mFromUser, this.mFromUser) &&
80 | Objects.equals(((MessageObject) obj).mMessageUuid, this.mMessageUuid)){
81 | match = true;
82 | }
83 | }
84 | return match;
85 | }
86 |
87 | /**
88 | * Get and Set methods for the MessageObject properties
89 | */
90 | public String getUsername() {
91 | return mUsername;
92 | }
93 |
94 | public String getMessageBody() {
95 | return mMessageBody;
96 | }
97 |
98 | public boolean getFromUser() {
99 | return mFromUser;
100 | }
101 |
102 | public String getAvatarColour() {
103 | return mAvatarColour;
104 | }
105 |
106 | public UUID getMessageUuid() {
107 | return mMessageUuid;
108 | }
109 |
110 | public String getMessageContent(){
111 | return mMessageContent;
112 | }
113 |
114 | public void setFromUser(boolean mFromUser) {
115 | this.mFromUser = mFromUser;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/java/com/danielcswain/nearbychat/Users/UserAdapter.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat.Users;
2 |
3 | import android.graphics.Color;
4 | import android.os.Handler;
5 | import android.os.Looper;
6 | import android.support.v4.content.ContextCompat;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.Toast;
12 |
13 | import com.danielcswain.nearbychat.ChatActivity;
14 | import com.danielcswain.nearbychat.MainActivity;
15 | import com.danielcswain.nearbychat.R;
16 | import com.tomergoldst.tooltips.ToolTip;
17 |
18 | import java.util.ArrayList;
19 |
20 |
21 | /**
22 | * Created by ulternate on 9/08/2016.
23 | *
24 | * Custom Adapter extending the RecyclerView.Adapter class. This represents a the current user's row and
25 | * uses the UserViewHolder class to hold the individual userIcon views which get their contents from the
26 | * mUserObjects array list of current users
27 | */
28 | public class UserAdapter extends RecyclerView.Adapter {
29 |
30 | private ArrayList mUserObjects;
31 |
32 | /**
33 | * Constructor for the UserAdapter
34 | * @param userObjects the array list of user objects
35 | */
36 | public UserAdapter(ArrayList userObjects){
37 | this.mUserObjects = userObjects;
38 | }
39 |
40 | /**
41 | * Called when the RecyclerView needs a new ViewHolder of the given type to represent an item.
42 | * Uses the same UserViewHolder class for the userIcon's or avatars of the current users in the chat
43 | *
44 | * @param parent the parent view group
45 | * @param viewType the type of view to be represented in the ViewHolder.
46 | * @return a new UserViewHolder with the given ViewType inflated
47 | */
48 | @Override
49 | public UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
50 | View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_user_item, parent, false);
51 | return new UserViewHolder(v);
52 | }
53 |
54 | /**
55 | * Displays the user icon for the given position in the RecyclerView. Uses the UserViewHolder to
56 | * get the correct references to the View Items
57 | * @param holder the UserViewHolder representing a single RecyclerView item containing all the
58 | * views for that item (the user's avatar))
59 | * @param position the position of the item in the RecyclerView.Adapters data set.
60 | */
61 | @Override
62 | public void onBindViewHolder(final UserViewHolder holder, int position) {
63 | // Get the userObject from the ArrayList at that position
64 | final UserObject userObject = mUserObjects.get(position);
65 |
66 | // Set the colour of the avatar to match the user's preference as stored by the userObject
67 | holder.userAvatar.setColorFilter(Color.parseColor(userObject.getAvatarColour()));
68 |
69 | // Set an onClickListener to the avatar to show the user's username on click
70 | holder.userAvatar.setOnClickListener(new View.OnClickListener() {
71 | @Override
72 | public void onClick(View view) {
73 | if (ChatActivity.toolTipsManager != null) {
74 | // There's a ToolTips manager so build and show the toolTip on click of the userAvatar
75 | // First, dismiss any current ToolTips
76 | ChatActivity.toolTipsManager.findAndDismiss(holder.toolTipTarget);
77 | buildAndShowToolTip(holder, userObject);
78 | } else {
79 | // Show a Toast if there's no ToolTips Manager
80 | Toast.makeText(MainActivity.mainContext, userObject.getUsername(), Toast.LENGTH_SHORT).show();
81 | }
82 | }
83 | });
84 | }
85 |
86 | /**
87 | * Build and show the ToolTip when the userAvatar in the view holder is clicked.
88 | * This will auto dismiss after the DELAY set above.
89 | * @param holder the UserViewHolder containing the userAvatar image and the tooltip anchor
90 | * @param userObject the UserObject relating to the clicked userAvatar image.
91 | */
92 | private void buildAndShowToolTip(final UserViewHolder holder, UserObject userObject){
93 | // Build the ToolTip containing the username
94 | ToolTip.Builder builder = new ToolTip.Builder(MainActivity.mainContext,
95 | holder.toolTipTarget,
96 | ChatActivity.mRootContainer,
97 | userObject.getUsername(),
98 | ToolTip.POSITION_BELOW);
99 | // Set the alignment of the ToolTip
100 | builder.setAlign(ToolTip.ALIGN_LEFT);
101 | // Set the background colour and text to match the user's avatar
102 | builder.setBackgroundColor(Color.parseColor(userObject.getAvatarColour()));
103 | builder.setTextColor(ContextCompat.getColor(MainActivity.mainContext, R.color.md_white));
104 | // Show the ToolTip
105 | ChatActivity.toolTipsManager.show(builder.build());
106 | // Dismiss the ToolTip after a short delay
107 | Handler dismissToolTipHandler = new Handler(Looper.getMainLooper());
108 | Runnable dismissToolTipRunnable = new Runnable() {
109 | @Override
110 | public void run() {
111 | ChatActivity.toolTipsManager.findAndDismiss(holder.toolTipTarget);
112 | }
113 | };
114 | dismissToolTipHandler.postDelayed(dismissToolTipRunnable, 2000);
115 | }
116 |
117 | /**
118 | * @return the total number of items in the data set held by the Adapter
119 | */
120 | @Override
121 | public int getItemCount() {
122 | if (mUserObjects != null) {
123 | return mUserObjects.size();
124 | } else {
125 | return 0;
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/com/danielcswain/nearbychat/Messages/MessageAdapter.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat.Messages;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.BitmapFactory;
5 | import android.graphics.Color;
6 | import android.support.v4.content.ContextCompat;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.util.Base64;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 |
13 | import com.danielcswain.nearbychat.MainActivity;
14 | import com.danielcswain.nearbychat.R;
15 |
16 | import java.util.ArrayList;
17 | import java.util.Objects;
18 |
19 | /**
20 | * Created by ulternate on 3/08/2016.
21 | *
22 | * Custom Adapter extending the RecyclerView.Adapter class. This represents a single chat window and uses
23 | * the MessageViewHolder class to hold the individual message views which get their contents from the
24 | * mMessageObjects ArrayList
25 | */
26 | public class MessageAdapter extends RecyclerView.Adapter {
27 |
28 | private ArrayList mMessageObjects;
29 |
30 | /**
31 | * Constructor for the MessageAdapter
32 | * @param messageObjects an ArrayList of MessageObject objects
33 | */
34 | public MessageAdapter(ArrayList messageObjects){
35 | this.mMessageObjects = messageObjects;
36 | }
37 |
38 | /**
39 | * Get the ViewType of the item. This is used to determine whether to inflate the message_item_received or
40 | * message_item_sent layouts in onCreateViewHolder.
41 | *
42 | * @param position the position of the ItemView object in the mMessageObjects arrayList / RecyclerView.Adapter
43 | * @return 0 for a message that was sent the user and 1 if it was a received message.
44 | */
45 | @Override
46 | public int getItemViewType(int position) {
47 | MessageObject messageObject = mMessageObjects.get(position);
48 | if (messageObject.getFromUser()){
49 | return 0;
50 | } else {
51 | return 1;
52 | }
53 | }
54 |
55 | /**
56 | * Called when the RecyclerView needs a new ViewHolder of the given type to represent an item.
57 | * Uses the same MessageViewHolder class for both sent and received messages
58 | *
59 | * @param parent the parent view group
60 | * @param viewType the type of view to be represented in the ViewHolder. This is determined by getItemViewType
61 | * and the message type (either sent from the user or a received message)
62 | * @return a new MessageViewHolder with the given ViewType inflated
63 | */
64 | @Override
65 | public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
66 | View v;
67 |
68 | if (viewType == 0) {
69 | v = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_item_sent, parent, false);
70 | } else {
71 | v = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_item_received, parent, false);
72 | }
73 |
74 | return new MessageViewHolder(v);
75 | }
76 |
77 | /**
78 | * Displays the message data for the given position in the RecyclerView. Uses the MessageViewHolder to
79 | * get the correct references to the View Items
80 | * @param holder the MessageViewHolder representing a single RecyclerView item containing all the
81 | * views for that item (i.e. the message body, username and avatar)
82 | * @param position the position of the item in the RecyclerView.Adapters data set.
83 | */
84 | @Override
85 | public void onBindViewHolder(MessageViewHolder holder, int position) {
86 | // Get the messageObject from the ArrayList at the position specified
87 | MessageObject messageObject = mMessageObjects.get(position);
88 |
89 | // Set the TextViews from the ViewHolder with the appropriate data from the messageObject.
90 | holder.usernameTV.setText(messageObject.getUsername());
91 |
92 | // Handle the messageBody based upon the message Type
93 | if (Objects.equals(messageObject.getMessageContent(), MessageObject.MESSAGE_CONTENT_TEXT)) {
94 | // Show the messageBody as text and hide the imageView
95 | holder.messageBodyTV.setVisibility(View.VISIBLE);
96 | holder.messageBodyTV.setText(messageObject.getMessageBody());
97 | holder.messageBodyIV.setVisibility(View.GONE);
98 | } else if (Objects.equals(messageObject.getMessageContent(), MessageObject.MESSAGE_CONTENT_IMAGE)){
99 | // Show the messageBody as an image and hide the textView
100 | holder.messageBodyTV.setVisibility(View.GONE);
101 | holder.messageBodyIV.setVisibility(View.VISIBLE);
102 | // Get the byte[] from the messageBody to retrieve the image
103 | byte[] imageString = Base64.decode(messageObject.getMessageBody(), Base64.NO_WRAP);
104 | Bitmap bm = BitmapFactory.decodeByteArray(imageString, 0, imageString.length);
105 | holder.messageBodyIV.setImageBitmap(bm);
106 | }
107 |
108 | // Set the user avatar colour to match the user's chosen colour.
109 | try{
110 | holder.userAvatarIV.setColorFilter(Color.parseColor(messageObject.getAvatarColour()));
111 | } catch (IllegalArgumentException e){
112 | // Use the default pink colour if the messageObject.avatarColour property is not a valid Hex String
113 | holder.userAvatarIV.setColorFilter(ContextCompat.getColor(MainActivity.mainContext, R.color.md_pink_500));
114 | }
115 |
116 | // If not the first messageObject then check if the previous messageObject was from the same user.
117 | // if so, then don't display the user's avatar or username
118 | if (position > 0){
119 | String prevUsername = mMessageObjects.get(position - 1).getUsername();
120 | if (Objects.equals(prevUsername, messageObject.getUsername())){
121 | holder.userAvatarIV.setVisibility(View.GONE);
122 | holder.usernameTV.setVisibility(View.GONE);
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * @return the total number of items in the data set held by the Adapter
129 | */
130 | @Override
131 | public int getItemCount() {
132 | if (mMessageObjects != null) {
133 | return mMessageObjects.size();
134 | } else {
135 | return 0;
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/main/java/com/danielcswain/nearbychat/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.SharedPreferences;
7 | import android.graphics.Color;
8 | import android.graphics.drawable.GradientDrawable;
9 | import android.os.Bundle;
10 | import android.support.annotation.ColorInt;
11 | import android.support.design.widget.Snackbar;
12 | import android.support.v4.content.ContextCompat;
13 | import android.support.v7.app.AppCompatActivity;
14 | import android.support.v7.widget.Toolbar;
15 | import android.view.Menu;
16 | import android.view.MenuItem;
17 | import android.view.View;
18 | import android.view.inputmethod.InputMethodManager;
19 | import android.widget.Button;
20 | import android.widget.EditText;
21 | import android.widget.ImageButton;
22 |
23 | import com.thebluealliance.spectrum.SpectrumDialog;
24 |
25 | import java.util.Random;
26 |
27 | /**
28 | * Launcher/Main activity for the NearbyChat application.
29 | */
30 | public class MainActivity extends AppCompatActivity{
31 |
32 | private static final String COLOUR_PICKER_TAG = "Avatar Colour Picker";
33 | public static final String SHARED_PREFS_FILE = "NearbyChatPreferences";
34 | public static final String USERNAME_KEY = "username";
35 | public static final String AVATAR_COLOUR_KEY = "avatar_colour";
36 | public static Context mainContext;
37 | public static SharedPreferences sharedPreferences;
38 |
39 | private EditText mUsernameField;
40 | private ImageButton mGenerateUsernameButton;
41 | private ImageButton mPickAvatarColourButton;
42 | private Button mEnterChatButton;
43 | private static Integer sCurrentAvatarColour;
44 |
45 | @Override
46 | protected void onCreate(Bundle savedInstanceState) {
47 | super.onCreate(savedInstanceState);
48 | // Set up the viw and toolbar
49 | setContentView(R.layout.activity_main);
50 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
51 | setSupportActionBar(toolbar);
52 |
53 | // Get the main context
54 | mainContext = getApplicationContext();
55 |
56 | // Get the buttons and fields from the view
57 | mUsernameField = (EditText) findViewById(R.id.username_field);
58 | mGenerateUsernameButton = (ImageButton) findViewById(R.id.button_username_generate);
59 | mPickAvatarColourButton = (ImageButton) findViewById(R.id.button_avatar_colour_picker);
60 | mEnterChatButton = (Button) findViewById(R.id.button_enter_chat);
61 |
62 | // Get any saved username and avatar colour from the shared preference file
63 | sharedPreferences = getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE);
64 | String username = sharedPreferences.getString(USERNAME_KEY, "");
65 | String avatarColour = sharedPreferences.getString(AVATAR_COLOUR_KEY, "");
66 |
67 | // Set the username field if the user had saved one previously
68 | if (!username.isEmpty() && !username.equals("")){
69 | mUsernameField.setText(username);
70 | mUsernameField.setSelection(mUsernameField.getText().length());
71 | }
72 |
73 | // Set sCurrentAvatarColour to the default colour (currently md_pink_500
74 | sCurrentAvatarColour = ContextCompat.getColor(getApplicationContext(), R.color.md_pink_500);
75 |
76 | // Change the sCurrentAvatarColour to the user's previous value if one exists
77 | if (!avatarColour.isEmpty() && !avatarColour.equals("")){
78 | try{
79 | // Try and parse the colour into a colour int
80 | sCurrentAvatarColour = Color.parseColor(avatarColour);
81 | // Set the background colour of the mPickAvatarColourButton to the users preference
82 | GradientDrawable buttonBackgroundShape = (GradientDrawable) mPickAvatarColourButton.getBackground();
83 | buttonBackgroundShape.setColor(sCurrentAvatarColour);
84 | } catch (IllegalArgumentException e){
85 | // Otherwise use the default md_pink_500 colour from the resources
86 | sCurrentAvatarColour = ContextCompat.getColor(getApplicationContext(), R.color.md_pink_500);
87 | }
88 | }
89 |
90 | // Generate a random username when the mGenerateUsernameButton is clicked
91 | mGenerateUsernameButton.setOnClickListener(new View.OnClickListener() {
92 | @Override
93 | public void onClick(View view) {
94 | mUsernameField.setText(generateUsername());
95 | mUsernameField.setSelection(mUsernameField.getText().length());
96 | // Hide the soft keyboard if it was up
97 | hideSoftKeyboard(MainActivity.this, view);
98 | }
99 | });
100 |
101 | // Launch a colour picker and select the avatar colour when mPickAvatarColourButton is clicked
102 | mPickAvatarColourButton.setOnClickListener(new View.OnClickListener() {
103 | @Override
104 | public void onClick(View view) {
105 | launchColourPickerDialog(sCurrentAvatarColour);
106 | // Hide the soft keyboard if it was up
107 | hideSoftKeyboard(MainActivity.this, view);
108 | }
109 | });
110 |
111 | // Enter the chat room when mEnterChatButton is clicked
112 | mEnterChatButton.setOnClickListener(new View.OnClickListener() {
113 | @Override
114 | public void onClick(View view) {
115 | // Get the username and sCurrentAvatarColour
116 | String username = mUsernameField.getText().toString();
117 | String avatarColour = Integer.toHexString(sCurrentAvatarColour);
118 |
119 | // If the username is empty prompt the user and don't enter the chat
120 | if (username.isEmpty() || username.equals("")){
121 | Snackbar.make(mEnterChatButton, getString(R.string.error_empty_username), Snackbar.LENGTH_SHORT).show();
122 | } else {
123 | // Ensure the avatarColour string starts with # prior to sending and saving
124 | if (!avatarColour.startsWith("#")){
125 | avatarColour = "#" + avatarColour;
126 | }
127 |
128 | // Save the username and sCurrentAvatarColour in the shared preferences
129 | storeUsernameAndAvatarColour(username, avatarColour);
130 |
131 | // Enter the chat with the username and avatarColour sent to the ChatActivity
132 | Intent enterChatIntent = new Intent(getApplicationContext(), ChatActivity.class);
133 | enterChatIntent.putExtra(USERNAME_KEY, username);
134 | enterChatIntent.putExtra(AVATAR_COLOUR_KEY, avatarColour);
135 | startActivity(enterChatIntent);
136 | }
137 | }
138 | });
139 | }
140 |
141 | @Override
142 | public boolean onCreateOptionsMenu(Menu menu) {
143 | // Inflate the menu; this adds items to the action bar if it is present.
144 | getMenuInflater().inflate(R.menu.menu_main, menu);
145 | return true;
146 | }
147 |
148 | @Override
149 | public boolean onOptionsItemSelected(MenuItem item) {
150 | // Handle action bar item clicks here. The action bar will
151 | // automatically handle clicks on the Home/Up button, so long
152 | // as you specify a parent activity in AndroidManifest.xml.
153 | int id = item.getItemId();
154 |
155 | //noinspection SimplifiableIfStatement
156 | if (id == R.id.action_settings) {
157 | return true;
158 | }
159 |
160 | return super.onOptionsItemSelected(item);
161 | }
162 |
163 | /**
164 | * Generate a random username drawing from an array of moods and adjectives and animal names.
165 | * @return a String of the form "Mood/Adjective Animal"
166 | */
167 | private String generateUsername(){
168 | String[] moodsAndAdjectivesArray = getApplicationContext().getResources().getStringArray(R.array.moods_and_adjectives);
169 | String[] animalsArray = getApplicationContext().getResources().getStringArray(R.array.animals);
170 | return moodsAndAdjectivesArray[new Random().nextInt(moodsAndAdjectivesArray.length)] + " " +
171 | animalsArray[new Random().nextInt(animalsArray.length)];
172 | }
173 |
174 | /**
175 | * Launch the colour picker dialog using the Spectrum Colour picker
176 | * https://github.com/the-blue-alliance/spectrum
177 | */
178 | private void launchColourPickerDialog(Integer currentColour){
179 | new SpectrumDialog.Builder(getApplicationContext())
180 | .setColors(R.array.avatar_colours)
181 | .setSelectedColor(currentColour)
182 | .setDismissOnColorSelected(true)
183 | .setOnColorSelectedListener(new SpectrumDialog.OnColorSelectedListener() {
184 | @Override public void onColorSelected(boolean positiveResult, @ColorInt int color) {
185 | if (positiveResult) {
186 | // Change the button colour to the selected colour.
187 | GradientDrawable buttonBackgroundShape = (GradientDrawable) mPickAvatarColourButton.getBackground();
188 | buttonBackgroundShape.setColor(color);
189 | // Update the sCurrentAvatarColour
190 | sCurrentAvatarColour = color;
191 | }
192 | }
193 | }).build().show(getSupportFragmentManager(), COLOUR_PICKER_TAG);
194 | }
195 |
196 | /**
197 | * Hide the software keyboard from the view
198 | */
199 | private static void hideSoftKeyboard(Activity activity, View view){
200 | InputMethodManager mInputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
201 | mInputMethodManager.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0);
202 | }
203 |
204 | /**
205 | * Store the username and avatarColour selections in the SharedPreference file to persist the user's choices.
206 | */
207 | private void storeUsernameAndAvatarColour(String username, String avatarColour){
208 | // Store the values in the SharedPreferences file
209 | sharedPreferences.edit().putString(USERNAME_KEY, username).apply();
210 | sharedPreferences.edit().putString(AVATAR_COLOUR_KEY, avatarColour).apply();
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/app/src/main/java/com/danielcswain/nearbychat/ChatActivity.java:
--------------------------------------------------------------------------------
1 | package com.danielcswain.nearbychat;
2 |
3 | import android.app.Activity;
4 | import android.app.DialogFragment;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.IntentSender;
8 | import android.content.res.Configuration;
9 | import android.os.Bundle;
10 | import android.support.annotation.NonNull;
11 | import android.support.annotation.Nullable;
12 | import android.support.design.widget.Snackbar;
13 | import android.support.v4.content.ContextCompat;
14 | import android.support.v7.app.AppCompatActivity;
15 | import android.support.v7.widget.LinearLayoutManager;
16 | import android.support.v7.widget.RecyclerView;
17 | import android.util.Log;
18 | import android.view.MotionEvent;
19 | import android.view.View;
20 | import android.view.animation.Animation;
21 | import android.view.animation.LinearInterpolator;
22 | import android.view.animation.RotateAnimation;
23 | import android.view.inputmethod.InputMethodManager;
24 | import android.widget.ImageButton;
25 | import android.widget.ImageView;
26 | import android.widget.RelativeLayout;
27 |
28 | import com.danielcswain.nearbychat.Messages.MessageAdapter;
29 | import com.danielcswain.nearbychat.Messages.MessageDialog;
30 | import com.danielcswain.nearbychat.Messages.MessageObject;
31 | import com.danielcswain.nearbychat.Tasks.ImageCompressAsyncTask;
32 | import com.danielcswain.nearbychat.Users.UserAdapter;
33 | import com.danielcswain.nearbychat.Users.UserObject;
34 | import com.google.android.gms.common.ConnectionResult;
35 | import com.google.android.gms.common.api.GoogleApiClient;
36 | import com.google.android.gms.common.api.ResultCallback;
37 | import com.google.android.gms.common.api.Status;
38 | import com.google.android.gms.nearby.Nearby;
39 | import com.google.android.gms.nearby.messages.Message;
40 | import com.google.android.gms.nearby.messages.MessageListener;
41 | import com.nguyenhoanglam.imagepicker.activity.ImagePickerActivity;
42 | import com.nguyenhoanglam.imagepicker.model.Image;
43 | import com.tomergoldst.tooltips.ToolTipsManager;
44 |
45 | import java.util.ArrayList;
46 |
47 | import hani.momanii.supernova_emoji_library.Actions.EmojIconActions;
48 | import hani.momanii.supernova_emoji_library.Helper.EmojiconEditText;
49 |
50 | /**
51 | * Activity using the GoogleApiClient and the Nearby Api to send custom messages to nearby devices.
52 | */
53 | public class ChatActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
54 |
55 | private static final int REQUEST_RESOLVE_ERROR = 1002;
56 | private static final int REQUEST_IMAGE_PICKER = 1003;
57 | private static final String TAG = ChatActivity.class.getSimpleName();
58 | public static final String DIALOG_COUNTER_KEY = "dialog_counter";
59 | public static final String DIALOG_DISMISSED_KEY = "dialog_dismissed";
60 | private static final String DIALOG_FRAGMENT_TAG = "Dialog Fragment";
61 |
62 | private static GoogleApiClient mGoogleApiClient;
63 | private static Message mPubMessage;
64 | private static MessageListener mMessageListener;
65 |
66 | private String mUsername;
67 | private String mAvatarColour;
68 | private static UserObject sCurrentUser;
69 |
70 | private static ArrayList mMessageObjects;
71 | private static ArrayList mUserObjects;
72 | private static ArrayList mSelectedImages;
73 | private static boolean mImagesBeingSent;
74 | private EmojIconActions mEmojiActions;
75 | private static EmojiconEditText mTextField;
76 | private static ImageButton mSubmitButton;
77 | private static RecyclerView.Adapter mMessageRecyclerAdapter;
78 | private static RecyclerView.Adapter mUserRecyclerAdapter;
79 |
80 | public static RelativeLayout mRootContainer;
81 |
82 | public static ToolTipsManager toolTipsManager;
83 |
84 | // Animation used to rotate the send button about it's center.
85 | private static Animation mRotateAnimation = new RotateAnimation(0.0f, 360.0f,
86 | Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
87 | 0.5f);
88 |
89 | @Override
90 | protected void onCreate(Bundle savedInstanceState) {
91 | super.onCreate(savedInstanceState);
92 | setContentView(R.layout.activity_chat);
93 |
94 | // Get the calling intent
95 | Intent intent = getIntent();
96 |
97 | // Get the username and avatarColour for the user from the calling intent and create a UserObject
98 | mUsername = intent.getStringExtra(MainActivity.USERNAME_KEY);
99 | mAvatarColour = intent.getStringExtra(MainActivity.AVATAR_COLOUR_KEY);
100 | sCurrentUser = new UserObject(mUsername, mAvatarColour);
101 |
102 | // Set up the ArrayLists
103 | mMessageObjects = new ArrayList<>();
104 | mUserObjects = new ArrayList<>();
105 | mSelectedImages = new ArrayList<>();
106 |
107 | // Get the View for the snackbar
108 | mRootContainer = (RelativeLayout) findViewById(R.id.root_view);
109 |
110 | // Initiate the Tooltips manager
111 | toolTipsManager = new ToolTipsManager();
112 |
113 | // Set up the users views and adapters
114 | setUpUsersViews();
115 |
116 | // Set up the message views and adapters
117 | setUpMessagesViews();
118 |
119 | // Set up the message send views and buttons
120 | setUpMessageSendViews();
121 |
122 | // Build the GoogleApiClient
123 | buildGoogleApiClient();
124 |
125 | // Build the Nearby MessageListener
126 | buildMessageListener();
127 |
128 | // Check if the MessageDialog needs to be shown
129 | checkAndShowMessageDialog();
130 | }
131 |
132 | @Override
133 | protected void onStart() {
134 | super.onStart();
135 | // Connect to the GoogleApiClient if disconnected
136 | if (!mGoogleApiClient.isConnected()){
137 | mGoogleApiClient.connect();
138 | }
139 | }
140 |
141 | @Override
142 | public void onStop() {
143 | // Unpublish, unsubscribe and disconnect from the GoogleApiClient if connected.
144 | if (mGoogleApiClient.isConnected()) {
145 | if (mPubMessage != null) {
146 | unpublish();
147 | }
148 | unsubscribe();
149 | mGoogleApiClient.disconnect();
150 | }
151 | // Remove the user's from the userRecyclerViewAdapter
152 | mUserRecyclerAdapter.notifyItemRangeRemoved(0, mUserObjects.size());
153 | mUserObjects.clear();
154 | // Remove any images that may have been selected from the ArrayList
155 | mSelectedImages.clear();
156 | super.onStop();
157 | }
158 |
159 | @Override
160 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
161 | switch (requestCode){
162 | case REQUEST_RESOLVE_ERROR:
163 | // If the GoogleApiClient couldn't connect it will prompt the user for permission to use Nearby
164 | // using the following request code. Handle this request and connect to the GoogleApiClient if successful
165 | if (resultCode == RESULT_OK) {
166 | mGoogleApiClient.connect();
167 | } else {
168 | Log.e(TAG, "GoogleApiClient connection failed. Unable to resolve.");
169 | }
170 | break;
171 | case REQUEST_IMAGE_PICKER:
172 | // The image picker request was used, handle the result and get the images that were selected.
173 | if (resultCode == RESULT_OK && data !=null){
174 | // Get the ArrayList of selected images and add it to the selectedImages ArrayList
175 | ArrayList selectedImages = data.getParcelableArrayListExtra(ImagePickerActivity.INTENT_EXTRA_SELECTED_IMAGES);
176 | mSelectedImages.addAll(selectedImages);
177 | // Disable the editing of the edit text field until the image is sent (this is re-enabled in
178 | // the async task that publishes the image)
179 | toggleTextEntryField(false);
180 | } else {
181 | Log.e(TAG, "Unable to get the selected Images from the Image Picker");
182 | }
183 | break;
184 | default:
185 | super.onActivityResult(requestCode, resultCode, data);
186 | break;
187 | }
188 | }
189 |
190 | /**
191 | * Override the default Configuration change handling, this is to stop the activity being recreated on an orientation
192 | * change
193 | */
194 | @Override
195 | public void onConfigurationChanged(Configuration newConfig) {
196 | super.onConfigurationChanged(newConfig);
197 | }
198 |
199 |
200 |
201 | /**
202 | * Prompt the user to approve the Nearby Api connection if the connection failed
203 | */
204 | @Override
205 | public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
206 | if (connectionResult.hasResolution()) {
207 | try {
208 | connectionResult.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
209 | } catch (IntentSender.SendIntentException e) {
210 | e.printStackTrace();
211 | }
212 | } else {
213 | Log.e(TAG, "GoogleApiClient connection failed");
214 | }
215 | }
216 |
217 | @Override
218 | public void onConnectionSuspended(int i) {
219 |
220 | }
221 |
222 | @Override
223 | public void onConnected(@Nullable Bundle bundle) {
224 | // Subscribe to the chat
225 | subscribe();
226 | // Say hi, i.e. send a message so other's can see how many users are in the chat
227 | if (!sCurrentUser.getUsername().isEmpty() && !sCurrentUser.getUsername().isEmpty()){
228 | publishHelloMessage(sCurrentUser);
229 | }
230 | // Send any selected images and then reset the selection list.
231 | if (mSelectedImages != null) {
232 | if (!mSelectedImages.isEmpty()) {
233 | // Try and send the images
234 | for (Image image : mSelectedImages) {
235 | // indicate that there are images being sent
236 | mImagesBeingSent = true;
237 | // Change the mSubmitButton drawable to the loop/sync icon and animate it.
238 | mSubmitButton.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_loop_light));
239 | mSubmitButton.startAnimation(mRotateAnimation);
240 |
241 | // Compress and publish as an async task
242 | ImageCompressAsyncTask imageCompressAsyncTask = new ImageCompressAsyncTask();
243 | String[] params = { image.getPath(), mUsername, mAvatarColour, ImageCompressAsyncTask.TRUE };
244 | imageCompressAsyncTask.execute(params);
245 | }
246 | // Clear the mSelectedImages arrayList
247 | mSelectedImages.clear();
248 | }
249 | }
250 | }
251 |
252 | /**
253 | * Set up the views, array lists and adapters for the current user's area
254 | */
255 | private void setUpUsersViews(){
256 | // Get a reference to the RecyclerView
257 | RecyclerView mUsersRecyclerView = (RecyclerView) findViewById(R.id.users_list);
258 |
259 | // Using a stock linear layout manager for the RecyclerView
260 | RecyclerView.LayoutManager mUsersLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
261 | mUsersRecyclerView.setLayoutManager(mUsersLayoutManager);
262 |
263 | // Set up the RecyclerView Adapter
264 | mUserRecyclerAdapter = new UserAdapter(mUserObjects);
265 | mUsersRecyclerView.setAdapter(mUserRecyclerAdapter);
266 | }
267 |
268 | /**
269 | * Set up the views, array lists and adapters for the message area
270 | */
271 | private void setUpMessagesViews(){
272 | // Get a reference to the RecyclerView and set the recycler view to have fixed layout size
273 | // (as the layout is already full screen)
274 | RecyclerView mMessagesRecyclerView = (RecyclerView) findViewById(R.id.messages_list);
275 | mMessagesRecyclerView.setHasFixedSize(true);
276 |
277 | // Using a stock linear layout manager for the RecyclerView
278 | RecyclerView.LayoutManager mMessagesLayoutManager = new LinearLayoutManager(this);
279 | mMessagesRecyclerView.setLayoutManager(mMessagesLayoutManager);
280 |
281 | // Set up the RecyclerView Adapter
282 | mMessageRecyclerAdapter = new MessageAdapter(mMessageObjects);
283 | mMessagesRecyclerView.setAdapter(mMessageRecyclerAdapter);
284 | }
285 |
286 | /**
287 | * Set up the views, buttons and onClickListeners for the message text entry/send area
288 | */
289 | private void setUpMessageSendViews() {
290 | // Get the message send views
291 | ImageView mEmojiButton = (ImageView) findViewById(R.id.text_emoji_button);
292 | ImageView mImagePickerButton = (ImageView) findViewById(R.id.text_entry_image_button);
293 | mTextField = (EmojiconEditText) findViewById(R.id.text_entry_field);
294 | mSubmitButton = (ImageButton) findViewById(R.id.message_send_button);
295 |
296 | // Configure the rotation animation to signify a message is being sent
297 | mRotateAnimation.setInterpolator(new LinearInterpolator());
298 | mRotateAnimation.setDuration(500);
299 | mRotateAnimation.setRepeatCount(Animation.INFINITE);
300 |
301 | // Use SuperNova-Emoji to show a Whatsapp style Emoji replacement keyboard when the mEmojiButton
302 | // is clicked. Using the system default emoji rather than any styled (i.e. Apple Style or FB)
303 | mEmojiActions = new EmojIconActions(this, mRootContainer, mTextField, mEmojiButton);
304 | mEmojiActions.setUseSystemEmoji(true);
305 | mTextField.setUseSystemDefault(true);
306 | // Use the onTouchListener on the mEmojiButton to only show the emoji keyboard when the image is touched.
307 | // ImageView won't have focus over the EditText so the onTouch is the only way to trigger it without first getting focus
308 | mEmojiButton.setOnTouchListener(new View.OnTouchListener() {
309 | @Override
310 | public boolean onTouch(View view, MotionEvent motionEvent) {
311 | // Open and show the SuperNova-Emoji keyboard.
312 | mEmojiActions.ShowEmojIcon();
313 | return false;
314 | }
315 | });
316 |
317 | // Use ImagePicker to show an image picker to send an image to the chat
318 | mImagePickerButton.setOnTouchListener(new View.OnTouchListener() {
319 | @Override
320 | public boolean onTouch(View view, MotionEvent motionEvent) {
321 | // Request the permissions required and open the imagePicker
322 | Intent intent = new Intent(ChatActivity.this, ImagePickerActivity.class);
323 | intent.putExtra(ImagePickerActivity.INTENT_EXTRA_MODE, ImagePickerActivity.MODE_SINGLE);
324 | intent.putExtra(ImagePickerActivity.INTENT_EXTRA_SHOW_CAMERA, false);
325 | startActivityForResult(intent, REQUEST_IMAGE_PICKER);
326 | return false;
327 | }
328 | });
329 |
330 |
331 | // Send a new message to the chat when the submit button is clicked
332 | mSubmitButton.setOnClickListener(new View.OnClickListener() {
333 | @Override
334 | public void onClick(View view) {
335 | // Get the edit text field's text
336 | String messageBody = mTextField.getText().toString();
337 |
338 | if (!messageBody.isEmpty() && !messageBody.matches("^(\\s+)$")){
339 | // Change the mSubmitbutton drawable to the loop/sync icon and animate it.
340 | mSubmitButton.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.ic_loop_light));
341 | mSubmitButton.startAnimation(mRotateAnimation);
342 |
343 | // Publish the message (it will be added to the chat when published successfully and the animation will be stopped)
344 | publishMessage(new MessageObject(mUsername, messageBody, MessageObject.MESSAGE_CONTENT_TEXT, mAvatarColour, true));
345 |
346 | // Hide the keyboard and reset the message text field
347 | hideSoftKeyboard(ChatActivity.this, view);
348 | mTextField.setText("");
349 | mTextField.clearFocus();
350 | } else {
351 | // The messageBody is empty or contains only whitespace characters
352 | showSnackbar(getString(R.string.error_empty_message));
353 | }
354 | }
355 | });
356 | }
357 |
358 | /**
359 | * Build the GoogleApiClient to use the Nearby Messages Api
360 | */
361 | private void buildGoogleApiClient() {
362 | if (mGoogleApiClient != null) {
363 | return;
364 | }
365 | mGoogleApiClient = new GoogleApiClient.Builder(this)
366 | .addApi(Nearby.MESSAGES_API)
367 | .addConnectionCallbacks(this)
368 | .addOnConnectionFailedListener(this)
369 | .build();
370 | }
371 |
372 | /**
373 | * Build the Nearby MessageListener attached in subscribe that handles the messages as they are received.
374 | */
375 | private void buildMessageListener(){
376 | mMessageListener = new MessageListener() {
377 | @Override
378 | public void onFound(Message message) {
379 | if (message.getType().equals(MessageObject.MESSAGE_TYPE)) {
380 | // If the message has the "Message" type then display the message in the chat
381 | // Set the fromUser to false as it is a received message
382 | displayMessageInChat(message, false);
383 | } else if(message.getType().equals(UserObject.MESSAGE_TYPE)){
384 | // If the message has the "Greeting" type then display the userObject
385 | addUserToUsersContainer(UserObject.fromNearbyMessage(message));
386 | }
387 | }
388 |
389 | @Override
390 | public void onLost(Message message) {
391 | if (message.getType().equals(MessageObject.MESSAGE_TYPE)) {
392 | // If the message has the "Message" type then remove it if it was lost
393 | removeMessageOnLost(message);
394 | } else if (message.getType().equals(UserObject.MESSAGE_TYPE)){
395 | // If the message has the "Greeting" type then remove it if it was lost
396 | removeUserFromUsersContainer(UserObject.fromNearbyMessage(message));
397 | }
398 | }
399 | };
400 | }
401 |
402 | /**
403 | * Check if the user hasn't permanently dismissed the Message Info dialog, if not then show it
404 | */
405 | private void checkAndShowMessageDialog() {
406 | // Get the amount of times the dialog has been dismissed and if it has been tagged as being permanently dismissed.
407 | int counter = MainActivity.sharedPreferences.getInt(DIALOG_COUNTER_KEY, 1);
408 | boolean dismissed = MainActivity.sharedPreferences.getBoolean(DIALOG_DISMISSED_KEY, false);
409 |
410 | // Only show the dialog if the user hasn't permanently dismissed it or the counter is less than 5.
411 | if (counter < 5 && !dismissed){
412 | DialogFragment mDialog = new MessageDialog();
413 | mDialog.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
414 | }
415 |
416 | }
417 |
418 | /**
419 | * Subscribe to the chat and receive nearby messages using the mMessageListener
420 | */
421 | private void subscribe(){
422 | Nearby.Messages.subscribe(mGoogleApiClient, mMessageListener)
423 | .setResultCallback(new ResultCallback() {
424 | @Override
425 | public void onResult(@NonNull Status status) {
426 | if (status.isSuccess()) {
427 | // Subscribed successfully, notify the user.
428 | if (mImagesBeingSent){
429 | // If there are image(s) being sent change the notification to indicate that
430 | showSnackbar(getApplicationContext().getString(R.string.message_image_send_in_progress));
431 | mImagesBeingSent = false;
432 | } else {
433 | // Otherwise, just show the stock connection successful message.
434 | showSnackbar(getApplicationContext().getString(R.string.nearby_subscription_success));
435 | }
436 | } else {
437 | // Unsuccessfully subscribed, notify the user.
438 | showSnackbar(getApplicationContext().getString(R.string.nearby_subscription_failed));
439 | }
440 | }
441 | });
442 | }
443 |
444 | /**
445 | * Publish the MessageObject to nearby devices.
446 | * @param messageObject the messageObject to be published
447 | */
448 | public static void publishMessage(final MessageObject messageObject) {
449 | mPubMessage = MessageObject.newNearbyMessage(messageObject);
450 | //re-enable the edittext field in case it has been disabled (by the imagepicker)
451 | toggleTextEntryField(true);
452 | // Publish the message and display in the chat on the device if the publish action was successful
453 | Nearby.Messages.publish(mGoogleApiClient, mPubMessage)
454 | .setResultCallback(new ResultCallback() {
455 | @Override
456 | public void onResult(@NonNull Status status) {
457 | if (status.isSuccess()){
458 | // Add to channel as the message was published successfully (set fromUser to true)
459 | displayMessageInChat(mPubMessage, true);
460 | } else {
461 | // Show a snackbar with a publish failed message
462 | showSnackbar(MainActivity.mainContext.getString(R.string.nearby_publish_message_failed));
463 | }
464 | // Stop the animation regardless of successful publishing or not
465 | resetMessageSendAnimation();
466 | // Reset the message button's drawable to the send resource
467 | mSubmitButton.setImageDrawable(ContextCompat.getDrawable(MainActivity.mainContext,R.drawable.ic_send_light));
468 | }
469 | });
470 | }
471 |
472 | /**
473 | * Publish a HelloMessage containing the UserObject of the user joining the chat to
474 | * enable a count of the current active users in the chat and show the avatar in the usersContainer.
475 | * @param userObject the user joining the chat
476 | */
477 | private void publishHelloMessage(final UserObject userObject){
478 | mPubMessage = UserObject.newNearbyMessage(userObject);
479 | // Publish the message to the channel and display in the user's bar the user avatar with the avatar colour
480 | Nearby.Messages.publish(mGoogleApiClient, mPubMessage)
481 | .setResultCallback(new ResultCallback() {
482 | @Override
483 | public void onResult(@NonNull Status status) {
484 | if (status.isSuccess()){
485 | // Show the user avatar in the chat user window with the user's colour.
486 | addUserToUsersContainer(userObject);
487 | } else {
488 | // Log the error
489 | Log.e(TAG, "Couldn't send the user greeting to the channel: " + status);
490 | }
491 | }
492 | });
493 | }
494 |
495 | /**
496 | * Unsubscribe and stop listening for nearby messages.
497 | */
498 | private void unsubscribe() {
499 | Nearby.Messages.unsubscribe(mGoogleApiClient, mMessageListener);
500 | }
501 |
502 | /**
503 | * Unpublish the message from the chat
504 | */
505 | private void unpublish() {
506 | Nearby.Messages.unpublish(mGoogleApiClient, mPubMessage);
507 | }
508 |
509 | /**
510 | * Display the received message in the chat feed
511 | * @param message the message received
512 | */
513 | private static void displayMessageInChat(Message message, boolean fromUser){
514 | // Get the messageObject from the received Nearby Message
515 | MessageObject receivedMessage = MessageObject.fromNearbyMessage(message);
516 | // Set the fromUser to the passed boolean
517 | receivedMessage.setFromUser(fromUser);
518 | // Add to the RecyclerView if it doesn't already exist (i.e. the user has left and come back quickly
519 | // without stopping the activity)
520 | if (!mMessageObjects.contains(receivedMessage)) {
521 | mMessageObjects.add(receivedMessage);
522 | mMessageRecyclerAdapter.notifyItemInserted(mMessageObjects.size() - 1);
523 | }
524 | }
525 |
526 | /**
527 | * Remove a lost message from the message feed
528 | * @param message the message lost
529 | */
530 | private void removeMessageOnLost(Message message){
531 | // Called when a message is no longer detectable nearby.
532 | MessageObject lostMessage = MessageObject.fromNearbyMessage(message);
533 | // Set the fromUser to false as it wasn't from the currentUser
534 | lostMessage.setFromUser(false);
535 | // Remove from the RecyclerView
536 | mMessageRecyclerAdapter.notifyItemRemoved(mMessageObjects.indexOf(lostMessage));
537 | mMessageObjects.remove(lostMessage);
538 | }
539 |
540 | /**
541 | * Remove the userIcon referring to the user from the usersContainer
542 | * @param userObject the user being removed
543 | */
544 | private void removeUserFromUsersContainer(UserObject userObject){
545 | mUserRecyclerAdapter.notifyItemRemoved(mUserObjects.indexOf(userObject));
546 | mUserObjects.remove(userObject);
547 | }
548 |
549 | /**
550 | * Add a userIcon to the User container to show who's in the chat currently
551 | * @param userObject the UserObject representing the user that has just joined the chat
552 | */
553 | private void addUserToUsersContainer(UserObject userObject){
554 | mUserObjects.add(userObject);
555 | mUserRecyclerAdapter.notifyItemInserted(mUserObjects.size() - 1);
556 |
557 | // Set the users_list background colour to the messageUsersList colour now that items are in the view
558 | RecyclerView usersList = (RecyclerView) mRootContainer.findViewById(R.id.users_list);
559 | usersList.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.messageUsersField));
560 | }
561 |
562 | /**
563 | * Hide the software keyboard from the view
564 | */
565 | private static void hideSoftKeyboard(Activity activity, View view){
566 | InputMethodManager mInputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
567 | mInputMethodManager.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0);
568 | }
569 |
570 | /**
571 | * Show a snackbar with a given message
572 | */
573 | public static void showSnackbar(String message){
574 | if (mRootContainer != null){
575 | Snackbar.make(mRootContainer, message, Snackbar.LENGTH_SHORT).show();
576 | }
577 | }
578 |
579 | /**
580 | * Toggle the editText field to be focusable (editable) or not.
581 | */
582 | public static void toggleTextEntryField(boolean enabled){
583 | mTextField.setFocusable(enabled);
584 | mTextField.setFocusableInTouchMode(enabled);
585 | }
586 |
587 | /**
588 | * Reset the MessageSend animation
589 | */
590 | private static void resetMessageSendAnimation(){
591 | mRotateAnimation.cancel();
592 | mRotateAnimation.reset();
593 | }
594 | }
595 |
--------------------------------------------------------------------------------