diff --git a/CHANGELOG.md b/CHANGELOG.md index 951c12a5d..d5c9310e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,17 @@ -# 2016-10-?? - 3.3.0 +# 2016-12-07 - 3.4.0 + +#### Improvements + +* Rebuilt Message Center to use modern `RecyclerView` for better performance and stability. +* Improved accessibility of our UI, including TalkBack support and fixing hit target sizes. +* Improved version targeting + +#### Bugs Fixed + +* Fixed a bug where the Profile Card in Message Center wouldn't let a user focus the email field. +* Fixed a bug where the Survey "thank you" message text was the wrong color. + +# 2016-10-21 - 3.3.0 #### Improvements diff --git a/README.md b/README.md index c245a6f37..a2993476d 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,18 @@ The Apptentive Android SDK is the best way to engage your mobile customers. Use it to learn about how your customers use your app, to talk to them at the right time, and in the right way. -#### To get started, read our [Integration Guide](http://www.apptentive.com/docs/android/integration/) +#### To get started, read our [Integration Guide](https://learn.apptentive.com/knowledge-base/android-integration/) #### Learn about SDK Features [Features](http://www.apptentive.com/docs/android/features/) -#### Browse the [Client API](http://www.apptentive.com/docs/android/api) +#### [Android Interface Customization](https://learn.apptentive.com/knowledge-base/interface-customization-android/) +#### [Apptentive SDK API Javadoc](http://www.apptentive.com/docs/android/api) -##### API Changes are tracked [here](docs/APIChanges.md) +##### [API Changes here](docs/APIChanges.md) -##### Version history is tracked [here](CHANGELOG.md) +##### [Release Notes](https://learn.apptentive.com/knowledge-base/android-sdk-releases-notes/) -##### Binary releases are hosted for Maven [here](http://search.maven.org/#artifactdetails|com.apptentive|apptentive-android|3.3.0|aar) +##### Binary releases are hosted for Maven [here](http://search.maven.org/#artifactdetails|com.apptentive|apptentive-android|3.4.0|aar) #### Reporting Bugs @@ -26,5 +27,5 @@ We appreciate contributions to make this SDK better. If you have an improvement #### Notes -* Make sure you have latest version or our SDK. We're always adding new features! +* Make sure you have latest version of our SDK. We're always adding new features! * Make sure to follow the repo to get updates about features and bug fixes. diff --git a/apptentive/build.gradle b/apptentive/build.gradle index 9367661d7..8eb7d47b4 100644 --- a/apptentive/build.gradle +++ b/apptentive/build.gradle @@ -129,6 +129,7 @@ task androidJavadocs(type: Javadoc) { include 'com/apptentive/android/sdk/module/rating/IRatingProvider.java' include 'com/apptentive/android/sdk/module/messagecenter/UnreadMessagesListener.java' include 'com/apptentive/android/sdk/module/survey/OnSurveyFinishedListener.java' + include 'com/apptentive/android/sdk/model/CustomData.java' include 'com/apptentive/android/sdk/model/ExtendedData.java' include 'com/apptentive/android/sdk/model/CommerceExtendedData.java' include 'com/apptentive/android/sdk/model/LocationExtendedData.java' diff --git a/apptentive/src/main/AndroidManifest.xml b/apptentive/src/main/AndroidManifest.xml index da73b6a3c..b086b9137 100644 --- a/apptentive/src/main/AndroidManifest.xml +++ b/apptentive/src/main/AndroidManifest.xml @@ -1,19 +1,20 @@ - + + - + diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java index c31beb89c..98e065672 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/Apptentive.java @@ -17,7 +17,12 @@ import android.text.TextUtils; import android.webkit.MimeTypeMap; -import com.apptentive.android.sdk.model.*; +import com.apptentive.android.sdk.model.CommerceExtendedData; +import com.apptentive.android.sdk.model.CustomData; +import com.apptentive.android.sdk.model.ExtendedData; +import com.apptentive.android.sdk.model.LocationExtendedData; +import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.model.TimeExtendedData; import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.messagecenter.MessageManager; import com.apptentive.android.sdk.module.messagecenter.UnreadMessagesListener; @@ -25,7 +30,8 @@ import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.module.rating.IRatingProvider; import com.apptentive.android.sdk.module.survey.OnSurveyFinishedListener; -import com.apptentive.android.sdk.storage.*; +import com.apptentive.android.sdk.storage.DeviceManager; +import com.apptentive.android.sdk.storage.PersonManager; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; @@ -33,9 +39,9 @@ import org.json.JSONObject; import java.io.ByteArrayInputStream; - import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.Map; /** * This class contains the complete API for accessing Apptentive features from within your app. @@ -471,7 +477,7 @@ public static boolean isApptentivePushNotification(Intent intent) { * Determines whether this Bundle came from an Apptentive push notification. This method is used with Urban Airship * integrations. * - * @param bundle The push payload bundle paseed to GCM onMessageReceived() callback + * @param bundle The push payload bundle passed to GCM onMessageReceived() callback * @return True if the push came from, and should be handled by Apptentive. */ public static boolean isApptentivePushNotification(Bundle bundle) { @@ -495,23 +501,19 @@ public static boolean isApptentivePushNotification(Map data) { } /** - *

- * Use this method in your push receiver to build a pending Intent when an Apptentive push + *

Use this method in your push receiver to build a pending Intent when an Apptentive push * notification is received. Pass the generated PendingIntent to * {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent} to allow Apptentive * to display Interactions such as Message Center. This method replaces the deprecated - * {@link #setPendingPushNotification(Intent)}. Calling this method for a push {@Intent} that did + * {@link #setPendingPushNotification(Intent)}. Calling this method for a push {@link Intent} that did * not come from Apptentive will return a null object. If you receive a null object, your app will - * need to handle this notification itself. - *

- *

- * This is the method you will likely need if you integrated using: + * need to handle this notification itself.

+ *

This is the method you will likely need if you integrated using:

* - *

* * @param intent An {@link Intent} containing the Apptentive Push data. Pass in what you receive * in the Service or BroadcastReceiver that is used by your chosen push provider. @@ -526,21 +528,17 @@ public static PendingIntent buildPendingIntentFromPushNotification(@NonNull Inte } /** - *

- * Use this method in your push receiver to build a pending Intent when an Apptentive push + *

Use this method in your push receiver to build a pending Intent when an Apptentive push * notification is received. Pass the generated PendingIntent to * {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent} to allow Apptentive * to display Interactions such as Message Center. This method replaces the deprecated * {@link #setPendingPushNotification(Bundle)}. Calling this method for a push {@link Bundle} that * did not come from Apptentive will return a null object. If you receive a null object, your app - * will need to handle this notification itself. - *

- *

- * This is the method you will likely need if you integrated using: + * will need to handle this notification itself.

+ *

This is the method you will likely need if you integrated using:

* - *

* * @param bundle A {@link Bundle} containing the Apptentive Push data. Pass in what you receive in * the the Service or BroadcastReceiver that is used by your chosen push provider. @@ -555,21 +553,17 @@ public static PendingIntent buildPendingIntentFromPushNotification(@NonNull Bund } /** - *

- * Use this method in your push receiver to build a pending Intent when an Apptentive push + *

Use this method in your push receiver to build a pending Intent when an Apptentive push * notification is received. Pass the generated PendingIntent to * {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent} to allow Apptentive * to display Interactions such as Message Center. This method replaces the deprecated * {@link #setPendingPushNotification(Bundle)}. Calling this method for a push {@link Bundle} that * did not come from Apptentive will return a null object. If you receive a null object, your app - * will need to handle this notification itself. - *

- *

- * This is the method you will likely need if you integrated using: + * will need to handle this notification itself.

+ *

This is the method you will likely need if you integrated using:

* - *

* * @param data A {@link Map}<{@link String},{@link String}> containing the Apptentive Push * data. Pass in what you receive in the the Service or BroadcastReceiver that is @@ -1086,8 +1080,7 @@ public static void sendAttachmentFile(InputStream is, String mimeType) { message.setSenderId(ApptentiveInternal.getInstance().getPersonId()); ArrayList attachmentStoredFiles = new ArrayList(); - String localFilePath = Util.generateCacheFilePathFromNonceOrPrefix(ApptentiveInternal.getInstance().getApplicationContext(), - message.getNonce(), null); + String localFilePath = Util.generateCacheFilePathFromNonceOrPrefix(ApptentiveInternal.getInstance().getApplicationContext(), message.getNonce(), null); String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); if (!TextUtils.isEmpty(extension)) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java index ed91a7169..422739037 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveInternal.java @@ -273,7 +273,7 @@ public boolean setApplicationDefaultTheme(int themeResId) { // If passed theme res id does not exist, an exception would be thrown and caught appContext.getResources().getResourceName(themeResId); - // Check if the theme to be inheritd from is an AppCompat theme. + // Check if the theme to be inherited from is an AppCompat theme. Resources.Theme appDefaultTheme = appContext.getResources().newTheme(); appDefaultTheme.applyStyle(themeResId, true); @@ -294,10 +294,15 @@ public boolean setApplicationDefaultTheme(int themeResId) { return false; } - /* - * Object getter methods through ApptentiveInternal instance - * Usage: ApptentiveInternal.getInstance().getxxxx() + /** + * Must be called after {@link ApptentiveInternal#setApplicationDefaultTheme(int)} + * @return true it the app is using an AppCompat theme */ + public boolean isAppUsingAppCompatTheme() { + return appDefaultAppCompatThemeId != 0; + } + + // Object getter methods reqiure an instance. Get an instance with ApptentiveInternal.getInstance() public Context getApplicationContext() { return appContext; @@ -353,7 +358,7 @@ public Resources.Theme getApptentiveToolbarTheme() { return apptentiveToolbarTheme; } - public int getDefaultStatusbarColor() { + public int getDefaultStatusBarColor() { return statusBarColorDefault; } @@ -445,13 +450,12 @@ public void onActivityStarted(Activity activity) { if (activity != null) { // Set current foreground activity reference whenever a new activity is started currentTaskStackTopActivity = new WeakReference(activity); - messageManager.setCurrentForgroundActivity(activity); + messageManager.setCurrentForegroundActivity(activity); } checkAndUpdateApptentiveConfigurations(); syncDevice(); - syncSdk(); syncPerson(); } @@ -459,7 +463,7 @@ public void onActivityResumed(Activity activity) { if (activity != null) { // Set current foreground activity reference whenever a new activity is started currentTaskStackTopActivity = new WeakReference(activity); - messageManager.setCurrentForgroundActivity(activity); + messageManager.setCurrentForegroundActivity(activity); } } @@ -473,7 +477,7 @@ public void onAppEnterForeground() { public void onAppEnterBackground() { appIsInForeground = false; currentTaskStackTopActivity = null; - messageManager.setCurrentForgroundActivity(null); + messageManager.setCurrentForegroundActivity(null); payloadWorker.appWentToBackground(); messageManager.appWentToBackground(); } @@ -515,8 +519,8 @@ public void updateApptentiveInteractionTheme(Resources.Theme interactionTheme, C interactionTheme.applyStyle(themeOverrideResId, true); } - // Step 5: Update statusbar color - /* Obtain the default statusbar color. When Apptentive Modal interaction is shown, + // Step 5: Update status bar color + /* Obtain the default status bar color. When an Apptentive Modal interaction is shown, * a translucent overlay would be applied on top of statusBarColorDefault */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -567,38 +571,26 @@ public boolean init() { manifestApiKey = Util.trim(metaData.getString(Constants.MANIFEST_KEY_APPTENTIVE_API_KEY)); logLevelOverride = Util.trim(metaData.getString(Constants.MANIFEST_KEY_APPTENTIVE_LOG_LEVEL)); apptentiveDebug = metaData.getBoolean(Constants.MANIFEST_KEY_APPTENTIVE_DEBUG); - isAppDebuggable = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; } - // If the app default theme specified in AndroidManifest is an AppCompat theme, use it for Apptentive theme inheritance. - boolean appThemeIsAppCompatTheme = setApplicationDefaultTheme(ai.theme); + // Used for application theme inheritance if the theme is an AppCompat theme. + setApplicationDefaultTheme(ai.theme); - // check if host app defines an apptentive theme override - int themeOverrideResId = appContext.getResources().getIdentifier("ApptentiveThemeOverride", "style", appPackageName); + AppRelease appRelease = AppRelease.generateCurrentAppRelease(appContext); + + isAppDebuggable = appRelease.getDebug(); + currentVersionCode = appRelease.getVersionCode(); + currentVersionName = appRelease.getVersionName(); - currentVersionCode = packageInfo.versionCode; - currentVersionName = packageInfo.versionName; VersionHistoryEntry lastVersionEntrySeen = VersionHistoryStore.getLastVersionSeen(); - AppRelease appRelease = new AppRelease(); - appRelease.setVersion(currentVersionName); - appRelease.setIdentifier(appPackageName); - appRelease.setBuildNumber(String.valueOf(currentVersionCode)); - appRelease.setTargetSdkVersion(String.valueOf(packageInfo.applicationInfo.targetSdkVersion)); - appRelease.setAppStore(Util.getInstallerPackageName(appContext)); - // Set Apptentive theme inheritance metrics - appRelease.setInheritStyle(appThemeIsAppCompatTheme); - appRelease.setOverrideStyle(themeOverrideResId != 0); - appRelease.setDebug(isAppDebuggable); if (lastVersionEntrySeen == null) { onVersionChanged(null, currentVersionCode, null, currentVersionName, appRelease); } else { int lastSeenVersionCode = lastVersionEntrySeen.getVersionCode(); - Apptentive.Version currentVersionNameVersion = new Apptentive.Version(); - currentVersionNameVersion.setVersion(currentVersionName); Apptentive.Version lastSeenVersionNameVersion = new Apptentive.Version(); lastSeenVersionNameVersion.setVersion(lastVersionEntrySeen.getVersionName()); - if (!(currentVersionCode == lastSeenVersionCode) || !currentVersionNameVersion.equals(lastSeenVersionNameVersion)) { + if (!(currentVersionCode == lastSeenVersionCode) || !currentVersionName.equals(lastSeenVersionNameVersion.getVersion())) { onVersionChanged(lastVersionEntrySeen.getVersionCode(), currentVersionCode, lastVersionEntrySeen.getVersionName(), currentVersionName, appRelease); } } @@ -669,10 +661,9 @@ public boolean init() { private void onVersionChanged(Integer previousVersionCode, Integer currentVersionCode, String previousVersionName, String currentVersionName, AppRelease currentAppRelease) { ApptentiveLog.i("Version changed: Name: %s => %s, Code: %d => %d", previousVersionName, currentVersionName, previousVersionCode, currentVersionCode); VersionHistoryStore.updateVersionHistory(currentVersionCode, currentVersionName); - AppRelease appRelease = AppReleaseManager.storeAppReleaseAndReturnDiff(currentAppRelease); - if (appRelease != null) { - ApptentiveLog.d("App release was updated."); - taskManager.addPayload(appRelease); + if (previousVersionCode != null) { + AppReleaseManager.storeAppRelease(currentAppRelease); + taskManager.addPayload(currentAppRelease); } invalidateCaches(); } @@ -680,6 +671,7 @@ private void onVersionChanged(Integer previousVersionCode, Integer currentVersio private void onSdkVersionChanged(Context context, String previousSdkVersion, String currentSdkVersion) { ApptentiveLog.i("SDK version changed: %s => %s", previousSdkVersion, currentSdkVersion); context.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE).edit().putString(Constants.PREF_KEY_LAST_SEEN_SDK_VERSION, currentSdkVersion).apply(); + syncSdk(); invalidateCaches(); } @@ -748,6 +740,9 @@ private boolean fetchConversationToken() { request.setDevice(DeviceManager.storeDeviceAndReturnIt()); request.setSdk(SdkManager.storeSdkAndReturnIt()); request.setPerson(PersonManager.storePersonAndReturnIt()); + AppRelease currentAppRelease = AppRelease.generateCurrentAppRelease(appContext); + AppReleaseManager.storeAppRelease(currentAppRelease); + request.setAppRelease(currentAppRelease); ApptentiveHttpResponse response = ApptentiveClient.getConversationToken(request); if (response == null) { @@ -865,17 +860,13 @@ void syncDevice() { } /** - * Sends current Sdk to the server if it differs from the last time it was sent. + * Sends current SDK to the server. */ private void syncSdk() { - Sdk sdk = SdkManager.storeSdkAndReturnDiff(); - if (sdk != null) { - ApptentiveLog.d("Sdk was updated."); - ApptentiveLog.v(sdk.toString()); - taskManager.addPayload(sdk); - } else { - ApptentiveLog.d("Sdk was not updated."); - } + Sdk sdk = SdkManager.generateCurrentSdk(); + SdkManager.storeSdk(sdk); + ApptentiveLog.v(sdk.toString()); + taskManager.addPayload(sdk); } /** @@ -1177,7 +1168,7 @@ public static PendingIntent prepareMessageCenterPendingIntent(Context context) { /** * Checks to see if Apptentive was properly registered, and logs a message if not. - * @return true if properly registered, elss false. + * @return true if properly registered, else false. */ public static boolean checkRegistered() { if (!ApptentiveInternal.isApptentiveRegistered()) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java index 73845b9e5..478d3295e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/ApptentiveViewActivity.java @@ -155,10 +155,13 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onPageSelected(int position) { - ApptentiveBaseFragment currentFragment = (ApptentiveBaseFragment) viewPager_Adapter.getItem(viewPager.getCurrentItem()); + final ApptentiveBaseFragment currentFragment = (ApptentiveBaseFragment) viewPager_Adapter.getItem(viewPager.getCurrentItem()); + // Set the Activity title for TalkBack support + final String title = currentFragment.getTitle(); + if (currentFragment != null && currentFragment.getActivity() != null) { + currentFragment.getActivity().setTitle(title); + } if (!currentFragment.isShownAsModalDialog()) { - - final String title = currentFragment.getTitle(); toolbar.post(new Runnable() { @Override public void run() { @@ -394,7 +397,7 @@ public void onGlobalLayout() { private void setStatusBarColor() { // Changing status bar color is a post-21 feature if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - int statusBarDefaultColor = ApptentiveInternal.getInstance().getDefaultStatusbarColor(); + int statusBarDefaultColor = ApptentiveInternal.getInstance().getDefaultStatusBarColor(); int overlayColor = ContextCompat.getColor(this, R.color.apptentive_activity_frame_dark); getWindow().setStatusBarColor(Util.alphaMixColors(statusBarDefaultColor, overlayColor)); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java index 27f6407af..b07adab94 100755 --- a/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/comm/ApptentiveClient.java @@ -28,7 +28,7 @@ public class ApptentiveClient { - public static final int API_VERSION = 6; + public static final int API_VERSION = 7; private static final String USER_AGENT_STRING = "Apptentive/%s (Android)"; // Format with SDK version string. @@ -60,7 +60,7 @@ public static ApptentiveHttpResponse getAppConfiguration() { } /** - * Gets all messages since the message specified by guid was sent. + * Gets all messages since the message specified by GUID was sent. * * @return An ApptentiveHttpResponse object with the HTTP response code, reason, and content. */ @@ -118,7 +118,7 @@ public static ApptentiveHttpResponse getInteractions() { * @param uri server url. * @param method Get/Post/Put * @param body Data to be POSTed/Put, not used for GET - * @return ApptentiveHttpResponse containg content and response returned from the server. + * @return ApptentiveHttpResponse containing content and response returned from the server. */ private static ApptentiveHttpResponse performHttpRequest(String oauthToken, String uri, Method method, String body) { uri = getEndpointBase() + uri; @@ -347,20 +347,20 @@ private static ApptentiveHttpResponse performMultipartFilePost(String oauthToken ret.setReason(connection.getResponseMessage()); // Read the normal response. - InputStream nis = null; - ByteArrayOutputStream nbaos = null; + InputStream responseInputStream = null; + ByteArrayOutputStream byteArrayOutputStream = null; try { - nis = connection.getInputStream(); - nbaos = new ByteArrayOutputStream(); + responseInputStream = connection.getInputStream(); + byteArrayOutputStream = new ByteArrayOutputStream(); byte[] eBuf = new byte[1024]; int eRead; - while (nis != null && (eRead = nis.read(eBuf, 0, 1024)) > 0) { - nbaos.write(eBuf, 0, eRead); + while (responseInputStream != null && (eRead = responseInputStream.read(eBuf, 0, 1024)) > 0) { + byteArrayOutputStream.write(eBuf, 0, eRead); } - ret.setContent(nbaos.toString()); + ret.setContent(byteArrayOutputStream.toString()); } finally { - Util.ensureClosed(nis); - Util.ensureClosed(nbaos); + Util.ensureClosed(responseInputStream); + Util.ensureClosed(byteArrayOutputStream); } ApptentiveLog.d("HTTP %d: %s", connection.getResponseCode(), connection.getResponseMessage()); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppRelease.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppRelease.java index 4182b05f4..48fbd5c44 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppRelease.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppRelease.java @@ -6,14 +6,23 @@ package com.apptentive.android.sdk.model; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.util.Util; import org.json.JSONException; public class AppRelease extends Payload { - private static final String KEY_VERSION = "version"; - private static final String KEY_BUILD_NUMBER = "build_number"; + private static final String KEY_TYPE = "type"; + private static final String KEY_VERSION_NAME = "version_name"; + private static final String KEY_VERSION_CODE = "version_code"; private static final String KEY_IDENTIFIER = "identifier"; private static final String KEY_TARGET_SDK_VERSION = "target_sdk_version"; private static final String KEY_APP_STORE = "app_store"; @@ -33,51 +42,54 @@ public void initBaseType() { setBaseType(BaseType.app_release); } - public String getVersion() { - try { - if (!isNull(KEY_VERSION)) { - return getString(KEY_VERSION); - } - } catch (JSONException e) { - // Ignore + public String getType() { + if (!isNull(KEY_TYPE)) { + return optString(KEY_TYPE, null); } return null; } - public void setVersion(String version) { + public void setType(String type) { try { - put(KEY_VERSION, version); + put(KEY_TYPE, type); } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION); + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_TYPE); } } - public String getBuildNumber() { - try { - if (!isNull(KEY_BUILD_NUMBER)) { - return getString(KEY_BUILD_NUMBER); - } - } catch (JSONException e) { - // Ignore + public String getVersionName() { + if (!isNull(KEY_VERSION_NAME)) { + return optString(KEY_VERSION_NAME, null); } return null; } - public void setBuildNumber(String buildNumber) { + public void setVersionName(String versionName) { try { - put(KEY_BUILD_NUMBER, buildNumber); + put(KEY_VERSION_NAME, versionName); } catch (JSONException e) { - ApptentiveLog.w("Error adding %s to AppRelease.", KEY_BUILD_NUMBER); + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION_NAME); } } - public String getIdentifier() { + public int getVersionCode() { + if (!isNull(KEY_VERSION_CODE)) { + return optInt(KEY_VERSION_CODE, -1); + } + return -1; + } + + public void setVersionCode(int versionCode) { try { - if (!isNull(KEY_IDENTIFIER)) { - return getString(KEY_IDENTIFIER); - } + put(KEY_VERSION_CODE, versionCode); } catch (JSONException e) { - // Ignore + ApptentiveLog.w("Error adding %s to AppRelease.", KEY_VERSION_CODE); + } + } + + public String getIdentifier() { + if (!isNull(KEY_IDENTIFIER)) { + return optString(KEY_IDENTIFIER, null); } return null; } @@ -91,12 +103,8 @@ public void setIdentifier(String identifier) { } public String getTargetSdkVersion() { - try { - if (!isNull(KEY_TARGET_SDK_VERSION)) { - return getString(KEY_TARGET_SDK_VERSION); - } - } catch (JSONException e) { - // Ignore + if (!isNull(KEY_TARGET_SDK_VERSION)) { + return optString(KEY_TARGET_SDK_VERSION); } return null; } @@ -110,12 +118,8 @@ public void setTargetSdkVersion(String targetSdkVersion) { } public String getAppStore() { - try { - if (!isNull(KEY_APP_STORE)) { - return getString(KEY_APP_STORE); - } - } catch (JSONException e) { - // Ignore + if (!isNull(KEY_APP_STORE)) { + return optString(KEY_APP_STORE, null); } return null; } @@ -133,9 +137,9 @@ public boolean getInheritStyle() { return optBoolean(KEY_STYLE_INHERIT); } - public void setInheritStyle(boolean bval) { + public void setInheritStyle(boolean inheritStyle) { try { - put(KEY_STYLE_INHERIT, bval); + put(KEY_STYLE_INHERIT, inheritStyle); } catch (JSONException e) { ApptentiveLog.w("Error adding %s to AppRelease.", KEY_STYLE_INHERIT); } @@ -146,9 +150,9 @@ public boolean getOverrideStyle() { return optBoolean(KEY_STYLE_OVERRIDE); } - public void setOverrideStyle(boolean bval) { + public void setOverrideStyle(boolean overrideStyle) { try { - put(KEY_STYLE_OVERRIDE, bval); + put(KEY_STYLE_OVERRIDE, overrideStyle); } catch (JSONException e) { ApptentiveLog.w("Error adding %s to AppRelease.", KEY_STYLE_OVERRIDE); } @@ -165,4 +169,44 @@ public void setDebug(boolean debug) { ApptentiveLog.w("Error adding %s to AppRelease.", KEY_DEBUG); } } + + public static AppRelease generateCurrentAppRelease(Context context) { + + AppRelease appRelease = new AppRelease(); + + String appPackageName = context.getPackageName(); + PackageManager packageManager = context.getPackageManager(); + + int currentVersionCode = 0; + String currentVersionName = "0"; + int targetSdkVersion = 0; + boolean isAppDebuggable = false; + try { + PackageInfo packageInfo = packageManager.getPackageInfo(appPackageName, PackageManager.GET_META_DATA | PackageManager.GET_RECEIVERS); + ApplicationInfo ai = packageInfo.applicationInfo; + currentVersionCode = packageInfo.versionCode; + currentVersionName = packageInfo.versionName; + targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion; + Bundle metaData = ai.metaData; + if (metaData != null) { + isAppDebuggable = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } + } catch (PackageManager.NameNotFoundException e) { + ApptentiveLog.e("Failed to read app's PackageInfo."); + } + + int themeOverrideResId = context.getResources().getIdentifier("ApptentiveThemeOverride", "style", appPackageName); + + appRelease.setType("android"); + appRelease.setVersionName(currentVersionName); + appRelease.setIdentifier(appPackageName); + appRelease.setVersionCode(currentVersionCode); + appRelease.setTargetSdkVersion(String.valueOf(targetSdkVersion)); + appRelease.setAppStore(Util.getInstallerPackageName(context)); + appRelease.setInheritStyle(ApptentiveInternal.getInstance().isAppUsingAppCompatTheme()); + appRelease.setOverrideStyle(themeOverrideResId != 0); + appRelease.setDebug(isAppDebuggable); + + return appRelease; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java index ad447e7ed..aa4236bfe 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/AppReleaseFactory.java @@ -7,11 +7,9 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; + import org.json.JSONException; -/** - * @author Sky Kelsey - */ public class AppReleaseFactory { public static AppRelease fromJson(String json) { try { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/CodePointStore.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/CodePointStore.java index 86718a3e1..acf4ffb11 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/CodePointStore.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/CodePointStore.java @@ -1,18 +1,18 @@ /* - * Copyright (c) 2014, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.model; -import android.content.Context; import android.content.SharedPreferences; import com.apptentive.android.sdk.ApptentiveInternal; import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; + import org.json.JSONException; import org.json.JSONObject; @@ -51,8 +51,6 @@ * } * } * - * - * @author Sky Kelsey */ public class CodePointStore { @@ -62,8 +60,8 @@ public class CodePointStore { public static final String KEY_INTERACTIONS = "interactions"; public static final String KEY_LAST = "last"; // The last time this codepoint was seen. public static final String KEY_TOTAL = "total"; // The total times this code point was seen. - public static final String KEY_VERSION = "version"; - public static final String KEY_BUILD = "build"; + public static final String KEY_VERSION_NAME = "version"; + public static final String KEY_VERSION_CODE = "build"; public CodePointStore() { @@ -101,18 +99,18 @@ public synchronized void storeInteractionForCurrentAppVersion(String fullCodePoi } private void storeRecordForCurrentAppVersion(boolean isInteraction, String fullCodePoint) { - String version = ApptentiveInternal.getInstance().getApplicationVersionName(); - int build = ApptentiveInternal.getInstance().getApplicationVersionCode(); - storeRecord(isInteraction, fullCodePoint, version, build); + String versionName = ApptentiveInternal.getInstance().getApplicationVersionName(); + int versionCode = ApptentiveInternal.getInstance().getApplicationVersionCode(); + storeRecord(isInteraction, fullCodePoint, versionName, versionCode); } - public synchronized void storeRecord(boolean isInteraction, String fullCodePoint, String version, int build) { - storeRecord(isInteraction, fullCodePoint, version, build, Util.currentTimeSeconds()); + public synchronized void storeRecord(boolean isInteraction, String fullCodePoint, String versionName, int versionCode) { + storeRecord(isInteraction, fullCodePoint, versionName, versionCode, Util.currentTimeSeconds()); } - public synchronized void storeRecord(boolean isInteraction, String fullCodePoint, String version, int build, double currentTimeSeconds) { - String buildString = String.valueOf(build); - if (fullCodePoint != null && version != null) { + public synchronized void storeRecord(boolean isInteraction, String fullCodePoint, String versionName, int versionCode, double currentTimeSeconds) { + String versionCodeString = String.valueOf(versionCode); + if (fullCodePoint != null && versionName != null) { try { String recordTypeKey = isInteraction ? CodePointStore.KEY_INTERACTIONS : CodePointStore.KEY_CODE_POINT; JSONObject recordType; @@ -142,37 +140,37 @@ public synchronized void storeRecord(boolean isInteraction, String fullCodePoint } codePointJson.put(KEY_TOTAL, total + 1); - // Get or create version object. - JSONObject versionJson; - if (!codePointJson.isNull(KEY_VERSION)) { - versionJson = codePointJson.getJSONObject(KEY_VERSION); + // Get or create versionName object. + JSONObject versionNameJson; + if (!codePointJson.isNull(KEY_VERSION_NAME)) { + versionNameJson = codePointJson.getJSONObject(KEY_VERSION_NAME); } else { - versionJson = new JSONObject(); - codePointJson.put(KEY_VERSION, versionJson); + versionNameJson = new JSONObject(); + codePointJson.put(KEY_VERSION_NAME, versionNameJson); } - // Set count for current version. - int existingVersionCount = 0; - if (!versionJson.isNull(version)) { - existingVersionCount = versionJson.getInt(version); + // Set count for current versionName. + int existingVersionNameCount = 0; + if (!versionNameJson.isNull(versionName)) { + existingVersionNameCount = versionNameJson.getInt(versionName); } - versionJson.put(version, existingVersionCount + 1); + versionNameJson.put(versionName, existingVersionNameCount + 1); - // Get or create build object. - JSONObject buildJson; - if (!codePointJson.isNull(KEY_BUILD)) { - buildJson = codePointJson.getJSONObject(KEY_BUILD); + // Get or create versionCode object. + JSONObject versionCodeJson; + if (!codePointJson.isNull(KEY_VERSION_CODE)) { + versionCodeJson = codePointJson.getJSONObject(KEY_VERSION_CODE); } else { - buildJson = new JSONObject(); - codePointJson.put(KEY_BUILD, buildJson); + versionCodeJson = new JSONObject(); + codePointJson.put(KEY_VERSION_CODE, versionCodeJson); } - // Set count for the current build - int existingBuildCount = 0; - if (!buildJson.isNull(buildString)) { - existingBuildCount = buildJson.getInt(buildString); + // Set count for the current versionCode + int existingVersionCodeCount = 0; + if (!versionCodeJson.isNull(versionCodeString)) { + existingVersionCodeCount = versionCodeJson.getInt(versionCodeString); } - buildJson.put(buildString, existingBuildCount + 1); + versionCodeJson.put(versionCodeString, existingVersionCodeCount + 1); saveToPreference(); } catch (JSONException e) { @@ -207,7 +205,7 @@ public Long getTotalInvokes(boolean interaction, String name) { } catch (JSONException e) { // Ignore } - return 0l; + return 0L; } public Double getLastInvoke(boolean interaction, String name) { @@ -222,28 +220,28 @@ public Double getLastInvoke(boolean interaction, String name) { return null; } - public Long getVersionInvokes(boolean interaction, String name, String version) { + public Long getVersionNameInvokes(boolean interaction, String name, String versionName) { try { JSONObject record = getRecord(interaction, name); - if (record != null && record.has(KEY_VERSION)) { - JSONObject versionJson = record.getJSONObject(KEY_VERSION); - if (versionJson.has(version)) { - return versionJson.getLong(version); + if (record != null && record.has(KEY_VERSION_NAME)) { + JSONObject versionNameJson = record.getJSONObject(KEY_VERSION_NAME); + if (versionNameJson.has(versionName)) { + return versionNameJson.getLong(versionName); } } } catch (JSONException e) { // Ignore } - return 0l; + return 0L; } - public Long getBuildInvokes(boolean interaction, String name, String build) { + public Long getVersionCodeInvokes(boolean interaction, String name, String versionCode) { try { JSONObject record = getRecord(interaction, name); - if (record != null && record.has(KEY_BUILD)) { - JSONObject buildJson = record.getJSONObject(KEY_BUILD); - if (buildJson.has(build)) { - return buildJson.getLong(build); + if (record != null && record.has(KEY_VERSION_CODE)) { + JSONObject versionCodeJson = record.getJSONObject(KEY_VERSION_CODE); + if (versionCodeJson.has(versionCode)) { + return versionCodeJson.getLong(versionCode); } } } catch (JSONException e) { @@ -261,5 +259,4 @@ public void clear() { prefs.edit().remove(Constants.PREF_KEY_CODE_POINT_STORE).apply(); store = new JSONObject(); } - } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java index 25aed331a..4674f82c8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/model/ConversationTokenRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -7,12 +7,10 @@ package com.apptentive.android.sdk.model; import com.apptentive.android.sdk.ApptentiveLog; + import org.json.JSONException; import org.json.JSONObject; -/** - * @author Sky Kelsey - */ public class ConversationTokenRequest extends JSONObject { @@ -43,5 +41,11 @@ public void setPerson(Person person) { } } - //TODO: Handle client info as well. + public void setAppRelease(AppRelease appRelease) { + try { + put(appRelease.getBaseType().name(), appRelease); + } catch (JSONException e) { + ApptentiveLog.e("Error adding %s to ConversationTokenRequest", appRelease.getBaseType().name()); + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java index 8ffa190ae..f67ffcbc4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/ApptentiveBaseFragment.java @@ -78,7 +78,7 @@ public interface OnFragmentTransitionListener { { - //Prepare the reflections to manage hidden fileds + // Prepare the reflections to manage hidden fields try { fragmentImplClass = Class.forName("android.support.v4.app.FragmentManagerImpl"); hostField = fragmentImplClass.getDeclaredField("mHost"); @@ -217,8 +217,13 @@ public void onCreate(Bundle savedInstanceState) { } } - if (!bShownAsModal && interaction != null) { - sectionTitle = interaction.getTitle(); + if (interaction != null) { + // Set the title for modal Interactions for TalkBack support here. Fullscreen Interactions will set title in the ViewPager when they page into view. + if (bShownAsModal) { + getActivity().setTitle(interaction.getTitle()); + } else { + sectionTitle = interaction.getTitle(); + } } if (toolbarLayoutId != 0 && getMenuResourceId() != 0) { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java index f50dac8f4..7e04ad23a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterErrorFragment.java @@ -82,7 +82,7 @@ private boolean wasLastAttemptServerError(Context context) { } - public boolean onBackPressed() { + public boolean onBackPressed(boolean hardwareBackButtonWasPressed) { EngagementModule.engage(getActivity(), "com.apptentive", "MessageCenter", null, EVENT_NAME_NO_INTERACTION_CLOSE, null, null, (ExtendedData[]) null); return false; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java index 6b8c20d89..f50710ef9 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/fragment/MessageCenterFragment.java @@ -7,9 +7,6 @@ package com.apptentive.android.sdk.module.engagement.interaction.fragment; -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -26,19 +23,18 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewCompat; +import android.support.v7.widget.LinearLayoutManager; import android.text.Editable; -import android.text.Layout; import android.text.TextUtils; -import android.view.MenuItem.OnMenuItemClickListener; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.AbsListView; import android.widget.EditText; -import android.widget.ListView; import com.apptentive.android.sdk.Apptentive; import com.apptentive.android.sdk.ApptentiveInternal; @@ -49,20 +45,24 @@ import com.apptentive.android.sdk.module.engagement.EngagementModule; import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; import com.apptentive.android.sdk.module.messagecenter.MessageManager; +import com.apptentive.android.sdk.module.messagecenter.OnListviewItemActionListener; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterComposingItem; +import com.apptentive.android.sdk.module.messagecenter.model.ContextMessage; +import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterStatus; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterUtil; +import com.apptentive.android.sdk.module.messagecenter.model.WhoCard; import com.apptentive.android.sdk.module.messagecenter.view.AttachmentPreviewDialog; -import com.apptentive.android.sdk.module.messagecenter.view.MessageAdapter; -import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterComposingActionBarView; -import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterListView; +import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerView; +import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter; +import com.apptentive.android.sdk.module.messagecenter.view.holder.MessageComposerHolder; import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.util.AnimationUtil; import com.apptentive.android.sdk.util.Constants; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.image.ApptentiveAttachmentLoader; +import com.apptentive.android.sdk.util.image.ApptentiveImageGridView; import com.apptentive.android.sdk.util.image.ImageGridViewAdapter; import com.apptentive.android.sdk.util.image.ImageItem; @@ -74,100 +74,86 @@ import java.lang.ref.WeakReference; import java.text.DateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Set; -public class MessageCenterFragment extends ApptentiveBaseFragment implements OnMenuItemClickListener, MessageManager.AfterSendMessageListener, - MessageAdapter.OnListviewItemActionListener, - MessageManager.OnNewIncomingMessagesListener, - AbsListView.OnScrollListener, - MessageCenterListView.OnListviewResizeListener, - ImageGridViewAdapter.Callback { +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_COMPOSER; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_CONTEXT; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_OUTGOING; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.STATUS; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.WHO_CARD; + +public class MessageCenterFragment extends ApptentiveBaseFragment implements + OnListviewItemActionListener, + MessageManager.AfterSendMessageListener, + MessageManager.OnNewIncomingMessagesListener, + OnMenuItemClickListener, + AbsListView.OnScrollListener, + ImageGridViewAdapter.Callback { private MenuItem profileMenuItem; private boolean bShowProfileMenuItem = true; // keys used to save instance in the event of rotation - private final static String LIST_TOP_INDEX = "list_top_index"; - private final static String LIST_TOP_OFFSET = "list_top_offset"; - private final static String COMPOSING_EDITTEXT_STATE = "edittext"; - private final static String COMPOSING_ATTACHMENTS = "attachments"; - private final static String WHO_CARD_MODE = "whocardmode"; - private final static String WHO_CARD_NAME = "whocardname"; - private final static String WHO_CARD_EMAIL = "whocardemail"; - private final static String WHO_CARD_AVATAR_FILE = "whocardavatar"; + private final static String LIST_TOP_INDEX = "key_list_top_index_state"; + private final static String LIST_TOP_OFFSET = "key_list_top_offset_state"; + private final static String COMPOSING_EDITTEXT_STATE = "key_edit_text_state"; + private final static String WHO_CARD_MODE = "key_who_card_mode_state"; + private final static String WHO_CARD_NAME = "key_who_card_name_state"; + private final static String WHO_CARD_EMAIL = "key_who_card_email_state"; + private final static String WHO_CARD_AVATAR_FILE = "key_who_card_avatar_state"; private final static String DIALOG_IMAGE_PREVIEW = "imagePreviewDialog"; - private final static int WHO_CARD_MODE_INIT = 1; - private final static int WHO_CARD_MODE_EDIT = 2; - private final static long DEFAULT_DELAYMILLIS = 200; /* Fragment.getActivity() may return null if not attached. * hostingActivityRef is always set in onAttach() - * Keeping a cached weak reference ensures it's save to use + * Keeping a cached weak reference ensures it's safe to use */ private WeakReference hostingActivityRef; - private ListView messageCenterListView; // List of apptentive messages - private EditText messageEditText; // Composing area private View fab; + private ArrayList listItems = new ArrayList(); + private MessageCenterRecyclerViewAdapter messageCenterRecyclerViewAdapter; + private MessageCenterRecyclerView messageCenterRecyclerView; - // Data backing of the listview - private ArrayList messages = new ArrayList(); - private MessageAdapter messageCenterListAdapter; - - // MesssageCenterView is set to paused when it fails to send message - private boolean isPaused = false; - // Count how many paused ongoing messages - private int unsendMessagesCount = 0; - - - private MessageCenterStatus statusItem; - private MessageCenterComposingItem composingItem; - private MessageCenterComposingItem actionBarItem; - private MessageCenterComposingItem whoCardItem; - private CompoundMessage contextualMessage; - - private ArrayList imageAttachmentstList = new ArrayList(); - - /** - * Used to save the state of the message text box if the user closes Message Center for a moment, - * , rotate device, attaches a file, etc. - */ + // Holder and view references + private MessageComposerHolder composer; + private EditText composerEditText; + private EditText whoCardNameEditText; + private EditText whoCardEmailEditText; private Parcelable composingViewSavedState; - private ArrayList savedAttachmentstList; - /* * Set to true when user launches image picker, and set to false once an image is picked * This is used to track if the user tried to attach an image but abandoned the image picker * without picking anything */ - private boolean imagePickerLaunched = false; + private boolean imagePickerStillOpen = false; + private ArrayList pendingAttachments = new ArrayList(); - /** - * Used to save the state of the who card if the user closes Message Center for a moment, - * , rotate device, attaches a file, etc. - */ - private int pendingWhoCardMode; + private boolean pendingWhoCardMode; + private String pendingWhoCardAvatarFile; private Parcelable pendingWhoCardName; private Parcelable pendingWhoCardEmail; - private String pendingWhoCardAvatarFile; + private boolean forceShowKeyboard; + + + // MesssageCenterView is set to paused when it fails to send message + private boolean isPaused = false; + // Count how many paused ongoing messages + private int unsentMessagesCount = 0; private int listViewSavedTopIndex = -1; private int listViewSavedTopOffset; // FAB y-offset in pixels from the bottom edge private int fabPaddingPixels; - private int attachmentsAllowed; protected static final int MSG_SCROLL_TO_BOTTOM = 1; protected static final int MSG_SCROLL_FROM_TOP = 2; @@ -178,6 +164,15 @@ public class MessageCenterFragment extends ApptentiveBaseFragment= Build.VERSION_CODES.LOLLIPOP) { - messageCenterListView.setNestedScrollingEnabled(true); + messageCenterRecyclerView.setNestedScrollingEnabled(true); } - ((MessageCenterListView) messageCenterListView).setOnListViewResizeListener(this); - messageCenterListView.setItemsCanFocus(true); - + LinearLayoutManager layoutManager = new LinearLayoutManager(this.getContext()); + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); + messageCenterRecyclerView.setLayoutManager(layoutManager); fab = rootView.findViewById(R.id.composing_fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + forceShowKeyboard = true; addComposingCard(); } }); - boolean showKeyboard = false; - if (isInitMessages) { - List items = ApptentiveInternal.getInstance().getMessageManager().getMessageCenterListItems(); + messageCenterRecyclerViewAdapter = new MessageCenterRecyclerViewAdapter(this, this, interaction, listItems); + + if (isInitialViewCreation) { + List items = ApptentiveInternal.getInstance().getMessageManager().getMessageCenterListItems(); if (items != null) { - // populate message list from db + // Get message list from DB, and use this as the starting point for the listItems array. prepareMessages(items); } - if (contextualMessage != null) { - addContextualMessageItem(); + String contextMessageBody = interaction.getContextualMessageBody(); + if (contextMessageBody != null) { + // Clear any pending composing message to present an empty composing area + clearPendingComposingMessage(); + messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS); + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_ADD_CONTEXT_MESSAGE, new ContextMessage(contextMessageBody))); + // If checkAddWhoCardIfRequired returns true, it will add WhoCard, otherwise add composing card + if (!checkAddWhoCardIfRequired()) { + addedAnInteractiveCard = true; + forceShowKeyboard = false; + addComposingCard(); + } } /* Add who card with pending contents - ** Pending contents would be saved if the user was in composing Who card mode and exitted through back button + ** Pending contents would be saved if the user was in composing Who card mode and exited through back button */ else if (pendingWhoCardName != null || pendingWhoCardEmail != null || pendingWhoCardAvatarFile != null) { - addWhoCardAsMessageItem(pendingWhoCardMode); + addedAnInteractiveCard = true; + addWhoCard(pendingWhoCardMode); } else if (!checkAddWhoCardIfRequired()) { - /* If there is only greeting message, show composing. - * If Who Card is required, show Who Card first + /* If there are no items in the list, then it means that the Greeting will be added, but nothing else. + * In that case, show the Composer, because Message Center hasn't been opened before. + * If Who Card is required, show Who Card first. */ - if (messages.size() == 1) { - addComposerMessageItems(); + if (listItems.size() == 0) { + addedAnInteractiveCard = true; + addComposingCard(); } else { // Finally check if status message need to be restored addExpectationStatusIfNeeded(); } } - - updateMessageSentStates(); // Force timestamp recompilation. - - } - - messageCenterListAdapter = new MessageAdapter(this, messages, interaction); - - if (whoCardItem != null) { - showKeyboard = true; - } else if (composingItem != null) { - showKeyboard = true; - if (messages.size() == 3 || contextualMessage != null) { - showKeyboard = false; + } else { + // Need to account for an input view that was added before orientation change, etc. + if (listItems != null) { + for (MessageCenterListItem item : listItems) { + if (item.getListItemType() == MESSAGE_COMPOSER || item.getListItemType() == WHO_CARD) { + addedAnInteractiveCard = true; + } + } } } - messageCenterListAdapter.setForceShowKeyboard(showKeyboard); - messageCenterListView.setAdapter(messageCenterListAdapter); + messageCenterRecyclerView.setAdapter(messageCenterRecyclerViewAdapter); // Calculate FAB y-offset fabPaddingPixels = calculateFabPadding(rootView.getContext()); - attachmentsAllowed = rootView.getContext().getResources().getInteger(R.integer.apptentive_image_grid_default_attachments_total); + + if (!addedAnInteractiveCard) { + showFab(); + } + + // Retrieve any saved attachments + final SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + if (prefs.contains(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS)) { + JSONArray savedAttachmentsJsonArray = null; + try { + savedAttachmentsJsonArray = new JSONArray(prefs.getString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS, "")); + } catch (JSONException e) { + e.printStackTrace(); + } + if (savedAttachmentsJsonArray != null && savedAttachmentsJsonArray.length() > 0) { + if (pendingAttachments == null) { + pendingAttachments = new ArrayList(); + } else { + pendingAttachments.clear(); + } + for (int i = 0; i < savedAttachmentsJsonArray.length(); i++) { + try { + JSONObject savedAttachmentJson = savedAttachmentsJsonArray.getJSONObject(i); + if (savedAttachmentJson != null) { + pendingAttachments.add(new ImageItem(savedAttachmentJson)); + } + } catch (JSONException e) { + continue; + } + } + } + // Stored pending attachments have been restored, remove it from the persistent storage + SharedPreferences.Editor editor = prefs.edit(); + editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS).apply(); + } + updateMessageSentStates(); } public boolean onMenuItemClick(MenuItem menuItem) { int menuItemId = menuItem.getItemId(); if (menuItemId == R.id.profile) { - // Only allow profile editing when not already editing profile or in message composing - if (whoCardItem == null && composingItem == null) { - final SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - boolean bWhoCardSet = prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_SET, false); - - JSONObject data = new JSONObject(); - try { - data.put("required", interaction.getWhoCardRequired()); - data.put("trigger", "button"); - } catch (JSONException e) { - // - } - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString()); - addWhoCard((!bWhoCardSet) ? WHO_CARD_MODE_INIT : WHO_CARD_MODE_EDIT); + JSONObject data = new JSONObject(); + try { + data.put("required", interaction.getWhoCardRequired()); + data.put("trigger", "button"); + } catch (JSONException e) { + // } + EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString()); + + boolean whoCardDisplayedBefore = wasWhoCardAsPreviouslyDisplayed(); + forceShowKeyboard = true; + addWhoCard(!whoCardDisplayedBefore); return true; } else { return false; @@ -468,31 +490,28 @@ public boolean onMenuItemClick(MenuItem menuItem) { @Override public void onSaveInstanceState(Bundle outState) { - int index = messageCenterListView.getFirstVisiblePosition(); - View v = messageCenterListView.getChildAt(0); - int top = (v == null) ? 0 : (v.getTop() - messageCenterListView.getPaddingTop()); - outState.putInt(LIST_TOP_INDEX, index); + savePendingComposingMessage(); + //int index = messageCenterRecyclerView.getFirstVisiblePosition(); + View v = messageCenterRecyclerView.getChildAt(0); + int top = (v == null) ? 0 : (v.getTop() - messageCenterRecyclerView.getPaddingTop()); outState.putInt(LIST_TOP_OFFSET, top); outState.putParcelable(COMPOSING_EDITTEXT_STATE, saveEditTextInstanceState()); - if (messageCenterListAdapter != null) { - outState.putParcelable(WHO_CARD_NAME, messageCenterListAdapter.getWhoCardNameState()); - outState.putParcelable(WHO_CARD_EMAIL, messageCenterListAdapter.getWhoCardEmailState()); - outState.putString(WHO_CARD_AVATAR_FILE, messageCenterListAdapter.getWhoCardAvatarFileName()); - } - outState.putInt(WHO_CARD_MODE, pendingWhoCardMode); - if (contextualMessage == null) { - interaction.clearContextualMessage(); + if (messageCenterRecyclerViewAdapter != null) { + outState.putParcelable(WHO_CARD_NAME, whoCardNameEditText != null ? whoCardNameEditText.onSaveInstanceState() : null); + outState.putParcelable(WHO_CARD_EMAIL, whoCardEmailEditText != null ? whoCardEmailEditText.onSaveInstanceState() : null); + outState.putString(WHO_CARD_AVATAR_FILE, messageCenterRecyclerViewAdapter.getWhoCardAvatarFileName()); } + outState.putBoolean(WHO_CARD_MODE, pendingWhoCardMode); super.onSaveInstanceState(outState); } public boolean onBackPressed(boolean hardwareButton) { + savePendingComposingMessage(); ApptentiveViewActivity hostingActivity = (ApptentiveViewActivity) hostingActivityRef.get(); if (hostingActivity != null) { DialogFragment myFrag = (DialogFragment) (hostingActivity.getSupportFragmentManager()).findFragmentByTag(DIALOG_IMAGE_PREVIEW); if (myFrag != null) { myFrag.dismiss(); - myFrag = null; } cleanup(); if (hardwareButton) { @@ -505,17 +524,13 @@ public boolean onBackPressed(boolean hardwareButton) { } public boolean cleanup() { - savePendingComposingMessage(); clearPendingMessageCenterPushNotification(); - clearComposingUi(null, null, 0); - clearWhoCardUi(null, null, 0); // Set to null, otherwise they will hold reference to the activity context MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); mgr.clearInternalOnMessagesUpdatedListeners(); mgr.setAfterSendMessageListener(null); - ApptentiveInternal.getInstance().getAndClearCustomData(); ApptentiveAttachmentLoader.getInstance().clearMemoryCache(); return true; @@ -545,186 +560,115 @@ private void clearPendingMessageCenterPushNotification() { } } - public void addContextualMessageItem() { - // Clear any pending composing message to present an empty composing area - clearPendingComposingMessage(); - clearStatusItem(); - messages.add(contextualMessage); - // If checkAddWhoCardIfRequired returns true, it will add WhoCard, otherwise add composing card - if (!checkAddWhoCardIfRequired()) { - addComposingCard(); - } - } - public void addComposingCard() { hideFab(); hideProfileButton(); messagingActionHandler.removeMessages(MSG_MESSAGE_ADD_WHOCARD); messagingActionHandler.removeMessages(MSG_MESSAGE_ADD_COMPOSING); + messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS); messagingActionHandler.sendEmptyMessage(MSG_MESSAGE_ADD_COMPOSING); - } - - - public void addComposerMessageItems() { - clearStatusItem(); - actionBarItem = interaction.getComposerBar(); - messages.add(actionBarItem); - composingItem = interaction.getComposerArea(); - messages.add(composingItem); + messagingActionHandler.sendEmptyMessage(MSG_SCROLL_TO_BOTTOM); } private boolean checkAddWhoCardIfRequired() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - boolean bWhoCardSet = prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_SET, false); + boolean whoCardDisplayedBefore = wasWhoCardAsPreviouslyDisplayed(); + boolean addedWhoCard = false; if (interaction.getWhoCardRequestEnabled() && interaction.getWhoCardRequired()) { - if (!bWhoCardSet) { - addWhoCard(WHO_CARD_MODE_INIT); - return true; + if (!whoCardDisplayedBefore) { + forceShowKeyboard = true; + addWhoCard(true); + addedWhoCard = true; } else { String savedEmail = Apptentive.getPersonEmail(); if (TextUtils.isEmpty(savedEmail)) { - addWhoCard(WHO_CARD_MODE_EDIT); - return true; + forceShowKeyboard = true; + addWhoCard(false); + addedWhoCard = true; } } } - return false; - } - - public void addWhoCard(int mode) { - hideFab(); - hideProfileButton(); - messagingActionHandler.removeMessages(MSG_MESSAGE_ADD_WHOCARD); - messagingActionHandler.removeMessages(MSG_MESSAGE_ADD_COMPOSING); - messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_MESSAGE_ADD_WHOCARD, - mode, 0)); - } - - public void addWhoCardAsMessageItem(int mode) { - pendingWhoCardMode = mode; - clearStatusItem(); - whoCardItem = (mode == WHO_CARD_MODE_INIT) ? interaction.getWhoCardInit() - : interaction.getWhoCardEdit(); - messages.add(whoCardItem); - } - - private boolean addExpectationStatusIfNeeded() { - ApptentiveMessage apptentiveMessage = null; - int numOfMessages = messages.size(); - if (numOfMessages == 0) { - return false; - } - - MessageCenterUtil.MessageCenterListItem message = messages.get(numOfMessages - 1); - - if (message != null && message instanceof ApptentiveMessage) { - apptentiveMessage = (ApptentiveMessage) message; - } - // Check if the last message in the view is a sent message - if (apptentiveMessage != null && - (apptentiveMessage.isOutgoingMessage())) { - Double createdTime = apptentiveMessage.getCreatedAt(); - if (createdTime != null && createdTime > Double.MIN_VALUE) { - MessageCenterStatus newItem = interaction.getRegularStatus(); - if (newItem != null && whoCardItem == null && composingItem == null) { - // Add expectation status message if the last is a sent - clearStatusItem(); - statusItem = newItem; - messages.add(newItem); - return true; - } + if (addedWhoCard) { + JSONObject data = new JSONObject(); + try { + data.put("required", interaction.getWhoCardRequired()); + data.put("trigger", "automatic"); + } catch (JSONException e) { + // } + EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString()); + return true; } return false; } - public void addNewStatusItem(MessageCenterUtil.MessageCenterListItem item) { - clearStatusItem(); - - if (composingItem != null) { - return; + public void addWhoCard(boolean initial) { + hideFab(); + hideProfileButton(); + JSONObject profile = interaction.getProfile(); + if (profile != null) { + pendingWhoCardMode = initial; + messagingActionHandler.removeMessages(MSG_MESSAGE_ADD_WHOCARD); + messagingActionHandler.removeMessages(MSG_MESSAGE_ADD_COMPOSING); + messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS); + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_MESSAGE_ADD_WHOCARD, initial ? 0 : 1, 0, profile)); } - - statusItem = (MessageCenterStatus) item; - messages.add(item); - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_STATUS); - } - public void addNewOutGoingMessageItem(ApptentiveMessage message) { - clearStatusItem(); - - messages.add(message); - unsendMessagesCount++; - - isPaused = false; - if (messageCenterListAdapter != null) { - messageCenterListAdapter.setPaused(isPaused); - } + private void addExpectationStatusIfNeeded() { + messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS); + messagingActionHandler.sendEmptyMessage(MSG_OPT_INSERT_REGULAR_STATUS); } + /** + * Call only from handler. + */ public void displayNewIncomingMessageItem(ApptentiveMessage message) { - clearStatusItem(); - + messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS); // Determine where to insert the new incoming message. It will be in front of any eidting // area, i.e. composing, Who Card ... - int insertIndex = messages.size(); - if (composingItem != null) { - // when in composing mode, there are composing action bar and composing area - insertIndex -= 2; - if (contextualMessage != null) { - insertIndex--; - } - } else if (whoCardItem != null) { - insertIndex -= 1; - if (contextualMessage != null) { - insertIndex--; + int insertIndex = listItems.size(); // If inserted onto the end, then the list will have grown by one. + + outside_loop: + // Starting at end of list, go back up the list to find the proper place to insert the incoming message. + for (int i = listItems.size() - 1; i > 0; i--) { + MessageCenterListItem item = listItems.get(i); + switch (item.getListItemType()) { + case MESSAGE_COMPOSER: + case MESSAGE_CONTEXT: + case WHO_CARD: + case STATUS: + insertIndex--; + break; + default: + // Any other type means we are past the temporary items. + break outside_loop; } } - messages.add(insertIndex, message); + listItems.add(insertIndex, message); + messageCenterRecyclerViewAdapter.notifyItemInserted(insertIndex); - int firstIndex = messageCenterListView.getFirstVisiblePosition(); - int lastIndex = messageCenterListView.getLastVisiblePosition(); + int firstIndex = messageCenterRecyclerView.getFirstVisiblePosition(); + int lastIndex = messageCenterRecyclerView.getLastVisiblePosition(); boolean composingAreaTakesUpVisibleArea = firstIndex <= insertIndex && insertIndex < lastIndex; if (composingAreaTakesUpVisibleArea) { - View v = messageCenterListView.getChildAt(0); + View v = messageCenterRecyclerView.getChildAt(0); int top = (v == null) ? 0 : v.getTop(); updateMessageSentStates(); - if (messageCenterListAdapter != null) { - messageCenterListAdapter.notifyDataSetChanged(); - } // Restore the position of listview to composing view - messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_SCROLL_FROM_TOP, - insertIndex, top)); + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_SCROLL_FROM_TOP, insertIndex, top)); } else { updateMessageSentStates(); - if (messageCenterListAdapter != null) { - messageCenterListAdapter.notifyDataSetChanged(); - } - } - - } - - private void clearStatusItem() { - // Remove the status message whenever a new incoming message is added - if (statusItem != null) { - messages.remove(statusItem); - statusItem = null; - if (messageCenterListAdapter != null) { - messageCenterListAdapter.notifyDataSetChanged(); - } } } - public void addAttachmentsToComposer(final List images) { - int numberOfExistingAttachments = imageAttachmentstList.size(); - ArrayList uniqueImages = new ArrayList(); + public void addAttachmentsToComposer(ImageItem... images) { + ArrayList newImages = new ArrayList(); // only add new images, and filter out duplicates - if (images != null && images.size() > 0) { + if (images != null && images.length > 0) { for (ImageItem newImage : images) { boolean bDupFound = false; - for (ImageItem existingImage : imageAttachmentstList) { - if (newImage.originalPath.equals(existingImage.originalPath)) { + for (ImageItem pendingAttachment : pendingAttachments) { + if (newImage.originalPath.equals(pendingAttachment.originalPath)) { bDupFound = true; break; } @@ -732,69 +676,34 @@ public void addAttachmentsToComposer(final List images) { if (bDupFound) { continue; } else { - uniqueImages.add(newImage); + pendingAttachments.add(newImage); + newImages.add(newImage); } } } - if (uniqueImages.size() == 0) { - return; - } - imageAttachmentstList.addAll(uniqueImages); - // New attachments are added to a composer with no prior attachment - if (imageAttachmentstList.size() > 0 && numberOfExistingAttachments == 0) { - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_ATTACHMENT_LIST_SHOWN); - } - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_ATTACHMENT_ADD); - - View v = messageCenterListView.getChildAt(0); + View v = messageCenterRecyclerView.getChildAt(0); int top = (v == null) ? 0 : v.getTop(); - if (messageCenterListAdapter != null) { - // Only update composing view if image is attached successfully - messageCenterListAdapter.addImagestoComposer(uniqueImages); - messageCenterListAdapter.notifyDataSetChanged(); - messageCenterListAdapter.setForceShowKeyboard(false); + if (newImages.isEmpty()) { + return; } - int firstIndex = messageCenterListView.getFirstVisiblePosition(); - messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_SCROLL_FROM_TOP, - firstIndex, top)); - - updateComposingBar(); + messageCenterRecyclerViewAdapter.addImagestoComposer(composer, newImages); + messageCenterRecyclerViewAdapter.notifyItemChanged(listItems.size() - 1); + int firstIndex = messageCenterRecyclerView.getFirstVisiblePosition(); + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_SCROLL_FROM_TOP, firstIndex, top)); } - public void restoreSavedAttachmentsToComposer(final List images) { - imageAttachmentstList.clear(); - imageAttachmentstList.addAll(images); - View v = messageCenterListView.getChildAt(0); - int top = (v == null) ? 0 : v.getTop(); - // Only update composing view if image is attached successfully - if (messageCenterListAdapter != null) { - messageCenterListAdapter.addImagestoComposer(images); - messageCenterListAdapter.setForceShowKeyboard(false); - } - int firstIndex = messageCenterListView.getFirstVisiblePosition(); - if (messageCenterListAdapter != null) { - messageCenterListAdapter.notifyDataSetChanged(); - } - messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_SCROLL_FROM_TOP, - firstIndex, top)); - updateComposingBar(); + public void setAttachmentsInComposer(final List images) { + messageCenterRecyclerViewAdapter.addImagestoComposer(composer, images); + // The view will resize. Scroll it into view after a short delay to ensure the view has already resized. + messagingActionHandler.sendEmptyMessageDelayed(MSG_SCROLL_TO_BOTTOM, 50); + } public void removeImageFromComposer(final int position) { EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_ATTACHMENT_DELETE); - imageAttachmentstList.remove(position); - if (messageCenterListAdapter != null) { - messageCenterListAdapter.removeImageFromComposer(position); - int count = imageAttachmentstList.size(); - // Show keyboard if all attachments have been removed - messageCenterListAdapter.setForceShowKeyboard(count == 0); - messageCenterListAdapter.notifyDataSetChanged(); - } + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_REMOVE_ATTACHMENT, position, 0)); messagingActionHandler.sendEmptyMessageDelayed(MSG_SCROLL_TO_BOTTOM, DEFAULT_DELAYMILLIS); - - updateComposingBar(); - } public void openNonImageAttachment(final ImageItem image) { @@ -842,111 +751,28 @@ public void showAttachmentDialog(final ImageItem image) { public synchronized void onMessageSent(ApptentiveHttpResponse response, final ApptentiveMessage apptentiveMessage) { if (response.isSuccessful() || response.isRejectedPermanently() || response.isBadPayload()) { messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_MESSAGE_SENT, - apptentiveMessage)); + apptentiveMessage)); } } public synchronized void onPauseSending(int reason) { - messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_PAUSE_SENDING, - reason, 0)); + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_PAUSE_SENDING, reason, 0)); } public synchronized void onResumeSending() { messagingActionHandler.sendEmptyMessage(MSG_RESUME_SENDING); } - public void clearWhoCardUi(Animator.AnimatorListener al, - ValueAnimator.AnimatorUpdateListener vl, long delay) { - if (whoCardItem != null && messageCenterListAdapter != null) { - if (al != null) { - deleteItemWithAnimation(messageCenterListAdapter.getWhoCardView(), al, vl, delay); - } else { - whoCardItem = null; - pendingWhoCardName = null; - pendingWhoCardEmail = null; - pendingWhoCardAvatarFile = null; - pendingWhoCardMode = 0; - messageCenterListAdapter.clearWhoCard(); - } - } - } - - public void clearComposingUi(Animator.AnimatorListener al, - ValueAnimator.AnimatorUpdateListener vl, long delay) { - if (composingItem != null && messageCenterListAdapter != null) { - if (al != null) { - deleteItemWithAnimation(messageCenterListAdapter.getComposingActionBarView(), null, null, delay); - deleteItemWithAnimation(messageCenterListAdapter.getComposingAreaView(), al, vl, delay); - } else { - if (contextualMessage != null) { - messages.remove(contextualMessage); - contextualMessage = null; - } - messages.remove(actionBarItem); - messages.remove(composingItem); - actionBarItem = null; - composingItem = null; - messageEditText = null; - messageCenterListAdapter.clearComposing(); - messageCenterListAdapter.notifyDataSetChanged(); - showFab(); - } - } - } - @Override - public void updateComposingBar() { - if (messageCenterListAdapter != null) { - MessageCenterComposingActionBarView barView = messageCenterListAdapter.getComposingActionBarView(); - if (barView != null) { - barView.showConfirmation = true; - int attachmentCount = imageAttachmentstList.size(); - if (attachmentCount == 0) { - Editable content = getPendingComposingContent(); - final String messageText = (content != null) ? content.toString().trim() : ""; - barView.showConfirmation = !(messageText.isEmpty()); - } - - if (attachmentCount == attachmentsAllowed) { - AnimationUtil.fadeOutGone(barView.attachButton); - } else { - if (barView.attachButton.getVisibility() != View.VISIBLE) { - AnimationUtil.fadeIn(barView.attachButton, null); - } - } + public void onComposingViewCreated(MessageComposerHolder composer, final EditText composerEditText, final ApptentiveImageGridView attachments) { + this.composer = composer; + this.composerEditText = composerEditText; - if (barView.showConfirmation == true) { - barView.sendButton.setEnabled(true); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - barView.sendButton.setColorFilter(Util.getThemeColor(hostingActivityRef.get(), R.attr.apptentiveButtonTintColor)); - } - } else { - barView.sendButton.setEnabled(false); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - barView.sendButton.setColorFilter(Util.getThemeColor(hostingActivityRef.get(), R.attr.apptentiveButtonTintColorDisabled)); - } - } - } - } - } - - @Override - public void onComposingViewCreated(View keyboardFocusedOnView) { - - hideProfileButton(); - hideFab(); - - EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_COMPOSE_OPEN); - if (messageCenterListAdapter != null) { - messageEditText = messageCenterListAdapter.getEditTextInComposing(); - } else { - messageEditText = null; - } SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); // Restore composing text editing state, such as cursor position, after rotation if (composingViewSavedState != null) { - if (messageEditText != null) { - messageEditText.onRestoreInstanceState(composingViewSavedState); + if (this.composerEditText != null) { + this.composerEditText.onRestoreInstanceState(composingViewSavedState); } composingViewSavedState = null; SharedPreferences.Editor editor = prefs.edit(); @@ -955,56 +781,40 @@ public void onComposingViewCreated(View keyboardFocusedOnView) { // Restore composing text if (prefs.contains(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE)) { String messageText = prefs.getString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE, null); - if (messageText != null && messageEditText != null) { - messageEditText.setText(messageText); + if (messageText != null && this.composerEditText != null) { + this.composerEditText.setText(messageText); } // Stored pending composing text has been restored, remove it from the persistent storage SharedPreferences.Editor editor = prefs.edit(); editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE).apply(); } + setAttachmentsInComposer(pendingAttachments); - // Restore composing attachments - if (prefs.contains(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS)) { - JSONArray jArray = null; - try { - jArray = new JSONArray(prefs.getString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS, "")); - } catch (JSONException e) { - e.printStackTrace(); - } - if (jArray != null && jArray.length() > 0) { - if (savedAttachmentstList == null) { - savedAttachmentstList = new ArrayList(); - } - for (int i = 0; i < jArray.length(); i++) { - try { - JSONObject json = jArray.getJSONObject(i); - if (json != null) { - savedAttachmentstList.add(new ImageItem(json)); + messageCenterRecyclerView.setPadding(0, 0, 0, 0); + + if (composerEditText != null) { + composerEditText.requestFocus(); + if (forceShowKeyboard) { + composerEditText.post(new Runnable() { + @Override + public void run() { + if (forceShowKeyboard) { + forceShowKeyboard = false; + Util.showSoftKeyboard(hostingActivityRef.get(), composerEditText); } - } catch (JSONException e) { - continue; } - } + }); } - // Stored pending attachemnts have been restored, remove it from the persistent storage - SharedPreferences.Editor editor = prefs.edit(); - editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS).apply(); - } - - if (savedAttachmentstList != null) { - restoreSavedAttachmentsToComposer(savedAttachmentstList); - savedAttachmentstList = null; - } - messageCenterListView.setPadding(0, 0, 0, 0); - - if (keyboardFocusedOnView != null) { - Util.showSoftKeyboard(hostingActivityRef.get(), keyboardFocusedOnView); } + hideFab(); + composer.setSendButtonState(); } @Override - public void onWhoCardViewCreated(EditText nameEditText, EditText emailEditText, View viewFocusedWithKeyboard) { + public void onWhoCardViewCreated(final EditText nameEditText, final EditText emailEditText, final View viewToFocus) { + this.whoCardNameEditText = nameEditText; + this.whoCardEmailEditText = emailEditText; if (pendingWhoCardName != null) { nameEditText.onRestoreInstanceState(pendingWhoCardName); pendingWhoCardName = null; @@ -1013,11 +823,23 @@ public void onWhoCardViewCreated(EditText nameEditText, EditText emailEditText, emailEditText.onRestoreInstanceState(pendingWhoCardEmail); pendingWhoCardEmail = null; } - messageCenterListView.setPadding(0, 0, 0, 0); + messageCenterRecyclerView.setPadding(0, 0, 0, 0); - if (viewFocusedWithKeyboard != null) { - Util.showSoftKeyboard(hostingActivityRef.get(), viewFocusedWithKeyboard); + if (viewToFocus != null) { + viewToFocus.requestFocus(); + if (forceShowKeyboard) { + viewToFocus.post(new Runnable() { + @Override + public void run() { + if (forceShowKeyboard) { + forceShowKeyboard = false; + Util.showSoftKeyboard(hostingActivityRef.get(), viewToFocus); + } + } + }); + } } + hideFab(); } @Override @@ -1030,16 +852,12 @@ public void onComposingTextChanged(CharSequence str) { } @Override - public void afterComposingTextChanged(String str) { - // Update display status of composing bar buttons when composing text changes - updateComposingBar(); + public void afterComposingTextChanged(String message) { + composer.setSendButtonState(); } @Override public void onCancelComposing() { - if (messageCenterListAdapter != null) { - messageCenterListAdapter.setForceShowKeyboard(false); - } Util.hideSoftKeyboard(hostingActivityRef.get(), getView()); JSONObject data = new JSONObject(); @@ -1051,110 +869,39 @@ public void onCancelComposing() { // } EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_COMPOSE_CLOSE, data.toString()); - - clearComposingUi(new Animator.AnimatorListener() { - - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - if (contextualMessage != null) { - messages.remove(contextualMessage); - contextualMessage = null; - } - messages.remove(actionBarItem); - messages.remove(composingItem); - actionBarItem = null; - composingItem = null; - messageEditText = null; - if (messageCenterListAdapter != null) { - messageCenterListAdapter.clearComposing(); - addExpectationStatusIfNeeded(); - messageCenterListAdapter.notifyDataSetChanged(); - } - imageAttachmentstList.clear(); - showFab(); - showProfileButton(); - // messageEditText has been set to null, pending composing message will reset - clearPendingComposingMessage(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - }, - null, - DEFAULT_DELAYMILLIS); - //clearComposingUi(null, null, 0); + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_REMOVE_COMPOSER)); + if (messageCenterRecyclerViewAdapter != null) { + addExpectationStatusIfNeeded(); + } + pendingAttachments.clear(); + composerEditText.getText().clear(); + composingViewSavedState = null; + clearPendingComposingMessage(); + showFab(); + showProfileButton(); } @Override public void onFinishComposing() { - if (messageCenterListAdapter != null) { - messageCenterListAdapter.setForceShowKeyboard(false); - } + messagingActionHandler.sendEmptyMessage(MSG_REMOVE_COMPOSER); + Util.hideSoftKeyboard(hostingActivityRef.get(), getView()); - if (contextualMessage != null) { - unsendMessagesCount++; - ApptentiveInternal.getInstance().getMessageManager().sendMessage(contextualMessage); - contextualMessage = null; + messagingActionHandler.sendEmptyMessage(MSG_SEND_PENDING_CONTEXT_MESSAGE); + if (!TextUtils.isEmpty(composerEditText.getText().toString().trim()) || pendingAttachments.size() > 0) { + CompoundMessage compoundMessage = new CompoundMessage(); + compoundMessage.setBody(composerEditText.getText().toString().trim()); + compoundMessage.setRead(true); + compoundMessage.setCustomData(ApptentiveInternal.getInstance().getAndClearCustomData()); + compoundMessage.setAssociatedImages(new ArrayList(pendingAttachments)); + + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_START_SENDING, compoundMessage)); + composingViewSavedState = null; + composerEditText.getText().clear(); + pendingAttachments.clear(); + clearPendingComposingMessage(); } - Editable content = getPendingComposingContent(); - final String messageText = (content != null) ? content.toString().trim() : ""; - final ArrayList messageAttachments = new ArrayList(); - messageAttachments.addAll(imageAttachmentstList); - // Close all composing UI - clearComposingUi(new Animator.AnimatorListener() { - - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - messages.remove(actionBarItem); - messages.remove(composingItem); - actionBarItem = null; - composingItem = null; - messageEditText = null; - if (messageCenterListAdapter != null) { - messageCenterListAdapter.clearComposing(); - messageCenterListAdapter.notifyDataSetChanged(); - } - clearPendingComposingMessage(); - // Send out the new message. The delay is added to ensure the CardView showing animation - // is visible after the keyboard is hidden - if (!messageText.isEmpty() || imageAttachmentstList.size() != 0) { - Bundle b = new Bundle(); - b.putString(COMPOSING_EDITTEXT_STATE, messageText); - b.putParcelableArrayList(COMPOSING_ATTACHMENTS, messageAttachments); - Message msg = messagingActionHandler.obtainMessage(MSG_START_SENDING, - messageText); - msg.setData(b); - messagingActionHandler.sendMessageDelayed(msg, DEFAULT_DELAYMILLIS); - } - - imageAttachmentstList.clear(); - showFab(); - showProfileButton(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - }, - null, - DEFAULT_DELAYMILLIS); + showFab(); + showProfileButton(); } @Override @@ -1168,7 +915,15 @@ public void onSubmitWhoCard(String buttonLabel) { } EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_SUBMIT, data.toString()); + setWhoCardAsPreviouslyDisplayed(); cleanupWhoCard(); + + if (shouldOpenComposerAfterClosingWhoCard()) { + addComposingCard(); + } else { + showFab(); + showProfileButton(); + } } @Override @@ -1182,63 +937,36 @@ public void onCloseWhoCard(String buttonLabel) { } EngagementModule.engageInternal(hostingActivityRef.get(), interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_CLOSE, data.toString()); + setWhoCardAsPreviouslyDisplayed(); cleanupWhoCard(); - } - public void cleanupWhoCard() { - if (messageCenterListAdapter != null) { - messageCenterListAdapter.setForceShowKeyboard(false); + if (shouldOpenComposerAfterClosingWhoCard()) { + addComposingCard(); + } else { + showFab(); + showProfileButton(); } - Util.hideSoftKeyboard(hostingActivityRef.get(), getView()); - clearWhoCardUi( - new Animator.AnimatorListener() { - - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } + } - @Override - public void onAnimationEnd(Animator animation) { - messages.remove(whoCardItem); - whoCardItem = null; - pendingWhoCardName = null; - pendingWhoCardEmail = null; - pendingWhoCardAvatarFile = null; - pendingWhoCardMode = 0; - if (messageCenterListAdapter != null) { - messageCenterListAdapter.clearWhoCard(); - addExpectationStatusIfNeeded(); - messageCenterListAdapter.notifyDataSetChanged(); - } - saveWhoCardSetState(); - // If Who card is required, it might be displayed before proceeding to composing, for instance - // when there was a contextual message or it was the first message. We need to resume composing - // after dismissing Who Card - if ((messages.size() == 1 || contextualMessage != null) && interaction.getWhoCardRequired()) { - addComposingCard(); - } else { - showFab(); - showProfileButton(); - } - } + private boolean shouldOpenComposerAfterClosingWhoCard() { + return interaction.getWhoCard().isRequire() && (recyclerViewContainsItemOfType(MESSAGE_CONTEXT) || recyclerViewContainsItemOfType(MESSAGE_OUTGOING)); + } - @Override - public void onAnimationCancel(Animator animation) { - } - }, - null, - DEFAULT_DELAYMILLIS - ); + public void cleanupWhoCard() { + messagingActionHandler.sendEmptyMessage(MSG_MESSAGE_REMOVE_WHOCARD); + Util.hideSoftKeyboard(hostingActivityRef.get(), getView()); + pendingWhoCardName = null; + pendingWhoCardEmail = null; + pendingWhoCardAvatarFile = null; + pendingWhoCardMode = false; + whoCardNameEditText = null; + whoCardEmailEditText = null; + addExpectationStatusIfNeeded(); } @Override public void onNewMessageReceived(final CompoundMessage apptentiveMsg) { - messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_MESSAGE_ADD_INCOMING, - apptentiveMsg)); + messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_MESSAGE_ADD_INCOMING, apptentiveMsg)); } @Override @@ -1254,45 +982,14 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun boolean bCanScrollUp; if (android.os.Build.VERSION.SDK_INT < 14) { bCanScrollUp = view.getChildCount() > 0 - && (view.getFirstVisiblePosition() > 0 || - view.getChildAt(0).getTop() < view.getPaddingTop()); + && (view.getFirstVisiblePosition() > 0 || + view.getChildAt(0).getTop() < view.getPaddingTop()); } else { bCanScrollUp = ViewCompat.canScrollVertically(view, -1); } showToolbarElevation(bCanScrollUp); } - @Override - public void OnListViewResize(int w, int h, int oldw, int oldh) { - // detect keyboard launching. If height difference is more than 100 pixels, probably due to keyboard - if (oldh > h && oldh - h > 100) { - if (composingItem != null) { - // When keyboard is up, adjust the scolling such that the cursor is always visible - final int firstIndex = messageCenterListView.getFirstVisiblePosition(); - int lastIndex = messageCenterListView.getLastVisiblePosition(); - View v = messageCenterListView.getChildAt(lastIndex - firstIndex); - int top = (v == null) ? 0 : v.getTop(); - if (messageEditText != null) { - int pos = messageEditText.getSelectionStart(); - Layout layout = messageEditText.getLayout(); - int line = layout.getLineForOffset(pos); - int baseline = layout.getLineBaseline(line); - int ascent = layout.getLineAscent(line); - if ( top + baseline - ascent > oldh - h) { - messagingActionHandler.sendMessageDelayed(messagingActionHandler.obtainMessage(MSG_SCROLL_FROM_TOP, - lastIndex, top - (oldh - h)), DEFAULT_DELAYMILLIS); - } else { - messagingActionHandler.sendMessageDelayed(messagingActionHandler.obtainMessage(MSG_SCROLL_FROM_TOP, - lastIndex, top), DEFAULT_DELAYMILLIS); - } - } - } - } - } - - /* Callback when the attach button is clicked - * - */ @Override public void onAttachImage() { try { @@ -1309,24 +1006,29 @@ public void onAttachImage() { Intent chooserIntent = Intent.createChooser(intent, null); startActivityForResult(chooserIntent, Constants.REQUEST_CODE_PHOTO_FROM_SYSTEM_PICKER); } - imagePickerLaunched = true; + imagePickerStillOpen = true; } catch (Exception e) { e.printStackTrace(); - imagePickerLaunched = false; + imagePickerStillOpen = false; ApptentiveLog.d("can't launch image picker"); } } - private void saveWhoCardSetState() { + private void setWhoCardAsPreviouslyDisplayed() { SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_SET, true); + editor.putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE, true); editor.apply(); } + private boolean wasWhoCardAsPreviouslyDisplayed() { + SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); + return prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE, false); + } + // Retrieve the content from the composing area public Editable getPendingComposingContent() { - return (messageEditText == null) ? null : messageEditText.getText(); + return (composerEditText == null) ? null : composerEditText.getText(); } @@ -1340,19 +1042,17 @@ public void savePendingComposingMessage() { editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE); } - JSONArray jArray = new JSONArray(); + JSONArray pendingAttachmentsJsonArray = new JSONArray(); // Save pending attachment - for (ImageItem pendingAttachment : imageAttachmentstList) { - JSONObject jobject = pendingAttachment.toJSON(); - if (jobject != null) { - jArray.put(jobject); - } + for (ImageItem pendingAttachment : pendingAttachments) { + pendingAttachmentsJsonArray.put(pendingAttachment.toJSON()); } - if (jArray.length() > 0) { - editor.putString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS, jArray.toString()); + if (pendingAttachmentsJsonArray.length() > 0) { + editor.putString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS, pendingAttachmentsJsonArray.toString()); } else { editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS); + editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS); } editor.apply(); } @@ -1362,18 +1062,18 @@ public void savePendingComposingMessage() { */ public void clearPendingComposingMessage() { SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - SharedPreferences.Editor editor = prefs.edit(); - editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE); - editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS); - editor.apply(); + prefs.edit() + .remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE) + .remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS) + .remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS) + .apply(); } private Parcelable saveEditTextInstanceState() { - savePendingComposingMessage(); - if (messageEditText != null) { + if (composerEditText != null) { // Hide keyboard if the keyboard was up prior to rotation Util.hideSoftKeyboard(hostingActivityRef.get(), getView()); - return messageEditText.onSaveInstanceState(); + return composerEditText.onSaveInstanceState(); } return null; } @@ -1384,15 +1084,19 @@ public void updateMessageSentStates() { dateStampsSeen.clear(); MessageCenterUtil.CompoundMessageCommonInterface lastSent = null; Set uniqueNonce = new HashSet(); - Iterator messageIterator = messages.iterator(); - while (messageIterator.hasNext()) { - MessageCenterUtil.MessageCenterListItem message = messageIterator.next(); + int removedItems = 0; + ListIterator listItemIterator = listItems.listIterator(); + while (listItemIterator.hasNext()) { + int adapterMessagePosition = listItemIterator.nextIndex() - removedItems; + MessageCenterListItem message = listItemIterator.next(); if (message instanceof ApptentiveMessage) { /* Check if there is any duplicate messages and remove if found. * add() of a Set returns false if the element already exists. */ if (!uniqueNonce.add(((ApptentiveMessage) message).getNonce())) { - messageIterator.remove(); + listItemIterator.remove(); + messageCenterRecyclerViewAdapter.notifyItemRemoved(adapterMessagePosition); + removedItems++; continue; } // Update timestamps @@ -1401,9 +1105,13 @@ public void updateMessageSentStates() { String dateStamp = createDatestamp(sentOrReceivedAt); if (dateStamp != null) { if (dateStampsSeen.add(dateStamp)) { - apptentiveMessage.setDatestamp(dateStamp); + if (apptentiveMessage.setDatestamp(dateStamp)) { + messageCenterRecyclerViewAdapter.notifyItemChanged(adapterMessagePosition); + } } else { - apptentiveMessage.clearDatestamp(); + if (apptentiveMessage.clearDatestamp()) { + messageCenterRecyclerViewAdapter.notifyItemChanged(adapterMessagePosition); + } } } @@ -1432,17 +1140,6 @@ protected String createDatestamp(Double seconds) { return null; } - - private void deleteItemWithAnimation(final View v, final Animator.AnimatorListener al, - final ValueAnimator.AnimatorUpdateListener vl, long delay) { - if (v == null) { - return; - } - AnimatorSet animatorSet = AnimationUtil.buildListViewRowRemoveAnimator(v, al, vl); - animatorSet.setStartDelay(delay); - animatorSet.start(); - } - private int calculateFabPadding(Context context) { Resources res = context.getResources(); float scale = res.getDisplayMetrics().density; @@ -1451,16 +1148,20 @@ private int calculateFabPadding(Context context) { } private void showFab() { - messageCenterListView.setPadding(0, 0, 0, fabPaddingPixels); + messageCenterRecyclerView.setPadding(0, 0, 0, fabPaddingPixels); // Re-enable Fab at the beginning of the animation - fab.setEnabled(true); - AnimationUtil.scaleFadeIn(fab); + if (fab.getVisibility() != View.VISIBLE) { + fab.setEnabled(true); + AnimationUtil.scaleFadeIn(fab); + } } private void hideFab() { // Make sure Fab is not clickable during fade-out animation - fab.setEnabled(false); - AnimationUtil.scaleFadeOutGone(fab); + if (fab.getVisibility() != View.GONE) { + fab.setEnabled(false); + AnimationUtil.scaleFadeOutGone(fab); + } } private void showProfileButton() { @@ -1478,22 +1179,22 @@ private void hideProfileButton() { * with seconds resolution. If messages were received by server within a second, messages may be out of order * This method uses insertion sort to re-sort the messages retrieved from the database */ - private void prepareMessages(final List originalItems) { - messages.clear(); - unsendMessagesCount = 0; + private void prepareMessages(final List originalItems) { + listItems.clear(); + unsentMessagesCount = 0; // Loop through each message item retrieved from database - for (MessageCenterUtil.MessageCenterListItem item : originalItems) { + for (MessageCenterListItem item : originalItems) { if (item instanceof ApptentiveMessage) { ApptentiveMessage apptentiveMessage = (ApptentiveMessage) item; Double createdAt = apptentiveMessage.getCreatedAt(); if (apptentiveMessage.isOutgoingMessage() && createdAt == null) { - unsendMessagesCount++; + unsentMessagesCount++; } /* - * Find proper location to insert into the messages list of the listview. + * Find proper location to insert into the listItems list of the listview. */ - ListIterator listIterator = messages.listIterator(); + ListIterator listIterator = listItems.listIterator(); ApptentiveMessage next = null; while (listIterator.hasNext()) { next = (ApptentiveMessage) listIterator.next(); @@ -1509,7 +1210,7 @@ private void prepareMessages(final List } if (next == null || next.getCreatedAt() == null || createdAt == null || next.getCreatedAt() <= createdAt || - createdAt <= Double.MIN_VALUE) { + createdAt <= Double.MIN_VALUE) { listIterator.add(item); } else { // Add in front of the message that has later created_at time @@ -1518,15 +1219,14 @@ private void prepareMessages(final List } } } - // Finally, add greeting message - messages.add(0, interaction.getGreeting()); + messagingActionHandler.sendEmptyMessage(MSG_ADD_GREETING); } @Override public void onClickAttachment(final int position, final ImageItem image) { if (Util.isMimeTypeImage(image.mimeType)) { - // "+" placeholder is clicked if (TextUtils.isEmpty(image.originalPath)) { + // "+" placeholder is clicked onAttachImage(); } else { // an image thumbnail is clicked @@ -1549,7 +1249,7 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in /* - * Called when attachment overlayed "selection" ui is tapped. The "selection" ui could be selection checkbox + * Called when attachment overlaid "selection" ui is tapped. The "selection" ui could be selection checkbox * or close button */ @Override @@ -1579,26 +1279,40 @@ public MessagingActionHandler(MessageCenterFragment fragment) { public void handleMessage(Message msg) { MessageCenterFragment fragment = (MessageCenterFragment) messageCenterFragmentWeakReference.get(); /* Message can be delayed. If so, make sure fragment is still available and attached to activity - * messageCenterListAdapter will always be set null in onDetach(). it's a good indication if + * messageCenterRecyclerViewAdapter will always be set null in onDetach(). it's a good indication if * fragment is attached. */ - if (fragment == null || fragment.messageCenterListAdapter == null) { + if (fragment == null || fragment.messageCenterRecyclerViewAdapter == null) { return; } switch (msg.what) { case MSG_MESSAGE_ADD_WHOCARD: { // msg.arg1 is either WHO_CARD_MODE_INIT or WHO_CARD_MODE_EDIT - fragment.addWhoCardAsMessageItem(msg.arg1); - fragment.messageCenterListAdapter.setForceShowKeyboard(true); - fragment.messageCenterListAdapter.notifyDataSetChanged(); - fragment.messageCenterListView.setSelection(fragment.messages.size() - 1); + boolean initial = msg.arg1 == 0; + WhoCard whoCard = fragment.interaction.getWhoCard(); + whoCard.setInitial(initial); + fragment.listItems.add(whoCard); + fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1); + fragment.messageCenterRecyclerView.setSelection(fragment.listItems.size() - 1); + break; + } + case MSG_MESSAGE_REMOVE_WHOCARD: { + ListIterator messageIterator = fragment.listItems.listIterator(); + while (messageIterator.hasNext()) { + int i = messageIterator.nextIndex(); + MessageCenterListItem next = messageIterator.next(); + if (next.getListItemType() == WHO_CARD) { + messageIterator.remove(); + fragment.messageCenterRecyclerViewAdapter.notifyItemRemoved(i); + } + } break; } case MSG_MESSAGE_ADD_COMPOSING: { - fragment.addComposerMessageItems(); - fragment.messageCenterListAdapter.setForceShowKeyboard(true); - fragment.messageCenterListAdapter.notifyDataSetChanged(); - fragment.messageCenterListView.setSelection(fragment.messages.size() - 1); + EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_COMPOSE_OPEN); + fragment.listItems.add(fragment.interaction.getComposer()); + fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1); + fragment.messageCenterRecyclerView.setSelection(fragment.listItems.size() - 1); break; } case MSG_MESSAGE_ADD_INCOMING: { @@ -1607,26 +1321,30 @@ public void handleMessage(Message msg) { break; } case MSG_SCROLL_TO_BOTTOM: { - fragment.messageCenterListView.setSelection(fragment.messages.size() - 1); + fragment.messageCenterRecyclerView.setSelection(fragment.listItems.size() - 1); + fragment.messageCenterRecyclerView.scrollToPosition(fragment.listItems.size() - 1); break; } case MSG_SCROLL_FROM_TOP: { int index = msg.arg1; int top = msg.arg2; - fragment.messageCenterListView.setSelectionFromTop(index, top); + fragment.messageCenterRecyclerView.setSelectionFromTop(index, top); break; } case MSG_MESSAGE_SENT: { // below is callback handling when receiving of message is acknowledged by server through POST response - fragment.unsendMessagesCount--; + fragment.unsentMessagesCount--; ApptentiveMessage apptentiveMessage = (ApptentiveMessage) msg.obj; - for (MessageCenterUtil.MessageCenterListItem message : fragment.messages) { + + for (int i = 0; i < fragment.listItems.size(); i++) { + MessageCenterListItem message = fragment.listItems.get(i); if (message instanceof ApptentiveMessage) { String nonce = ((ApptentiveMessage) message).getNonce(); if (nonce != null) { String sentNonce = apptentiveMessage.getNonce(); if (sentNonce != null && nonce.equals(sentNonce)) { ((ApptentiveMessage) message).setCreatedAt(apptentiveMessage.getCreatedAt()); + fragment.messageCenterRecyclerViewAdapter.notifyItemChanged(i); break; } } @@ -1637,13 +1355,12 @@ public void handleMessage(Message msg) { fragment.addExpectationStatusIfNeeded(); // Calculate the listview offset to make sure updating sent timestamp does not push the current view port - int firstIndex = fragment.messageCenterListView.getFirstVisiblePosition(); - View v = fragment.messageCenterListView.getChildAt(0); + int firstIndex = fragment.messageCenterRecyclerView.getFirstVisiblePosition(); + View v = fragment.messageCenterRecyclerView.getChildAt(0); int top = (v == null) ? 0 : v.getTop(); - fragment.messageCenterListAdapter.notifyDataSetChanged(); // If Who Card is being shown while a message is sent, make sure Who Card is still in view by scrolling to bottom - if (fragment.whoCardItem != null) { + if (fragment.recyclerViewContainsItemOfType(WHO_CARD)) { sendEmptyMessageDelayed(MSG_SCROLL_TO_BOTTOM, DEFAULT_DELAYMILLIS); } else { sendMessageDelayed(obtainMessage(MSG_SCROLL_FROM_TOP, firstIndex, top), DEFAULT_DELAYMILLIS); @@ -1651,24 +1368,16 @@ public void handleMessage(Message msg) { break; } case MSG_START_SENDING: { - Bundle b = msg.getData(); - CompoundMessage message = new CompoundMessage(); - message.setBody(b.getString(COMPOSING_EDITTEXT_STATE)); - message.setRead(true); - message.setCustomData(ApptentiveInternal.getInstance().getAndClearCustomData()); - ArrayList imagesToAttach = b.getParcelableArrayList(COMPOSING_ATTACHMENTS); - message.setAssociatedImages(imagesToAttach); + CompoundMessage message = (CompoundMessage) msg.obj; + fragment.listItems.add(message); + fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1); + fragment.unsentMessagesCount++; + fragment.setPaused(false); ApptentiveInternal.getInstance().getMessageManager().sendMessage(message); - - // Add new outgoing message with animation - fragment.addNewOutGoingMessageItem(message); - fragment.messageCenterListAdapter.notifyDataSetChanged(); - // After the message is sent, check if Who Card need to be shown for the 1st time(When Who Card is either requested or required) - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - boolean bWhoCardSet = prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_SET, false); - if (!bWhoCardSet) { + // After the message is sent, show the Who Card if it has never been seen before, and the configuration specifies it should be requested. + if (!fragment.wasWhoCardAsPreviouslyDisplayed() && fragment.interaction.getWhoCardRequestEnabled()) { JSONObject data = new JSONObject(); try { data.put("required", fragment.interaction.getWhoCardRequired()); @@ -1677,46 +1386,171 @@ public void handleMessage(Message msg) { // } EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString()); - // The delay is to ensure the animation of adding Who Card play after the animation of new outgoing message - if (fragment.interaction.getWhoCardRequestEnabled()) { - fragment.addWhoCard(WHO_CARD_MODE_INIT); + fragment.forceShowKeyboard = true; + fragment.addWhoCard(true); + } + break; + } + case MSG_SEND_PENDING_CONTEXT_MESSAGE: { + ContextMessage contextMessage = null; + // If the list has a context message, get it, remove it from the list, and notify the RecyclerView to update. + ListIterator iterator = fragment.listItems.listIterator(); + while (iterator.hasNext()) { + int index = iterator.nextIndex(); + MessageCenterListItem item = iterator.next(); + if (item.getListItemType() == MESSAGE_CONTEXT) { + contextMessage = (ContextMessage) item; + iterator.remove(); + fragment.messageCenterRecyclerViewAdapter.notifyItemRemoved(index); + break; } } + + if (contextMessage != null) { + // Create a CompoundMessage for sending and final display + CompoundMessage message = new CompoundMessage(); + message.setBody(contextMessage.getBody()); + message.setAutomated(true); + message.setRead(true); + + // Add it to the RecyclerView + fragment.unsentMessagesCount++; + fragment.listItems.add(message); + fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1); + + // Send it to the server + ApptentiveInternal.getInstance().getMessageManager().sendMessage(message); + } break; } case MSG_PAUSE_SENDING: { - if (!fragment.isPaused) { - fragment.isPaused = true; - if (fragment.unsendMessagesCount > 0) { - fragment.messageCenterListAdapter.setPaused(fragment.isPaused); + if (!fragment.isPaused()) { + fragment.setPaused(true); + if (fragment.unsentMessagesCount > 0) { int reason = msg.arg1; - if (reason == MessageManager.SEND_PAUSE_REASON_NETWORK) { - EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_MESSAGE_NETWORK_ERROR); - MessageCenterStatus newItem = fragment.interaction.getErrorStatusNetwork(); - fragment.addNewStatusItem(newItem); - } else if (reason == MessageManager.SEND_PAUSE_REASON_SERVER) { - EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_MESSAGE_HTTP_ERROR); - MessageCenterStatus newItem = fragment.interaction.getErrorStatusServer(); - fragment.addNewStatusItem(newItem); - } - fragment.messageCenterListAdapter.notifyDataSetChanged(); + Message handlerMessage = fragment.messagingActionHandler.obtainMessage(MSG_ADD_STATUS_ERROR, reason, 0); + fragment.messagingActionHandler.sendMessage(handlerMessage); } } break; } case MSG_RESUME_SENDING: { - if (fragment.isPaused) { - fragment.isPaused = false; - if (fragment.unsendMessagesCount > 0) { - fragment.clearStatusItem(); + if (fragment.isPaused()) { + fragment.setPaused(false); + if (fragment.unsentMessagesCount > 0) { + fragment.messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS); } - - fragment.messageCenterListAdapter.setPaused(fragment.isPaused); - fragment.messageCenterListAdapter.notifyDataSetChanged(); } break; } + case MSG_REMOVE_COMPOSER: { + for (int i = 0; i < fragment.listItems.size(); i++) { + MessageCenterListItem item = fragment.listItems.get(i); + if (item.getListItemType() == MESSAGE_COMPOSER) { + fragment.listItems.remove(i); + fragment.messageCenterRecyclerViewAdapter.notifyItemRemoved(i); + } + } + break; + } + case MSG_OPT_INSERT_REGULAR_STATUS: { + List listItems = fragment.listItems; + // Only add status if the last item in the list is a sent message. + if (listItems.size() > 0) { + MessageCenterListItem lastItem = listItems.get(listItems.size() - 1); + if (lastItem != null && lastItem.getListItemType() == MESSAGE_OUTGOING) { + ApptentiveMessage apptentiveMessage = (ApptentiveMessage) lastItem; + if (apptentiveMessage.isOutgoingMessage()) { + Double createdTime = apptentiveMessage.getCreatedAt(); + if (createdTime != null && createdTime > Double.MIN_VALUE) { + MessageCenterStatus status = fragment.interaction.getRegularStatus(); + if (status != null) { + EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_STATUS); + // Add expectation status message if the last is a sent + listItems.add(status); + fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(listItems.size() - 1); + } + } + } + } + } + break; + } + case MSG_REMOVE_STATUS: { + List listItems = fragment.listItems; + for (int i = 0; i < listItems.size(); i++) { + MessageCenterListItem item = listItems.get(i); + if (item.getListItemType() == STATUS) { + listItems.remove(i); + fragment.messageCenterRecyclerViewAdapter.notifyItemRemoved(i); + } + } + break; + } + case MSG_ADD_CONTEXT_MESSAGE: { + ContextMessage contextMessage = (ContextMessage) msg.obj; + fragment.listItems.add(contextMessage); + fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1); + break; + } + case MSG_ADD_GREETING: { + fragment.listItems.add(0, fragment.interaction.getGreeting()); + fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(0); + break; + } + case MSG_ADD_STATUS_ERROR: { + int reason = msg.arg1; + MessageCenterStatus status = null; + if (reason == MessageManager.SEND_PAUSE_REASON_NETWORK) { + status = fragment.interaction.getErrorStatusNetwork(); + EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_MESSAGE_NETWORK_ERROR); + } else if (reason == MessageManager.SEND_PAUSE_REASON_SERVER) { + status = fragment.interaction.getErrorStatusServer(); + EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_MESSAGE_HTTP_ERROR); + } + if (status != null) { + EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction, MessageCenterInteraction.EVENT_NAME_STATUS); + fragment.listItems.add(status); + fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1); + } + break; + } + case MSG_REMOVE_ATTACHMENT: { + int position = msg.arg1; + fragment.pendingAttachments.remove(position); + fragment.messageCenterRecyclerViewAdapter.removeImageFromComposer(fragment.composer, position); + break; + } + } + } + } + + public boolean recyclerViewContainsItemOfType(int type) { + for (MessageCenterListItem item : listItems) { + if (item.getListItemType() == type) { + return true; } } + return false; + } + + public void setPaused(boolean paused) { + if (isPaused ^ paused) { + // Invalidate any unsent messages, as these will have status and progress bars that need to change. + for (int i = 0; i < listItems.size(); i++) { + MessageCenterListItem item = listItems.get(i); + if (item instanceof ApptentiveMessage) { + ApptentiveMessage message = (ApptentiveMessage) item; + if (message.isOutgoingMessage() && message.getCreatedAt() == null) { + messageCenterRecyclerViewAdapter.notifyItemChanged(i); + } + } + } + } + isPaused = paused; + } + + public boolean isPaused() { + return isPaused; } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interactions.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interactions.java index 9fd659c3d..b6568a131 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interactions.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/Interactions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -7,6 +7,7 @@ package com.apptentive.android.sdk.module.engagement.interaction.model; import com.apptentive.android.sdk.ApptentiveLog; + import org.json.JSONException; import org.json.JSONObject; @@ -16,7 +17,7 @@ /** * A map of "interaction_id" => {Interaction} - * @author Sky Kelsey + * */ public class Interactions extends JSONObject { public static final String KEY_NAME = "interactions"; @@ -42,7 +43,7 @@ public Interaction getInteraction(String id) { public List getInteractionList() { List ret = new ArrayList(); - Iterator keys = (Iterator) keys(); + Iterator keys = keys(); while (keys.hasNext()) { String key = keys.next(); JSONObject interactionObject = optJSONObject(key); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/MessageCenterInteraction.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/MessageCenterInteraction.java index fb2da4101..816d6b689 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/MessageCenterInteraction.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/interaction/model/MessageCenterInteraction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -11,17 +11,15 @@ import com.apptentive.android.sdk.ApptentiveViewActivity; import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterComposingItem; +import com.apptentive.android.sdk.module.messagecenter.model.Composer; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterGreeting; import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterStatus; +import com.apptentive.android.sdk.module.messagecenter.model.WhoCard; import com.apptentive.android.sdk.util.Constants; import org.json.JSONException; import org.json.JSONObject; -/** - * @author Sky Kelsey - */ public class MessageCenterInteraction extends Interaction { public static final String KEY_TITLE = "title"; @@ -63,31 +61,32 @@ public class MessageCenterInteraction extends Interaction { public static final String KEY_PROFILE_EDIT_SAVE_BUTTON = "save_button"; - // The server guarantees that an instance of this Interaction will be targetted to the following internal event name. + // The server guarantees that an instance of this Interaction will be targeted to the following internal event name. public static final String DEFAULT_INTERNAL_EVENT_NAME = "show_message_center"; // Events public static final String EVENT_NAME_CLOSE = "close"; public static final String EVENT_NAME_CANCEL = "cancel"; - public static final String EVENT_NAME_ATTACH = "attach"; public static final String EVENT_NAME_READ = "read"; - public static final String EVENT_NAME_GREETING_MESSAGE = "greeting_message"; public static final String EVENT_NAME_COMPOSE_OPEN = "compose_open"; public static final String EVENT_NAME_COMPOSE_CLOSE = "compose_close"; - public static final String EVENT_NAME_KEYBOARD_OPEN = "keyboard_open"; - public static final String EVENT_NAME_KEYBOARD_CLOSE = "keyboard_close"; public static final String EVENT_NAME_STATUS = "status"; public static final String EVENT_NAME_MESSAGE_HTTP_ERROR = "message_http_error"; public static final String EVENT_NAME_MESSAGE_NETWORK_ERROR = "message_network_error"; public static final String EVENT_NAME_PROFILE_OPEN = "profile_open"; public static final String EVENT_NAME_PROFILE_CLOSE = "profile_close"; - public static final String EVENT_NAME_PROFILE_NAME = "profile_name"; - public static final String EVENT_NAME_PROFILE_EMAIL = "profile_email"; public static final String EVENT_NAME_PROFILE_SUBMIT = "profile_submit"; - public static final String EVENT_NAME_ATTACHMENT_LIST_SHOWN = "attachment_list_open"; - public static final String EVENT_NAME_ATTACHMENT_ADD = "attachment_add"; + public static final String EVENT_NAME_ATTACH = "attach"; public static final String EVENT_NAME_ATTACHMENT_DELETE = "attachment_delete"; public static final String EVENT_NAME_ATTACHMENT_CANCEL = "attachment_cancel"; +/* + // Not implemented on Android + public static final String EVENT_NAME_GREETING_MESSAGE = "greeting_message"; + public static final String EVENT_NAME_KEYBOARD_OPEN = "keyboard_open"; + public static final String EVENT_NAME_KEYBOARD_CLOSE = "keyboard_close"; + public static final String EVENT_NAME_PROFILE_NAME = "profile_name"; + public static final String EVENT_NAME_PROFILE_EMAIL = "profile_email"; +*/ public MessageCenterInteraction(String json) throws JSONException { super(json); @@ -109,36 +108,20 @@ public String getBranding() { return null; } - public MessageCenterComposingItem getComposerArea() { - InteractionConfiguration configuration = getConfiguration(); - if (configuration == null) { - return null; - } - JSONObject composer = configuration.optJSONObject(KEY_COMPOSER); - return new MessageCenterComposingItem( - MessageCenterComposingItem.COMPOSING_ITEM_AREA, - null, - composer.optString(KEY_COMPOSER_HINT_TEXT, null), - null, - null, - null, - null); - } - - public MessageCenterComposingItem getComposerBar() { + public Composer getComposer() { InteractionConfiguration configuration = getConfiguration(); if (configuration == null) { return null; } JSONObject composer = configuration.optJSONObject(KEY_COMPOSER); - return new MessageCenterComposingItem( - MessageCenterComposingItem.COMPOSING_ITEM_ACTIONBAR, - composer.optString(KEY_COMPOSER_TITLE, null), - composer.optString(KEY_COMPOSER_CLOSE_BODY, null), - composer.optString(KEY_COMPOSER_CLOSE_DISCARD, null), - composer.optString(KEY_COMPOSER_CLOSE_CANCEL, null), - composer.optString(KEY_COMPOSER_SEND_BUTTON, null), - null); + return new Composer( + composer.optString(KEY_COMPOSER_TITLE, null), + composer.optString(KEY_COMPOSER_CLOSE_BODY, null), + composer.optString(KEY_COMPOSER_CLOSE_DISCARD, null), + composer.optString(KEY_COMPOSER_CLOSE_CANCEL, null), + composer.optString(KEY_COMPOSER_SEND_BUTTON, null), + composer.optString(KEY_COMPOSER_HINT_TEXT, null) + ); } //When enabled, display Who Card to request profile info @@ -160,64 +143,23 @@ public boolean getWhoCardRequired() { return profile.optBoolean(KEY_PROFILE_REQUIRE, false); } - public MessageCenterComposingItem getWhoCardInit() { + public JSONObject getProfile() { InteractionConfiguration configuration = getConfiguration(); if (configuration == null) { return null; } - JSONObject profile = configuration.optJSONObject(KEY_PROFILE); - JSONObject profileInitial = profile.optJSONObject(KEY_PROFILE_INIT); - if (profile.optBoolean(KEY_PROFILE_REQUIRE, false)) { - return new MessageCenterComposingItem( - MessageCenterComposingItem.COMPOSING_ITEM_WHOCARD_REQUIRED_INIT, - profileInitial.optString(KEY_PROFILE_INIT_TITLE, null), - // Hide name field if profile is required and never set - null, - profileInitial.optString(KEY_PROFILE_INIT_EMAIL_HINT, null), - profileInitial.optString(KEY_PROFILE_INIT_EMAIL_EXPLANATION, null), - // Hide Skip button - null, - profileInitial.optString(KEY_PROFILE_INIT_SAVE_BUTTON, null)); - } - return new MessageCenterComposingItem( - MessageCenterComposingItem.COMPOSING_ITEM_WHOCARD_REQUESTED_INIT, - profileInitial.optString(KEY_PROFILE_INIT_TITLE, null), - profileInitial.optString(KEY_PROFILE_INIT_NAME_HINT, null), - profileInitial.optString(KEY_PROFILE_INIT_EMAIL_HINT, null), - profileInitial.optString(KEY_PROFILE_INIT_EMAIL_EXPLANATION, null), - profileInitial.optString(KEY_PROFILE_INIT_SKIP_BUTTON, null), - profileInitial.optString(KEY_PROFILE_INIT_SAVE_BUTTON, null)); + return configuration.optJSONObject(KEY_PROFILE); } - public MessageCenterComposingItem getWhoCardEdit() { - InteractionConfiguration configuration = getConfiguration(); - if (configuration == null) { - return null; - } - JSONObject profile = configuration.optJSONObject(KEY_PROFILE); - JSONObject profileEdit = configuration.optJSONObject(KEY_PROFILE).optJSONObject(KEY_PROFILE_EDIT); - if (profile.optBoolean(KEY_PROFILE_REQUIRE, false)) { - JSONObject profileInitial = profile.optJSONObject(KEY_PROFILE_INIT); - return new MessageCenterComposingItem( - MessageCenterComposingItem.COMPOSING_ITEM_WHOCARD_REQUIRED_EDIT, - profileEdit.optString(KEY_PROFILE_EDIT_TITLE, null), - profileEdit.optString(KEY_PROFILE_EDIT_NAME_HINT, null), - profileInitial.optString(KEY_PROFILE_INIT_EMAIL_HINT, null), // Show "Email(required)" - profileEdit.optString(KEY_PROFILE_EDIT_EMAIL_EXPLANATION, null), - profileEdit.optString(KEY_PROFILE_EDIT_SKIP_BUTTON, null), - profileEdit.optString(KEY_PROFILE_EDIT_SAVE_BUTTON, null)); + public WhoCard getWhoCard() { + try { + return new WhoCard(getProfile().toString()); + } catch (JSONException e) { + // Never happens. } - return new MessageCenterComposingItem( - MessageCenterComposingItem.COMPOSING_ITEM_WHOCARD_REQUESTED_EDIT, - profileEdit.optString(KEY_PROFILE_EDIT_TITLE, null), - profileEdit.optString(KEY_PROFILE_EDIT_NAME_HINT, null), - profileEdit.optString(KEY_PROFILE_EDIT_EMAIL_HINT, null), - profileEdit.optString(KEY_PROFILE_EDIT_EMAIL_EXPLANATION, null), - profileEdit.optString(KEY_PROFILE_EDIT_SKIP_BUTTON, null), - profileEdit.optString(KEY_PROFILE_EDIT_SAVE_BUTTON, null)); + return null; } - public MessageCenterGreeting getGreeting() { InteractionConfiguration configuration = getConfiguration(); if (configuration == null) { @@ -228,7 +170,7 @@ public MessageCenterGreeting getGreeting() { return null; } return new MessageCenterGreeting(greeting.optString(KEY_GREETING_TITLE, null), - greeting.optString(KEY_GREETING_BODY, null), greeting.optString(KEY_GREETING_IMAGE, null)); + greeting.optString(KEY_GREETING_BODY, null), greeting.optString(KEY_GREETING_IMAGE, null)); } public JSONObject getContextualMessage() { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/Clause.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/Clause.java index 31db6fa6d..fdbd22421 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/Clause.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/Clause.java @@ -1,16 +1,11 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.module.engagement.logic; -import android.content.Context; - -/** - * @author Sky Kelsey - */ public interface Clause { boolean evaluate(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/ConditionalOperator.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/ConditionalOperator.java index d4b7f1f0f..0c60233d4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/ConditionalOperator.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/ConditionalOperator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -12,9 +12,6 @@ import java.math.BigDecimal; -/** - * @author Sky Kelsey - */ public enum ConditionalOperator { $exists { @Override @@ -39,6 +36,9 @@ public boolean apply(Comparable first, Comparable second) { if (first.getClass() != second.getClass()) { return false; } + if (first instanceof String && second instanceof String) { + return !((String) first).toLowerCase().equals(((String) second).toLowerCase()); + } return first.compareTo(second) != 0; } }, @@ -54,6 +54,9 @@ public boolean apply(Comparable first, Comparable second) { if (first.getClass() != second.getClass()) { return false; } + if (first instanceof String && second instanceof String) { + return ((String) first).toLowerCase().equals(((String) second).toLowerCase()); + } return first.compareTo(second) == 0; } }, diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java index 7e16de0ab..aa54f1dd1 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/engagement/logic/FieldManager.java @@ -41,13 +41,6 @@ public static Object doGetValue(String query) { case application: { QueryPart applicationQuery = QueryPart.parse(tokens[1]); switch (applicationQuery) { - case version: { - int version = Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext()); - if (version == -1) { - version = 0; // Default - } - return new Apptentive.Version(version); - } case version_code: { int version = Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext()); if (version == -1) { @@ -84,7 +77,6 @@ public static Object doGetValue(String query) { case is_update: { QueryPart subQuery = QueryPart.parse(tokens[1]); switch (subQuery) { - case version: case version_code: return VersionHistoryStore.isUpdate(VersionHistoryStore.Selector.version_code); case version_name: @@ -99,7 +91,6 @@ public static Object doGetValue(String query) { switch (subQuery) { case total: return VersionHistoryStore.getTimeAtInstall(VersionHistoryStore.Selector.total); - case version: case version_code: return VersionHistoryStore.getTimeAtInstall(VersionHistoryStore.Selector.version_code); case version_name: @@ -119,9 +110,12 @@ public static Object doGetValue(String query) { switch (queryPart2) { case total: // Get total for all versions of the app. return new BigDecimal(ApptentiveInternal.getInstance().getCodePointStore().getTotalInvokes(isInteraction, name)); - case version: - String appVersion = String.valueOf(Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext())); - return new BigDecimal(ApptentiveInternal.getInstance().getCodePointStore().getBuildInvokes(isInteraction, name, appVersion)); + case version_code: + String appVersionCode = String.valueOf(Util.getAppVersionCode(ApptentiveInternal.getInstance().getApplicationContext())); + return new BigDecimal(ApptentiveInternal.getInstance().getCodePointStore().getVersionCodeInvokes(isInteraction, name, appVersionCode)); + case version_name: + String appVersionName = Util.getAppVersionName(ApptentiveInternal.getInstance().getApplicationContext()); + return new BigDecimal(ApptentiveInternal.getInstance().getCodePointStore().getVersionNameInvokes(isInteraction, name, appVersionName)); default: break; } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java index 0f7490db3..bb8390de8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/MessageManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -25,7 +25,7 @@ import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveToastNotification; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterUtil; +import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; import com.apptentive.android.sdk.module.messagecenter.model.MessageFactory; import com.apptentive.android.sdk.module.metric.MetricModule; import com.apptentive.android.sdk.storage.MessageStore; @@ -43,9 +43,6 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; -/** - * @author Sky Kelsey - */ public class MessageManager { // The reason of pause message sending @@ -59,7 +56,7 @@ public class MessageManager { private static final int UI_THREAD_MESSAGE_ON_UNREAD_INTERNAL = 2; private static final int UI_THREAD_MESSAGE_ON_TOAST_NOTIFICATION = 3; - private WeakReference currentForgroundApptentiveActivity; + private WeakReference currentForegroundApptentiveActivity; private WeakReference afterSendMessageListener; @@ -121,7 +118,7 @@ public void handleMessage(android.os.Message msg) { } /* - * Starts an asynctask to pre-fetch messages. This is to be called as part of Push notification action + * Starts an AsyncTask to pre-fetch messages. This is to be called as part of Push notification action * when push is received on the device. */ public void startMessagePreFetchTask() { @@ -199,7 +196,7 @@ public synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForegro } incomingUnreadMessages++; // for every new message received, notify Message Center - Message msg = uiHandler.obtainMessage(UI_THREAD_MESSAGE_ON_UNREAD_INTERNAL, (CompoundMessage) apptentiveMessage); + Message msg = uiHandler.obtainMessage(UI_THREAD_MESSAGE_ON_UNREAD_INTERNAL, apptentiveMessage); msg.sendToTarget(); } } @@ -224,8 +221,8 @@ public synchronized boolean fetchAndStoreMessages(boolean isMessageCenterForegro return false; } - public List getMessageCenterListItems() { - List messagesToShow = new ArrayList(); + public List getMessageCenterListItems() { + List messagesToShow = new ArrayList(); try { List messagesAll = getMessageStore().getAllMessages().get(); // Do not display hidden messages on Message Center @@ -418,27 +415,27 @@ public void notifyInternalNewMessagesListeners(final CompoundMessage apptentiveM } @Deprecated - public void setHostUnreadMessagesListener(UnreadMessagesListener newlistener) { + public void setHostUnreadMessagesListener(UnreadMessagesListener listener) { clearHostUnreadMessagesListeners(); - if (newlistener != null) { - hostUnreadMessagesListeners.add(new WeakReference(newlistener)); + if (listener != null) { + hostUnreadMessagesListeners.add(new WeakReference(listener)); } } - public void addHostUnreadMessagesListener(UnreadMessagesListener newlistener) { - if (newlistener != null) { + public void addHostUnreadMessagesListener(UnreadMessagesListener newListener) { + if (newListener != null) { // Defer message polling thread creation, if not created yet, and host app adds an unread message listener init(); for (Iterator> iterator = hostUnreadMessagesListeners.iterator(); iterator.hasNext(); ) { WeakReference listenerRef = iterator.next(); UnreadMessagesListener listener = listenerRef.get(); - if (listener != null && listener == newlistener) { + if (listener != null && listener == newListener) { return; } else if (listener == null) { iterator.remove(); } } - hostUnreadMessagesListeners.add(new WeakReference(newlistener)); + hostUnreadMessagesListeners.add(new WeakReference(newListener)); } } @@ -456,15 +453,15 @@ public void notifyHostUnreadMessagesListeners(int unreadMessages) { } // Set when Activity.onStart() and onStop() are called - public void setCurrentForgroundActivity(Activity activity) { + public void setCurrentForegroundActivity(Activity activity) { if (activity != null) { - currentForgroundApptentiveActivity = new WeakReference(activity); + currentForegroundApptentiveActivity = new WeakReference(activity); } else { ApptentiveToastNotificationManager manager = ApptentiveToastNotificationManager.getInstance(null, false); if (manager != null) { manager.cleanUp(); } - currentForgroundApptentiveActivity = null; + currentForegroundApptentiveActivity = null; } } @@ -477,8 +474,8 @@ public boolean isMessageCenterInForeground() { } private void showUnreadMessageToastNotification(final CompoundMessage apptentiveMsg) { - if (currentForgroundApptentiveActivity != null && currentForgroundApptentiveActivity.get() != null) { - Activity foreground = currentForgroundApptentiveActivity.get(); + if (currentForegroundApptentiveActivity != null && currentForegroundApptentiveActivity.get() != null) { + Activity foreground = currentForegroundApptentiveActivity.get(); if (foreground != null) { PendingIntent pendingIntent = ApptentiveInternal.prepareMessageCenterPendingIntent(foreground.getApplicationContext()); if (pendingIntent != null) { @@ -502,7 +499,6 @@ public void run() { } } - public void appWentToForeground() { appInForeground.set(true); if (pollingWorker != null) { @@ -516,5 +512,4 @@ public void appWentToBackground() { pollingWorker.appWentToBackground(); } } - } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/OnListviewItemActionListener.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/OnListviewItemActionListener.java new file mode 100644 index 000000000..478a5fce7 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/OnListviewItemActionListener.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter; + +import android.view.View; +import android.widget.EditText; + +import com.apptentive.android.sdk.module.messagecenter.view.holder.MessageComposerHolder; +import com.apptentive.android.sdk.util.image.ApptentiveImageGridView; +import com.apptentive.android.sdk.util.image.ImageItem; + +public interface OnListviewItemActionListener { + void onComposingViewCreated(MessageComposerHolder composer, EditText composerEditText, ApptentiveImageGridView attachments); + + void beforeComposingTextChanged(CharSequence str); + + void onComposingTextChanged(CharSequence str); + + void afterComposingTextChanged(String str); + + void onCancelComposing(); + + void onFinishComposing(); + + void onWhoCardViewCreated(EditText nameEditText, EditText emailEditText, View viewToFocus); + + void onSubmitWhoCard(String buttonLabel); + + void onCloseWhoCard(String buttonLabel); + + void onAttachImage(); + + void onClickAttachment(int position, ImageItem image); +} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java index 03f1e3cba..6f82cc376 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -14,10 +14,7 @@ import java.util.Map; -/** - * @author Sky Kelsey - */ -public abstract class ApptentiveMessage extends ConversationItem implements MessageCenterUtil.MessageCenterListItem { +public abstract class ApptentiveMessage extends ConversationItem implements MessageCenterListItem { public static final String KEY_ID = "id"; public static final String KEY_CREATED_AT = "created_at"; @@ -248,12 +245,33 @@ public String getDatestamp() { return datestamp; } - public void setDatestamp(String datestamp) { - this.datestamp = datestamp; + /** + * Sets the datestamp for this message. + * + * @param datestamp A datestamp + * @return true if the datestamp was added or changed. + */ + public boolean setDatestamp(String datestamp) { + if (this.datestamp == null || !this.datestamp.equals(datestamp)) { + this.datestamp = datestamp; + return true; + } else { + return false; + } } - public void clearDatestamp() { - this.datestamp = null; + /** + * Clears the datestamp from a message + * + * @return true If the datestamp existed and was cleared, false if it was already cleared. + */ + public boolean clearDatestamp() { + if (datestamp != null) { + this.datestamp = null; + return true; + } else { + return false; + } } public abstract boolean isOutgoingMessage(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveToastNotification.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveToastNotification.java index 7dda2fe45..1b595b07a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveToastNotification.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ApptentiveToastNotification.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/Composer.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/Composer.java new file mode 100644 index 000000000..96dbd3701 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/Composer.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter.model; + +public class Composer implements MessageCenterListItem { + + public String title; + public String closeBody; + public String closeDiscard; + public String closeCancel; + public String sendButton; + public String messageHint; + + public Composer(String title, String closeBody, String closeDiscard, String closeCancel, String sendButton, String messageHint) { + this.title = title; + this.closeDiscard = closeDiscard; + this.closeBody = closeBody; + this.closeCancel = closeCancel; + this.sendButton = sendButton; + this.messageHint = messageHint; + } + + @Override + public int getListItemType() { + return MESSAGE_COMPOSER; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java index 255a26129..672587b8d 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/CompoundMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -21,9 +21,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -/** - * @author Barry Li - */ public class CompoundMessage extends ApptentiveMessage implements MessageCenterUtil.CompoundMessageCommonInterface { private static final String KEY_BODY = "body"; @@ -238,21 +235,6 @@ public boolean isOutgoingMessage() { return isOutgoing; } - public static CompoundMessage createAutoMessage(String title, String body) { - if (title == null && body == null) { - return null; - } - CompoundMessage message = new CompoundMessage(); - if (title != null) { - message.setTitle(title); - } - if (body != null) { - message.setBody(body); - } - message.setAutomated(true); - return message; - } - public List getRemoteAttachments() { return remoteAttachmentStoredFiles; } @@ -286,4 +268,15 @@ private boolean parseAttachmentsArray(String messageString) throws JSONException } return false; } + + @Override + public int getListItemType() { + if (isAutomatedMessage()) { + return MESSAGE_AUTO; + } else if (isOutgoing) { + return MESSAGE_OUTGOING; + } else { + return MESSAGE_INCOMING; + } + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ContextMessage.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ContextMessage.java new file mode 100644 index 000000000..7fedb1ebc --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/ContextMessage.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter.model; + +public class ContextMessage implements MessageCenterListItem { + + private String body; + + public ContextMessage(String body) { + this.body = body; + } + + @Override + public int getListItemType() { + return MESSAGE_CONTEXT; + } + + public String getBody() { + return body; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterComposingItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterComposingItem.java deleted file mode 100644 index be13e9d05..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterComposingItem.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.model; - -/** - * @author Sky Kelsey - */ -public class MessageCenterComposingItem implements MessageCenterUtil.MessageCenterListItem { - public static int COMPOSING_ITEM_AREA = 0; - public static int COMPOSING_ITEM_ACTIONBAR = 1; - // Scenarios of presenting Who Card - public static int COMPOSING_ITEM_WHOCARD_REQUIRED_INIT = 2; - public static int COMPOSING_ITEM_WHOCARD_REQUIRED_EDIT = 3; - public static int COMPOSING_ITEM_WHOCARD_REQUESTED_INIT = 4; - public static int COMPOSING_ITEM_WHOCARD_REQUESTED_EDIT = 5; - - public final int type; - /* - * Refer to https://apptentive.atlassian.net/wiki/display/APPTENTIVE/Message+Center+Interaction - * for how following strings are mapped to different composing view strings - */ - public final String str_1; - public final String str_2; - public final String str_3; - public final String str_4; - public final String button_1; - public final String button_2; - - - - public MessageCenterComposingItem(int type, String str_1, String str_2, - String str_3, String str_4, - String button_1, String button_2) { - this.type = type; - this.str_1 = str_1; - this.str_2 = str_2; - this.str_3 = str_3; - this.str_4 = str_4; - this.button_1 = button_1; - this.button_2 = button_2; - } - - public int getType() { - return type; - } - -} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterGreeting.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterGreeting.java index 4b4329439..d86db977a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterGreeting.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterGreeting.java @@ -1,18 +1,12 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.module.messagecenter.model; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * @author Sky Kelsey - */ -public class MessageCenterGreeting implements MessageCenterUtil.MessageCenterListItem { +public class MessageCenterGreeting implements MessageCenterListItem { public final String title; public final String body; public final String avatar; @@ -23,5 +17,8 @@ public MessageCenterGreeting(String title, String body, String avatar) { this.avatar = avatar; } - + @Override + public int getListItemType() { + return GREETING; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterListItem.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterListItem.java new file mode 100644 index 000000000..b1464227c --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterListItem.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter.model; + +public interface MessageCenterListItem { + int GREETING = 1; + int STATUS = 2; + int MESSAGE_CONTEXT = 3; + int MESSAGE_AUTO = 4; + int MESSAGE_OUTGOING = 5; + int MESSAGE_INCOMING = 6; + int MESSAGE_COMPOSER = 7; + int WHO_CARD = 8; + + int getListItemType(); +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterStatus.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterStatus.java index da77ad204..be337293a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterStatus.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterStatus.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -8,10 +8,7 @@ import org.json.JSONObject; -/** - * @author Sky Kelsey - */ -public class MessageCenterStatus extends JSONObject implements MessageCenterUtil.MessageCenterListItem { +public class MessageCenterStatus extends JSONObject implements MessageCenterListItem { public final String body; public final Integer icon; @@ -21,5 +18,8 @@ public MessageCenterStatus(String body, Integer icon) { this.icon = icon; } - + @Override + public int getListItemType() { + return STATUS; + } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterUtil.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterUtil.java index 59fec42c3..37ae80423 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterUtil.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageCenterUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -8,18 +8,14 @@ public class MessageCenterUtil { - public interface MessageCenterListItem { - - } - // Combine both incoming and outgoing interfaces into one public interface CompoundMessageCommonInterface { + void setBody(String body); - public void setBody(String body); - public String getBody(); - public void setLastSent(boolean bVal); - public boolean isLastSent(); + String getBody(); - } + void setLastSent(boolean bVal); + boolean isLastSent(); + } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java index 5b09a56bc..0e160d268 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/MessageFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -14,9 +14,6 @@ import org.json.JSONException; import org.json.JSONObject; -/** - * @author Sky Kelsey - */ public class MessageFactory { public static ApptentiveMessage fromJson(String json) { try { diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/WhoCard.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/WhoCard.java new file mode 100644 index 000000000..6fe5aee85 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/model/WhoCard.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter.model; + +import org.json.JSONException; +import org.json.JSONObject; + +public class WhoCard extends JSONObject implements MessageCenterListItem { + + private static final String KEY_REQUEST = "request"; + private static final String KEY_REQUIRE = "require"; + private static final String KEY_INITIAL = "initial"; + private static final String KEY_EDIT = "edit"; + private static final String KEY_TITLE = "title"; + private static final String KEY_NAME_HINT = "name_hint"; + private static final String KEY_EMAIL_HINT = "email_hint"; + private static final String KEY_EMAIL_EXPLANATION = "email_explanation"; + private static final String KEY_SKIP_BUTTON = "skip_button"; + private static final String KEY_SAVE_BUTTON = "save_button"; + + private boolean initial; + + public WhoCard(String json) throws JSONException { + super(json); + } + + public boolean isInitial() { + return initial; + } + + public void setInitial(boolean initial) { + this.initial = initial; + } + + @Override + public int getListItemType() { + return WHO_CARD; + } + + public boolean isRequest() { + return optBoolean(KEY_REQUEST, false); + } + + public boolean isRequire() { + return optBoolean(KEY_REQUIRE, false); + } + + private JSONObject getInitial() { + return optJSONObject(KEY_INITIAL); + } + + private JSONObject getEdit() { + return optJSONObject(KEY_EDIT); + } + + private JSONObject getApplicableConfig() { + if (isInitial()) { + return getInitial(); + } else { + return getEdit(); + } + } + + public String getTitle() { + return getApplicableConfig().optString(KEY_TITLE, null); + } + + public String getNameHint() { + if (isRequire() && isInitial()) { + // The Who Card will show up right when MC is opened in this scenario. Don't ask for the name at this time. + return null; + } + return getApplicableConfig().optString(KEY_NAME_HINT, null); + } + + public String getEmailHint() { + if (isRequire() && !isInitial()) { + // Email is required when initial, but also if not initial, if the form itself was ever required. + return getInitial().optString(KEY_EMAIL_HINT, null); + + } + return getApplicableConfig().optString(KEY_EMAIL_HINT, null); + } + + public String getEmailExplanation() { + return getApplicableConfig().optString(KEY_EMAIL_EXPLANATION, null); + } + + public String getSkipButton() { + if (isRequire() && isInitial()) { + // The Who Card will show up right when MC is opened in this scenario. Don't allow skipping it at this time. + return null; + } + return getApplicableConfig().optString(KEY_SKIP_BUTTON, null); + } + + public String getSaveButton() { + return getApplicableConfig().optString(KEY_SAVE_BUTTON, null); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/AttachmentPreviewDialog.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/AttachmentPreviewDialog.java index fa028591b..cb701d34e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/AttachmentPreviewDialog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/AttachmentPreviewDialog.java @@ -7,7 +7,6 @@ package com.apptentive.android.sdk.module.messagecenter.view; import android.app.Dialog; -import android.content.Context; import android.content.DialogInterface; import android.graphics.Bitmap; import android.graphics.Color; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/AutomatedMessageView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/AutomatedMessageView.java deleted file mode 100644 index 55be1eeae..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/AutomatedMessageView.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view; - -import android.content.Context; -import android.view.LayoutInflater; - -import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; - -/** - * @author Sky Kelsey - */ -public class AutomatedMessageView extends MessageView { - - public AutomatedMessageView(Context context, CompoundMessage message) { - super(context, message); - } - - protected void init(Context context, CompoundMessage message) { - super.init(context, message); - LayoutInflater inflater = LayoutInflater.from(context); - inflater.inflate(R.layout.apptentive_message_auto, this); - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/CompoundMessageView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/CompoundMessageView.java deleted file mode 100644 index a086abbd0..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/CompoundMessageView.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view; - -import android.content.Context; -import android.view.View; -import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; -import com.apptentive.android.sdk.util.image.ApptentiveImageGridView; -import com.apptentive.android.sdk.util.image.ImageItem; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; - -/** - * @author Barry Li - */ -public class CompoundMessageView extends PersonalMessageView { - - private WeakReference listenerRef; - private boolean isOutGoingView = false; - - public CompoundMessageView(Context context, CompoundMessage message, final MessageAdapter.OnListviewItemActionListener listener) { - super(context, message); - this.listenerRef = new WeakReference(listener); - isOutGoingView = message.isOutgoingMessage(); - } - - protected void init(Context context, CompoundMessage message) { - super.init(context, message); - ApptentiveImageGridView imageBandView = (ApptentiveImageGridView) findViewById(R.id.grid); - imageBandView.setupUi(); - - imageBandView.setListener(new ApptentiveImageGridView.ImageItemClickedListener() { - @Override - public void onClick(int position, ImageItem image) { - MessageAdapter.OnListviewItemActionListener listener = listenerRef.get(); - if (listener != null) { - listener.onClickAttachment(position, image); - } - } - }); - imageBandView.setVisibility(View.GONE); - imageBandView.setAdapterIndicator(0); - imageBandView.setData(new ArrayList()); - } - - public boolean isViewShowingOutgoingMessage() { - return isOutGoingView; - } - - public MessageAdapter.OnListviewItemActionListener getListener() { - return listenerRef.get(); - } - -} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageAdapter.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageAdapter.java deleted file mode 100644 index 2edf27a0f..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageAdapter.java +++ /dev/null @@ -1,808 +0,0 @@ -/* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Point; -import android.os.Build; -import android.os.Parcelable; -import android.support.v4.app.Fragment; -import android.text.TextUtils; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.os.AsyncTask; -import android.widget.EditText; - -import com.apptentive.android.sdk.Apptentive; -import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.model.*; -import com.apptentive.android.sdk.module.engagement.EngagementModule; -import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; -import com.apptentive.android.sdk.module.messagecenter.MessageManager; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; -import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterComposingItem; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterGreeting; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterStatus; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterUtil; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterUtil.MessageCenterListItem; -import com.apptentive.android.sdk.module.messagecenter.view.holder.AutomatedMessageHolder; -import com.apptentive.android.sdk.module.messagecenter.view.holder.HolderFactory; -import com.apptentive.android.sdk.module.messagecenter.view.holder.IncomingCompoundMessageHolder; -import com.apptentive.android.sdk.module.messagecenter.view.holder.MessageCenterListItemHolder; -import com.apptentive.android.sdk.module.messagecenter.view.holder.OutgoingCompoundMessageHolder; -import com.apptentive.android.sdk.module.messagecenter.view.holder.StatusHolder; -import com.apptentive.android.sdk.util.AnimationUtil; -import com.apptentive.android.sdk.util.image.ImageItem; -import com.apptentive.android.sdk.util.image.ImageUtil; -import com.apptentive.android.sdk.util.Util; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.FileInputStream; - -import java.lang.ref.WeakReference; - -import java.util.ArrayList; -import java.util.List; - -public class MessageAdapter extends ArrayAdapter - implements MessageCenterListView.ApptentiveMessageCenterListAdapter { - - private static final int - TYPE_COMPOUND_INCOMING = 0, - TYPE_COMPOUND_OUTGOING = 1, - TYPE_GREETING = 2, - TYPE_STATUS = 3, - TYPE_AUTO = 4, - TYPE_COMPOSING_AREA = 5, - TYPE_COMPOSING_BAR = 6, - TYPE_WHOCARD = 7; - - private static final int INVALID_POSITION = -1; - - private final static float MAX_IMAGE_SCREEN_PROPORTION_X = 0.5f; - private final static float MAX_IMAGE_SCREEN_PROPORTION_Y = 0.6f; - - // Some absolute size limits to keep bitmap sizes down. - private final static int MAX_IMAGE_DISPLAY_WIDTH = 800; - private final static int MAX_IMAGE_DISPLAY_HEIGHT = 800; - - // If message sending is paused or not - private boolean isInPauseState = false; - - private Fragment fragment; - - private MessageCenterInteraction interaction; - - // Variables used in composing message - private int composingViewIndex = INVALID_POSITION; - private MessageCenterComposingView composingView; - private EditText composingEditText; - private boolean updateComposingViewImageBand = true; - - private MessageCenterComposingActionBarView composingActionBarView; - - private boolean forceShowKeyboard = true; - - // Variables used in Who Card - private int whoCardViewIndex = INVALID_POSITION; - private boolean focusOnNameField = false; - private MessageCenterWhoCardView whoCardView; - private EditText emailEditText; - private EditText nameEditText; - - // Variables to track showing animation on incoming/outgoing messages - private int lastAnimatedMessagePosition; - private boolean showMessageAnimation; - - private boolean showComposingBarAnimation = true; - - // maps to prevent redundant asynctasks - private ArrayList positionsWithPendingUpdateTask = new ArrayList(); - - private OnListviewItemActionListener composingActionListener; - - public interface OnListviewItemActionListener { - void onComposingViewCreated(View keyboardFocusedView); - - void updateComposingBar(); - - void beforeComposingTextChanged(CharSequence str); - - void onComposingTextChanged(CharSequence str); - - void afterComposingTextChanged(String str); - - void onCancelComposing(); - - void onFinishComposing(); - - void onWhoCardViewCreated(EditText nameEt, EditText emailEt, View keyboardFocusedView); - - void onSubmitWhoCard(String buttonLabel); - - void onCloseWhoCard(String buttonLabel); - - void onAttachImage(); - - void onClickAttachment(int position, ImageItem image); - } - - public MessageAdapter(Fragment fragment, List items, MessageCenterInteraction interaction) { - super(fragment.getContext().getApplicationContext(), 0, (List) items); - this.fragment = fragment; - this.composingActionListener = (OnListviewItemActionListener) fragment; - this.interaction = interaction; - } - - @Override - public int getItemViewType(int position) { - MessageCenterListItem listItem = getItem(position); - if (listItem instanceof ApptentiveMessage) { - ApptentiveMessage apptentiveMessage = (ApptentiveMessage) listItem; - if (apptentiveMessage.getBaseType() == Payload.BaseType.message) { - switch (apptentiveMessage.getType()) { - case CompoundMessage: - if (apptentiveMessage.isAutomatedMessage()) { - return TYPE_AUTO; - } else if (!apptentiveMessage.isOutgoingMessage()) { - return TYPE_COMPOUND_INCOMING; - } else { - return TYPE_COMPOUND_OUTGOING; - } - default: - break; - } - } - } else if (listItem instanceof MessageCenterGreeting) { - return TYPE_GREETING; - } else if (listItem instanceof MessageCenterStatus) { - return TYPE_STATUS; - } else if (listItem instanceof MessageCenterComposingItem) { - if (((MessageCenterComposingItem) listItem).getType() == - MessageCenterComposingItem.COMPOSING_ITEM_AREA) { - return TYPE_COMPOSING_AREA; - } else if (((MessageCenterComposingItem) listItem).getType() == - MessageCenterComposingItem.COMPOSING_ITEM_ACTIONBAR) { - return TYPE_COMPOSING_BAR; - } else { - return TYPE_WHOCARD; - } - } - return IGNORE_ITEM_VIEW_TYPE; - } - - @Override - public int getViewTypeCount() { - return 8; - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - final View view; - showMessageAnimation = false; - - MessageCenterListItem listItem = getItem(position); - int type = getItemViewType(position); - MessageCenterListItemHolder holder = null; - boolean bLoadAvatar = false; - if (null == convertView) { - // TODO: Do we need this switch anymore? - switch (type) { - case TYPE_COMPOUND_INCOMING: - view = new CompoundMessageView(parent.getContext(), (CompoundMessage) listItem, composingActionListener); - bLoadAvatar = true; - break; - case TYPE_COMPOUND_OUTGOING: - view = new CompoundMessageView(parent.getContext(), (CompoundMessage) listItem, composingActionListener); - break; - case TYPE_GREETING: { - MessageCenterGreeting greeting = (MessageCenterGreeting) listItem; - MessageCenterGreetingView newView = new MessageCenterGreetingView(parent.getContext(), greeting); - ImageUtil.startDownloadAvatarTask(newView.avatar, - ((MessageCenterGreeting) listItem).avatar); - view = newView; - break; - } - case TYPE_STATUS: { - MessageCenterStatusView newView = new MessageCenterStatusView(parent.getContext()); - view = newView; - break; - } - case TYPE_COMPOSING_AREA: { - if (composingView == null) { - composingView = new MessageCenterComposingView(fragment, (MessageCenterComposingItem) listItem, composingActionListener); - setupComposingView(position); - } - view = composingView; - break; - } - case TYPE_COMPOSING_BAR: { - if (composingActionBarView == null) { - composingActionBarView = new MessageCenterComposingActionBarView(fragment, (MessageCenterComposingItem) listItem, composingActionListener); - } - showComposingBarAnimation(); - view = composingActionBarView; - break; - } - case TYPE_WHOCARD: { - if (whoCardView == null) { - whoCardView = new MessageCenterWhoCardView(fragment, composingActionListener); - whoCardView.updateUi((MessageCenterComposingItem) listItem, Apptentive.getPersonName(), - Apptentive.getPersonEmail()); - setupWhoCardView(position); - } - view = whoCardView; - break; - } - case TYPE_AUTO: - view = new AutomatedMessageView(parent.getContext(), (CompoundMessage) listItem); - break; - default: - view = null; - ApptentiveLog.i("Unrecognized type: %d", type); - break; - } - if (view != null) { - holder = HolderFactory.createHolder((MessageCenterListItemView) view); - view.setTag(holder); - } - } else { - /* System may recycle the view after composing view - ** is removed and recreated - */ - if (type == TYPE_COMPOSING_AREA) { - if (composingView == null) { - updateComposingViewImageBand = true; - composingView = (MessageCenterComposingView) convertView; - setupComposingView(position); - } else if (updateComposingViewImageBand) { - updateComposingViewImageBand = false; - } - view = composingView; - } else if (type == TYPE_WHOCARD) { - if (whoCardView == null) { - whoCardView = (MessageCenterWhoCardView) convertView; - whoCardView.updateUi((MessageCenterComposingItem) listItem, Apptentive.getPersonName(), - Apptentive.getPersonEmail()); - setupWhoCardView(position); - } - view = whoCardView; - } else if (type == TYPE_COMPOSING_BAR) { - composingActionBarView = (MessageCenterComposingActionBarView) convertView; - showComposingBarAnimation(); - view = composingActionBarView; - } else { - view = convertView; - holder = (MessageCenterListItemHolder) convertView.getTag(); - } - } - - if (holder != null) { - switch (type) { - case TYPE_COMPOUND_INCOMING: { - showMessageAnimation = true; - if (bLoadAvatar) { - ImageUtil.startDownloadAvatarTask(((IncomingCompoundMessageHolder) holder).avatar, - ((CompoundMessage) listItem).getSenderProfilePhoto()); - } - final CompoundMessage compoundMessage = (CompoundMessage) listItem; - String datestamp = ((CompoundMessage) listItem).getDatestamp(); - View container = view.findViewById(R.id.apptentive_compound_message_body_container); - int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); - view.measure(widthMeasureSpec, 0); - int viewWidth = container.getMeasuredWidth(); - ((IncomingCompoundMessageHolder) holder).updateMessage(compoundMessage.getSenderUsername(), - datestamp, compoundMessage.getBody(), viewWidth - container.getPaddingLeft() - container.getPaddingRight(), - fragment.getResources().getInteger(R.integer.apptentive_image_grid_default_column_number_incoming), - compoundMessage.getRemoteAttachments()); - if (!compoundMessage.isRead() && !positionsWithPendingUpdateTask.contains(position)) { - positionsWithPendingUpdateTask.add(position); - startUpdateUnreadMessageTask(compoundMessage, position); - } - break; - } - case TYPE_COMPOUND_OUTGOING: { - showMessageAnimation = true; - - CompoundMessage textMessage = (CompoundMessage) listItem; - String datestamp = textMessage.getDatestamp(); - Double createdTime = textMessage.getCreatedAt(); - List files = textMessage.getAssociatedFiles(); - String messageBody = textMessage.getBody(); - String status; - boolean bShowProgress; - if (createdTime == null || createdTime > Double.MIN_VALUE) { - status = createStatus(createdTime, textMessage.isLastSent()); - // show progress bar if: 1. no sent time set, and 2. not paused, and 3. have either text or files to sent - bShowProgress = createdTime == null && !isInPauseState && (files != null || !TextUtils.isEmpty(messageBody)); - } else { - status = fragment.getResources().getString(R.string.apptentive_failed); - bShowProgress = false; - } - int imagebandWidth = 0; - if (files != null && files.size() > 0) { - View container = view.findViewById(R.id.apptentive_compound_message_body_container); - int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); - view.measure(widthMeasureSpec, 0); - int viewWidth = container.getMeasuredWidth(); - imagebandWidth = viewWidth - container.getPaddingLeft() - container.getPaddingRight(); - } - - int statusTextColor = getStatusColor(createdTime); - ((OutgoingCompoundMessageHolder) holder).updateMessage(datestamp, status, statusTextColor, - bShowProgress, messageBody, imagebandWidth, - fragment.getResources().getInteger(R.integer.apptentive_image_grid_default_column_number), files); - break; - } - case TYPE_STATUS: { - MessageCenterStatus status = (MessageCenterStatus) listItem; - ((StatusHolder) holder).updateMessage(status.body, status.icon); - break; - } - case TYPE_AUTO: { - CompoundMessage autoMessage = (CompoundMessage) listItem; - String dateStamp = autoMessage.getDatestamp(); - ((AutomatedMessageHolder) holder).updateMessage(dateStamp, autoMessage); - break; - } - default: - return null; - } - holder.position = position; - } - if (composingEditText != null) { - if (composingViewIndex != INVALID_POSITION && composingViewIndex == position) { - composingEditText.post(new Runnable() { - public void run() { - if (composingEditText != null) { - if (!composingEditText.hasFocus()) { - composingEditText.requestFocus(); - } - } - } - }); - } - } else if (nameEditText != null) { - if (whoCardViewIndex != INVALID_POSITION && whoCardViewIndex == position) { - if (focusOnNameField) { - nameEditText.post( - new Runnable() { - public void run() { - if (nameEditText != null) { - nameEditText.requestFocus(); - } - } - } - ); - } else { - emailEditText.post( - new Runnable() { - public void run() { - if (emailEditText != null) { - emailEditText.requestFocus(); - } - } - } - ); - } - } - } - if (showMessageAnimation && position > lastAnimatedMessagePosition) { - AnimatorSet set = AnimationUtil.buildListViewRowShowAnimator(view, new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - view.invalidate(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - }, null); - set.start(); - lastAnimatedMessagePosition = position; - } - return view; - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - return false; - } - - private void showComposingBarAnimation() { - if (showComposingBarAnimation) { - AnimatorSet set = AnimationUtil.buildListViewRowShowAnimator(composingActionBarView, new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - composingActionListener.updateComposingBar(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - }, null); - set.start(); - showComposingBarAnimation = false; - } - } - - public Parcelable getWhoCardNameState() { - if (whoCardView != null) { - return whoCardView.getNameField().onSaveInstanceState(); - } - return null; - } - - public Parcelable getWhoCardEmailState() { - if (whoCardView != null) { - return whoCardView.getEmailField().onSaveInstanceState(); - } - return null; - } - - public String getWhoCardAvatarFileName() { - return null; - } - - public EditText getEditTextInComposing() { - if (composingView != null) { - return composingView.getEditText(); - } - return null; - } - - private void setupComposingView(final int position) { - composingEditText = composingView.getEditText(); - composingViewIndex = position; - composingEditText.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - composingViewIndex = position; - } - return false; - } - }); - AnimatorSet set = AnimationUtil.buildListViewRowShowAnimator(composingView, new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - composingActionListener.onComposingViewCreated(forceShowKeyboard ? composingEditText : null); - if (updateComposingViewImageBand) { - updateComposingViewImageBand = false; - } - } - - @Override - public void onAnimationCancel(Animator animation) { - } - }, null); - set.start(); - } - - public void clearComposing() { - // Composing view may be recycled for later usage. Clear the content from previous usage - if (composingEditText != null) { - composingEditText.setText(""); - composingEditText = null; - } - if (composingView != null) { - composingView.clearImageAttachmentBand(); - } - composingView = null; - composingActionBarView = null; - composingViewIndex = INVALID_POSITION; - showComposingBarAnimation = true; - updateComposingViewImageBand = true; - } - - private void setupWhoCardView(final int position) { - emailEditText = whoCardView.getEmailField(); - whoCardViewIndex = position; - focusOnNameField = true; - emailEditText.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - whoCardViewIndex = position; - focusOnNameField = false; - } - return false; - } - }); - nameEditText = whoCardView.getNameField(); - if (nameEditText.getVisibility() == View.VISIBLE) { - nameEditText.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - whoCardViewIndex = position; - focusOnNameField = true; - } - return false; - } - }); - } else { - focusOnNameField = false; - } - AnimatorSet set = AnimationUtil.buildListViewRowShowAnimator(whoCardView, new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - View focusedView = null; - if (forceShowKeyboard) { - if (focusOnNameField) { - focusedView = nameEditText; - } else { - focusedView = emailEditText; - } - } - composingActionListener.onWhoCardViewCreated(nameEditText, emailEditText, focusedView); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - }, null); - set.start(); - } - - public View getWhoCardView() { - return whoCardView; - } - - public View getComposingAreaView() { - return composingView; - } - - public MessageCenterComposingActionBarView getComposingActionBarView() { - return composingActionBarView; - } - - public void clearWhoCard() { - whoCardView = null; - emailEditText = null; - nameEditText = null; - whoCardViewIndex = INVALID_POSITION; - } - - public void setPaused(boolean bPause) { - isInPauseState = bPause; - } - - public void setForceShowKeyboard(boolean bVal) { - forceShowKeyboard = bVal; - } - - public void addImagestoComposer(final List images) { - composingView.addImagesToImageAttachmentBand(images); - notifyDataSetChanged(); - } - - public void removeImageFromComposer(int position) { - composingView.removeImageFromImageAttachmentBand(position); - notifyDataSetChanged(); - } - - protected String createStatus(Double seconds, boolean showSent) { - if (seconds == null) { - return isInPauseState ? fragment.getResources().getString(R.string.apptentive_failed) : null; - } - return (showSent) ? fragment.getResources().getString(R.string.apptentive_sent) : null; - } - - protected int getStatusColor(Double seconds) { - if (seconds == null) { - // failed color (red) - return isInPauseState ? Util.getThemeColor(fragment.getContext(), R.attr.apptentiveValidationFailedColor) : 0; - } - // other status color - return Util.getThemeColor(fragment.getContext(), android.R.attr.textColorSecondary); - } - - private Point getBitmapDimensions(StoredFile storedFile) { - Point ret = null; - FileInputStream fis = null; - try { - fis = fragment.getContext().openFileInput(storedFile.getLocalFilePath()); - - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(fis, null, options); - - Point point = Util.getScreenSize(fragment.getContext()); - int maxImageWidth = (int) (MAX_IMAGE_SCREEN_PROPORTION_X * point.x); - int maxImageHeight = (int) (MAX_IMAGE_SCREEN_PROPORTION_Y * point.x); - maxImageWidth = maxImageWidth > MAX_IMAGE_DISPLAY_WIDTH ? MAX_IMAGE_DISPLAY_WIDTH : maxImageWidth; - maxImageHeight = maxImageHeight > MAX_IMAGE_DISPLAY_HEIGHT ? MAX_IMAGE_DISPLAY_HEIGHT : maxImageHeight; - float scale = ImageUtil.calculateBitmapScaleFactor(options.outWidth, options.outHeight, maxImageWidth, maxImageHeight); - ret = new Point((int) (scale * options.outWidth), (int) (scale * options.outHeight)); - } catch (Exception e) { - ApptentiveLog.e("Error opening stored file.", e); - } finally { - Util.ensureClosed(fis); - } - return ret; - } - - private void startUpdateUnreadMessageTask(CompoundMessage message, int position) { - UpdateUnreadMessageTask task = new UpdateUnreadMessageTask(position); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); - } else { - task.execute(message); - } - } - - @Override - public boolean isItemSticky(int viewType) { - return (viewType == TYPE_COMPOSING_BAR); - } - - private class UpdateUnreadMessageTask extends AsyncTask { - private int position; - - public UpdateUnreadMessageTask(int position) { - this.position = position; - } - - @Override - protected Void doInBackground(CompoundMessage... textMessages) { - textMessages[0].setRead(true); - JSONObject data = new JSONObject(); - try { - data.put("message_id", textMessages[0].getId()); - data.put("message_type", textMessages[0].getType().name()); - } catch (JSONException e) { - // - } - EngagementModule.engageInternal(fragment.getContext(), interaction, MessageCenterInteraction.EVENT_NAME_READ, data.toString()); - - MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); - if (mgr != null) { - mgr.updateMessage(textMessages[0]); - mgr.notifyHostUnreadMessagesListeners(mgr.getUnreadMessageCount()); - } - return null; - } - - @Override - protected void onCancelled() { - positionsWithPendingUpdateTask.remove(Integer.valueOf(position)); - } - - @Override - protected void onPostExecute(Void result) { - positionsWithPendingUpdateTask.remove(Integer.valueOf(position)); - } - - } - - private void startLoadAttachedImageTask(CompoundMessage message, int position, OutgoingCompoundMessageHolder holder) { - List storedFiles = message.getAssociatedFiles(); - if (storedFiles == null || storedFiles.size() == 0) { - return; - } - for (int i = 0; i < storedFiles.size(); i++) { - StoredFile storedFile = storedFiles.get(i); - String mimeType = storedFile.getMimeType(); - String imagePath; - - if (mimeType != null) { - imagePath = storedFile.getLocalFilePath(); - if (mimeType.contains("image")) { - //holder.image.setVisibility(View.INVISIBLE); - - Point dimensions = getBitmapDimensions(storedFile); - if (dimensions != null) { - //holder.image.setPadding(dimensions.x, dimensions.y, 0, 0); - } - } - LoadAttachedImageTask task = new LoadAttachedImageTask(position, holder); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, imagePath); - } else { - task.execute(imagePath); - } - } - } - } - - private class LoadAttachedImageTask extends AsyncTask { - private int position; - private WeakReference holderRef; - - public LoadAttachedImageTask(int position, OutgoingCompoundMessageHolder holder) { - this.position = position; - this.holderRef = new WeakReference(holder); - } - - @Override - protected Bitmap doInBackground(String... paths) { - Bitmap imageBitmap = null; - try { - Point point = Util.getScreenSize(fragment.getContext().getApplicationContext()); - int maxImageWidth = (int) (MAX_IMAGE_SCREEN_PROPORTION_X * point.x); - int maxImageHeight = (int) (MAX_IMAGE_SCREEN_PROPORTION_Y * point.x); - maxImageWidth = maxImageWidth > MAX_IMAGE_DISPLAY_WIDTH ? MAX_IMAGE_DISPLAY_WIDTH : maxImageWidth; - maxImageHeight = maxImageHeight > MAX_IMAGE_DISPLAY_HEIGHT ? MAX_IMAGE_DISPLAY_HEIGHT : maxImageHeight; - // Loading image from File Store. Pass 0 for orientation because images have been rotated when stored - imageBitmap = ImageUtil.createScaledBitmapFromLocalImageSource(paths[0], maxImageWidth, maxImageHeight, null, 0); - ApptentiveLog.v("Loaded bitmap and re-sized to: %d x %d", imageBitmap.getWidth(), imageBitmap.getHeight()); - } catch (Exception e) { - ApptentiveLog.e("Error opening stored image.", e); - } catch (OutOfMemoryError e) { - // It's generally not a good idea to catch an OutOfMemoryException. But in this case, the OutOfMemoryException - // had to result from allocating a bitmap, so the system should be in a good state. - // TODO: ApptentiveLog an event to the server so we know an OutOfMemoryException occurred. - ApptentiveLog.e("Ran out of memory opening image.", e); - } - return imageBitmap; - } - - @Override - protected void onCancelled() { - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - if (null == bitmap) { - return; - } - OutgoingCompoundMessageHolder holder = holderRef.get(); - if (holder != null && holder.position == position) { - //Todo: load image into imageview - } - } - } - -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterComposingActionBarView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterComposingActionBarView.java deleted file mode 100644 index 052cdc8dd..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterComposingActionBarView.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view; - -import android.content.res.ColorStateList; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; - -import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.TextView; - -import com.apptentive.android.sdk.ApptentiveLog; -import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterComposingItem; -import com.apptentive.android.sdk.util.Constants; -import com.apptentive.android.sdk.util.Util; -import com.apptentive.android.sdk.view.ApptentiveAlertDialog; - -import java.lang.ref.WeakReference; - - -public class MessageCenterComposingActionBarView extends FrameLayout implements MessageCenterListItemView { - - public boolean showConfirmation = false; - public ImageButton sendButton; - public ImageButton attachButton; - private WeakReference listenerRef; - - public MessageCenterComposingActionBarView(final Fragment fragment, final MessageCenterComposingItem item, final MessageAdapter.OnListviewItemActionListener listener) { - super(fragment.getContext()); - this.listenerRef = new WeakReference(listener); - - LayoutInflater inflater = fragment.getActivity().getLayoutInflater(); - - try { - inflater.inflate(R.layout.apptentive_message_center_composing_actionbar, this); - } catch (Exception e) { - ApptentiveLog.e("Error:", e); - } - - ColorStateList colors = ContextCompat.getColorStateList(getContext(), Util.getResourceIdFromAttribute(getContext().getTheme(), R.attr.apptentiveButtonTintColorStateList)); - - ImageButton closeButton = (ImageButton) findViewById(R.id.cancel_composing); - // Use a color state list for button tint state on Lollipop. On prior platforms, need to apply state color manually. - Drawable closeButtonDrawable = DrawableCompat.wrap(closeButton.getDrawable()); - DrawableCompat.setTintList(closeButtonDrawable, colors); - closeButton.setImageDrawable(closeButtonDrawable); - - closeButton.setOnClickListener(new OnClickListener() { - public void onClick(View view) { - MessageAdapter.OnListviewItemActionListener locallistener = listenerRef.get(); - if (locallistener == null) { - return; - } - if (showConfirmation) { - Bundle bundle = new Bundle(); - bundle.putString("message", item.str_2); - bundle.putString("positive", item.str_3); - bundle.putString("negative", item.str_4); - ApptentiveAlertDialog.show(fragment, bundle, Constants.REQUEST_CODE_CLOSE_COMPOSING_CONFIRMATION); - } else { - locallistener.onCancelComposing(); - } - } - }); - - TextView composing = (TextView) findViewById(R.id.composing); - - if (item.str_1 != null) { - composing.setText(item.str_1); - } - - sendButton = (ImageButton) findViewById(R.id.btn_send_message); - // Use a color state list for button tint state on Lollipop. On prior platforms, need to apply state color manually. - Drawable sendButtonDrawable = DrawableCompat.wrap(sendButton.getDrawable()); - DrawableCompat.setTintList(sendButtonDrawable, colors); - sendButton.setImageDrawable(sendButtonDrawable); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - sendButton.setColorFilter(Util.getThemeColor(fragment.getContext(), R.attr.apptentiveButtonTintColorDisabled)); - } - sendButton.setEnabled(false); - if (item.button_1 != null) { - sendButton.setContentDescription(item.button_1); - } - sendButton.setOnClickListener(new OnClickListener() { - public void onClick(View view) { - MessageAdapter.OnListviewItemActionListener locallistener = listenerRef.get(); - if (locallistener == null) { - return; - } - locallistener.onFinishComposing(); - } - }); - - attachButton = (ImageButton) findViewById(R.id.btn_attach_image); - // Use a color state list for button tint state on Lollipop. On prior platforms, need to apply state color manually. - Drawable attachButtonDrawable = DrawableCompat.wrap(attachButton.getDrawable()); - DrawableCompat.setTintList(attachButtonDrawable, colors); - attachButton.setImageDrawable(attachButtonDrawable); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - attachButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View view) { - MessageAdapter.OnListviewItemActionListener locallistener = listenerRef.get(); - if (locallistener == null) { - return; - } - locallistener.onAttachImage(); - } - }); - } else { - attachButton.setVisibility(GONE); - } - } - - -} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterComposingView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterComposingView.java deleted file mode 100644 index bd9712b04..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterComposingView.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view; - -import android.support.v4.app.Fragment; -import android.text.Editable; - -import android.text.Layout; -import android.text.Selection; -import android.text.Spannable; -import android.text.TextWatcher; -import android.text.method.ArrowKeyMovementMethod; -import android.text.method.MovementMethod; -import android.text.style.ClickableSpan; -import android.text.util.Linkify; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.TextView; - - -import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterComposingItem; -import com.apptentive.android.sdk.util.image.ApptentiveImageGridView; -import com.apptentive.android.sdk.util.image.ImageGridViewAdapter; -import com.apptentive.android.sdk.util.image.ImageItem; - -import java.util.ArrayList; -import java.util.List; - - -/** - * @author Barry Li - */ -public class MessageCenterComposingView extends FrameLayout implements MessageCenterListItemView { - - private EditText et; - // Image Band - private ApptentiveImageGridView imageBandView; - List images = new ArrayList(); - - public MessageCenterComposingView(Fragment fragment, final MessageCenterComposingItem item, final MessageAdapter.OnListviewItemActionListener listener) { - super(fragment.getContext()); - - LayoutInflater inflater = fragment.getActivity().getLayoutInflater(); - View parentView = inflater.inflate(R.layout.apptentive_message_center_composing_area, this); - et = (EditText) parentView.findViewById(R.id.composing_et); - if (item.str_2 != null) { - et.setHint(item.str_2); - } - et.setLinksClickable(true); - et.setAutoLinkMask(Linkify.WEB_URLS | Linkify.PHONE_NUMBERS | Linkify.EMAIL_ADDRESSES | Linkify.MAP_ADDRESSES); - /* - * LinkMovementMethod would enable clickable links in EditView, but disables copy/paste through Long Press. - * Use a custom MovementMethod instead - * - */ - et.setMovementMethod(ApptentiveMovementMethod.getInstance()); - //If the edit text contains previous text with potential links - Linkify.addLinks(et, Linkify.WEB_URLS); - - et.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { - listener.beforeComposingTextChanged(charSequence); - } - - @Override - public void onTextChanged(CharSequence charSequence, int start, int before, int count) { - listener.onComposingTextChanged(charSequence); - } - - @Override - public void afterTextChanged(Editable editable) { - listener.afterComposingTextChanged(editable.toString()); - Linkify.addLinks(editable, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS | Linkify.EMAIL_ADDRESSES | Linkify.MAP_ADDRESSES); - } - }); - - - imageBandView = (ApptentiveImageGridView) parentView.findViewById(R.id.grid); - imageBandView.setupUi(); - imageBandView.setupLayoutListener(); - imageBandView.setListener(new ApptentiveImageGridView.ImageItemClickedListener() { - @Override - public void onClick(int position, ImageItem image) { - listener.onClickAttachment(position, image); - } - }); - imageBandView.setAdapterIndicator(R.drawable.apptentive_ic_remove_attachment); - - imageBandView.setImageIndicatorCallback((ImageGridViewAdapter.Callback) listener); - // Initialize image attachments band with empty data - clearImageAttachmentBand(); - } - - public EditText getEditText() { - return et; - } - - /** - * Remove all images from attchment band. - */ - public void clearImageAttachmentBand() { - imageBandView.setVisibility(View.GONE); - images.clear(); - - imageBandView.setData(images); - } - - /** - * Add new images to attchment band. - * - * @param imagesToAttach an array of new images to add - */ - public void addImagesToImageAttachmentBand(final List imagesToAttach) { - - if (imagesToAttach == null || imagesToAttach.size() == 0) { - return; - } - - imageBandView.setupLayoutListener(); - imageBandView.setVisibility(View.VISIBLE); - - images.addAll(imagesToAttach); - addAdditionalAttchItem(); - } - - /** - * Remove an image from attchment band. - * - * @param position the postion index of the image to be removed - */ - public void removeImageFromImageAttachmentBand(final int position) { - images.remove(position); - imageBandView.setupLayoutListener(); - if (images.size() == 0) { - // Hide attachment band after last attachment is removed - imageBandView.setVisibility(View.GONE); - return; - } - addAdditionalAttchItem(); - } - - private void addAdditionalAttchItem() { - ArrayList imagesToAdd = new ArrayList(images); - if (imagesToAdd.size() < getResources().getInteger(R.integer.apptentive_image_grid_default_attachments_total)) { - imagesToAdd.add(new ImageItem("", "", "Image/*", 0)); - } - imageBandView.setData(imagesToAdd); - } - /* - * Extends Android default movement method to enable selecting text and openning the links at the same time - */ - private static class ApptentiveMovementMethod extends ArrowKeyMovementMethod { - - private static ApptentiveMovementMethod sInstance; - - public static MovementMethod getInstance() { - if (sInstance == null) { - sInstance = new ApptentiveMovementMethod(); - } - return sInstance; - } - - @Override - public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { - int action = event.getAction(); - - if (action == MotionEvent.ACTION_UP || - action == MotionEvent.ACTION_DOWN) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - - ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); - - if (link.length != 0) { - if (action == MotionEvent.ACTION_UP) { - link[0].onClick(widget); - } else if (action == MotionEvent.ACTION_DOWN) { - Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0])); - } - - return true; - } - - } - - return super.onTouchEvent(widget, buffer, event); - } - - } -} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterGreetingView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterGreetingView.java deleted file mode 100644 index 4ff905d0a..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterGreetingView.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view; - -import android.content.Context; -import android.os.Handler; -import android.os.Message; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.TextView; - -import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterGreeting; -import com.apptentive.android.sdk.util.Util; - -public class MessageCenterGreetingView extends FrameLayout implements MessageCenterListItemView { - - public ApptentiveAvatarView avatar; - - // Prevent info button being clicked multiple time, resulting multiple About - protected static final int DELAY_TIME = 100; - - protected Handler mClickHandler = new Handler() { - - public void handleMessage(Message msg) { - - findViewById(msg.what).setClickable(true); - super.handleMessage(msg); - } - }; - - public MessageCenterGreetingView(final Context context, MessageCenterGreeting messageCenterGreeting) { - super(context); - - LayoutInflater inflater = LayoutInflater.from(context); - View view = inflater.inflate(R.layout.apptentive_message_center_greeting, this); - TextView title = (TextView) view.findViewById(R.id.greeting_title); - TextView body = (TextView) view.findViewById(R.id.greeting_body); - if (title != null) { - title.setText(messageCenterGreeting.title); - } - if (body != null) { - body.setText(messageCenterGreeting.body); - } - - avatar = (ApptentiveAvatarView) view.findViewById(R.id.avatar); - - ImageButton infoButton = (ImageButton) findViewById(R.id.btn_info); - infoButton.setOnClickListener(new OnClickListener() { - public void onClick(View view) { - view.setClickable(false); - mClickHandler.sendEmptyMessageDelayed(view.getId(), DELAY_TIME); - ApptentiveInternal.getInstance().showAboutInternal(Util.castContextToActivity(context), false); - } - }); - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterListView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterListView.java deleted file mode 100644 index 68a4395c9..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterListView.java +++ /dev/null @@ -1,455 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ -package com.apptentive.android.sdk.module.messagecenter.view; - -import android.content.Context; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.drawable.GradientDrawable; -import android.os.Parcelable; -import android.support.v4.content.ContextCompat; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.widget.AbsListView; - -import android.widget.HeaderViewListAdapter; -import android.widget.ListAdapter; -import android.widget.ListView; - -import com.apptentive.android.sdk.R; - - -public class MessageCenterListView extends ListView { - - public interface ApptentiveMessageCenterListAdapter extends ListAdapter { - /** - * True if views of given type will be sticky at the top - */ - boolean isItemSticky(int viewType); - } - - /** - * Wrapper class for sticky view and its position in the list. - */ - static class StickyWrapper { - public View view; - public int position; - public long id; - public int additionalIndent; - } - - private final Rect touchRect = new Rect(); - private final PointF touchPt = new PointF(); - private int touchSlop; - private View touchTarget; - private MotionEvent downEvent; - - // fields used for drawing shadow under the sticky header - private GradientDrawable shadowDrawable; - private int shadowHeight; - - // Optional delegating listener - OnScrollListener delegateScrollListener; - - // shadow for being recycled - StickyWrapper recycledHeaderView; - - /** - * shadow instance with a sticky view, can be null. - */ - StickyWrapper stickyWrapper; - - private OnListviewResizeListener resizeListener; - /** - * Scroll listener - */ - private final OnScrollListener mOnScrollListener = new OnScrollListener() { - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - if (delegateScrollListener != null) { - delegateScrollListener.onScrollStateChanged(view, scrollState); - } - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - - if (delegateScrollListener != null) { - delegateScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); - } - - ListAdapter adapter = getAdapter(); - if (adapter == null || visibleItemCount == 0) return; // nothing to do - - final boolean isFirstVisibleItemHeader = - isItemSticky(adapter, adapter.getItemViewType(firstVisibleItem)); - - if (isFirstVisibleItemHeader) { - View headerView = getChildAt(0); - int headerTop = headerView.getTop(); - int pad = getPaddingTop(); - if (headerTop == pad) { - // view sticks to the top, do not render shadow - destroyStickyShadow(); - } else { - tryCreateShadowAtPosition(firstVisibleItem, firstVisibleItem, visibleItemCount); - } - - } else { - // header is not at the first visible position - int headerPosition = findCurrentHeaderPosition(firstVisibleItem); - if (headerPosition > -1) { - tryCreateShadowAtPosition(headerPosition, firstVisibleItem, visibleItemCount); - } else { // there is no section for the first visible item, destroy shadow - destroyStickyShadow(); - } - } - } - - }; - - /** - * Default change observer. - */ - private final DataSetObserver dataSetObserver = new DataSetObserver() { - @Override - public void onChanged() { - recreateStickyShadow(); - } - - @Override - public void onInvalidated() { - recreateStickyShadow(); - } - }; - - public interface OnListviewResizeListener { - void OnListViewResize(int w, int h, int oldw, int oldh); - } - - public void setOnListViewResizeListener(OnListviewResizeListener l) { - resizeListener = l; - } - - public MessageCenterListView(Context context, AttributeSet attrs) { - super(context, attrs); - initView(); - } - - public MessageCenterListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initView(); - } - - private void initView() { - setOnScrollListener(mOnScrollListener); - touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - initShadow(true); - } - - - public void initShadow(boolean visible) { - if (visible) { - if (shadowDrawable == null) { - shadowDrawable = (GradientDrawable) ContextCompat.getDrawable(getContext(), R.drawable.apptentive_listview_item_shadow); - shadowHeight = (int) (4 * getResources().getDisplayMetrics().density); - } - } else { - if (shadowDrawable != null) { - shadowDrawable = null; - shadowHeight = 0; - } - } - } - - /** - * Create shadow wrapper with a sticky view at given position - */ - void createStickyShadow(int position) { - - // recycle shadow - StickyWrapper stickyViewShadow = recycledHeaderView; - recycledHeaderView = null; - - // create new shadow, if needed - if (stickyViewShadow == null) { - stickyViewShadow = new StickyWrapper(); - } - // request new view using recycled view, if such - View stickyView = getAdapter().getView(position, stickyViewShadow.view, MessageCenterListView.this); - - // read layout parameters - LayoutParams layoutParams = (LayoutParams) stickyView.getLayoutParams(); - if (layoutParams == null) { - layoutParams = (LayoutParams) generateDefaultLayoutParams(); - stickyView.setLayoutParams(layoutParams); - } - - View childLayout = ((ViewGroup) stickyView).getChildAt(0); - int heightMode = MeasureSpec.getMode(layoutParams.height); - int heightSize = MeasureSpec.getSize(layoutParams.height); - - if (heightMode == MeasureSpec.UNSPECIFIED) { - heightMode = MeasureSpec.EXACTLY; - } - - int maxHeight = getHeight() - getListPaddingTop() - getListPaddingBottom(); - if (heightSize > maxHeight) { - heightSize = maxHeight; - } - // assuming left and right additional paddings are the same - int ws = MeasureSpec.makeMeasureSpec(getWidth() - getListPaddingLeft() - getListPaddingRight(), MeasureSpec.EXACTLY); - int hs = MeasureSpec.makeMeasureSpec(heightSize, heightMode); - stickyView.measure(ws, hs); - stickyView.layout(0, 0, stickyView.getMeasuredWidth(), stickyView.getMeasuredHeight()); - - // initialize shadow - stickyViewShadow.view = stickyView; - stickyViewShadow.position = position; - stickyViewShadow.id = getAdapter().getItemId(position); - stickyViewShadow.additionalIndent = childLayout.getPaddingLeft(); - - stickyWrapper = stickyViewShadow; - } - - /** - * Destroy shadow wrapper for current sticky view - */ - void destroyStickyShadow() { - if (stickyWrapper != null) { - // keep shadow for being recycled later - recycledHeaderView = stickyWrapper; - stickyWrapper = null; - } - } - - /** - * Create sticky shadowded view at a given item position. - */ - void tryCreateShadowAtPosition(int headerPosition, int firstVisibleItem, int visibleItemCount) { - if (visibleItemCount < 1) { - // no need for creating shadow if no visible item - destroyStickyShadow(); - return; - } - - if (stickyWrapper != null - && stickyWrapper.position != headerPosition) { - // invalidate shadow, if required - destroyStickyShadow(); - } - - if (stickyWrapper == null) { - createStickyShadow(headerPosition); - } - - } - - - int findCurrentHeaderPosition(int fromPosition) { - ListAdapter adapter = getAdapter(); - - if (fromPosition >= adapter.getCount()) return -1; // dataset has changed, no candidate - - // Only need to look through to the next section item above - for (int position = fromPosition; position >= 0; position--) { - int viewType = adapter.getItemViewType(position); - if (isItemSticky(adapter, viewType)) return position; - } - return -1; - } - - void recreateStickyShadow() { - destroyStickyShadow(); - ListAdapter adapter = getAdapter(); - if (adapter != null && adapter.getCount() > 0) { - int firstVisiblePosition = getFirstVisiblePosition(); - int headerPosition = findCurrentHeaderPosition(firstVisiblePosition); - if (headerPosition == -1) { - return; - } - tryCreateShadowAtPosition(headerPosition, - firstVisiblePosition, getLastVisiblePosition() - firstVisiblePosition + 1); - } - } - - @Override - public void setOnScrollListener(OnScrollListener listener) { - if (listener == mOnScrollListener) { - super.setOnScrollListener(listener); - } else { - delegateScrollListener = listener; - } - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - super.onRestoreInstanceState(state); - post(new Runnable() { - @Override - public void run() { - // restore view after configuration change - recreateStickyShadow(); - } - }); - } - - @Override - public void setAdapter(ListAdapter adapter) { - // unregister observer at old adapter and register on new one - ListAdapter oldAdapter = getAdapter(); - if (oldAdapter != null) { - oldAdapter.unregisterDataSetObserver(dataSetObserver); - } - if (adapter != null) { - adapter.registerDataSetObserver(dataSetObserver); - } - - if (oldAdapter != adapter) { - destroyStickyShadow(); - } - - super.setAdapter(adapter); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - if (stickyWrapper != null) { - int parentWidth = r - l - getPaddingLeft() - getPaddingRight(); - int shadowWidth = stickyWrapper.view.getWidth(); - if (parentWidth != shadowWidth) { - recreateStickyShadow(); - } - } - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (resizeListener != null) { - resizeListener.OnListViewResize(w, h, oldw, oldh); - } - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - - if (stickyWrapper != null) { - - int pLeft = getListPaddingLeft(); - int pTop = getListPaddingTop(); - View view = stickyWrapper.view; - int headerTop = view.getTop(); - pLeft += stickyWrapper.additionalIndent; - // draw child - canvas.save(); - - int clipHeight = view.getHeight() + - (shadowDrawable == null ? 0 : shadowHeight); - canvas.clipRect(pLeft, pTop, pLeft + view.getWidth() - 2 * stickyWrapper.additionalIndent, pTop + clipHeight); - - canvas.translate(pLeft - stickyWrapper.additionalIndent, pTop - headerTop); - drawChild(canvas, stickyWrapper.view, getDrawingTime()); - - if (shadowDrawable != null) { - shadowDrawable.setBounds(stickyWrapper.view.getLeft(), - stickyWrapper.view.getBottom(), - stickyWrapper.view.getRight(), - stickyWrapper.view.getBottom() + shadowHeight); - shadowDrawable.draw(canvas); - } - - canvas.restore(); - } - } - - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - - final float x = ev.getX(); - final float y = ev.getY(); - final int action = ev.getAction(); - - if (action == MotionEvent.ACTION_DOWN - && touchTarget == null - && stickyWrapper != null - && isStickyViewTouched(stickyWrapper.view, x, y)) { - touchTarget = stickyWrapper.view; - touchPt.x = x; - touchPt.y = y; - - downEvent = MotionEvent.obtain(ev); - } - - if (touchTarget != null) { - if (isStickyViewTouched(touchTarget, x, y)) { - // forward event to header view - touchTarget.dispatchTouchEvent(ev); - } - - if (action == MotionEvent.ACTION_UP) { - super.dispatchTouchEvent(ev); - clearTouchTarget(); - - } else if (action == MotionEvent.ACTION_CANCEL) { - clearTouchTarget(); - - } else if (action == MotionEvent.ACTION_MOVE) { - if (Math.abs(y - touchPt.y) > touchSlop) { - - MotionEvent event = MotionEvent.obtain(ev); - event.setAction(MotionEvent.ACTION_CANCEL); - touchTarget.dispatchTouchEvent(event); - event.recycle(); - - super.dispatchTouchEvent(downEvent); - super.dispatchTouchEvent(ev); - clearTouchTarget(); - - } - } - - return true; - } - - return super.dispatchTouchEvent(ev); - } - - private boolean isStickyViewTouched(View view, float x, float y) { - view.getHitRect(touchRect); - - touchRect.bottom += getPaddingTop() - view.getTop(); - touchRect.left += getPaddingLeft(); - touchRect.right -= getPaddingRight(); - return touchRect.contains((int) x, (int) y); - } - - private void clearTouchTarget() { - touchTarget = null; - if (downEvent != null) { - downEvent.recycle(); - downEvent = null; - } - } - - - public static boolean isItemSticky(ListAdapter adapter, int viewType) { - if (adapter instanceof HeaderViewListAdapter) { - adapter = ((HeaderViewListAdapter) adapter).getWrappedAdapter(); - } - return ((ApptentiveMessageCenterListAdapter) adapter).isItemSticky(viewType); - } - -} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerView.java new file mode 100644 index 000000000..5903be6e0 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerView.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter.view; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; + + +public class MessageCenterRecyclerView extends RecyclerView { + + public MessageCenterRecyclerView(Context context) { + super(context); + } + + public MessageCenterRecyclerView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public MessageCenterRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + + + + + public int getFirstVisiblePosition() { + return 0; // TODO + } + + public int getLastVisiblePosition() { + return 0; // TODO + } + + public void setSelection(int selection) { + return; // TODO + } + + public void setSelectionFromTop(int selection, int top) { + return; // TODO + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java new file mode 100644 index 000000000..e16cc4702 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterRecyclerViewAdapter.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter.view; + +import android.os.AsyncTask; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.ApptentiveLog; +import com.apptentive.android.sdk.R; +import com.apptentive.android.sdk.module.engagement.EngagementModule; +import com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment; +import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction; +import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction; +import com.apptentive.android.sdk.module.messagecenter.MessageManager; +import com.apptentive.android.sdk.module.messagecenter.OnListviewItemActionListener; +import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; +import com.apptentive.android.sdk.module.messagecenter.model.Composer; +import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.module.messagecenter.model.ContextMessage; +import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterGreeting; +import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem; +import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterStatus; +import com.apptentive.android.sdk.module.messagecenter.model.WhoCard; +import com.apptentive.android.sdk.module.messagecenter.view.holder.AutomatedMessageHolder; +import com.apptentive.android.sdk.module.messagecenter.view.holder.ContextMessageHolder; +import com.apptentive.android.sdk.module.messagecenter.view.holder.GreetingHolder; +import com.apptentive.android.sdk.module.messagecenter.view.holder.IncomingCompoundMessageHolder; +import com.apptentive.android.sdk.module.messagecenter.view.holder.MessageComposerHolder; +import com.apptentive.android.sdk.module.messagecenter.view.holder.OutgoingCompoundMessageHolder; +import com.apptentive.android.sdk.module.messagecenter.view.holder.StatusHolder; +import com.apptentive.android.sdk.module.messagecenter.view.holder.WhoCardHolder; +import com.apptentive.android.sdk.util.image.ImageItem; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.GREETING; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_AUTO; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_COMPOSER; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_CONTEXT; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_INCOMING; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_OUTGOING; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.STATUS; +import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.WHO_CARD; + +public class MessageCenterRecyclerViewAdapter extends RecyclerView.Adapter { + + MessageCenterFragment fragment; + OnListviewItemActionListener listener; + RecyclerView recyclerView; + Interaction interaction; + List listItems; + // maps to prevent redundant asynctasks + private ArrayList messagesWithPendingReadStatusUpdate = new ArrayList(); + + public MessageCenterRecyclerViewAdapter(MessageCenterFragment fragment, OnListviewItemActionListener listener, Interaction interaction, List listItems) { + this.fragment = fragment; + this.listener = listener; + this.interaction = interaction; + this.listItems = listItems; + } + + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + this.recyclerView = recyclerView; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case MESSAGE_COMPOSER: { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.apptentive_message_center_composer, parent, false); + return new MessageComposerHolder(view); + } + case STATUS: { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.apptentive_message_center_status, parent, false); + return new StatusHolder(view); + } + case GREETING: { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.apptentive_message_center_greeting, parent, false); + return new GreetingHolder(view); + } + case MESSAGE_OUTGOING: { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.apptentive_message_outgoing, parent, false); + return new OutgoingCompoundMessageHolder(view); + } + case MESSAGE_INCOMING: { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.apptentive_message_incoming, parent, false); + return new IncomingCompoundMessageHolder(view); + } + case MESSAGE_AUTO: { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.apptentive_message_auto, parent, false); + return new AutomatedMessageHolder(view); + } + case WHO_CARD: { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.apptentive_message_center_who_card, parent, false); + return new WhoCardHolder(this, view); + } + case MESSAGE_CONTEXT: { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.apptentive_message_center_context_message, parent, false); + return new ContextMessageHolder(view); + } + } + ApptentiveLog.w("onCreateViewHolder(%d) returning null.", viewType); + return null; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (getItemViewType(position)) { + case MESSAGE_COMPOSER: { + Composer composer = (Composer) listItems.get(position); + MessageComposerHolder composerHolder = (MessageComposerHolder) holder; + composerHolder.bindView(fragment, this, composer); + break; + } + case STATUS: { + MessageCenterStatus status = (MessageCenterStatus) listItems.get(position); + StatusHolder statusHolder = (StatusHolder) holder; + statusHolder.body.setText(status.body); + if (status.icon != null) { + statusHolder.icon.setImageResource(status.icon); + statusHolder.icon.setVisibility(View.VISIBLE); + } else { + statusHolder.icon.setVisibility(View.GONE); + } + break; + } + case GREETING: { + MessageCenterGreeting greeting = (MessageCenterGreeting) listItems.get(position); + GreetingHolder greetingHolder = (GreetingHolder) holder; + greetingHolder.bindView(greeting); + break; + } + case MESSAGE_INCOMING: { + CompoundMessage compoundMessage = (CompoundMessage) listItems.get(position); + IncomingCompoundMessageHolder compoundHolder = (IncomingCompoundMessageHolder) holder; + compoundHolder.bindView(fragment, recyclerView, this, compoundMessage); + // Mark as read + if (!compoundMessage.isRead() && !messagesWithPendingReadStatusUpdate.contains(compoundMessage)) { + messagesWithPendingReadStatusUpdate.add(compoundMessage); + startUpdateUnreadMessageTask(compoundMessage); + } + break; + } + case MESSAGE_OUTGOING: { + CompoundMessage compoundMessage = (CompoundMessage) listItems.get(position); + OutgoingCompoundMessageHolder compoundHolder = (OutgoingCompoundMessageHolder) holder; + compoundHolder.bindView(fragment, recyclerView, this, compoundMessage); + break; + } + case MESSAGE_AUTO: { + CompoundMessage autoMessage = (CompoundMessage) listItems.get(position); + AutomatedMessageHolder autoHolder = (AutomatedMessageHolder) holder; + autoHolder.bindView(recyclerView, autoMessage); + break; + } + case WHO_CARD: { + WhoCard whoCard = (WhoCard) listItems.get(position); + WhoCardHolder whoCardHolder = (WhoCardHolder) holder; + whoCardHolder.bindView(recyclerView, whoCard); + break; + } + case MESSAGE_CONTEXT: { + ContextMessage contextMessage = (ContextMessage) listItems.get(position); + ContextMessageHolder contextMessageHolder = (ContextMessageHolder) holder; + contextMessageHolder.bindView(contextMessage); + break; + } + } + } + + @Override + public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { + super.onViewAttachedToWindow(holder); + if (holder instanceof MessageComposerHolder) { + MessageComposerHolder composer = (MessageComposerHolder) holder; + composer.onViewAttachedToWindow(); + } + } + + @Override + public int getItemCount() { + return listItems.size(); + } + + @Override + public int getItemViewType(int position) { + MessageCenterListItem message = listItems.get(position); + return message.getListItemType(); + } + + public String getWhoCardAvatarFileName() { + return null; // TODO + } + + public void addImagestoComposer(MessageComposerHolder composer, List images) { + composer.addImagesToImageAttachmentBand(images); + composer.setSendButtonState(); + } + + public void removeImageFromComposer(MessageComposerHolder composer, int position) { + if (composer != null) { + composer.removeImageFromImageAttachmentBand(position); + composer.setSendButtonState(); + } + } + + public OnListviewItemActionListener getListener() { + return listener; + } + + private void startUpdateUnreadMessageTask(CompoundMessage message) { + UpdateUnreadMessageTask task = new UpdateUnreadMessageTask(message); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); + } + + private class UpdateUnreadMessageTask extends AsyncTask { + private ApptentiveMessage message; + + public UpdateUnreadMessageTask(ApptentiveMessage message) { + this.message = message; + } + + @Override + protected Void doInBackground(ApptentiveMessage... messages) { + messages[0].setRead(true); + JSONObject data = new JSONObject(); + try { + data.put("message_id", messages[0].getId()); + data.put("message_type", messages[0].getType().name()); + } catch (JSONException e) { + // + } + EngagementModule.engageInternal(fragment.getContext(), interaction, MessageCenterInteraction.EVENT_NAME_READ, data.toString()); + + MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager(); + if (mgr != null) { + mgr.updateMessage(messages[0]); + mgr.notifyHostUnreadMessagesListeners(mgr.getUnreadMessageCount()); + } + return null; + } + + @Override + protected void onCancelled() { + messagesWithPendingReadStatusUpdate.remove(message); + } + + @Override + protected void onPostExecute(Void result) { + messagesWithPendingReadStatusUpdate.remove(message); + } + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterStatusView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterStatusView.java deleted file mode 100644 index 22e3d6bad..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterStatusView.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import com.apptentive.android.sdk.R; - -/** - * @author Barry Li - */ -public class MessageCenterStatusView extends FrameLayout implements MessageCenterListItemView { - - public TextView bodyTextView; - public ImageView iconImageView; - - public MessageCenterStatusView(Context context) { - super(context); - - LayoutInflater inflater = LayoutInflater.from(context); - View view = inflater.inflate(R.layout.apptentive_message_center_status, this); - bodyTextView = (TextView) view.findViewById(R.id.body); - iconImageView = (ImageView) view.findViewById(R.id.icon); - } - -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterWhoCardView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterWhoCardView.java deleted file mode 100644 index 71ba02d69..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/MessageCenterWhoCardView.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view; - - -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.Fragment; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.TextView; - - -import com.apptentive.android.sdk.Apptentive; -import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterComposingItem; -import com.apptentive.android.sdk.util.Util; - - -/** - * @author Barry Li - */ -public class MessageCenterWhoCardView extends FrameLayout implements MessageCenterListItemView { - - private MessageAdapter.OnListviewItemActionListener listener; - private EditText emailEditText; - private TextInputLayout emailLayout; - private EditText nameEditText; - private TextInputLayout nameLayout; - private TextView title; - private TextView emailExplanation; - private Button skipButton; - private Button sendButton; - - public MessageCenterWhoCardView(final Fragment fragment, final MessageAdapter.OnListviewItemActionListener listener) { - super(fragment.getContext()); - this.listener = listener; - - LayoutInflater inflater = fragment.getActivity().getLayoutInflater(); - View parentView = inflater.inflate(R.layout.apptentive_message_center_who_card, this); - title = (TextView) parentView.findViewById(R.id.who_title); - - sendButton = (Button) parentView.findViewById(R.id.btn_send); - - emailEditText = (EditText) parentView.findViewById(R.id.who_email); - emailLayout = (TextInputLayout) parentView.findViewById(R.id.input_layout_who_email); - - nameEditText = (EditText) parentView.findViewById(R.id.who_name); - nameLayout = (TextInputLayout) parentView.findViewById(R.id.input_layout_who_name); - - emailExplanation = (TextView) parentView.findViewById(R.id.email_explanation); - - skipButton = (Button) parentView.findViewById(R.id.btn_skip); - - } - - /** - * Update Who Card UI under different scenarios, defined in {@link com.apptentive.android.sdk.module.messagecenter.model.MessageCenterComposingItem} - * - * - * @param item The generic object containing the data to update composing view. - * @param name Stored profile name, maybe null - * @param email Stored profile email, maybe null - * @return - */ - public void updateUi(final MessageCenterComposingItem item, String name, String email) { - if (item.str_1 != null) { - title.setText(item.str_1); - } - if (item.str_2 != null) { - nameLayout.setVisibility(VISIBLE); - nameLayout.setHint(item.str_2); - } else { - nameLayout.setVisibility(GONE); - } - if (item.str_3 != null) { - emailLayout.setHint(item.str_3); - } - - if (item.str_4 != null) { - emailExplanation.setVisibility(VISIBLE); - emailExplanation.setText(item.str_4); - } else { - emailExplanation.setVisibility(GONE); - } - - skipButton.setEnabled(true); - skipButton.setActivated(true); - if (item.button_1 == null) { - skipButton.setVisibility(INVISIBLE); - } else { - skipButton.setVisibility(VISIBLE); - skipButton.setText(item.button_1); - skipButton.setOnClickListener(new OnClickListener() { - public void onClick(View view) { - listener.onCloseWhoCard(item.button_1); - } - }); - } - - if (item.button_2 != null) { - sendButton.setText(item.button_2); - sendButton.setActivated(true); - sendButton.setOnClickListener(new OnClickListener() { - public void onClick(View view) { - if (isWhoCardContentValid(item.getType())) { - Apptentive.setPersonEmail(emailEditText.getText().toString().trim()); - Apptentive.setPersonName(nameEditText.getText().toString().trim()); - listener.onSubmitWhoCard(item.button_2); - } - } - }); - } - - nameEditText.setText(name); - - TextWatcher emailTextWatcher = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence existingContent, int i, int i2, int i3) { - // Disable send button when the content hasn't change yet - if (Util.isEmailValid(existingContent.toString())) { - sendButton.setEnabled(true); - } else { - sendButton.setEnabled(false); - } - } - - @Override - public void onTextChanged(CharSequence charSequence, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable editable) { - String emailContent = editable.toString().trim(); - if (Util.isEmailValid(emailContent)) { - // email must be in valid format after the change. If it is, enable send button - sendButton.setEnabled(true); - } else - // Allow user remove email completely when editing prifle of "Email Requested" - if (TextUtils.isEmpty(emailContent) && item.getType() >= MessageCenterComposingItem.COMPOSING_ITEM_WHOCARD_REQUESTED_INIT) { - sendButton.setEnabled(true); - } else { - // email not valid after change, so disable the send button - sendButton.setEnabled(false); - } - } - }; - - emailEditText.addTextChangedListener(emailTextWatcher); - emailEditText.setText(email); - - } - - public EditText getNameField() { - return nameEditText; - } - - public EditText getEmailField() { - return emailEditText; - } - - private boolean isWhoCardContentValid(int type) { - String emailContent = emailEditText.getText().toString(); - if (Util.isEmailValid(emailContent)) { - return true; - } - // Allow user only change name but leave email blank if profile is only requested, not required - if (TextUtils.isEmpty(emailContent) && type >= MessageCenterComposingItem.COMPOSING_ITEM_WHOCARD_REQUESTED_INIT) { - return true; - } - return false; - } - -} \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/PersonalMessageView.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/PersonalMessageView.java deleted file mode 100644 index 32913e624..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/PersonalMessageView.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view; - -import android.content.Context; -import android.view.LayoutInflater; - -import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage; - -/** - * @author Sky Kelsey - */ -abstract public class PersonalMessageView extends MessageView { - - public PersonalMessageView(Context context, final T message) { - super(context, message); - } - - /** - * Perform any view initialization here. Make sure to call super.init() first to initialise the parent hierarchy. - */ - protected void init(Context context, T message) { - super.init(context, message); - LayoutInflater inflater = LayoutInflater.from(context); - if (message.isOutgoingMessage()) { - inflater.inflate(R.layout.apptentive_message_outgoing, this); - } else { - inflater.inflate(R.layout.apptentive_message_incoming, this); - } - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/ZeroMinSizeDrawable.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/ZeroMinSizeDrawable.java deleted file mode 100644 index b23a864f4..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/ZeroMinSizeDrawable.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2012, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view; - -import android.content.res.Resources; -import android.graphics.drawable.BitmapDrawable; - -/** - * This class is a hack that lets you tile a bitmap that is actually larger in either dimension than the view it will - * tile into. - * @author Sky Kelsey - */ -public class ZeroMinSizeDrawable extends BitmapDrawable { - - public ZeroMinSizeDrawable(Resources res, int resId) { - super(res, ((BitmapDrawable)res.getDrawable(resId)).getBitmap()); - } - - public ZeroMinSizeDrawable(Resources res, BitmapDrawable drawable) { - super(res, drawable.getBitmap()); - } - - @Override - public int getMinimumWidth() { - return 0; - } - - @Override - public int getMinimumHeight() { - return 0; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/AutomatedMessageHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/AutomatedMessageHolder.java index 0e63ef883..df6bdc28b 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/AutomatedMessageHolder.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/AutomatedMessageHolder.java @@ -1,35 +1,27 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.module.messagecenter.view.holder; +import android.support.v7.widget.RecyclerView; +import android.view.View; import android.widget.TextView; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; -import com.apptentive.android.sdk.module.messagecenter.view.AutomatedMessageView; -/** - * @author Barry Li - */ -public class AutomatedMessageHolder extends MessageHolder { +public class AutomatedMessageHolder extends RecyclerView.ViewHolder { public TextView body; - public AutomatedMessageHolder(AutomatedMessageView view) { - super(view); - body = (TextView) view.findViewById(R.id.apptentive_message_auto_body); + public AutomatedMessageHolder(View itemView) { + super(itemView); + body = (TextView) itemView.findViewById(R.id.apptentive_message_auto_body); } - public void updateMessage(String dateStamp, final CompoundMessage newMessage) { - super.updateMessage(dateStamp, 0, null); - body.post(new Runnable() { - @Override - public void run() { - body.setText(newMessage.getBody()); - } - }); + public void bindView(final RecyclerView parent, final CompoundMessage message) { + body.setText(message.getBody()); } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/ContextMessageHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/ContextMessageHolder.java new file mode 100644 index 000000000..f1f1af45a --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/ContextMessageHolder.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter.view.holder; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import com.apptentive.android.sdk.R; +import com.apptentive.android.sdk.module.messagecenter.model.ContextMessage; + +public class ContextMessageHolder extends RecyclerView.ViewHolder { + + private TextView bodyTextView; + + public ContextMessageHolder(View itemView) { + super(itemView); + bodyTextView = (TextView) itemView.findViewById(R.id.body); + } + + public void bindView(ContextMessage contextMessage) { + bodyTextView.setText(contextMessage.getBody()); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/GreetingHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/GreetingHolder.java new file mode 100644 index 000000000..6caa7937d --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/GreetingHolder.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter.view.holder; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ImageButton; +import android.widget.TextView; + +import com.apptentive.android.sdk.ApptentiveInternal; +import com.apptentive.android.sdk.R; +import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterGreeting; +import com.apptentive.android.sdk.module.messagecenter.view.ApptentiveAvatarView; +import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.image.ImageUtil; + + +public class GreetingHolder extends RecyclerView.ViewHolder { + + public TextView title; + public TextView body; + public ApptentiveAvatarView avatar; + public ImageButton infoButton; + + public GreetingHolder(View itemView) { + super(itemView); + + title = (TextView) itemView.findViewById(R.id.greeting_title); + body = (TextView) itemView.findViewById(R.id.greeting_body); + avatar = (ApptentiveAvatarView) itemView.findViewById(R.id.avatar); + infoButton = (ImageButton) itemView.findViewById(R.id.btn_info); + } + + public void bindView(MessageCenterGreeting greeting) { + title.setText(greeting.title); + body.setText(greeting.body); + ImageUtil.startDownloadAvatarTask(avatar, greeting.avatar); + infoButton.setOnClickListener(new View.OnClickListener() { + public void onClick(final View view) { + // Don't let the info button open multiple copies of the About page. + view.setClickable(false); + view.postDelayed(new Runnable() { + @Override + public void run() { + view.setClickable(true); + } + }, 300); + ApptentiveInternal.getInstance().showAboutInternal(Util.castContextToActivity(itemView.getContext()), false); + } + }); + infoButton.setClickable(true); + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/HolderFactory.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/HolderFactory.java deleted file mode 100644 index e54bc1d67..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/HolderFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view.holder; - -import com.apptentive.android.sdk.module.messagecenter.view.AutomatedMessageView; -import com.apptentive.android.sdk.module.messagecenter.view.CompoundMessageView; -import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterListItemView; -import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterStatusView; - -/** - * @author Sky Kelsey - */ -public class HolderFactory { - - public static MessageCenterListItemHolder createHolder(MessageCenterListItemView messageCenterListItemView) { - MessageCenterListItemHolder holder = null; - if (messageCenterListItemView instanceof CompoundMessageView) { - CompoundMessageView textMessageView = (CompoundMessageView) messageCenterListItemView; - holder = (textMessageView.isViewShowingOutgoingMessage())? new OutgoingCompoundMessageHolder(textMessageView) : - new IncomingCompoundMessageHolder(textMessageView); - } else if (messageCenterListItemView instanceof AutomatedMessageView) { - AutomatedMessageView automatedView = (AutomatedMessageView) messageCenterListItemView; - holder = new AutomatedMessageHolder(automatedView); - } else if (messageCenterListItemView instanceof MessageCenterStatusView) { - MessageCenterStatusView messageCenterStatusView = (MessageCenterStatusView) messageCenterListItemView; - holder = new StatusHolder(messageCenterStatusView); - } - return holder; - } -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java index 1a88446db..3302f5b1a 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/IncomingCompoundMessageHolder.java @@ -1,90 +1,92 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.module.messagecenter.view.holder; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.View; +import android.view.accessibility.AccessibilityManager; import android.widget.TextView; import com.apptentive.android.sdk.R; - import com.apptentive.android.sdk.model.StoredFile; +import com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment; +import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; import com.apptentive.android.sdk.module.messagecenter.view.ApptentiveAvatarView; -import com.apptentive.android.sdk.module.messagecenter.view.CompoundMessageView; -import com.apptentive.android.sdk.module.messagecenter.view.MessageAdapter; +import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter; import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.image.ApptentiveImageGridView; import com.apptentive.android.sdk.util.image.ImageItem; +import com.apptentive.android.sdk.util.image.ImageUtil; import java.io.File; import java.util.ArrayList; import java.util.List; -/** - * @author Barry Li - */ +import static android.content.Context.ACCESSIBILITY_SERVICE; + public class IncomingCompoundMessageHolder extends MessageHolder { public ApptentiveAvatarView avatar; + private View root; + private View container; private TextView messageBodyView; private TextView nameView; private ApptentiveImageGridView imageBandView; - private MessageAdapter.OnListviewItemActionListener listener; - public IncomingCompoundMessageHolder(CompoundMessageView view) { - super(view); - avatar = (ApptentiveAvatarView) view.findViewById(R.id.avatar); - nameView = (TextView) view.findViewById(R.id.sender_name); - messageBodyView = (TextView) view.findViewById(R.id.apptentive_compound_message_body); - imageBandView =(ApptentiveImageGridView) view.findViewById(R.id.grid); - listener = view.getListener(); - } + private static final boolean loadAvatar = false; - public void updateMessage(final String name, final String datestamp, final String text, final int viewWidth, final int desiredColumn, final List imagesToAttach) { - super.updateMessage(datestamp, 0, null); + public IncomingCompoundMessageHolder(View itemView) { + super(itemView); + root = itemView.findViewById(R.id.message_root); + container = itemView.findViewById(R.id.apptentive_compound_message_body_container); + avatar = (ApptentiveAvatarView) itemView.findViewById(R.id.avatar); + nameView = (TextView) itemView.findViewById(R.id.sender_name); + messageBodyView = (TextView) itemView.findViewById(R.id.apptentive_compound_message_body); + imageBandView = (ApptentiveImageGridView) itemView.findViewById(R.id.grid); + } - if (messageBodyView != null) { - messageBodyView.post(new Runnable() { - @Override - public void run() { - messageBodyView.setText(text); - } - }); + public void bindView(MessageCenterFragment fragment, final RecyclerView parent, final MessageCenterRecyclerViewAdapter adapter, final CompoundMessage message) { + super.bindView(fragment, parent, message); + imageBandView.setupUi(); + if (loadAvatar) { + ImageUtil.startDownloadAvatarTask(avatar, message.getSenderProfilePhoto()); } - if (nameView != null) { - if (name != null && !name.isEmpty()) { - nameView.post(new Runnable() { - @Override - public void run() { - nameView.setVisibility(View.VISIBLE); - nameView.setText(name); - } - }); - } else { - nameView.post(new Runnable() { - @Override - public void run() { - nameView.setVisibility(View.GONE); - } - }); - } + int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); + root.measure(widthMeasureSpec, 0); + int viewWidth = container.getMeasuredWidth(); + + messageBodyView.setText(message.getBody()); + // We have to disable text selection, or the Google TalkBack won't read this unless it's selected. It's too tiny to select by itself easily. + AccessibilityManager accessibilityManager = (AccessibilityManager) fragment.getContext().getSystemService(ACCESSIBILITY_SERVICE); + if (accessibilityManager != null) { + boolean talkbackNotEnabled = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty(); + messageBodyView.setTextIsSelectable(talkbackNotEnabled); + } + String name = message.getSenderUsername(); + if (name != null && !name.isEmpty()) { + nameView.setVisibility(View.VISIBLE); + nameView.setText(name); + } else { + nameView.setVisibility(View.GONE); } - // Set up attahments view + final List files = message.getRemoteAttachments(); if (imageBandView != null) { - if (imagesToAttach == null || imagesToAttach.size() == 0) { + if (files == null || files.size() == 0) { imageBandView.setVisibility(View.GONE); } else { imageBandView.setVisibility(View.VISIBLE); - imageBandView.setAdapterItemSize(viewWidth, desiredColumn); + imageBandView.setAdapterItemSize(viewWidth, itemView.getResources().getInteger(R.integer.apptentive_image_grid_default_column_number_incoming)); List images = new ArrayList(); final File cacheDir = Util.getDiskCacheDir(imageBandView.getContext()); - for (StoredFile file: imagesToAttach) { + for (StoredFile file : files) { String thumbnailUrl = file.getSourceUriOrPath(); String remoteUrl = file.getApptentiveUri(); String thumbnailStorageFilePath; @@ -100,15 +102,14 @@ public void run() { imageBandView.setListener(new ApptentiveImageGridView.ImageItemClickedListener() { @Override public void onClick(int position, ImageItem image) { - StoredFile file = imagesToAttach.get(position); + StoredFile file = files.get(position); String remoteUrl = file.getApptentiveUri(); String localFilePath = Util.generateCacheFileFullPath(remoteUrl, cacheDir); - if (listener != null) { - listener.onClickAttachment(position, new ImageItem(remoteUrl, localFilePath, file.getMimeType(), file.getCreationTime())); + if (adapter.getListener() != null) { + adapter.getListener().onClickAttachment(position, new ImageItem(remoteUrl, localFilePath, file.getMimeType(), file.getCreationTime())); } } }); - } } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageCenterListItemHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageCenterListItemHolder.java deleted file mode 100644 index 652d35464..000000000 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageCenterListItemHolder.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. - * Please refer to the LICENSE file for the terms and conditions - * under which redistribution and use of this file is permitted. - */ - -package com.apptentive.android.sdk.module.messagecenter.view.holder; - -/** - * @author Sky Kelsey - */ -public class MessageCenterListItemHolder { - public int position = -1; -} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageComposerHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageComposerHolder.java new file mode 100644 index 000000000..8e42a781a --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageComposerHolder.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter.view.holder; + +import android.content.res.ColorStateList; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.content.ContextCompat; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.util.Linkify; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.TextView; + +import com.apptentive.android.sdk.R; +import com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment; +import com.apptentive.android.sdk.module.messagecenter.model.Composer; +import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter; +import com.apptentive.android.sdk.util.Constants; +import com.apptentive.android.sdk.util.Util; +import com.apptentive.android.sdk.util.image.ApptentiveImageGridView; +import com.apptentive.android.sdk.util.image.ImageItem; +import com.apptentive.android.sdk.view.ApptentiveAlertDialog; + +import java.util.ArrayList; +import java.util.List; + +public class MessageComposerHolder extends RecyclerView.ViewHolder { + + private List images; + + private ImageButton closeButton; + private TextView title; + private ImageButton attachButton; + private ImageButton sendButton; + public EditText message; + private ApptentiveImageGridView attachments; + + private TextWatcher textWatcher; + + private int maxAllowedAttachments; + + public MessageComposerHolder(View itemView) { + super(itemView); + images = new ArrayList(); + closeButton = (ImageButton) itemView.findViewById(R.id.close_button); + title = (TextView) itemView.findViewById(R.id.title); + attachButton = (ImageButton) itemView.findViewById(R.id.attach_button); + sendButton = (ImageButton) itemView.findViewById(R.id.send_button); + message = (EditText) itemView.findViewById(R.id.message); + attachments = (ApptentiveImageGridView) itemView.findViewById(R.id.attachments); + + maxAllowedAttachments = itemView.getResources().getInteger(R.integer.apptentive_image_grid_default_attachments_total); + + ColorStateList colors = ContextCompat.getColorStateList(itemView.getContext(), Util.getResourceIdFromAttribute(itemView.getContext().getTheme(), R.attr.apptentiveButtonTintColorStateList)); + // Use a color state list for button tint state on Lollipop. On prior platforms, need to apply state color manually. + Drawable closeButtonDrawable = DrawableCompat.wrap(closeButton.getDrawable()); + DrawableCompat.setTintList(closeButtonDrawable, colors); + closeButton.setImageDrawable(closeButtonDrawable); + // Use a color state list for button tint state on Lollipop. On prior platforms, need to apply state color manually. + Drawable sendButtonDrawable = DrawableCompat.wrap(sendButton.getDrawable()); + DrawableCompat.setTintList(sendButtonDrawable, colors); + sendButton.setImageDrawable(sendButtonDrawable); + // Use a color state list for button tint state on Lollipop. On prior platforms, need to apply state color manually. + Drawable attachButtonDrawable = DrawableCompat.wrap(attachButton.getDrawable()); + DrawableCompat.setTintList(attachButtonDrawable, colors); + attachButton.setImageDrawable(attachButtonDrawable); + } + + public void bindView(final MessageCenterFragment fragment, final MessageCenterRecyclerViewAdapter adapter, final Composer composer) { + title.setText(composer.title); + + + closeButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + if (!TextUtils.isEmpty(message.getText().toString().trim()) || !images.isEmpty()) { + Bundle bundle = new Bundle(); + bundle.putString("message", composer.closeBody); + bundle.putString("positive", composer.closeDiscard); + bundle.putString("negative", composer.closeCancel); + ApptentiveAlertDialog.show(fragment, bundle, Constants.REQUEST_CODE_CLOSE_COMPOSING_CONFIRMATION); + } else { + if (adapter.getListener() != null) { + adapter.getListener().onCancelComposing(); + } + } + } + }); + + sendButton.setContentDescription(composer.sendButton); + sendButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + if (adapter.getListener() != null) { + adapter.getListener().onFinishComposing(); + } + } + }); + + message.setHint(composer.messageHint); + + message.removeTextChangedListener(textWatcher); + textWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + if (adapter.getListener() != null) { + adapter.getListener().beforeComposingTextChanged(charSequence); + } + } + + @Override + public void onTextChanged(CharSequence charSequence, int start, int before, int count) { + if (adapter.getListener() != null) { + adapter.getListener().onComposingTextChanged(charSequence); + } + } + + @Override + public void afterTextChanged(Editable editable) { + if (adapter.getListener() != null) { + adapter.getListener().afterComposingTextChanged(editable.toString()); + } + Linkify.addLinks(editable, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS | Linkify.EMAIL_ADDRESSES | Linkify.MAP_ADDRESSES); + } + }; + message.addTextChangedListener(textWatcher); + + attachButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + if (adapter.getListener() != null) { + adapter.getListener().onAttachImage(); + } + } + }); + + + attachments.setupUi(); + attachments.setupLayoutListener(); + attachments.setListener(new ApptentiveImageGridView.ImageItemClickedListener() { + @Override + public void onClick(int position, ImageItem image) { + if (adapter.getListener() != null) { + adapter.getListener().onClickAttachment(position, image); + } + } + }); + attachments.setAdapterIndicator(R.drawable.apptentive_ic_remove_attachment); + + attachments.setImageIndicatorCallback(fragment); + //Initialize image attachments band with empty data + clearImageAttachmentBand(); + attachments.setVisibility(View.GONE); + attachments.setData(new ArrayList()); + setAttachButtonState(); + + if (adapter.getListener() != null) { + adapter.getListener().onComposingViewCreated(this, message, attachments); + } + } + + /** + * Workaround for this issue: https://code.google.com/p/android/issues/detail?id=208169 + */ + public void onViewAttachedToWindow() { + message.setEnabled(false); + message.setEnabled(true); + } + + /** + * Remove all images from attachment band. + */ + public void clearImageAttachmentBand() { + attachments.setVisibility(View.GONE); + images.clear(); + attachments.setData(null); + } + + /** + * Add new images to attachment band. + * + * @param imagesToAttach an array of new images to add + */ + public void addImagesToImageAttachmentBand(final List imagesToAttach) { + if (imagesToAttach == null || imagesToAttach.size() == 0) { + return; + } + attachments.setupLayoutListener(); + attachments.setVisibility(View.VISIBLE); + images.addAll(imagesToAttach); + setAttachButtonState(); + addAdditionalAttachItem(); + attachments.notifyDataSetChanged(); + } + + /** + * Remove an image from attachment band. + * + * @param position the postion index of the image to be removed + */ + public void removeImageFromImageAttachmentBand(final int position) { + images.remove(position); + attachments.setupLayoutListener(); + setAttachButtonState(); + if (images.size() == 0) { + // Hide attachment band after last attachment is removed + attachments.setVisibility(View.GONE); + return; + } + addAdditionalAttachItem(); + } + + private void addAdditionalAttachItem() { + ArrayList imagesToAdd = new ArrayList(images); + if (imagesToAdd.size() < maxAllowedAttachments) { + imagesToAdd.add(new ImageItem("", "", "Image/*", 0)); + } + attachments.setData(imagesToAdd); + } + + public void setAttachButtonState() { + boolean enabled = images.size() < maxAllowedAttachments; + setButtonState(attachButton, enabled); + } + + public void setSendButtonState() { + boolean enabled = !TextUtils.isEmpty(message.getText().toString().trim()) || !images.isEmpty(); + setButtonState(sendButton, enabled); + } + + public void setButtonState(ImageButton button, boolean enabled) { + button.setEnabled(enabled); + if (enabled) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + button.setColorFilter(Util.getThemeColor(itemView.getContext(), R.attr.apptentiveButtonTintColor)); + } + } else { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + button.setColorFilter(Util.getThemeColor(itemView.getContext(), R.attr.apptentiveButtonTintColorDisabled)); + } + } + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageHolder.java index 703784235..fef57d5a4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageHolder.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/MessageHolder.java @@ -1,50 +1,31 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.module.messagecenter.view.holder; +import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.View; import android.widget.TextView; import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.view.MessageView; +import com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment; +import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; -/** - * @author Sky Kelsey - */ -public class MessageHolder extends MessageCenterListItemHolder { +public abstract class MessageHolder extends RecyclerView.ViewHolder { public TextView datestamp; - public TextView status; - - public MessageHolder(MessageView view) { - datestamp = (TextView) view.findViewById(R.id.datestamp); - status = (TextView) view.findViewById(R.id.status); + public MessageHolder(View itemView) { + super(itemView); + datestamp = (TextView) itemView.findViewById(R.id.datestamp); } - public void updateMessage(final String datestampString, final int statusColor, final String statusString) { - if (datestamp != null) { - datestamp.post(new Runnable() { - @Override - public void run() { - datestamp.setText(datestampString); - datestamp.setVisibility(!TextUtils.isEmpty(datestampString) ? View.VISIBLE : View.GONE); - } - }); - } - if (status != null) { - status.post(new Runnable() { - @Override - public void run() { - status.setText(statusString); - status.setTextColor(statusColor); - status.setVisibility(!TextUtils.isEmpty(statusString) ? View.VISIBLE : View.GONE); - } - }); - } + public void bindView(MessageCenterFragment fragment, RecyclerView recyclerView, CompoundMessage message) { + String datestampString = message.getDatestamp(); + datestamp.setText(datestampString); + datestamp.setVisibility(!TextUtils.isEmpty(datestampString) ? View.VISIBLE : View.GONE); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/OutgoingCompoundMessageHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/OutgoingCompoundMessageHolder.java index a74bd325b..fa8583d7f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/OutgoingCompoundMessageHolder.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/OutgoingCompoundMessageHolder.java @@ -1,17 +1,24 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.module.messagecenter.view.holder; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; import android.view.View; +import android.view.accessibility.AccessibilityManager; import android.widget.TextView; import com.apptentive.android.sdk.R; import com.apptentive.android.sdk.model.StoredFile; -import com.apptentive.android.sdk.module.messagecenter.view.CompoundMessageView; +import com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment; +import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage; +import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter; +import com.apptentive.android.sdk.util.Util; import com.apptentive.android.sdk.util.image.ApptentiveImageGridView; import com.apptentive.android.sdk.util.image.ImageItem; import com.apptentive.android.sdk.view.ApptentiveMaterialIndeterminateProgressBar; @@ -19,52 +26,106 @@ import java.util.ArrayList; import java.util.List; +import static android.content.Context.ACCESSIBILITY_SERVICE; public class OutgoingCompoundMessageHolder extends MessageHolder { + + public View root; + public View container; public ApptentiveMaterialIndeterminateProgressBar progressBar; public TextView messageBodyView; - private ApptentiveImageGridView imageBandView; + public ApptentiveImageGridView imageBandView; + public TextView status; - public OutgoingCompoundMessageHolder(CompoundMessageView view) { - super(view); - progressBar = (ApptentiveMaterialIndeterminateProgressBar) view.findViewById(R.id.progressBar); - messageBodyView = (TextView) view.findViewById(R.id.apptentive_compound_message_body); - imageBandView = (ApptentiveImageGridView) view.findViewById(R.id.grid); + public OutgoingCompoundMessageHolder(View itemView) { + super(itemView); + root = itemView.findViewById(R.id.message_root); + container = itemView.findViewById(R.id.apptentive_compound_message_body_container); + progressBar = (ApptentiveMaterialIndeterminateProgressBar) itemView.findViewById(R.id.progressBar); + messageBodyView = (TextView) itemView.findViewById(R.id.apptentive_compound_message_body); + imageBandView = (ApptentiveImageGridView) itemView.findViewById(R.id.grid); + status = (TextView) itemView.findViewById(R.id.status); } - public void updateMessage(String datestamp, String status, int statusColor, - boolean progressBarVisible, final String body, final int viewWidth, final int desiredColumn, final List imagesToAttach) { - super.updateMessage(datestamp, statusColor, status); - if (progressBar != null) { - if (progressBarVisible) { - progressBar.start(); - progressBar.setVisibility(View.VISIBLE); - } else { - progressBar.stop(); - progressBar.setVisibility(View.GONE); - } + public void bindView(MessageCenterFragment fragment, final RecyclerView recyclerView, final MessageCenterRecyclerViewAdapter adapter, final CompoundMessage message) { + super.bindView(fragment, recyclerView, message); + imageBandView.setupUi(); + + messageBodyView.setText(message.getBody()); + // We have to disable text selection, or the Google TalkBack won't read this unless it's selected. It's too tiny to select by itself easily. + AccessibilityManager accessibilityManager = (AccessibilityManager) fragment.getContext().getSystemService(ACCESSIBILITY_SERVICE); + if (accessibilityManager != null) { + boolean talkbackNotEnabled = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty(); + messageBodyView.setTextIsSelectable(talkbackNotEnabled); + } + + boolean showProgress; + Double createdAt = message.getCreatedAt(); + String statusText; + if (createdAt == null || createdAt > Double.MIN_VALUE) { + // show progress bar if: 1. no sent time set, and 2. not paused, and 3. have either text or files to sent + showProgress = createdAt == null && !fragment.isPaused() && (message.getAssociatedFiles() != null || !TextUtils.isEmpty(message.getBody())); + statusText = createStatus(createdAt, message.isLastSent(), fragment.isPaused()); + } else { + showProgress = false; + statusText = itemView.getResources().getString(R.string.apptentive_failed); + } + + + if (showProgress) { + progressBar.start(); + progressBar.setVisibility(View.VISIBLE); + } else { + progressBar.stop(); + progressBar.setVisibility(View.GONE); + } + + List files = message.getAssociatedFiles(); + int imagebandWidth = 0; + if (files != null && files.size() > 0) { + int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), View.MeasureSpec.EXACTLY); + root.measure(widthMeasureSpec, 0); + int viewWidth = container.getMeasuredWidth(); + imagebandWidth = viewWidth - container.getPaddingLeft() - container.getPaddingRight(); } - if (messageBodyView != null) { - messageBodyView.post(new Runnable() { + + if (files == null || files.size() == 0) { + imageBandView.setVisibility(View.GONE); + } else { + imageBandView.setVisibility(View.VISIBLE); + imageBandView.setAdapterItemSize(imagebandWidth, itemView.getResources().getInteger(R.integer.apptentive_image_grid_default_column_number)); + List images = new ArrayList(); + for (StoredFile file : files) { + images.add(new ImageItem(file.getSourceUriOrPath(), file.getLocalFilePath(), file.getMimeType(), file.getCreationTime())); + } + imageBandView.setData(images); + imageBandView.setListener(new ApptentiveImageGridView.ImageItemClickedListener() { @Override - public void run() { - messageBodyView.setText(body); + public void onClick(int position, ImageItem image) { + if (adapter.getListener() != null) { + adapter.getListener().onClickAttachment(position, image); + } } }); } - // Set up attachments view - if (imageBandView != null) { - if (imagesToAttach == null || imagesToAttach.size() == 0) { - imageBandView.setVisibility(View.GONE); - } else { - imageBandView.setVisibility(View.VISIBLE); - imageBandView.setAdapterItemSize(viewWidth, desiredColumn); - List images = new ArrayList(); - for (StoredFile file : imagesToAttach) { - images.add(new ImageItem(file.getSourceUriOrPath(), file.getLocalFilePath(), file.getMimeType(), file.getCreationTime())); - } - imageBandView.setData(images); - } + status.setText(statusText); + status.setTextColor(getStatusColor(createdAt, fragment.isPaused())); + status.setVisibility(!TextUtils.isEmpty(statusText) ? View.VISIBLE : View.GONE); + } + + protected String createStatus(Double seconds, boolean showSent, boolean isPaused) { + if (seconds == null) { + return isPaused ? itemView.getResources().getString(R.string.apptentive_failed) : null; + } + return (showSent) ? itemView.getResources().getString(R.string.apptentive_sent) : null; + } + + private int getStatusColor(Double seconds, boolean isPaused) { + if (seconds == null) { + // failed color (red) + return isPaused ? Util.getThemeColor(itemView.getContext(), R.attr.apptentiveValidationFailedColor) : 0; } + // other status color + return Util.getThemeColor(itemView.getContext(), android.R.attr.textColorSecondary); } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/StatusHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/StatusHolder.java index 10604c82b..d59ce11d8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/StatusHolder.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/StatusHolder.java @@ -1,54 +1,25 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ package com.apptentive.android.sdk.module.messagecenter.view.holder; +import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.apptentive.android.sdk.R; -import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterStatusView; -/** - * @author Sky Kelsey - */ -public class StatusHolder extends MessageCenterListItemHolder { - private TextView body; - private ImageView icon; - - public StatusHolder(MessageCenterStatusView view) { - body = (TextView) view.findViewById(R.id.status_body); - icon = (ImageView) view.findViewById(R.id.icon); - } - - public void updateMessage(final String bodyText, Integer iconRes) { - if (body != null && body != null) { - body.post(new Runnable() { - @Override - public void run() { - body.setVisibility(TextView.VISIBLE); - body.setText(bodyText); - } - }); +public class StatusHolder extends RecyclerView.ViewHolder { + public TextView body; + public ImageView icon; - } else { - body.post(new Runnable() { - @Override - public void run() { - body.setVisibility(View.GONE); - } - }); - } - if (icon != null && iconRes != null) { - icon.setImageResource(iconRes); - icon.setVisibility(View.VISIBLE); - } - else { - icon.setVisibility(View.GONE); - } + public StatusHolder(View itemView) { + super(itemView); + body = (TextView) itemView.findViewById(R.id.status_body); + icon = (ImageView) itemView.findViewById(R.id.icon); } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/WhoCardHolder.java b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/WhoCardHolder.java new file mode 100644 index 000000000..b6c1cba50 --- /dev/null +++ b/apptentive/src/main/java/com/apptentive/android/sdk/module/messagecenter/view/holder/WhoCardHolder.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. + * Please refer to the LICENSE file for the terms and conditions + * under which redistribution and use of this file is permitted. + */ + +package com.apptentive.android.sdk.module.messagecenter.view.holder; + +import android.support.design.widget.TextInputLayout; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import com.apptentive.android.sdk.Apptentive; +import com.apptentive.android.sdk.R; +import com.apptentive.android.sdk.module.messagecenter.model.WhoCard; +import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter; +import com.apptentive.android.sdk.util.Util; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +public class WhoCardHolder extends RecyclerView.ViewHolder { + + private MessageCenterRecyclerViewAdapter adapter; + + private TextView title; + private TextInputLayout nameLayout; + private EditText nameEditText; + private TextInputLayout emailLayout; + private EditText emailEditText; + private TextView emailExplanation; + private Button skipButton; + private Button saveButton; + + public WhoCardHolder(MessageCenterRecyclerViewAdapter adapter, View itemView) { + super(itemView); + + this.adapter = adapter; + + title = (TextView) itemView.findViewById(R.id.who_title); + nameEditText = (EditText) itemView.findViewById(R.id.who_name); + nameLayout = (TextInputLayout) itemView.findViewById(R.id.input_layout_who_name); + emailEditText = (EditText) itemView.findViewById(R.id.who_email); + emailLayout = (TextInputLayout) itemView.findViewById(R.id.input_layout_who_email); + emailExplanation = (TextView) itemView.findViewById(R.id.email_explanation); + skipButton = (Button) itemView.findViewById(R.id.btn_skip); + saveButton = (Button) itemView.findViewById(R.id.btn_send); + } + + public void bindView(RecyclerView recyclerView, final WhoCard whoCard) { + + if (TextUtils.isEmpty(whoCard.getTitle())) { + title.setVisibility(GONE); + } else { + title.setVisibility(VISIBLE); + title.setHint(whoCard.getTitle()); + } + + View viewToFocus; + if (TextUtils.isEmpty(whoCard.getNameHint())) { + nameLayout.setVisibility(GONE); + viewToFocus = emailEditText; + } else { + nameLayout.setVisibility(VISIBLE); + nameLayout.setHint(whoCard.getNameHint()); + viewToFocus = nameEditText; + } + nameEditText.setText(Apptentive.getPersonName()); + + emailLayout.setHint(whoCard.getEmailHint()); + emailEditText.setText(Apptentive.getPersonEmail()); + if (Util.isEmailValid(emailEditText.getText().toString().trim())) { + saveButton.setEnabled(true); + } else { + saveButton.setEnabled(false); + } + + TextWatcher emailTextWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence existingContent, int i, int i2, int i3) { + // Disable send button when the content hasn't change yet + if (Util.isEmailValid(existingContent.toString().trim())) { + saveButton.setEnabled(true); + } else { + saveButton.setEnabled(false); + } + } + + @Override + public void onTextChanged(CharSequence charSequence, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable editable) { + String emailContent = editable.toString().trim(); + if (Util.isEmailValid(emailContent)) { + // email must be in valid format after the change. If it is, enable send button + saveButton.setEnabled(true); + } else + // Allow user remove email completely when editing profile of that doesn't have "Email Required" + if (TextUtils.isEmpty(emailContent) && !whoCard.isRequire()) { + saveButton.setEnabled(true); + } else { + // email not valid after change, so disable the send button + saveButton.setEnabled(false); + } + } + }; + emailEditText.addTextChangedListener(emailTextWatcher); + + if (TextUtils.isEmpty(whoCard.getEmailExplanation())) { + emailExplanation.setVisibility(GONE); + } else { + emailExplanation.setVisibility(VISIBLE); + emailExplanation.setText(whoCard.getEmailExplanation()); + } + + if (TextUtils.isEmpty(whoCard.getSkipButton())) { + skipButton.setVisibility(GONE); + } else { + skipButton.setVisibility(VISIBLE); + skipButton.setText(whoCard.getSkipButton()); + skipButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + if (adapter.getListener() != null) { + adapter.getListener().onCloseWhoCard(skipButton.getText().toString()); + } + } + }); + } + + if (TextUtils.isEmpty(whoCard.getSaveButton())) { + saveButton.setVisibility(GONE); + } else { + saveButton.setVisibility(VISIBLE); + saveButton.setText(whoCard.getSaveButton()); + } + + saveButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + if (isWhoCardContentValid(whoCard.isRequire())) { + Apptentive.setPersonEmail(emailEditText.getText().toString().trim()); + Apptentive.setPersonName(nameEditText.getText().toString().trim()); + if (adapter.getListener() != null) { + adapter.getListener().onSubmitWhoCard(saveButton.getText().toString()); + } + } + } + }); + if (adapter.getListener() != null) { + adapter.getListener().onWhoCardViewCreated(nameEditText, emailEditText, viewToFocus); + } + } + + private boolean isWhoCardContentValid(boolean required) { + String emailContent = emailEditText.getText().toString().trim(); + if (Util.isEmailValid(emailContent)) { + return true; + } + // Allow user only change name but leave email blank if profile is only requested, not required + if (TextUtils.isEmpty(emailContent) && !required) { + return true; + } + return false; + } +} diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java index b88f3f1d4..272678752 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/AppReleaseManager.java @@ -9,42 +9,12 @@ import android.content.SharedPreferences; import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.AppRelease; import com.apptentive.android.sdk.util.Constants; -import com.apptentive.android.sdk.util.JsonDiffer; - -import org.json.JSONException; public class AppReleaseManager { - public static AppRelease storeAppReleaseAndReturnDiff(AppRelease currentAppRelease) { - AppRelease stored = getStoredAppRelease(); - - Object diff = JsonDiffer.getDiff(stored, currentAppRelease); - if (diff != null) { - try { - storeAppRelease(currentAppRelease); - return new AppRelease(diff.toString()); - } catch (JSONException e) { - ApptentiveLog.e("Error casting to AppRelease.", e); - } - } - return null; - } - - public static AppRelease getStoredAppRelease() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String appReleaseString = prefs.getString(Constants.PREF_KEY_APP_RELEASE, null); - try { - return new AppRelease(appReleaseString); - } catch (Exception e) { - // Ignore - } - return null; - } - - private static void storeAppRelease(AppRelease appRelease) { + public static void storeAppRelease(AppRelease appRelease) { SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); prefs.edit().putString(Constants.PREF_KEY_APP_RELEASE, appRelease.toString()).apply(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java index f2053f015..35c08a82e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/ApptentiveDatabaseHelper.java @@ -105,11 +105,11 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { FILESTORE_KEY_APPTENTIVE_URL + " TEXT" + ");"; - /* Compund Message FileStore: - * For compound message stored in TABLE_MESSAGE, each associated file will add a row to this table - * uing the message's "nonce" key + /* Compound Message FileStore: + * For Compound Messages stored in TABLE_MESSAGE, each associated file will add a row to this table + * using the message's "nonce" key */ - private static final String TABLE_COMPOUND_MESSSAGE_FILESTORE = "compound_message_file_store"; // table filePath + private static final String TABLE_COMPOUND_MESSAGE_FILESTORE = "compound_message_file_store"; // table filePath private static final String COMPOUND_FILESTORE_KEY_DB_ID = "_id"; // 0 private static final String COMPOUND_FILESTORE_KEY_MESSAGE_NONCE = "nonce"; // message nonce of the compound message private static final String COMPOUND_FILESTORE_KEY_MIME_TYPE = "mime_type"; // mine type of the file @@ -119,7 +119,7 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { private static final String COMPOUND_FILESTORE_KEY_CREATION_TIME = "creation_time"; // creation time of the original file // Create the initial table. Use nonce and local cache path as primary key because both sent/received files will have a local cached copy private static final String TABLE_CREATE_COMPOUND_FILESTORE = - "CREATE TABLE " + TABLE_COMPOUND_MESSSAGE_FILESTORE + + "CREATE TABLE " + TABLE_COMPOUND_MESSAGE_FILESTORE + " (" + COMPOUND_FILESTORE_KEY_DB_ID + " INTEGER PRIMARY KEY, " + COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " TEXT, " + @@ -131,7 +131,7 @@ public class ApptentiveDatabaseHelper extends SQLiteOpenHelper { ");"; // Query all files associated with a given compound message nonce id - private static final String QUERY_MESSAGE_FILES_GET_BY_NONCE = "SELECT * FROM " + TABLE_COMPOUND_MESSSAGE_FILESTORE + " WHERE " + COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?"; + private static final String QUERY_MESSAGE_FILES_GET_BY_NONCE = "SELECT * FROM " + TABLE_COMPOUND_MESSAGE_FILESTORE + " WHERE " + COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?"; private File fileDir; // data dir of the application @@ -451,7 +451,7 @@ private void migrateToCompoundMessage(SQLiteDatabase db) { values.put(COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI, originalFileName); values.put(COMPOUND_FILESTORE_KEY_REMOTE_URL, cursor.getString(4)); values.put(COMPOUND_FILESTORE_KEY_CREATION_TIME, 0); // we didn't store creation time of legacy file message - db.insert(TABLE_COMPOUND_MESSSAGE_FILESTORE, null, values); + db.insert(TABLE_COMPOUND_MESSAGE_FILESTORE, null, values); } while (cursor.moveToNext()); } @@ -562,7 +562,7 @@ public void deleteAssociatedFiles(String messageNonce) { SQLiteDatabase db = null; try { db = getWritableDatabase(); - int deleted = db.delete(TABLE_COMPOUND_MESSSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); + int deleted = db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); ApptentiveLog.d("Deleted %d stored files.", deleted); } catch (SQLException sqe) { ApptentiveLog.e("deleteAssociatedFiles EXCEPTION: " + sqe.getMessage()); @@ -612,7 +612,7 @@ public boolean addCompoundMessageFiles(List associatedFiles) { db = getWritableDatabase(); db.beginTransaction(); // Always delete existing rows with the same nonce to ensure add/update both work - db.delete(TABLE_COMPOUND_MESSSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); + db.delete(TABLE_COMPOUND_MESSAGE_FILESTORE, COMPOUND_FILESTORE_KEY_MESSAGE_NONCE + " = ?", new String[]{messageNonce}); for (StoredFile file : associatedFiles) { ContentValues values = new ContentValues(); @@ -622,7 +622,7 @@ public boolean addCompoundMessageFiles(List associatedFiles) { values.put(COMPOUND_FILESTORE_KEY_LOCAL_ORIGINAL_URI, file.getSourceUriOrPath()); values.put(COMPOUND_FILESTORE_KEY_REMOTE_URL, file.getApptentiveUri()); values.put(COMPOUND_FILESTORE_KEY_CREATION_TIME, file.getCreationTime()); - ret = db.insert(TABLE_COMPOUND_MESSSAGE_FILESTORE, null, values); + ret = db.insert(TABLE_COMPOUND_MESSAGE_FILESTORE, null, values); } db.setTransactionSuccessful(); db.endTransaction(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java index bd45b9039..f997d4c4e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/PayloadSendWorker.java @@ -183,9 +183,6 @@ public void run() { ApptentiveLog.v("Rejected json:", payload.toString()); ApptentiveInternal.getInstance().getApptentiveTaskManager().deletePayload(payload); } else if (response.isRejectedTemporarily()) { - if (mgr != null) { - mgr.pauseSending(MessageManager.SEND_PAUSE_REASON_SERVER); - } ApptentiveLog.d("Unable to send JSON. Leaving in queue."); if (response.isException()) { retryLater(NO_CONNECTION_SLEEP_TIME); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java index 97b148f55..b8e7e5ee5 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/storage/SdkManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -9,73 +9,38 @@ import android.content.SharedPreferences; import com.apptentive.android.sdk.ApptentiveInternal; -import com.apptentive.android.sdk.ApptentiveLog; import com.apptentive.android.sdk.model.Sdk; import com.apptentive.android.sdk.util.Constants; -import com.apptentive.android.sdk.util.JsonDiffer; import com.apptentive.android.sdk.util.Util; -import org.json.JSONException; -/** - * @author Sky Kelsey - */ public class SdkManager { - public static Sdk storeSdkAndReturnDiff() { - Sdk stored = getStoredSdk(); - Sdk current = generateCurrentSdk(); - - Object diff = JsonDiffer.getDiff(stored, current); - if(diff != null) { - try { - storeSdk(current); - return new Sdk(diff.toString()); - } catch (JSONException e) { - ApptentiveLog.e("Error casting to Sdk.", e); - } - } - return null; - } - public static Sdk storeSdkAndReturnIt() { Sdk current = generateCurrentSdk(); storeSdk(current); return current; } - private static Sdk generateCurrentSdk() { + public static Sdk generateCurrentSdk() { Sdk sdk = new Sdk(); // First, get all the information we can load from static resources. sdk.setVersion(Constants.APPTENTIVE_SDK_VERSION); sdk.setPlatform("Android"); - - // Distribution and distribution version are optionally set in the manifest by the wrapping platform (trigger, etc.) + // Distribution and distribution version are optionally set in the manifest by the wrapping platform (Cordova, mParticle, etc.) Object distribution = Util.getPackageMetaDataSingleQuotedString(ApptentiveInternal.getInstance().getApplicationContext(), Constants.MANIFEST_KEY_SDK_DISTRIBUTION); - if(distribution != null && distribution.toString().length() != 0) { + if (distribution != null && distribution.toString().length() != 0) { sdk.setDistribution(distribution.toString()); } Object distributionVersion = Util.getPackageMetaDataSingleQuotedString(ApptentiveInternal.getInstance().getApplicationContext(), Constants.MANIFEST_KEY_SDK_DISTRIBUTION_VERSION); - if(distributionVersion != null && distributionVersion.toString().length() != 0) { + if (distributionVersion != null && distributionVersion.toString().length() != 0) { sdk.setDistributionVersion(distributionVersion.toString()); } - return sdk; } - public static Sdk getStoredSdk() { - SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); - String sdkString = prefs.getString(Constants.PREF_KEY_SDK, null); - try { - return new Sdk(sdkString); - } catch (Exception e) { - // Ignore - } - return null; - } - - private static void storeSdk(Sdk sdk) { + public static void storeSdk(Sdk sdk) { SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs(); prefs.edit().putString(Constants.PREF_KEY_SDK, sdk.toString()).apply(); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java index fc830e64d..8a7ea6ce8 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Constants.java @@ -8,7 +8,7 @@ public class Constants { - public static final String APPTENTIVE_SDK_VERSION = "3.3.0"; + public static final String APPTENTIVE_SDK_VERSION = "3.4.0"; public static final int REQUEST_CODE_PHOTO_FROM_SYSTEM_PICKER = 10; @@ -46,7 +46,7 @@ public class Constants { public static final String PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS = "messageCenterPendingComposingAttachments"; public static final String PREF_KEY_MESSAGE_CENTER_SERVER_ERROR_LAST_ATTEMPT = "messageCenterServerErrorLastAttempt"; - public static final String PREF_KEY_MESSAGE_CENTER_WHO_CARD_SET = "messageCenterWhoCardSet"; + public static final String PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE = "messageCenterWhoCardSet"; public static final String PREF_KEY_APP_CONFIG_PREFIX = "appConfiguration."; public static final String PREF_KEY_APP_CONFIG_JSON = PREF_KEY_APP_CONFIG_PREFIX+"json"; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/JsonDiffer.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/JsonDiffer.java index ac8162788..23dce5d9e 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/JsonDiffer.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/JsonDiffer.java @@ -143,8 +143,7 @@ public static boolean areObjectsEqual(Object left, Object right) { private static Set getKeys(JSONObject jsonObject) { Set keys = new HashSet(); if (jsonObject != null) { - @SuppressWarnings("unchecked") - Iterator it = (Iterator) jsonObject.keys(); + Iterator it = jsonObject.keys(); while (it.hasNext()) { keys.add(it.next()); } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java index 1b33c5213..188cb3349 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/Util.java @@ -295,27 +295,6 @@ public static Integer parseWebColorAsAndroidColor(String input) { return null; } - public static void calculateListViewHeightBasedOnChildren(ListView listView) { - ListAdapter listAdapter = listView.getAdapter(); - if (listAdapter == null) { - - return; - } - - int totalHeight = 0; - for (int i = 0; i < listAdapter.getCount(); i++) { - View listItem = listAdapter.getView(i, null, listView); - listItem.measure(0, 0); - totalHeight += listItem.getMeasuredHeight(); - } - - ViewGroup.LayoutParams params = listView.getLayoutParams(); - int newHeight = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); - int HeightDifference = params.height - newHeight; - - //listView.setLayoutParams(params); - } - /** * helper method to set the background depending on the android version * @@ -442,11 +421,11 @@ public static int alphaMixColors(int backgroundColor, int foregroundColor) { int a = ((int) (ap * 255d)) & 0xff; int r = ((int) (((float) (backgroundColor >> RED_CHANNEL & 0xff) * amount1) + - ((float) (foregroundColor >> RED_CHANNEL & 0xff) * amount2))) & 0xff; + ((float) (foregroundColor >> RED_CHANNEL & 0xff) * amount2))) & 0xff; int g = ((int) (((float) (backgroundColor >> GREEN_CHANNEL & 0xff) * amount1) + - ((float) (foregroundColor >> GREEN_CHANNEL & 0xff) * amount2))) & 0xff; + ((float) (foregroundColor >> GREEN_CHANNEL & 0xff) * amount2))) & 0xff; int b = ((int) (((float) (backgroundColor & 0xff) * amount1) + - ((float) (foregroundColor & 0xff) * amount2))) & 0xff; + ((float) (foregroundColor & 0xff) * amount2))) & 0xff; return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b << BLUE_CHANNEL; } @@ -488,8 +467,8 @@ public static String getRealFilePathFromUri(Context context, Uri contentUri) { cursor.close(); cursor = context.getContentResolver().query( - android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null); + android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null); if (cursor != null && cursor.moveToFirst()) { String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); cursor.close(); @@ -510,8 +489,8 @@ public static long getContentCreationTime(Context context, Uri contentUri) { cursor.close(); cursor = context.getContentResolver().query( - android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null); + android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null); if (cursor != null && cursor.moveToFirst()) { long time = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.DATE_ADDED)); cursor.close(); @@ -565,8 +544,8 @@ public static String generateCacheFileFullPath(Uri fileOriginalUri, File cacheDi public static File getDiskCacheDir(Context context) { File appCacheDir = null; if ((Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) - || !Environment.isExternalStorageRemovable()) - && hasPermission(context, "android.permission.WRITE_EXTERNAL_STORAGE")) { + || !Environment.isExternalStorageRemovable()) + && hasPermission(context, "android.permission.WRITE_EXTERNAL_STORAGE")) { appCacheDir = context.getExternalCacheDir(); } @@ -601,8 +580,8 @@ public static boolean hasPermission(Context context, final String permission) { */ public static boolean openFileAttachment(final Context context, final String sourcePath, final String selectedFilePath, final String mimeTypeString) { if ((Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) - || !Environment.isExternalStorageRemovable()) - && hasPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + || !Environment.isExternalStorageRemovable()) + && hasPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { File selectedFile = new File(selectedFilePath); String selectedFileName = null; @@ -742,7 +721,7 @@ public static StoredFile createLocalStoredFile(InputStream is, String sourceUrl, try { File localFile = new File(localFilePath); /* Local cache file name may not be unique, and can be reused, in which case, the previously created - * cache file need to be deleted before it is being copied over. + * cache file need to be deleted before it is being copied over. */ if (localFile.exists()) { localFile.delete(); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java index 422f4683f..67fb5d9be 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveAttachmentLoader.java @@ -216,7 +216,7 @@ public void doDownload() { mDrawableDownloaderTask = new ApptentiveDownloaderTask(imageView, this); try { ApptentiveLog.v("ApptentiveAttachmentLoader doDownload: " + uri); - // Conversation token is needed if the download url is an redrect link from an Apptentive endpoint + // Conversation token is needed if the download url is a redirect link from an Apptentive endpoint String conversationToken = ApptentiveInternal.getInstance().getApptentiveConversationToken(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { mDrawableDownloaderTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri, diskCacheFilePath, conversationToken); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveImageGridView.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveImageGridView.java index bb4219713..7b716e629 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveImageGridView.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ApptentiveImageGridView.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -26,12 +26,9 @@ public class ApptentiveImageGridView extends GridView implements AdapterView.OnI private ImageItemClickedListener listener; - private Context context; - public ApptentiveImageGridView(Context context, AttributeSet attrs) { super(context, attrs); setOnItemClickListener(this); - this.context = context; } @Override @@ -39,11 +36,9 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightSpec; if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) { - // The two leftmost bits in the height measure spec have // a special meaning, hence we can't use them to describe height. - heightSpec = MeasureSpec.makeMeasureSpec( - Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); } else { // Any other height should be respected as is. heightSpec = heightMeasureSpec; @@ -63,7 +58,7 @@ public void setListener(ImageItemClickedListener l) { } public void setupUi() { - imageBandAdapter = new ImageGridViewAdapter(context, false); + imageBandAdapter = new ImageGridViewAdapter(getContext(), false); setAdapter(imageBandAdapter); } @@ -86,15 +81,12 @@ public void onGlobalLayout() { } else { getViewTreeObserver().removeGlobalOnLayoutListener(this); } - - } }); } public void setAdapterItemSize(int width, int desiredNumCount) { final int columnSpace = getResources().getDimensionPixelOffset(R.dimen.apptentive_image_grid_space_size); - int columnWidth = (width - columnSpace * (desiredNumCount - 1)) / desiredNumCount; Point point = Util.getScreenSize(getContext().getApplicationContext()); imageBandAdapter.setItemSize(columnWidth, (int) (((float) point.y / (float) point.x) * columnWidth)); @@ -110,7 +102,7 @@ public void setAdapterIndicator(int rid) { } public void setImageIndicatorCallback(ImageGridViewAdapter.Callback callback) { - imageBandAdapter.setIndicatorCallback(callback); + imageBandAdapter.setIndicatorCallback(callback); } public void setData(List images) { @@ -120,4 +112,8 @@ public void setData(List images) { public interface ImageItemClickedListener { void onClick(int position, ImageItem image); } + + public void notifyDataSetChanged() { + imageBandAdapter.notifyDataSetChanged(); + } } \ No newline at end of file diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageGridViewAdapter.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageGridViewAdapter.java index 90f4b6e88..acfc3f1c0 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageGridViewAdapter.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageGridViewAdapter.java @@ -347,10 +347,15 @@ public void onClick(View v) { } } + // Always reset contentDescription to original state. + image.setContentDescription(image.getContext().getResources().getString(R.string.apptentive_message_center_content_description_attachment)); + if (Util.isMimeTypeImage(data.mimeType)) { if (TextUtils.isEmpty(data.originalPath)) { + // This is the "Add Attachment" image image.setScaleType(ImageView.ScaleType.CENTER_INSIDE); placeholderResId = R.drawable.apptentive_ic_add; + image.setContentDescription(image.getContext().getResources().getString(R.string.apptentive_message_center_content_description_attachment_add)); indicator.setVisibility(View.GONE); bLoadThumbnail = false; progressBarLoading.setVisibility(View.GONE); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java index d9ebf3dbc..faa946dad 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/image/ImageUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved. + * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved. * Please refer to the LICENSE file for the terms and conditions * under which redistribution and use of this file is permitted. */ @@ -32,9 +32,6 @@ import java.lang.ref.WeakReference; import java.net.URL; -/** - * @author Sky Kelsey - */ public class ImageUtil { private static final int MAX_SENT_IMAGE_EDGE = 1024; @@ -115,7 +112,9 @@ private static Bitmap createLightweightScaledBitmap(String fileAbsolutePath, Uri int width, height; if (orientation == 90 || orientation == 270) { + //noinspection SuspiciousNameCombination width = decodeBoundsOptions.outHeight; + //noinspection SuspiciousNameCombination height = decodeBoundsOptions.outWidth; } else { width = decodeBoundsOptions.outWidth; diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java b/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java index 99aa81d7e..65470057f 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/util/task/ApptentiveDownloaderTask.java @@ -114,7 +114,7 @@ protected void onProgressUpdate(Integer... progress) { /** * This function download the large file from the server */ - private ApptentiveHttpResponse downloadBitmap(String urlstr, String destFilePath, String conversationToken) { + private ApptentiveHttpResponse downloadBitmap(String urlString, String destFilePath, String conversationToken) { if (isCancelled()) { return null; } @@ -129,7 +129,7 @@ private ApptentiveHttpResponse downloadBitmap(String urlstr, String destFilePath URL httpUrl; try { while (true) { - httpUrl = new URL(urlstr); + httpUrl = new URL(urlString); connection = (HttpURLConnection) httpUrl.openConnection(); if (bRequestRedirectThroughApptentive) { connection.setRequestProperty("User-Agent", ApptentiveClient.getUserAgentString()); @@ -153,9 +153,9 @@ private ApptentiveHttpResponse downloadBitmap(String urlstr, String destFilePath case HttpURLConnection.HTTP_SEE_OTHER: { bRequestRedirectThroughApptentive = false; String location = connection.getHeaderField("Location"); - URL base = new URL(urlstr); + URL base = new URL(urlString); URL next = new URL(base, location); // Deal with relative URLs - urlstr = next.toExternalForm(); + urlString = next.toExternalForm(); // get the cookie if need, for login cookies = connection.getHeaderField("Set-Cookie"); // Follow redirection @@ -213,8 +213,8 @@ private ApptentiveHttpResponse downloadBitmap(String urlstr, String destFilePath // flushing output output.flush(); if (!this.download) { - File fileTodelete = new File(destFilePath); - fileTodelete.delete(); + File fileToDelete = new File(destFilePath); + fileToDelete.delete(); publishProgress(-1); } else { publishProgress(100); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveAlertDialog.java b/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveAlertDialog.java index b67410637..477337df4 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveAlertDialog.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveAlertDialog.java @@ -72,6 +72,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { @Override public void onClick(View v) { dismiss(); + // TODO getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, getActivity().getIntent()); } }); diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveMaterialDeterminateProgressBar.java b/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveMaterialDeterminateProgressBar.java index d04cdb8bd..ffb50889c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveMaterialDeterminateProgressBar.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveMaterialDeterminateProgressBar.java @@ -27,7 +27,7 @@ public class ApptentiveMaterialDeterminateProgressBar extends FrameLayout { int backgroundColor; int progressBarColor; - int pendindProgress = -1; + int pendingProgress = -1; int progress = 0; View bar; View background; @@ -57,8 +57,8 @@ public ApptentiveMaterialDeterminateProgressBar(Context context, AttributeSet at @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - if (pendindProgress != -1) { - setProgress(pendindProgress); + if (pendingProgress != -1) { + setProgress(pendingProgress); } } @@ -78,7 +78,7 @@ private void setup() { public void setProgress(int progress) { if (getWidth() == 0) { - pendindProgress = progress; + pendingProgress = progress; } else { this.progress = progress; progress = Math.min(progress, MAX); @@ -90,7 +90,7 @@ public void setProgress(int progress) { params.width = progressWidth; params.height = LayoutParams.MATCH_PARENT; bar.setLayoutParams(params); - pendindProgress = -1; + pendingProgress = -1; } } diff --git a/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveNestedScrollView.java b/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveNestedScrollView.java index bc50eb19b..54f6ed98c 100644 --- a/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveNestedScrollView.java +++ b/apptentive/src/main/java/com/apptentive/android/sdk/view/ApptentiveNestedScrollView.java @@ -669,7 +669,7 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { case MotionEvent.ACTION_DOWN: { final int y = (int) ev.getY(); - if (!inChild((int) ev.getX(), (int) y)) { + if (!inChild((int) ev.getX(), y)) { mIsBeingDragged = false; recycleVelocityTracker(); break; diff --git a/apptentive/src/main/res/color/apptentive_interaction_button_color_state_list.xml b/apptentive/src/main/res/color/apptentive_interaction_button_color_state_list.xml index 3069be385..983391fb4 100644 --- a/apptentive/src/main/res/color/apptentive_interaction_button_color_state_list.xml +++ b/apptentive/src/main/res/color/apptentive_interaction_button_color_state_list.xml @@ -23,9 +23,9 @@ Define color as: - - - + + + --> diff --git a/apptentive/src/main/res/color/apptentive_tinted_button.xml b/apptentive/src/main/res/color/apptentive_tinted_button.xml index f7ed0a2ab..1968fe46a 100644 --- a/apptentive/src/main/res/color/apptentive_tinted_button.xml +++ b/apptentive/src/main/res/color/apptentive_tinted_button.xml @@ -12,7 +12,7 @@ --> \ No newline at end of file diff --git a/apptentive/src/main/res/layout/apptentive_dialog_alert.xml b/apptentive/src/main/res/layout/apptentive_dialog_alert.xml index aee1f5e0d..b223ecee4 100644 --- a/apptentive/src/main/res/layout/apptentive_dialog_alert.xml +++ b/apptentive/src/main/res/layout/apptentive_dialog_alert.xml @@ -18,7 +18,7 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginTop="16dp" - android:layout_marginBottom="24dp" + android:layout_marginBottom="16dp" android:layout_weight="1" android:paddingLeft="24dp" android:paddingRight="24dp" @@ -32,18 +32,11 @@ android:textAppearance="@style/Apptentive.TextAppearance.Subhead.Secondary"/> - -