├── .gitignore ├── .gitmodules ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── layer │ │ │ └── messenger │ │ │ ├── App.java │ │ │ ├── AppSettingsActivity.java │ │ │ ├── BaseActivity.java │ │ │ ├── ConversationSettingsActivity.java │ │ │ ├── ConversationsListActivity.java │ │ │ ├── MessagesListActivity.java │ │ │ ├── PushNotificationReceiver.java │ │ │ ├── ResumeActivity.java │ │ │ ├── makemoji │ │ │ ├── MakeMojiAtlasComposer.java │ │ │ ├── MakeMojiCellFactory.java │ │ │ ├── MakeMojiConversationsAdapter.java │ │ │ └── MakeMojiConversationsRecyclerView.java │ │ │ └── util │ │ │ ├── AuthenticationProvider.java │ │ │ ├── Log.java │ │ │ └── Util.java │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_block_white_24dp.png │ │ └── notification.png │ │ ├── drawable-ldpi │ │ └── notification.png │ │ ├── drawable-mdpi │ │ ├── ic_block_white_24dp.png │ │ └── notification.png │ │ ├── drawable-nodpi │ │ └── layer_launcher.png │ │ ├── drawable-xhdpi │ │ ├── ic_block_white_24dp.png │ │ └── notification.png │ │ ├── drawable-xxhdpi │ │ ├── ic_block_white_24dp.png │ │ └── notification.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_block_white_24dp.png │ │ └── notification.png │ │ ├── layout │ │ ├── activity_app_settings.xml │ │ ├── activity_conversation_settings.xml │ │ ├── activity_conversations_list.xml │ │ ├── activity_messages_list.xml │ │ ├── activity_resume.xml │ │ ├── atlas_message_composer.xml │ │ └── participant_item.xml │ │ ├── menu │ │ ├── menu_conversation_details.xml │ │ ├── menu_conversations_list.xml │ │ ├── menu_messages_list.xml │ │ └── menu_settings.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml │ ├── providerdemo │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── layer │ │ │ └── messenger │ │ │ └── flavor │ │ │ ├── AppIdScanner.java │ │ │ ├── DemoAtlasIdScannerActivity.java │ │ │ ├── DemoAuthenticationProvider.java │ │ │ ├── DemoLoginActivity.java │ │ │ ├── DemoParticipant.java │ │ │ ├── DemoParticipantProvider.java │ │ │ └── Flavor.java │ └── res │ │ ├── drawable │ │ └── app_id_scanner_outline.xml │ │ ├── layout │ │ ├── activity_app_id_scanner.xml │ │ └── activity_login_demo.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ └── strings.xml │ └── providerrails │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── layer │ │ └── messenger │ │ └── flavor │ │ ├── Flavor.java │ │ ├── RailsAuthenticationProvider.java │ │ ├── RailsLoginActivity.java │ │ ├── RailsParticipant.java │ │ ├── RailsParticipantProvider.java │ │ └── util │ │ └── CustomEndpoint.java │ └── res │ ├── layout │ └── activity_login_rails.xml │ └── values │ └── strings.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | local.properties 3 | .idea/ 4 | .DS_Store 5 | build 6 | captures 7 | *.iml 8 | app/build 9 | *.apk 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/.gitmodules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MakeMoji Atlas Messenger Example 2 | 3 | An example of how to use the the Atlas Messaging Framework together with the MakeMoji Android SDK. 4 | Refer to the original README below for Atlas instructions and [here](https://github.com/makemoji/MakemojiSDK-Android) for the MakeMoji SDK. 5 | To run the sample, add the line "mm.key=Your-key" to local.properties or directly into the projects build.gradle so it can be loaded in App.java. 6 | 7 | 8 | All new classes have been added under [com.layer.messenger.makemoji](app/src/main/java/com/layer/messenger/makemoji/). 9 | Use MakeMojiAtlasComposer and MakeMojiCellFactory and to compose and display MakeMoji messages. 10 | Copy the new version of [atlas_message_composer.xml](app/src/main/res/layout/atlas_message_composer.xml) to your project so the composer will use a compatible MojiEditText. 11 | Refer to the modified [MessagesListActivity](app/src/main/java/com/layer/messenger/MessagesListActivity.java) to see how to use the Atlas composer and MojiInputLayout together. 12 | 13 | # Atlas Messenger for Android 14 | 15 | Atlas Messenger is a fully-featured messaging app following [Material Design guidelines](https://www.google.com/design/spec/material-design/introduction.html#introduction-goals), built on top of the [Layer SDK](https://layer.com/), using the [Atlas UI toolkit](https://github.com/layerhq/Atlas-Android). 16 | 17 | ##Structure 18 | 19 | * **App:** Application class. 20 | * Activities: 21 | * **BaseActivity:** Base Activity class for handling menu titles and the menu back button and ensuring the `LayerClient` is connected when resuming Activities. 22 | * **ConversationsListActivity:** List of all Conversations for the authenticated user. 23 | * **MessagesListActivity:** List of Messages within a particular Conversation. Also handles message composition and addressing. 24 | * **SettingsActivity:** Global application settings. 25 | * **ConversationDetailsActivity:** Settings for a particular Conversation. 26 | * **PushNotificationReceiver:** Handles `com.layer.sdk.PUSH` Intents and displays notifications. 27 | * **AuthenticationProvider:** Interface used by the Messenger app to authenticate users. Default implementations are provided by gradle `flavors`; see *Build Variants* below. 28 | 29 | ##Identity Providers 30 | 31 | Atlas Messenger uses the `AuthenticationProvider` interface to authenticate with various backends. Additional identity providers can integrate with Atlas Messenger by implementing `AuthenticationProvider` and using a custom login Activity, similar to the provided flavors below. 32 | 33 | ###Provided Flavors 34 | Two default implementations are provided via [product flavors](http://developer.android.com/tools/building/configuring-gradle.html#workBuildVariants), where each flavor implements a custom `AuthenticationProvider`, a custom Atlas `Participant`, and provides login Activities for gathering their required credentials: 35 | 36 | 1. **atlasprovider:** For use in the [Layer Atlas demo](https://getatlas.layer.com/android). This authentication flow utilizes a QR-Code scanner to capture a Layer App ID from the Layer developer dashboard. The scanner can be bypassed by supplying your Atlas demo App ID in the `App.LAYER_APP_ID` constant. 37 | 2. **herokuprovider:** For use with the deployable [Rails Provider](https://github.com/layerhq/layer-identity-provider) backend. Your Layer App ID must be set in the `App.LAYER_APP_ID` constant. 38 | 39 | In Android Studio, switch flavors using Build Variants, typically in the side tab on the lower left of the Android Studio window. 40 | 41 | ##Contributing 42 | Atlas is an Open Source project maintained by Layer. Feedback and contributions are always welcome and the maintainers try to process patches as quickly as possible. Feel free to open up a Pull Request or Issue on Github. 43 | 44 | ##License 45 | 46 | Atlas is licensed under the terms of the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). Please see the [LICENSE](LICENSE) file for full details. 47 | 48 | ##Contact 49 | 50 | Atlas was developed in San Francisco by the Layer team. If you have any technical questions or concerns about this project feel free to reach out to [Layer Support](mailto:support@layer.com). 51 | 52 | ###Credits 53 | 54 | * [Steven Jones](https://github.com/sjones94549) 55 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | src/providerrails/res/raw/ -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.layer.messenger" 9 | minSdkVersion 15 10 | targetSdkVersion 23 11 | versionCode 30 12 | versionName "$versionCode" 13 | buildConfigField "String", "MakeMojiKey", "\""+getProps('mm.key')+"\"" 14 | } 15 | 16 | productFlavors { 17 | providerrails { 18 | applicationId = defaultConfig.applicationId + ".providerrails" 19 | } 20 | 21 | providerdemo { 22 | applicationId = defaultConfig.applicationId + ".providerdemo" 23 | } 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | } 33 | 34 | dependencies { 35 | // All flavors 36 | compile 'com.layer.atlas:layer-atlas:0.2.11' 37 | 38 | // Flavor-specific dependencies 39 | providerdemoCompile 'com.google.android.gms:play-services-vision:8.4.0' 40 | compile 'com.makemoji:makemoji-sdk-android:0.9.725' 41 | } 42 | repositories { 43 | jcenter() 44 | maven { 45 | url "https://dl.bintray.com/mm/maven/" 46 | } 47 | } 48 | def getProps(String propName) { 49 | def propsFile = rootProject.file('local.properties') 50 | if (propsFile.exists()) { 51 | def props = new Properties() 52 | props.load(new FileInputStream(propsFile)) 53 | return props[propName] 54 | } else { 55 | return ""; 56 | } 57 | } -------------------------------------------------------------------------------- /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/steven/dev/android-sdk-macosx/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 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 28 | 29 | 30 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 70 | 71 | 72 | 73 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/App.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.Context; 6 | 7 | import com.layer.atlas.messagetypes.text.TextCellFactory; 8 | import com.layer.atlas.messagetypes.threepartimage.ThreePartImageUtils; 9 | import com.layer.atlas.provider.ParticipantProvider; 10 | import com.layer.atlas.util.Util; 11 | import com.layer.atlas.util.picasso.requesthandlers.MessagePartRequestHandler; 12 | import com.layer.messenger.util.AuthenticationProvider; 13 | import com.layer.sdk.LayerClient; 14 | import com.makemoji.mojilib.Moji; 15 | import com.squareup.picasso.Picasso; 16 | 17 | import java.util.Arrays; 18 | 19 | /** 20 | * App provides static access to a LayerClient and other Atlas and Messenger context, including 21 | * AuthenticationProvider, ParticipantProvider, Participant, and Picasso. 22 | * 23 | * App.Flavor allows build variants to target different environments, such as the Atlas Demo and the 24 | * open source Rails Identity Provider. Switch flavors with the Android Studio `Build Variant` tab. 25 | * When using a flavor besides the Atlas Demo you must manually set your Layer App ID and GCM Sender 26 | * ID in that flavor's Flavor.java. 27 | * 28 | * @see com.layer.messenger.App.Flavor 29 | * @see com.layer.messenger.flavor.Flavor 30 | * @see LayerClient 31 | * @see ParticipantProvider 32 | * @see Picasso 33 | * @see AuthenticationProvider 34 | */ 35 | public class App extends Application { 36 | 37 | private static Application sInstance; 38 | private static Flavor sFlavor = new com.layer.messenger.flavor.Flavor(); 39 | 40 | private static LayerClient sLayerClient; 41 | private static ParticipantProvider sParticipantProvider; 42 | private static AuthenticationProvider sAuthProvider; 43 | private static Picasso sPicasso; 44 | 45 | 46 | //============================================================================================== 47 | // Application Overrides 48 | //============================================================================================== 49 | 50 | @Override 51 | public void onCreate() { 52 | super.onCreate(); 53 | Moji.initialize(this,BuildConfig.MakeMojiKey); 54 | 55 | // Enable verbose logging in debug builds 56 | if (BuildConfig.DEBUG) { 57 | com.layer.atlas.util.Log.setLoggingEnabled(true); 58 | com.layer.messenger.util.Log.setAlwaysLoggable(true); 59 | LayerClient.setLoggingEnabled(this, true); 60 | } 61 | 62 | // Allow the LayerClient to track app state 63 | LayerClient.applicationCreated(this); 64 | 65 | sInstance = this; 66 | } 67 | 68 | public static Application getInstance() { 69 | return sInstance; 70 | } 71 | 72 | 73 | //============================================================================================== 74 | // Identity Provider Methods 75 | //============================================================================================== 76 | 77 | /** 78 | * Routes the user to the proper Activity depending on their authenticated state. Returns 79 | * `true` if the user has been routed to another Activity, or `false` otherwise. 80 | * 81 | * @param from Activity to route from. 82 | * @return `true` if the user has been routed to another Activity, or `false` otherwise. 83 | */ 84 | public static boolean routeLogin(Activity from) { 85 | return getAuthenticationProvider().routeLogin(getLayerClient(), getLayerAppId(), from); 86 | } 87 | 88 | /** 89 | * Authenticates with the AuthenticationProvider and Layer, returning asynchronous results to 90 | * the provided callback. 91 | * 92 | * @param credentials Credentials associated with the current AuthenticationProvider. 93 | * @param callback Callback to receive authentication results. 94 | */ 95 | @SuppressWarnings("unchecked") 96 | public static void authenticate(Object credentials, AuthenticationProvider.Callback callback) { 97 | LayerClient client = getLayerClient(); 98 | if (client == null) return; 99 | String layerAppId = getLayerAppId(); 100 | if (layerAppId == null) return; 101 | getAuthenticationProvider() 102 | .setCredentials(credentials) 103 | .setCallback(callback); 104 | client.authenticate(); 105 | } 106 | 107 | /** 108 | * Deauthenticates with Layer and clears cached AuthenticationProvider credentials. 109 | * 110 | * @param callback Callback to receive deauthentication success and failure. 111 | */ 112 | public static void deauthenticate(final Util.DeauthenticationCallback callback) { 113 | Util.deauthenticate(getLayerClient(), new Util.DeauthenticationCallback() { 114 | @Override 115 | @SuppressWarnings("unchecked") 116 | public void onDeauthenticationSuccess(LayerClient client) { 117 | getAuthenticationProvider().setCredentials(null); 118 | callback.onDeauthenticationSuccess(client); 119 | } 120 | 121 | @Override 122 | public void onDeauthenticationFailed(LayerClient client, String reason) { 123 | callback.onDeauthenticationFailed(client, reason); 124 | } 125 | }); 126 | } 127 | 128 | 129 | //============================================================================================== 130 | // Getters / Setters 131 | //============================================================================================== 132 | 133 | /** 134 | * Gets or creates a LayerClient, using a default set of LayerClient.Options and flavor-specific 135 | * App ID and Options from the `generateLayerClient` method. Returns `null` if the flavor was 136 | * unable to create a LayerClient (due to no App ID, etc.). 137 | * 138 | * @return New or existing LayerClient, or `null` if a LayerClient could not be constructed. 139 | * @see Flavor#generateLayerClient(Context, LayerClient.Options) 140 | */ 141 | public static LayerClient getLayerClient() { 142 | if (sLayerClient == null) { 143 | // Custom options for constructing a LayerClient 144 | LayerClient.Options options = new LayerClient.Options() 145 | 146 | /* Fetch the minimum amount per conversation when first authenticated */ 147 | .historicSyncPolicy(LayerClient.Options.HistoricSyncPolicy.FROM_LAST_MESSAGE) 148 | 149 | /* Automatically download text and ThreePartImage info/preview */ 150 | .autoDownloadMimeTypes(Arrays.asList( 151 | TextCellFactory.MIME_TYPE, 152 | ThreePartImageUtils.MIME_TYPE_INFO, 153 | ThreePartImageUtils.MIME_TYPE_PREVIEW)); 154 | 155 | // Allow flavor to specify Layer App ID and customize Options. 156 | sLayerClient = sFlavor.generateLayerClient(sInstance, options); 157 | 158 | // Flavor was unable to generate Layer Client (no App ID, etc.) 159 | if (sLayerClient == null) return null; 160 | 161 | /* Register AuthenticationProvider for handling authentication challenges */ 162 | sLayerClient.registerAuthenticationListener(getAuthenticationProvider()); 163 | } 164 | return sLayerClient; 165 | } 166 | 167 | public static String getLayerAppId() { 168 | return sFlavor.getLayerAppId(); 169 | } 170 | 171 | public static ParticipantProvider getParticipantProvider() { 172 | if (sParticipantProvider == null) { 173 | sParticipantProvider = sFlavor.generateParticipantProvider(sInstance, getAuthenticationProvider()); 174 | } 175 | return sParticipantProvider; 176 | } 177 | 178 | public static AuthenticationProvider getAuthenticationProvider() { 179 | if (sAuthProvider == null) { 180 | sAuthProvider = sFlavor.generateAuthenticationProvider(sInstance); 181 | 182 | // If we have cached credentials, try authenticating with Layer 183 | LayerClient layerClient = getLayerClient(); 184 | if (layerClient != null && sAuthProvider.hasCredentials()) layerClient.authenticate(); 185 | } 186 | return sAuthProvider; 187 | } 188 | 189 | public static Picasso getPicasso() { 190 | if (sPicasso == null) { 191 | // Picasso with custom RequestHandler for loading from Layer MessageParts. 192 | sPicasso = new Picasso.Builder(sInstance) 193 | .addRequestHandler(new MessagePartRequestHandler(getLayerClient())) 194 | .build(); 195 | } 196 | return sPicasso; 197 | } 198 | 199 | /** 200 | * Flavor is used by Atlas Messenger to switch environments. 201 | * 202 | * @see com.layer.messenger.flavor.Flavor 203 | */ 204 | public interface Flavor { 205 | String getLayerAppId(); 206 | 207 | LayerClient generateLayerClient(Context context, LayerClient.Options options); 208 | 209 | AuthenticationProvider generateAuthenticationProvider(Context context); 210 | 211 | ParticipantProvider generateParticipantProvider(Context context, AuthenticationProvider authenticationProvider); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/AppSettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.ProgressDialog; 5 | import android.content.DialogInterface; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.text.TextUtils; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.CompoundButton; 12 | import android.widget.Switch; 13 | import android.widget.TextView; 14 | import android.widget.Toast; 15 | 16 | import com.layer.atlas.AtlasAvatar; 17 | import com.layer.atlas.provider.Participant; 18 | import com.layer.atlas.util.Util; 19 | import com.layer.messenger.util.Log; 20 | import com.layer.sdk.LayerClient; 21 | import com.layer.sdk.changes.LayerChangeEvent; 22 | import com.layer.sdk.exceptions.LayerException; 23 | import com.layer.sdk.listeners.LayerAuthenticationListener; 24 | import com.layer.sdk.listeners.LayerChangeEventListener; 25 | import com.layer.sdk.listeners.LayerConnectionListener; 26 | import com.layer.sdk.messaging.Conversation; 27 | 28 | import java.util.List; 29 | 30 | public class AppSettingsActivity extends BaseActivity implements LayerConnectionListener, LayerAuthenticationListener, LayerChangeEventListener, View.OnLongClickListener { 31 | /* Account */ 32 | private AtlasAvatar mAvatar; 33 | private TextView mUserName; 34 | private TextView mUserState; 35 | private Button mLogoutButton; 36 | 37 | /* Notifications */ 38 | private Switch mShowNotifications; 39 | 40 | /* Debug */ 41 | private Switch mVerboseLogging; 42 | private TextView mAppVersion; 43 | private TextView mAndroidVersion; 44 | private TextView mAtlasVersion; 45 | private TextView mLayerVersion; 46 | private TextView mUserId; 47 | 48 | /* Statistics */ 49 | private TextView mConversationCount; 50 | private TextView mMessageCount; 51 | private TextView mUnreadMessageCount; 52 | 53 | /* Rich Content */ 54 | private TextView mDiskUtilization; 55 | private TextView mDiskAllowance; 56 | private TextView mAutoDownloadMimeTypes; 57 | 58 | public AppSettingsActivity() { 59 | super(R.layout.activity_app_settings, R.menu.menu_settings, R.string.title_settings, true); 60 | } 61 | 62 | @Override 63 | protected void onCreate(Bundle savedInstanceState) { 64 | super.onCreate(savedInstanceState); 65 | 66 | // View cache 67 | mAvatar = (AtlasAvatar) findViewById(R.id.avatar); 68 | mUserName = (TextView) findViewById(R.id.user_name); 69 | mUserState = (TextView) findViewById(R.id.user_state); 70 | mLogoutButton = (Button) findViewById(R.id.logout_button); 71 | mShowNotifications = (Switch) findViewById(R.id.show_notifications_switch); 72 | mVerboseLogging = (Switch) findViewById(R.id.logging_switch); 73 | mAppVersion = (TextView) findViewById(R.id.app_version); 74 | mAtlasVersion = (TextView) findViewById(R.id.atlas_version); 75 | mLayerVersion = (TextView) findViewById(R.id.layer_version); 76 | mAndroidVersion = (TextView) findViewById(R.id.android_version); 77 | mUserId = (TextView) findViewById(R.id.user_id); 78 | mConversationCount = (TextView) findViewById(R.id.conversation_count); 79 | mMessageCount = (TextView) findViewById(R.id.message_count); 80 | mUnreadMessageCount = (TextView) findViewById(R.id.unread_message_count); 81 | mDiskUtilization = (TextView) findViewById(R.id.disk_utilization); 82 | mDiskAllowance = (TextView) findViewById(R.id.disk_allowance); 83 | mAutoDownloadMimeTypes = (TextView) findViewById(R.id.auto_download_mime_types); 84 | mAvatar.init(getParticipantProvider(), getPicasso()); 85 | 86 | // Long-click copy-to-clipboard 87 | mUserName.setOnLongClickListener(this); 88 | mUserState.setOnLongClickListener(this); 89 | mAppVersion.setOnLongClickListener(this); 90 | mAndroidVersion.setOnLongClickListener(this); 91 | mAtlasVersion.setOnLongClickListener(this); 92 | mLayerVersion.setOnLongClickListener(this); 93 | mUserId.setOnLongClickListener(this); 94 | mConversationCount.setOnLongClickListener(this); 95 | mMessageCount.setOnLongClickListener(this); 96 | mUnreadMessageCount.setOnLongClickListener(this); 97 | mDiskUtilization.setOnLongClickListener(this); 98 | mDiskAllowance.setOnLongClickListener(this); 99 | mAutoDownloadMimeTypes.setOnLongClickListener(this); 100 | 101 | // Buttons and switches 102 | mLogoutButton.setOnClickListener(new View.OnClickListener() { 103 | @Override 104 | public void onClick(final View v) { 105 | setEnabled(false); 106 | new AlertDialog.Builder(AppSettingsActivity.this) 107 | .setCancelable(false) 108 | .setMessage(R.string.alert_message_logout) 109 | .setPositiveButton(R.string.alert_button_logout, new DialogInterface.OnClickListener() { 110 | @Override 111 | public void onClick(DialogInterface dialog, int which) { 112 | if (Log.isLoggable(Log.VERBOSE)) { 113 | Log.v("Deauthenticating"); 114 | } 115 | dialog.dismiss(); 116 | final ProgressDialog progressDialog = new ProgressDialog(AppSettingsActivity.this); 117 | progressDialog.setMessage(getResources().getString(R.string.alert_dialog_logout)); 118 | progressDialog.setCancelable(false); 119 | progressDialog.show(); 120 | App.deauthenticate(new Util.DeauthenticationCallback() { 121 | @Override 122 | public void onDeauthenticationSuccess(LayerClient client) { 123 | if (Log.isLoggable(Log.VERBOSE)) { 124 | Log.v("Successfully deauthenticated"); 125 | } 126 | progressDialog.dismiss(); 127 | setEnabled(true); 128 | App.routeLogin(AppSettingsActivity.this); 129 | } 130 | 131 | @Override 132 | public void onDeauthenticationFailed(LayerClient client, String reason) { 133 | if (Log.isLoggable(Log.ERROR)) { 134 | Log.e("Failed to deauthenticate: " + reason); 135 | } 136 | progressDialog.dismiss(); 137 | setEnabled(true); 138 | Toast.makeText(AppSettingsActivity.this, getString(R.string.toast_failed_to_deauthenticate, reason), Toast.LENGTH_SHORT).show(); 139 | } 140 | }); 141 | } 142 | }) 143 | .setNegativeButton(R.string.alert_button_cancel, new DialogInterface.OnClickListener() { 144 | @Override 145 | public void onClick(DialogInterface dialog, int which) { 146 | dialog.dismiss(); 147 | setEnabled(true); 148 | } 149 | }) 150 | .show(); 151 | } 152 | }); 153 | 154 | mShowNotifications.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 155 | @Override 156 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 157 | PushNotificationReceiver.getNotifications(AppSettingsActivity.this).setEnabled(isChecked); 158 | } 159 | }); 160 | 161 | mVerboseLogging.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 162 | @Override 163 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 164 | LayerClient.setLoggingEnabled(AppSettingsActivity.this, isChecked); 165 | com.layer.atlas.util.Log.setLoggingEnabled(isChecked); 166 | Log.setAlwaysLoggable(isChecked); 167 | } 168 | }); 169 | } 170 | 171 | @Override 172 | protected void onResume() { 173 | super.onResume(); 174 | getLayerClient() 175 | .registerAuthenticationListener(this) 176 | .registerConnectionListener(this) 177 | .registerEventListener(this); 178 | refresh(); 179 | } 180 | 181 | @Override 182 | protected void onPause() { 183 | getLayerClient() 184 | .unregisterAuthenticationListener(this) 185 | .unregisterConnectionListener(this) 186 | .unregisterEventListener(this); 187 | super.onPause(); 188 | } 189 | 190 | public void setEnabled(final boolean enabled) { 191 | runOnUiThread(new Runnable() { 192 | @Override 193 | public void run() { 194 | mLogoutButton.setEnabled(enabled); 195 | mShowNotifications.setEnabled(enabled); 196 | mVerboseLogging.setEnabled(enabled); 197 | } 198 | }); 199 | } 200 | 201 | private void refresh() { 202 | if (!getLayerClient().isAuthenticated()) return; 203 | 204 | /* Account */ 205 | Participant participant = getParticipantProvider().getParticipant(getLayerClient().getAuthenticatedUserId()); 206 | mAvatar.setParticipants(getLayerClient().getAuthenticatedUserId()); 207 | mUserName.setText(participant.getName()); 208 | mUserState.setText(getLayerClient().isConnected() ? R.string.settings_content_connected : R.string.settings_content_disconnected); 209 | 210 | /* Notifications */ 211 | mShowNotifications.setChecked(PushNotificationReceiver.getNotifications(this).isEnabled()); 212 | 213 | /* Debug */ 214 | // enable logging through adb: `adb shell setprop log.tag.LayerSDK VERBOSE` 215 | boolean enabledByEnvironment = android.util.Log.isLoggable("LayerSDK", Log.VERBOSE); 216 | mVerboseLogging.setEnabled(!enabledByEnvironment); 217 | mVerboseLogging.setChecked(enabledByEnvironment || LayerClient.isLoggingEnabled()); 218 | mAppVersion.setText(getString(R.string.settings_content_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); 219 | mAtlasVersion.setText(Util.getVersion()); 220 | mLayerVersion.setText(LayerClient.getVersion()); 221 | mAndroidVersion.setText(getString(R.string.settings_content_android_version, Build.VERSION.RELEASE, Build.VERSION.SDK_INT)); 222 | mUserId.setText(getLayerClient().getAuthenticatedUserId()); 223 | 224 | /* Statistics */ 225 | long totalMessages = 0; 226 | long totalUnread = 0; 227 | List conversations = getLayerClient().getConversations(); 228 | for (Conversation conversation : conversations) { 229 | totalMessages += conversation.getTotalMessageCount(); 230 | totalUnread += conversation.getTotalUnreadMessageCount(); 231 | } 232 | mConversationCount.setText(String.format("%d", conversations.size())); 233 | mMessageCount.setText(String.format("%d", totalMessages)); 234 | mUnreadMessageCount.setText(String.format("%d", totalUnread)); 235 | 236 | /* Rich Content */ 237 | mDiskUtilization.setText(readableByteFormat(getLayerClient().getDiskUtilization())); 238 | long allowance = getLayerClient().getDiskCapacity(); 239 | if (allowance == 0) { 240 | mDiskAllowance.setText(R.string.settings_content_disk_unlimited); 241 | } else { 242 | mDiskAllowance.setText(readableByteFormat(allowance)); 243 | } 244 | mAutoDownloadMimeTypes.setText(TextUtils.join("\n", getLayerClient().getAutoDownloadMimeTypes())); 245 | } 246 | 247 | private String readableByteFormat(long bytes) { 248 | long kb = 1024; 249 | long mb = kb * 1024; 250 | long gb = mb * 1024; 251 | 252 | double value; 253 | int suffix; 254 | if (bytes >= gb) { 255 | value = (double) bytes / (double) gb; 256 | suffix = R.string.settings_content_disk_gb; 257 | } else if (bytes >= mb) { 258 | value = (double) bytes / (double) mb; 259 | suffix = R.string.settings_content_disk_mb; 260 | } else if (bytes >= kb) { 261 | value = (double) bytes / (double) kb; 262 | suffix = R.string.settings_content_disk_kb; 263 | } else { 264 | value = (double) bytes; 265 | suffix = R.string.settings_content_disk_b; 266 | } 267 | return getString(R.string.settings_content_disk_usage, value, getString(suffix)); 268 | } 269 | 270 | 271 | @Override 272 | public void onAuthenticated(LayerClient layerClient, String s) { 273 | refresh(); 274 | } 275 | 276 | @Override 277 | public void onDeauthenticated(LayerClient layerClient) { 278 | refresh(); 279 | } 280 | 281 | @Override 282 | public void onAuthenticationChallenge(LayerClient layerClient, String s) { 283 | 284 | } 285 | 286 | @Override 287 | public void onAuthenticationError(LayerClient layerClient, LayerException e) { 288 | 289 | } 290 | 291 | @Override 292 | public void onConnectionConnected(LayerClient layerClient) { 293 | refresh(); 294 | } 295 | 296 | @Override 297 | public void onConnectionDisconnected(LayerClient layerClient) { 298 | refresh(); 299 | } 300 | 301 | @Override 302 | public void onConnectionError(LayerClient layerClient, LayerException e) { 303 | 304 | } 305 | 306 | @Override 307 | public void onChangeEvent(LayerChangeEvent layerChangeEvent) { 308 | refresh(); 309 | } 310 | 311 | @Override 312 | public boolean onLongClick(View v) { 313 | if (v instanceof TextView) { 314 | Util.copyToClipboard(v.getContext(), R.string.settings_clipboard_description, ((TextView) v).getText().toString()); 315 | Toast.makeText(this, R.string.toast_copied_to_clipboard, Toast.LENGTH_SHORT).show(); 316 | return true; 317 | } 318 | return false; 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.ActionBar; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | 9 | import com.layer.atlas.provider.ParticipantProvider; 10 | import com.layer.sdk.LayerClient; 11 | import com.squareup.picasso.Picasso; 12 | 13 | public abstract class BaseActivity extends AppCompatActivity { 14 | private final int mLayoutResId; 15 | private final int mMenuResId; 16 | private final int mMenuTitleResId; 17 | private final boolean mMenuBackEnabled; 18 | 19 | public BaseActivity(int layoutResId, int menuResId, int menuTitleResId, boolean menuBackEnabled) { 20 | mLayoutResId = layoutResId; 21 | mMenuResId = menuResId; 22 | mMenuTitleResId = menuTitleResId; 23 | mMenuBackEnabled = menuBackEnabled; 24 | } 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(mLayoutResId); 30 | 31 | ActionBar actionBar = getSupportActionBar(); 32 | if (actionBar == null) return; 33 | if (mMenuBackEnabled) actionBar.setDisplayHomeAsUpEnabled(true); 34 | actionBar.setTitle(mMenuTitleResId); 35 | } 36 | 37 | @Override 38 | public void setTitle(CharSequence title) { 39 | ActionBar actionBar = getSupportActionBar(); 40 | if (actionBar == null) { 41 | super.setTitle(title); 42 | } else { 43 | actionBar.setTitle(title); 44 | } 45 | } 46 | 47 | @Override 48 | public void setTitle(int titleId) { 49 | ActionBar actionBar = getSupportActionBar(); 50 | if (actionBar == null) { 51 | super.setTitle(titleId); 52 | } else { 53 | actionBar.setTitle(titleId); 54 | } 55 | } 56 | 57 | @Override 58 | protected void onResume() { 59 | super.onResume(); 60 | LayerClient client = App.getLayerClient(); 61 | if (client == null) return; 62 | if (client.isAuthenticated()) { 63 | client.connect(); 64 | } else { 65 | client.authenticate(); 66 | } 67 | } 68 | 69 | @Override 70 | public boolean onCreateOptionsMenu(Menu menu) { 71 | getMenuInflater().inflate(mMenuResId, menu); 72 | return true; 73 | } 74 | 75 | @Override 76 | public boolean onOptionsItemSelected(MenuItem item) { 77 | if (item.getItemId() == android.R.id.home) { 78 | // Menu "Navigate Up" acts like hardware back button 79 | onBackPressed(); 80 | return true; 81 | } 82 | return super.onOptionsItemSelected(item); 83 | } 84 | 85 | protected LayerClient getLayerClient() { 86 | return App.getLayerClient(); 87 | } 88 | 89 | protected ParticipantProvider getParticipantProvider() { 90 | return App.getParticipantProvider(); 91 | } 92 | 93 | protected Picasso getPicasso() { 94 | return App.getPicasso(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/ConversationSettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.view.KeyEvent; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.view.inputmethod.EditorInfo; 15 | import android.widget.Button; 16 | import android.widget.CompoundButton; 17 | import android.widget.EditText; 18 | import android.widget.ImageView; 19 | import android.widget.LinearLayout; 20 | import android.widget.Switch; 21 | import android.widget.TextView; 22 | import android.widget.Toast; 23 | 24 | import com.layer.atlas.AtlasAvatar; 25 | import com.layer.atlas.provider.Participant; 26 | import com.layer.atlas.provider.ParticipantProvider; 27 | import com.layer.atlas.util.Util; 28 | import com.layer.sdk.LayerClient; 29 | import com.layer.sdk.changes.LayerChangeEvent; 30 | import com.layer.sdk.listeners.LayerChangeEventListener; 31 | import com.layer.sdk.listeners.LayerPolicyListener; 32 | import com.layer.sdk.messaging.Conversation; 33 | import com.layer.sdk.policy.Policy; 34 | 35 | import java.util.ArrayList; 36 | import java.util.Collections; 37 | import java.util.HashSet; 38 | import java.util.List; 39 | import java.util.Set; 40 | 41 | public class ConversationSettingsActivity extends BaseActivity implements LayerPolicyListener, LayerChangeEventListener { 42 | private EditText mConversationName; 43 | private Switch mShowNotifications; 44 | private RecyclerView mParticipantRecyclerView; 45 | private Button mLeaveButton; 46 | private Button mAddParticipantsButton; 47 | 48 | private Conversation mConversation; 49 | private ParticipantAdapter mParticipantAdapter; 50 | 51 | public ConversationSettingsActivity() { 52 | super(R.layout.activity_conversation_settings, R.menu.menu_conversation_details, R.string.title_conversation_details, true); 53 | } 54 | 55 | @Override 56 | protected void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | mConversationName = (EditText) findViewById(R.id.conversation_name); 59 | mShowNotifications = (Switch) findViewById(R.id.show_notifications_switch); 60 | mParticipantRecyclerView = (RecyclerView) findViewById(R.id.participants); 61 | mLeaveButton = (Button) findViewById(R.id.leave_button); 62 | mAddParticipantsButton = (Button) findViewById(R.id.add_participant_button); 63 | 64 | // Get Conversation from Intent extras 65 | Uri conversationId = getIntent().getParcelableExtra(PushNotificationReceiver.LAYER_CONVERSATION_KEY); 66 | mConversation = getLayerClient().getConversation(conversationId); 67 | if (mConversation == null && !isFinishing()) finish(); 68 | 69 | mParticipantAdapter = new ParticipantAdapter(); 70 | mParticipantRecyclerView.setAdapter(mParticipantAdapter); 71 | 72 | LinearLayoutManager manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); 73 | mParticipantRecyclerView.setLayoutManager(manager); 74 | 75 | mConversationName.setOnEditorActionListener(new TextView.OnEditorActionListener() { 76 | @Override 77 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 78 | if (actionId == EditorInfo.IME_ACTION_DONE || (event != null && event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) { 79 | String title = ((EditText) v).getText().toString().trim(); 80 | Util.setConversationMetadataTitle(mConversation, title); 81 | Toast.makeText(v.getContext(), R.string.toast_group_name_updated, Toast.LENGTH_SHORT).show(); 82 | return true; 83 | } 84 | return false; 85 | } 86 | }); 87 | 88 | mShowNotifications.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 89 | @Override 90 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 91 | PushNotificationReceiver.getNotifications(ConversationSettingsActivity.this) 92 | .setEnabled(mConversation.getId(), isChecked); 93 | } 94 | }); 95 | 96 | mLeaveButton.setOnClickListener(new View.OnClickListener() { 97 | @Override 98 | public void onClick(View v) { 99 | setEnabled(false); 100 | mConversation.removeParticipants(getLayerClient().getAuthenticatedUserId()); 101 | refresh(); 102 | Intent intent = new Intent(ConversationSettingsActivity.this, ConversationsListActivity.class); 103 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 104 | setEnabled(true); 105 | ConversationSettingsActivity.this.startActivity(intent); 106 | } 107 | }); 108 | 109 | mAddParticipantsButton.setOnClickListener(new View.OnClickListener() { 110 | @Override 111 | public void onClick(View v) { 112 | // TODO 113 | Toast.makeText(v.getContext(), "Coming soon", Toast.LENGTH_LONG).show(); 114 | } 115 | }); 116 | } 117 | 118 | public void setEnabled(boolean enabled) { 119 | mShowNotifications.setEnabled(enabled); 120 | mLeaveButton.setEnabled(enabled); 121 | } 122 | 123 | private void refresh() { 124 | if (!getLayerClient().isAuthenticated()) return; 125 | 126 | mConversationName.setText(Util.getConversationMetadataTitle(mConversation)); 127 | mShowNotifications.setChecked(PushNotificationReceiver.getNotifications(this).isEnabled(mConversation.getId())); 128 | 129 | Set participantsMinusMe = new HashSet(mConversation.getParticipants()); 130 | participantsMinusMe.remove(getLayerClient().getAuthenticatedUserId()); 131 | 132 | if (participantsMinusMe.size() == 0) { 133 | // I've been removed 134 | mConversationName.setEnabled(false); 135 | mLeaveButton.setVisibility(View.GONE); 136 | } else if (participantsMinusMe.size() == 1) { 137 | // 1-on-1 138 | mConversationName.setEnabled(false); 139 | mLeaveButton.setVisibility(View.GONE); 140 | } else { 141 | // Group 142 | mConversationName.setEnabled(true); 143 | mLeaveButton.setVisibility(View.VISIBLE); 144 | } 145 | mParticipantAdapter.refresh(); 146 | } 147 | 148 | @Override 149 | protected void onResume() { 150 | super.onResume(); 151 | getLayerClient().registerPolicyListener(this).registerEventListener(this); 152 | setEnabled(true); 153 | refresh(); 154 | } 155 | 156 | @Override 157 | protected void onPause() { 158 | getLayerClient().unregisterPolicyListener(this).unregisterEventListener(this); 159 | super.onPause(); 160 | } 161 | 162 | @Override 163 | public void onPolicyListUpdate(LayerClient layerClient, List list, List list1) { 164 | refresh(); 165 | } 166 | 167 | @Override 168 | public void onChangeEvent(LayerChangeEvent layerChangeEvent) { 169 | refresh(); 170 | } 171 | 172 | private class ParticipantAdapter extends RecyclerView.Adapter { 173 | List mParticipants = new ArrayList(); 174 | 175 | public void refresh() { 176 | // Get new sorted list of Participants 177 | ParticipantProvider provider = App.getParticipantProvider(); 178 | mParticipants.clear(); 179 | for (String participantId : mConversation.getParticipants()) { 180 | if (participantId.equals(getLayerClient().getAuthenticatedUserId())) continue; 181 | Participant participant = provider.getParticipant(participantId); 182 | if (participant == null) continue; 183 | mParticipants.add(participant); 184 | } 185 | Collections.sort(mParticipants); 186 | 187 | // Adjust participant container height 188 | int height = Math.round(mParticipants.size() * getResources().getDimensionPixelSize(com.layer.atlas.R.dimen.atlas_secondary_item_height)); 189 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height); 190 | mParticipantRecyclerView.setLayoutParams(params); 191 | 192 | // Notify changes 193 | notifyDataSetChanged(); 194 | } 195 | 196 | @Override 197 | public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) { 198 | ViewHolder viewHolder = new ViewHolder(parent); 199 | viewHolder.mAvatar.init(App.getParticipantProvider(), App.getPicasso()); 200 | viewHolder.itemView.setTag(viewHolder); 201 | 202 | // Click to display remove / block dialog 203 | viewHolder.itemView.setOnClickListener(new View.OnClickListener() { 204 | @Override 205 | public void onClick(View v) { 206 | final ViewHolder holder = (ViewHolder) v.getTag(); 207 | 208 | AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext()) 209 | .setMessage(holder.mTitle.getText().toString()); 210 | 211 | if (mConversation.getParticipants().size() > 2) { 212 | builder.setNeutralButton(R.string.alert_button_remove, new DialogInterface.OnClickListener() { 213 | @Override 214 | public void onClick(DialogInterface dialog, int which) { 215 | mConversation.removeParticipants(holder.mParticipant.getId()); 216 | } 217 | }); 218 | } 219 | 220 | builder.setPositiveButton(holder.mBlockPolicy != null ? R.string.alert_button_unblock : R.string.alert_button_block, 221 | new DialogInterface.OnClickListener() { 222 | @Override 223 | public void onClick(DialogInterface dialog, int which) { 224 | Participant participant = holder.mParticipant; 225 | if (holder.mBlockPolicy == null) { 226 | // Block 227 | holder.mBlockPolicy = new Policy.Builder(Policy.PolicyType.BLOCK).sentByUserId(participant.getId()).build(); 228 | getLayerClient().addPolicy(holder.mBlockPolicy); 229 | holder.mBlocked.setVisibility(View.VISIBLE); 230 | } else { 231 | getLayerClient().removePolicy(holder.mBlockPolicy); 232 | holder.mBlockPolicy = null; 233 | holder.mBlocked.setVisibility(View.INVISIBLE); 234 | } 235 | } 236 | }).setNegativeButton(R.string.alert_button_cancel, new DialogInterface.OnClickListener() { 237 | @Override 238 | public void onClick(DialogInterface dialog, int which) { 239 | dialog.dismiss(); 240 | } 241 | }).show(); 242 | } 243 | }); 244 | 245 | return viewHolder; 246 | } 247 | 248 | @Override 249 | public void onBindViewHolder(ViewHolder viewHolder, int position) { 250 | Participant participant = mParticipants.get(position); 251 | viewHolder.mTitle.setText(participant.getName()); 252 | viewHolder.mAvatar.setParticipants(participant.getId()); 253 | viewHolder.mParticipant = participant; 254 | 255 | Policy block = null; 256 | for (Policy policy : getLayerClient().getPolicies()) { 257 | if (policy.getPolicyType() != Policy.PolicyType.BLOCK) continue; 258 | if (!policy.getSentByUserID().equals(participant.getId())) continue; 259 | block = policy; 260 | break; 261 | } 262 | 263 | viewHolder.mBlockPolicy = block; 264 | viewHolder.mBlocked.setVisibility(block == null ? View.INVISIBLE : View.VISIBLE); 265 | } 266 | 267 | @Override 268 | public int getItemCount() { 269 | return mParticipants.size(); 270 | } 271 | } 272 | 273 | private static class ViewHolder extends RecyclerView.ViewHolder { 274 | AtlasAvatar mAvatar; 275 | TextView mTitle; 276 | ImageView mBlocked; 277 | Participant mParticipant; 278 | Policy mBlockPolicy; 279 | 280 | public ViewHolder(ViewGroup parent) { 281 | super(LayoutInflater.from(parent.getContext()).inflate(R.layout.participant_item, parent, false)); 282 | mAvatar = (AtlasAvatar) itemView.findViewById(R.id.avatar); 283 | mTitle = (TextView) itemView.findViewById(R.id.title); 284 | mBlocked = (ImageView) itemView.findViewById(R.id.blocked); 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/ConversationsListActivity.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.view.MenuItem; 8 | import android.view.View; 9 | 10 | import com.layer.atlas.AtlasConversationsRecyclerView; 11 | import com.layer.atlas.adapters.AtlasConversationsAdapter; 12 | import com.layer.atlas.util.views.SwipeableItem; 13 | import com.layer.messenger.makemoji.MakeMojiConversationsAdapter; 14 | import com.layer.messenger.makemoji.MakeMojiConversationsRecyclerView; 15 | import com.layer.messenger.util.Log; 16 | import com.layer.sdk.LayerClient; 17 | import com.layer.sdk.messaging.Conversation; 18 | 19 | public class ConversationsListActivity extends BaseActivity { 20 | public ConversationsListActivity() { 21 | super(R.layout.activity_conversations_list, R.menu.menu_conversations_list, R.string.title_conversations_list, false); 22 | } 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | if (App.routeLogin(this)) { 28 | if (!isFinishing()) finish(); 29 | return; 30 | } 31 | 32 | final MakeMojiConversationsRecyclerView conversationsList = (MakeMojiConversationsRecyclerView) findViewById(R.id.conversations_list); 33 | 34 | // Atlas methods 35 | conversationsList.init(getLayerClient(), getParticipantProvider(), getPicasso()) 36 | .setInitialHistoricMessagesToFetch(20) 37 | .setOnConversationClickListener(new MakeMojiConversationsAdapter.OnConversationClickListener() { 38 | @Override 39 | public void onConversationClick(MakeMojiConversationsAdapter adapter, Conversation conversation) { 40 | Intent intent = new Intent(ConversationsListActivity.this, MessagesListActivity.class); 41 | if (Log.isLoggable(Log.VERBOSE)) { 42 | Log.v("Launching MessagesListActivity with existing conversation ID: " + conversation.getId()); 43 | } 44 | intent.putExtra(PushNotificationReceiver.LAYER_CONVERSATION_KEY, conversation.getId()); 45 | startActivity(intent); 46 | } 47 | 48 | @Override 49 | public boolean onConversationLongClick(MakeMojiConversationsAdapter adapter, Conversation conversation) { 50 | return false; 51 | } 52 | }) 53 | .setOnConversationSwipeListener(new SwipeableItem.OnSwipeListener() { 54 | @Override 55 | public void onSwipe(final Conversation conversation, int direction) { 56 | new AlertDialog.Builder(ConversationsListActivity.this) 57 | .setMessage(R.string.alert_message_delete_conversation) 58 | .setNegativeButton(R.string.alert_button_cancel, new DialogInterface.OnClickListener() { 59 | @Override 60 | public void onClick(DialogInterface dialog, int which) { 61 | // TODO: simply update this one conversation 62 | conversationsList.getAdapter().notifyDataSetChanged(); 63 | dialog.dismiss(); 64 | } 65 | }) 66 | .setNeutralButton(R.string.alert_button_delete_my_devices, new DialogInterface.OnClickListener() { 67 | @Override 68 | public void onClick(DialogInterface dialog, int which) { 69 | conversation.delete(LayerClient.DeletionMode.ALL_MY_DEVICES); 70 | } 71 | }) 72 | .setPositiveButton(R.string.alert_button_delete_all_participants, new DialogInterface.OnClickListener() { 73 | @Override 74 | public void onClick(DialogInterface dialog, int which) { 75 | conversation.delete(LayerClient.DeletionMode.ALL_PARTICIPANTS); 76 | } 77 | }) 78 | .show(); 79 | } 80 | }); 81 | 82 | findViewById(R.id.floating_action_button) 83 | .setOnClickListener(new View.OnClickListener() { 84 | public void onClick(View v) { 85 | startActivity(new Intent(ConversationsListActivity.this, MessagesListActivity.class)); 86 | } 87 | }); 88 | } 89 | 90 | @Override 91 | public boolean onOptionsItemSelected(MenuItem item) { 92 | switch (item.getItemId()) { 93 | case R.id.action_settings: 94 | startActivity(new Intent(this, AppSettingsActivity.class)); 95 | return true; 96 | 97 | case R.id.action_sendlogs: 98 | LayerClient.sendLogs(getLayerClient(), this); 99 | return true; 100 | } 101 | 102 | return super.onOptionsItemSelected(item); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/ResumeActivity.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.ActionBar; 7 | import android.support.v7.app.AppCompatActivity; 8 | 9 | import com.layer.messenger.util.Log; 10 | import com.layer.sdk.LayerClient; 11 | import com.layer.sdk.exceptions.LayerException; 12 | import com.layer.sdk.listeners.LayerAuthenticationListener; 13 | 14 | import java.util.concurrent.atomic.AtomicReference; 15 | 16 | public class ResumeActivity extends AppCompatActivity implements LayerAuthenticationListener { 17 | public static final String EXTRA_LOGGED_IN_ACTIVITY_CLASS_NAME = "loggedInActivity"; 18 | public static final String EXTRA_LOGGED_OUT_ACTIVITY_CLASS_NAME = "loggedOutActivity"; 19 | 20 | private AtomicReference> mLoggedInActivity = new AtomicReference>(null); 21 | private AtomicReference> mLoggedOutActivity = new AtomicReference>(null); 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_resume); 27 | final ActionBar actionBar = getSupportActionBar(); 28 | if (actionBar != null) actionBar.hide(); 29 | } 30 | 31 | @SuppressWarnings("unchecked") 32 | @Override 33 | protected void onResume() { 34 | super.onResume(); 35 | App.getLayerClient().registerAuthenticationListener(this).authenticate(); 36 | try { 37 | mLoggedInActivity.set((Class) Class.forName(getIntent().getStringExtra(EXTRA_LOGGED_IN_ACTIVITY_CLASS_NAME))); 38 | mLoggedOutActivity.set((Class) Class.forName(getIntent().getStringExtra(EXTRA_LOGGED_OUT_ACTIVITY_CLASS_NAME))); 39 | } catch (ClassNotFoundException e) { 40 | if (Log.isLoggable(Log.ERROR)) { 41 | Log.e("Could not find class: " + e.getCause(), e); 42 | } 43 | } 44 | } 45 | 46 | @Override 47 | protected void onPause() { 48 | App.getLayerClient().unregisterAuthenticationListener(this); 49 | super.onPause(); 50 | } 51 | 52 | @Override 53 | public void onAuthenticated(LayerClient layerClient, String s) { 54 | startActivity(mLoggedInActivity.get()); 55 | } 56 | 57 | @Override 58 | public void onDeauthenticated(LayerClient layerClient) { 59 | startActivity(mLoggedOutActivity.get()); 60 | } 61 | 62 | @Override 63 | public void onAuthenticationChallenge(LayerClient layerClient, String s) { 64 | 65 | } 66 | 67 | @Override 68 | public void onAuthenticationError(LayerClient layerClient, LayerException e) { 69 | startActivity(mLoggedOutActivity.get()); 70 | } 71 | 72 | private void startActivity(Class activityClass) { 73 | Intent intent = new Intent(this, activityClass); 74 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); 75 | startActivity(intent); 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/makemoji/MakeMojiCellFactory.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger.makemoji; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.GradientDrawable; 5 | import android.text.Spanned; 6 | import android.util.TypedValue; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | 13 | import com.layer.atlas.messagetypes.AtlasCellFactory; 14 | import com.layer.atlas.provider.Participant; 15 | import com.layer.atlas.provider.ParticipantProvider; 16 | import com.layer.atlas.util.Util; 17 | import com.layer.sdk.LayerClient; 18 | import com.layer.sdk.messaging.Actor; 19 | import com.layer.sdk.messaging.Message; 20 | import com.layer.sdk.messaging.MessagePart; 21 | 22 | import com.makemoji.mojilib.HyperMojiListener; 23 | import com.makemoji.mojilib.Moji; 24 | import com.makemoji.mojilib.ParsedAttributes; 25 | 26 | public class MakeMojiCellFactory extends AtlasCellFactory implements View.OnLongClickListener { 27 | public final static String MIME_TYPE = "text/plain"; 28 | HyperMojiListener hyperMojiListener; 29 | 30 | public MakeMojiCellFactory(HyperMojiListener hyperMojiListener) { 31 | super(256 * 1024); 32 | this.hyperMojiListener = hyperMojiListener; 33 | } 34 | 35 | public static boolean isType(Message message) { 36 | return message.getMessageParts().get(0).getMimeType().equals(MIME_TYPE); 37 | } 38 | 39 | public static String getMessagePreview(Context context, Message message) { 40 | MessagePart part = message.getMessageParts().get(0); 41 | // For large text content, the MessagePart may not be downloaded yet. 42 | return part.isContentReady() ? new String(part.getData()) : ""; 43 | } 44 | 45 | public static void setMessagePreview(TextView textView, Message message){ 46 | MessagePart part = message.getMessageParts().get(0); 47 | String html = part.isContentReady() ? new String(part.getData()) : ""; 48 | Moji.setText(html,textView,true); 49 | } 50 | @Override 51 | public boolean isBindable(Message message) { 52 | return MakeMojiCellFactory.isType(message); 53 | } 54 | 55 | @SuppressWarnings("ResourceAsColor") 56 | @Override 57 | public CellHolder createCellHolder(ViewGroup cellView, boolean isMe, LayoutInflater layoutInflater) { 58 | View v = layoutInflater.inflate(com.layer.atlas.R.layout.atlas_message_item_cell_text, cellView, true); 59 | v.setBackgroundResource(isMe ? com.layer.atlas.R.drawable.atlas_message_item_cell_me : com.layer.atlas.R.drawable.atlas_message_item_cell_them); 60 | ((GradientDrawable) v.getBackground()).setColor(isMe ? mMessageStyle.getMyBubbleColor() : mMessageStyle.getOtherBubbleColor()); 61 | 62 | TextView t = (TextView) v.findViewById(com.layer.atlas.R.id.cell_text); 63 | t.setTextSize(TypedValue.COMPLEX_UNIT_PX, isMe ? mMessageStyle.getMyTextSize() : mMessageStyle.getOtherTextSize()); 64 | t.setTextColor(isMe ? mMessageStyle.getMyTextColor() : mMessageStyle.getOtherTextColor()); 65 | t.setLinkTextColor(isMe ? mMessageStyle.getMyTextColor() : mMessageStyle.getOtherTextColor()); 66 | t.setTypeface(isMe ? mMessageStyle.getMyTextTypeface() : mMessageStyle.getOtherTextTypeface(), isMe ? mMessageStyle.getMyTextStyle() : mMessageStyle.getOtherTextStyle()); 67 | return new CellHolder(v,hyperMojiListener); 68 | } 69 | 70 | @Override 71 | public TextInfo parseContent(LayerClient layerClient, ParticipantProvider participantProvider, Message message) { 72 | MessagePart part = message.getMessageParts().get(0); 73 | String html = part.isContentReady() ? new String(part.getData()) : ""; 74 | ParsedAttributes parsedAttributes = Moji.parseHtml(html,null,true); //also contains size, color. 75 | String name; 76 | Actor sender = message.getSender(); 77 | if (sender.getName() != null) { 78 | name = sender.getName() + ": "; 79 | } else { 80 | Participant participant = participantProvider.getParticipant(sender.getUserId()); 81 | name = participant == null ? "" : (participant.getName() + ": "); 82 | } 83 | return new TextInfo(html,parsedAttributes.spanned, name); 84 | } 85 | 86 | @Override 87 | public void bindCellHolder(CellHolder cellHolder, final TextInfo parsed, Message message, CellHolderSpecs specs) { 88 | // Moji.setText(parsed.getSpanned(),cellHolder.mTextView); 89 | MessagePart part = message.getMessageParts().get(0); 90 | String text = part.isContentReady() ? new String(part.getData()) : ""; 91 | Moji.setText(text,cellHolder.mTextView,true); 92 | cellHolder.mTextView.setTag(parsed); 93 | cellHolder.mTextView.setOnLongClickListener(this); 94 | } 95 | 96 | /** 97 | * Long click copies message text and sender name to clipboard 98 | */ 99 | @Override 100 | public boolean onLongClick(View v) { 101 | TextInfo parsed = (TextInfo) v.getTag(); 102 | String text = parsed.getmHtml(); 103 | Util.copyToClipboard(v.getContext(), com.layer.atlas.R.string.atlas_text_cell_factory_clipboard_description, text); 104 | Toast.makeText(v.getContext(), com.layer.atlas.R.string.atlas_text_cell_factory_copied_to_clipboard, Toast.LENGTH_SHORT).show(); 105 | return true; 106 | } 107 | 108 | public static class CellHolder extends AtlasCellFactory.CellHolder { 109 | TextView mTextView; 110 | 111 | public CellHolder(View view,HyperMojiListener hyperMojiListener) { 112 | mTextView = (TextView) view.findViewById(com.layer.atlas.R.id.cell_text); 113 | mTextView.setTag(com.makemoji.mojilib.R.id._makemoji_hypermoji_listener_tag_id,hyperMojiListener); 114 | } 115 | } 116 | 117 | public static class TextInfo implements AtlasCellFactory.ParsedContent { 118 | private final String mHtml; 119 | private final Spanned mSpanned; 120 | private final String mClipboardPrefix; 121 | private final int mSize; 122 | 123 | public TextInfo(String html,Spanned spanned, String clipboardPrefix) { 124 | mHtml = html; 125 | mSpanned = spanned; 126 | mClipboardPrefix = clipboardPrefix; 127 | mSize = mSpanned.toString().getBytes().length + mClipboardPrefix.getBytes().length; 128 | } 129 | 130 | public Spanned getSpanned() { 131 | return mSpanned; 132 | } 133 | 134 | public String getmHtml(){return mHtml;} 135 | 136 | public String getClipboardPrefix() { 137 | return mClipboardPrefix; 138 | } 139 | 140 | @Override 141 | public int sizeOf() { 142 | return mSize; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/makemoji/MakeMojiConversationsRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger.makemoji; 2 | 3 | /** 4 | * Created by s_baa on 5/30/2016. 5 | */ 6 | /* 7 | * Copyright (c) 2015 Layer. All rights reserved. 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | import android.content.Context; 23 | import android.content.res.TypedArray; 24 | import android.graphics.Color; 25 | import android.graphics.Typeface; 26 | import android.support.v7.widget.LinearLayoutManager; 27 | import android.support.v7.widget.RecyclerView; 28 | import android.support.v7.widget.helper.ItemTouchHelper; 29 | import android.util.AttributeSet; 30 | import android.view.View; 31 | 32 | import com.layer.atlas.provider.ParticipantProvider; 33 | import com.layer.atlas.util.AvatarStyle; 34 | import com.layer.atlas.util.ConversationStyle; 35 | import com.layer.atlas.util.itemanimators.NoChangeAnimator; 36 | import com.layer.atlas.util.views.SwipeableItem; 37 | import com.layer.sdk.LayerClient; 38 | import com.layer.sdk.messaging.Conversation; 39 | import com.squareup.picasso.Picasso; 40 | 41 | 42 | public class MakeMojiConversationsRecyclerView extends RecyclerView { 43 | MakeMojiConversationsAdapter mAdapter; 44 | private ItemTouchHelper mSwipeItemTouchHelper; 45 | 46 | private ConversationStyle conversationStyle; 47 | 48 | public MakeMojiConversationsRecyclerView(Context context, AttributeSet attrs, int defStyle) { 49 | super(context, attrs, defStyle); 50 | parseStyle(context, attrs, defStyle); 51 | } 52 | 53 | public MakeMojiConversationsRecyclerView(Context context, AttributeSet attrs) { 54 | this(context, attrs, 0); 55 | } 56 | 57 | public MakeMojiConversationsRecyclerView(Context context) { 58 | super(context); 59 | } 60 | 61 | public MakeMojiConversationsRecyclerView init(LayerClient layerClient, ParticipantProvider participantProvider, Picasso picasso) { 62 | // Linear layout manager 63 | LinearLayoutManager manager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false); 64 | manager.setStackFromEnd(false); 65 | setLayoutManager(manager); 66 | 67 | // Don't flash items when changing content 68 | setItemAnimator(new NoChangeAnimator()); 69 | 70 | mAdapter = new MakeMojiConversationsAdapter(getContext(), layerClient, participantProvider, picasso); 71 | mAdapter.setStyle(conversationStyle); 72 | super.setAdapter(mAdapter); 73 | 74 | return this; 75 | } 76 | 77 | @Override 78 | public void setAdapter(Adapter adapter) { 79 | throw new RuntimeException("AtlasConversationsRecyclerView sets its own Adapter"); 80 | } 81 | 82 | /** 83 | * Automatically refresh on resume 84 | */ 85 | @Override 86 | protected void onVisibilityChanged(View changedView, int visibility) { 87 | super.onVisibilityChanged(changedView, visibility); 88 | if (visibility != View.VISIBLE) return; 89 | refresh(); 90 | } 91 | 92 | public MakeMojiConversationsRecyclerView refresh() { 93 | if (mAdapter != null) mAdapter.refresh(); 94 | return this; 95 | } 96 | 97 | /** 98 | * Convenience pass-through to this list's AtlasConversationsAdapter. 99 | * 100 | * @see MakeMojiConversationsAdapter#setOnConversationClickListener(MakeMojiConversationsAdapter.OnConversationClickListener) 101 | */ 102 | public MakeMojiConversationsRecyclerView setOnConversationClickListener(MakeMojiConversationsAdapter.OnConversationClickListener listener) { 103 | mAdapter.setOnConversationClickListener(listener); 104 | return this; 105 | } 106 | 107 | public MakeMojiConversationsRecyclerView setOnConversationSwipeListener(SwipeableItem.OnSwipeListener listener) { 108 | if (mSwipeItemTouchHelper != null) { 109 | mSwipeItemTouchHelper.attachToRecyclerView(null); 110 | } 111 | if (listener == null) { 112 | mSwipeItemTouchHelper = null; 113 | } else { 114 | listener.setBaseAdapter((MakeMojiConversationsAdapter) getAdapter()); 115 | mSwipeItemTouchHelper = new ItemTouchHelper(listener); 116 | mSwipeItemTouchHelper.attachToRecyclerView(this); 117 | } 118 | return this; 119 | } 120 | 121 | /** 122 | * Convenience pass-through to this list's AtlasConversationsAdapter. 123 | * 124 | * @see MakeMojiConversationsAdapter#setInitialHistoricMessagesToFetch(long) 125 | */ 126 | public MakeMojiConversationsRecyclerView setInitialHistoricMessagesToFetch(long count) { 127 | mAdapter.setInitialHistoricMessagesToFetch(count); 128 | return this; 129 | } 130 | 131 | public MakeMojiConversationsRecyclerView setTypeface(Typeface titleTypeface, Typeface titleUnreadTypeface, Typeface subtitleTypeface, Typeface subtitleUnreadTypeface, Typeface dateTypeface) { 132 | conversationStyle.setTitleTextTypeface(titleTypeface); 133 | conversationStyle.setTitleUnreadTextTypeface(titleUnreadTypeface); 134 | conversationStyle.setSubtitleTextTypeface(subtitleTypeface); 135 | conversationStyle.setSubtitleUnreadTextTypeface(subtitleUnreadTypeface); 136 | conversationStyle.setDateTextTypeface(dateTypeface); 137 | return this; 138 | } 139 | 140 | private void parseStyle(Context context, AttributeSet attrs, int defStyle) { 141 | ConversationStyle.Builder styleBuilder = new ConversationStyle.Builder(); 142 | TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, com.layer.atlas.R.styleable.AtlasConversationsRecyclerView, com.layer.atlas.R.attr.AtlasConversationsRecyclerView, defStyle); 143 | styleBuilder.titleTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_text_gray))); 144 | int titleTextStyle = ta.getInt(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleTextStyle, Typeface.NORMAL); 145 | styleBuilder.titleTextStyle(titleTextStyle); 146 | String titleTextTypefaceName = ta.getString(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleTextTypeface); 147 | styleBuilder.titleTextTypeface(titleTextTypefaceName != null ? Typeface.create(titleTextTypefaceName, titleTextStyle) : null); 148 | 149 | styleBuilder.titleUnreadTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleUnreadTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_text_black))); 150 | int titleUnreadTextStyle = ta.getInt(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleUnreadTextStyle, Typeface.BOLD); 151 | styleBuilder.titleUnreadTextStyle(titleUnreadTextStyle); 152 | String titleUnreadTextTypefaceName = ta.getString(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellTitleUnreadTextTypeface); 153 | styleBuilder.titleUnreadTextTypeface(titleUnreadTextTypefaceName != null ? Typeface.create(titleUnreadTextTypefaceName, titleUnreadTextStyle) : null); 154 | 155 | styleBuilder.subtitleTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_text_gray))); 156 | int subtitleTextStyle = ta.getInt(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleTextStyle, Typeface.NORMAL); 157 | styleBuilder.subtitleTextStyle(subtitleTextStyle); 158 | String subtitleTextTypefaceName = ta.getString(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleTextTypeface); 159 | styleBuilder.subtitleTextTypeface(subtitleTextTypefaceName != null ? Typeface.create(subtitleTextTypefaceName, subtitleTextStyle) : null); 160 | 161 | styleBuilder.subtitleUnreadTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleUnreadTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_text_black))); 162 | int subtitleUnreadTextStyle = ta.getInt(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleUnreadTextStyle, Typeface.NORMAL); 163 | styleBuilder.subtitleUnreadTextStyle(subtitleUnreadTextStyle); 164 | String subtitleUnreadTextTypefaceName = ta.getString(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellSubtitleUnreadTextTypeface); 165 | styleBuilder.subtitleUnreadTextTypeface(subtitleUnreadTextTypefaceName != null ? Typeface.create(subtitleUnreadTextTypefaceName, subtitleUnreadTextStyle) : null); 166 | 167 | styleBuilder.cellBackgroundColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellBackgroundColor, Color.TRANSPARENT)); 168 | styleBuilder.cellUnreadBackgroundColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_cellUnreadBackgroundColor, Color.TRANSPARENT)); 169 | styleBuilder.dateTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_dateTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_color_primary_blue))); 170 | 171 | AvatarStyle.Builder avatarStyleBuilder = new AvatarStyle.Builder(); 172 | avatarStyleBuilder.avatarTextColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_avatarTextColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_avatar_text))); 173 | avatarStyleBuilder.avatarBackgroundColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_avatarBackgroundColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_avatar_background))); 174 | avatarStyleBuilder.avatarBorderColor(ta.getColor(com.layer.atlas.R.styleable.AtlasConversationsRecyclerView_avatarBorderColor, context.getResources().getColor(com.layer.atlas.R.color.atlas_avatar_border))); 175 | styleBuilder.avatarStyle(avatarStyleBuilder.build()); 176 | ta.recycle(); 177 | conversationStyle = styleBuilder.build(); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/util/AuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger.util; 2 | 3 | import android.app.Activity; 4 | 5 | import com.layer.sdk.LayerClient; 6 | import com.layer.sdk.listeners.LayerAuthenticationListener; 7 | 8 | /** 9 | * AuthenticationProvider implementations authenticate users with backend "Identity Providers." 10 | * 11 | * @param Session credentials for this AuthenticationProvider used to resume an 12 | * authenticated session. 13 | */ 14 | public interface AuthenticationProvider extends LayerAuthenticationListener.BackgroundThread.Weak { 15 | 16 | /** 17 | * Sets this AuthenticationProvider's credentials. Credentials should be cached to handle 18 | * future authentication challenges. When `credentials` is `null`, the cached credentials 19 | * should be cleared. 20 | * 21 | * @param credentials Credentials to cache. 22 | * @return This AuthenticationProvider. 23 | */ 24 | AuthenticationProvider setCredentials(Tcredentials credentials); 25 | 26 | 27 | /** 28 | * Returns `true` if this AuthenticationProvider has cached credentials, or `false` otherwise. 29 | * 30 | * @return `true` if this AuthenticationProvider has cached credentials, or `false` otherwise. 31 | */ 32 | boolean hasCredentials(); 33 | 34 | /** 35 | * Sets the authentication callback for reporting authentication success and failure. 36 | * 37 | * @param callback Callback to receive authentication success and failure. 38 | * @return This AuthenticationProvider. 39 | */ 40 | AuthenticationProvider setCallback(Callback callback); 41 | 42 | /** 43 | * Routes the user to a login screen if required. If routing, return `true` and start the 44 | * desired login Activity. 45 | * 46 | * @param layerClient 47 | * @param layerAppId 48 | * @param from 49 | * @return 50 | */ 51 | boolean routeLogin(LayerClient layerClient, String layerAppId, Activity from); 52 | 53 | /** 54 | * Callback for handling authentication success and failure. 55 | */ 56 | interface Callback { 57 | void onSuccess(AuthenticationProvider provider, String userId); 58 | 59 | void onError(AuthenticationProvider provider, String error); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/util/Log.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger.util; 2 | 3 | /** 4 | * Unified Log class used by Atlas Messenger classes that maintains similar signatures to 5 | * `android.util.Log`. Logs are tagged with `Atlas`. 6 | */ 7 | public class Log { 8 | public static final String TAG = "LayerAtlasMsgr"; 9 | 10 | // Makes IDE auto-completion easy 11 | public static final int VERBOSE = android.util.Log.VERBOSE; 12 | public static final int DEBUG = android.util.Log.DEBUG; 13 | public static final int INFO = android.util.Log.INFO; 14 | public static final int WARN = android.util.Log.WARN; 15 | public static final int ERROR = android.util.Log.ERROR; 16 | 17 | private static volatile boolean sAlwaysLoggable = false; 18 | 19 | /** 20 | * Returns `true` if the provided log level is loggable either through environment options or 21 | * a previous call to setAlwaysLoggable(). 22 | * 23 | * @param level Log level to check. 24 | * @return `true` if the provided log level is loggable. 25 | * @see #setAlwaysLoggable(boolean) 26 | */ 27 | public static boolean isLoggable(int level) { 28 | return sAlwaysLoggable || android.util.Log.isLoggable(TAG, level); 29 | } 30 | 31 | public static void setAlwaysLoggable(boolean alwaysOn) { 32 | sAlwaysLoggable = alwaysOn; 33 | } 34 | 35 | public static void v(String message) { 36 | android.util.Log.v(TAG, message); 37 | } 38 | 39 | public static void v(String message, Throwable error) { 40 | android.util.Log.v(TAG, message, error); 41 | } 42 | 43 | public static void d(String message) { 44 | android.util.Log.d(TAG, message); 45 | } 46 | 47 | public static void d(String message, Throwable error) { 48 | android.util.Log.d(TAG, message, error); 49 | } 50 | 51 | public static void i(String message) { 52 | android.util.Log.i(TAG, message); 53 | } 54 | 55 | public static void i(String message, Throwable error) { 56 | android.util.Log.i(TAG, message, error); 57 | } 58 | 59 | public static void w(String message) { 60 | android.util.Log.w(TAG, message); 61 | } 62 | 63 | public static void w(String message, Throwable error) { 64 | android.util.Log.w(TAG, message, error); 65 | } 66 | 67 | public static void w(Throwable error) { 68 | android.util.Log.w(TAG, error); 69 | } 70 | 71 | public static void e(String message) { 72 | android.util.Log.e(TAG, message); 73 | } 74 | 75 | public static void e(String message, Throwable error) { 76 | android.util.Log.e(TAG, message, error); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/layer/messenger/util/Util.java: -------------------------------------------------------------------------------- 1 | package com.layer.messenger.util; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.InputStreamReader; 6 | import java.io.StringWriter; 7 | 8 | public class Util { 9 | public static String streamToString(InputStream stream) throws IOException { 10 | int n = 0; 11 | char[] buffer = new char[1024 * 4]; 12 | InputStreamReader reader = new InputStreamReader(stream, "UTF8"); 13 | StringWriter writer = new StringWriter(); 14 | while (-1 != (n = reader.read(buffer))) writer.write(buffer, 0, n); 15 | return writer.toString(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_block_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-hdpi/ic_block_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-hdpi/notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-ldpi/notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_block_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-mdpi/ic_block_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-mdpi/notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/layer_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-nodpi/layer_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_block_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xhdpi/ic_block_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xhdpi/notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_block_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xxhdpi/ic_block_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xxhdpi/notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_block_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xxxhdpi/ic_block_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makemoji/MakemojiSDK-Android-AtlasDemo/5571277fb2e70f82b8498199c88061bc220469d3/app/src/main/res/drawable-xxxhdpi/notification.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_conversation_settings.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 15 | 16 | 22 | 23 | 24 | 29 | 30 | 39 | 40 | 41 | 42 | 50 | 51 | 58 | 59 | 64 | 65 | 66 | 67 |