public interface DependenciesContainer {

	NotificationsManager notificationsManager();

	ActionListener actionListener();

	OnNotificationClickedListener notificationHandler();

} public final class NotificationsManager {

	private final Context _context;
	private final Set<WeakReference<Listener>> _listeners = new HashSet<>();
	private final boolean _isLoadingMore = false;
	private int _notificationsCount;
	private final List<Notification> _notifications = new ArrayList<>();
	private Set<String> _actionTypes = new HashSet<>();
	private Set<String> _filterStatus = new HashSet<>();
	private Set<String> _chosenTypes = new HashSet<>();

	public NotificationsManager(final Context context) {
		_context = context;
		loadFromPrefs();
	}

	public void saveFilter(final Set<String> statuses, final Set<String> chosenTypes, final Set<String> actionTypes) {
		_filterStatus = new HashSet<>(statuses);
		_chosenTypes = new HashSet<>(chosenTypes);
		_actionTypes = actionTypes;
		saveToPrefs();
		sync();
	}

	public Set<String> getFilterStatus() {
		return _filterStatus;
	}

	public Set<String> getChosenTypes() {
		return _chosenTypes;
	}

	public Set<String> getActionTypes() {
		return _actionTypes;
	}

	public void addListener(final Listener listener) {
		_listeners.add(new WeakReference<>(listener));
	}

	public int getNewNotificationsCount() {
		return _notificationsCount;
	}

	public List<Notification> getNotifications() {
		return _notifications;
	}

	public void markAllAsRead() {
		setStatus(collectAllIds(), NotificationStatus.READ);
	}

	public void setStatus(final String id, final String status) {
		setStatus(Collections.singletonList(id), status);
	}

	private void setStatus(final List<String> ids, final String newStatus) {
		Notifications.setStatus(
						newStatus, ids,
						() -> updateStatuses(ids, newStatus),
						exception -> Log.e("Notifications", "Failed to mark notification, exception: " + exception.getMessage())
		);
	}

	public void sync() {
		if (!GetSocial.isInitialized()) {
			return;
		}
		Notifications.getCount(createNotificationsQuery(), result -> {
			_notificationsCount = result;
		}, error -> {
			Log.e("Notifications", "Failed to load notifications count: " + error);
		});
	}

	public NotificationsQuery createNotificationsQuery() {
		return NotificationsQuery.withStatuses(_filterStatus.toArray(new String[0]))
						.withActions(_actionTypes.toArray(new String[0]))
						.ofTypes(_chosenTypes.toArray(new String[0]));
	}

	private void updateStatuses(final List<String> ids, final String newStatus) {
		for (int i = 0; i < _notifications.size(); i++) {
			final Notification oldNotification = _notifications.get(i);
			if (ids.contains(oldNotification.getId())) {
				final Notification notification = new Notification(
								oldNotification.getId(), oldNotification.getActionButtons(), newStatus, oldNotification.getType(), oldNotification.getCreatedAt(),
								oldNotification.getTitle(), oldNotification.getText(), oldNotification.getAction(),
								oldNotification.getAttachment(), oldNotification.getSender(), oldNotification.getCustomization()
				);
				_notifications.set(i, notification);
			}
		}
		notifyListeners();
	}

	private List<String> collectAllIds() {
		final List<String> ids = new ArrayList<>(_notifications.size());
		for (final Notification notification : _notifications) {
			ids.add(notification.getId());
		}
		return ids;
	}

	private void notifyListeners() {
		for (final Iterator<WeakReference<Listener>> iterator = _listeners.iterator(); iterator.hasNext(); ) {
			final Listener listener = iterator.next().get();
			if (listener != null) {
				listener.onSync();
			} else {
				iterator.remove();
			}
		}
	}

	private void saveToPrefs() {
		_context.getSharedPreferences("notifications", Context.MODE_PRIVATE)
						.edit()
						.putStringSet("status", _filterStatus)
						.putStringSet("types", _chosenTypes)
						.putStringSet("actions", _actionTypes)
						.apply();
	}

	private void loadFromPrefs() {
		final SharedPreferences sharedPreferences = _context.getSharedPreferences("notifications", Context.MODE_PRIVATE);

		_chosenTypes = sharedPreferences.getStringSet("types", Collections.emptySet());
		_filterStatus = sharedPreferences.getStringSet("status", Collections.emptySet());
		_actionTypes = sharedPreferences.getStringSet("actions", Collections.emptySet());
	}

	public interface Listener {
		void onSync();
	}
} public class CurrentUserInfoDialog extends DialogFragment {

	@BindView(R.id.detailedUserInfo_userInfo)
	UserInfoView _userInfoView;

	@BindView(R.id.detailedUserInfo_detailedInfo)
	TextView _detailedTextView;

	public CurrentUserInfoDialog() {
		// Empty constructor required for DialogFragment
	}

	public static void show(final FragmentManager fm) {
		new CurrentUserInfoDialog().show(fm, "current_user_info_dialog");
	}

	private static String getUserInfo() {
		if (!GetSocial.isInitialized()) {
			return "";
		}

		final Map<String, Object> userDetails = new HashMap<>();
		userDetails.put("Anonymous", GetSocial.getCurrentUser().isAnonymous());
		userDetails.put("User ID", GetSocial.getCurrentUser().getId());
		userDetails.put("Display Name", GetSocial.getCurrentUser().getDisplayName());
		userDetails.put("Avatar URL", GetSocial.getCurrentUser().getAvatarUrl());
		userDetails.put("Identities", GetSocial.getCurrentUser().getIdentities());
		userDetails.put("Public Properties", GetSocial.getCurrentUser().getPublicProperties());
		userDetails.put("Private Properties", GetSocial.getCurrentUser().getPrivateProperties());

		final UserInfoBuilder sb = new UserInfoBuilder();
		sb.append("Anonymous: ").append(GetSocial.getCurrentUser().isAnonymous())
						.endLine()
						.append("User ID: ").append(GetSocial.getCurrentUser().getId()).endLine()
						.append("Avatar URL: ").append(GetSocial.getCurrentUser().getAvatarUrl()).endLine()
						.endLine();

		if (!GetSocial.getCurrentUser().isAnonymous()) {
			sb.append("IDENTITIES:").endLine()
							.append(GetSocial.getCurrentUser().getIdentities());
		}
		sb.append("PUBLIC PROPERTIES:").endLine()
						.append(GetSocial.getCurrentUser().getPublicProperties());

		sb.append("PRIVATE PROPERTIES:").endLine()
						.append(GetSocial.getCurrentUser().getPrivateProperties());

		sb.append("BAN INFO").endLine();
		sb.append("Is banned: ").append(GetSocial.getCurrentUser().isBanned()).endLine();
		if (GetSocial.getCurrentUser().isBanned()) {
			sb.append("Expiry: ").append("" + GetSocial.getCurrentUser().getBanInfo().getExpiry()).endLine();
			sb.append("Reason: ").append(GetSocial.getCurrentUser().getBanInfo().getReason()).endLine();
		}

		sb.append("JSON:").endLine()
						.append(toJson(userDetails));

		return sb.toString();
	}

	private static String toJson(final Object object) {
		if (object instanceof Map) {
			return mapToJson((Map<String, Object>) object);
		}
		if (object instanceof Iterable) {
			return iterableToJson((Iterable) object);
		}
		if (object instanceof Boolean) {
			return object.toString();
		}
		if (object instanceof Number) {
			return object.toString();
		}
		return "\"" + object.toString() + "\"";
	}

	private static String iterableToJson(final Iterable iterable) {
		final StringBuilder sb = new StringBuilder();
		sb.append("{");

		for (final Object entry : iterable) {
			sb.append(toJson(entry)).append(",");
		}
		if (sb.charAt(sb.length() - 1) == ',') {
			sb.deleteCharAt(sb.length() - 1);
		}
		sb.append("}");
		return sb.toString();
	}

	private static String mapToJson(final Map<String, Object> map) {
		final StringBuilder sb = new StringBuilder();
		sb.append("{");

		for (final Map.Entry<String, Object> entry : map.entrySet()) {
			sb.append("\"").append(entry.getKey()).append("\"").append(":")
							.append(toJson(entry.getValue())).append(",");
		}
		if (sb.charAt(sb.length() - 1) == ',') {
			sb.deleteCharAt(sb.length() - 1);
		}
		sb.append("}");
		return sb.toString();
	}

	@OnClick(R.id.detailedUserInfo_closeButton)
	public void closeView() {
		dismiss();
	}

	@Override
	public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
													 final Bundle savedInstanceState) {
		getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);

		final View view = inflater.inflate(R.layout.dialog_detailed_user_info, container);

		ButterKnife.bind(this, view);

		_userInfoView.updateView(GetSocial.getCurrentUser());
		_detailedTextView.setText(getUserInfo());
		Linkify.addLinks(_detailedTextView, Linkify.WEB_URLS);

		return view;
	}

	private static class UserInfoBuilder {
		private final StringBuilder _stringBuilder;

		UserInfoBuilder() {
			_stringBuilder = new StringBuilder();
		}

		UserInfoBuilder append(final String string) {
			_stringBuilder.append(string);
			return this;
		}

		UserInfoBuilder append(final boolean bool) {
			_stringBuilder.append(bool);
			return this;
		}

		UserInfoBuilder append(final Map<String, String> map) {
			for (final Map.Entry<String, String> entry : map.entrySet()) {
				append(entry.getKey())
								.append(": ")
								.append(entry.getValue())
								.endLine();
			}
			return this;
		}

		UserInfoBuilder endLine() {
			_stringBuilder.append("\n\n");
			return this;
		}

		@Override
		public String toString() {
			return _stringBuilder.toString();
		}
	}
} public class ReferralDataDialog extends DialogFragment {

	@BindView(R.id.referralDataInfo)
	TextView _referralDataInfo;

	@BindView(R.id.referralDataInfo_actionButton)
	Button _actionButton;

	@Nullable
	private String _promoCode;

	public ReferralDataDialog() {

	}

	public static void showReferralData(final FragmentManager fm, final ReferralData referralData) {
		final ReferralDataDialog referralDataDialog = new ReferralDataDialog();
		final Bundle referralDataBundle = new Bundle();

		referralDataBundle.putString("REFERRAL_DATA", "Referral data received: [ " + referralData + " ]");
		referralDataBundle.putString("PROMO_CODE", referralData.getLinkParams().get(LinkParams.KEY_PROMO_CODE));

		referralDataDialog.setArguments(referralDataBundle);
		referralDataDialog.show(fm, "referral_data_dialog");
	}

	@Override
	public View onCreateView(@Nullable final LayoutInflater inflater, final ViewGroup container,
													 @Nullable final Bundle savedInstanceState) {
		getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);

		final View view = inflater.inflate(R.layout.dialog_referral_data_info, container);

		ButterKnife.bind(this, view);

		String text = getArguments().getString("REFERRAL_DATA");
		_promoCode = getArguments().getString("PROMO_CODE");
		if (_promoCode != null) {
			text += "\n\nPROMO CODE:\n" + _promoCode;
		}
		_referralDataInfo.setText(text);
		_actionButton.setVisibility(_promoCode == null ? View.INVISIBLE : View.VISIBLE);

		return view;
	}

	@OnClick(R.id.referralDataInfo_actionButton)
	public void doAction() {
		PromoCodes.claim(_promoCode, result -> {
			Toast.makeText(getContext(), "PromoCode claimed: " + result, Toast.LENGTH_SHORT).show();
		}, error -> {
			Toast.makeText(getContext(), "Failed to claim PromoCode: " + error.getMessage(), Toast.LENGTH_SHORT).show();
		});
	}

	@OnClick(R.id.referralDataInfo_closeButton)
	public void doClose() {
		dismiss();
	}
} public class UserInfoDialog extends DialogFragment {

	@BindView(R.id.display_name)
	TextView _displayName;
	@BindView(R.id.user_avatar)
	ImageView _avatar;
	private User _user;

	public static void show(final FragmentManager fm, final User user) {
		final UserInfoDialog dialog = new UserInfoDialog();
		dialog._user = user;
		dialog.show(fm, "user_info_dialog");
	}

	@OnClick(R.id.buttonClose)
	public void closeView() {
		dismiss();
	}

	@Override
	public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
		getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);

		final View view = inflater.inflate(R.layout.dialog_userinfo, container);
		ButterKnife.bind(this, view);
		_displayName.setText(_user.getDisplayName());

		if (TextUtils.isEmpty(_user.getAvatarUrl())) {
			Picasso.with(getContext())
							.load(R.drawable.avatar_default)
							.transform(new CircleTransform())
							.into(_avatar);
		} else {
			Picasso.with(getContext())
							.load(_user.getAvatarUrl())
							.placeholder(R.drawable.avatar_default)
							.transform(new CircleTransform())
							.into(_avatar);
		}

		return view;
	}
} public abstract class BaseFragment extends Fragment implements HasTitle, HasFragmentTag {

	protected static final String CUSTOM_PROVIDER = "custom";
	protected static final List<String> FACEBOOK_PERMISSIONS = Arrays.asList("email", "user_friends", "public_profile");
	protected SimpleLogger _log;

	protected ActivityListener _activityListener;
	private ProgressDialog _currentProgressDialog;
	private ImagePicker _imagePicker;

	protected void inject(final DependenciesContainer dependencies) {
		//
	}

	@Override
	public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
		if (!(_imagePicker != null && _imagePicker.onActivityResult(requestCode, resultCode, data))) {
			super.onActivityResult(requestCode, resultCode, data);
		}
	}

	@Override
	public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
		if (!(_imagePicker != null && _imagePicker.onRequestPermissionResult(requestCode, permissions, grantResults))) {
			super.onRequestPermissionsResult(requestCode, permissions, grantResults);
		}
	}

	@Override
	public void onAttach(final Context context) {
		super.onAttach(context);

		final Activity activity = getActivity();

		_log = new SimpleLogger(activity, getClass().getSimpleName());

		// This makes sure that the container activity has implemented
		// the callback interface. If not, it throws an exception
		try {
			_activityListener = (ActivityListener) activity;
		} catch (final ClassCastException exception) {
			throw new ClassCastException(activity.toString()
							+ " must implement ActivityListener");
		}

		inject(_activityListener.dependencies());
	}

	@Override
	public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
		if (_imagePicker != null) {
			_imagePicker.onViewStateRestored(savedInstanceState);
		}
		super.onViewStateRestored(savedInstanceState);
	}

	@Override
	public void onSaveInstanceState(final Bundle outState) {
		if (_imagePicker != null) {
			_imagePicker.onSaveInstanceState(outState);
		}
		super.onSaveInstanceState(outState);
	}

	protected void pickImageFromDevice(final int requestCode) {
		_imagePicker = new ImagePicker(this, requestCode);
		_imagePicker.pickImageFromDevice(createImagePickerCallback());
	}

	protected void pickVideoFromDevice(final int requestCode) {
		_imagePicker = new ImagePicker(this, requestCode);
		_imagePicker.pickVideoFromDevice(createImagePickerCallback());
	}

	private ImagePicker.Callback createImagePickerCallback() {
		return new ImagePicker.Callback() {
			@Override
			public void onImageChosen(final Uri imageUri, final int requestCode) {
				onImagePickedFromDevice(imageUri, requestCode);
				_imagePicker = null;
			}

			@Override
			public void onVideoChosen(final Uri videoUri, final int requestCode) {
				onVideoPickedFromDevice(videoUri, requestCode);
			}

			@Override
			public void onCancel() {
				_imagePicker = null;
			}
		};
	}

	protected void onImagePickedFromDevice(final Uri imageUri, final int requestCode) {
		// Override in children, if you need to handle image picking
	}

	protected void onVideoPickedFromDevice(final Uri videoUri, final int requestCode) {
		// Override in children, if you need to handle video picking
	}

	protected void showLoading(final String title, final String text) {
		if (_currentProgressDialog == null) {
			_currentProgressDialog = ProgressDialog.show(getContext(), title, text);
		} else {
			hideLoading();
			showLoading(title, text);
		}
	}

	protected void hideLoading() {
		if (_currentProgressDialog != null) {
			_currentProgressDialog.dismiss();
			_currentProgressDialog = null;
		}
	}

	protected void showAuthorizeUserDialogForPendingAction(final String actionDescription, final UiAction.Pending pendingAction) {
		new AlertDialog.Builder(getContext()).setTitle("Authorize to " + actionDescription)
						.setNegativeButton("Cancel", (dialog, which) ->
										_log.logInfoAndToast("Can not " + actionDescription + " without authorization.")
						)
						.setItems(new CharSequence[] {"Facebook", "Custom", "Trusted"}, (dialog, which) -> {
							switch (which) {
								case 0:
									addFacebookUserIdentity(pendingAction::proceed);
									break;
								case 1:
									addCustomUserIdentity(pendingAction::proceed);
									break;
								case 2:
									addTrustedUserIdentity(pendingAction::proceed);
									break;
								default:
									break;
							}
							dialog.dismiss();
						})
						.show();
	}


	protected void showAlert(final String title, final String message) {
		new AlertDialog.Builder(getContext())
						.setTitle(title)
						.setMessage(message)
						.setPositiveButton("OK", (dialogInterface, i) -> dialogInterface.dismiss())
						.show();
	}

	protected void addContentFragment(final Fragment fragment) {
		_activityListener.addContentFragment(fragment);
	}

	protected void addFacebookUserIdentity(final CompletionCallback completionCallback) {
		final CompletionCallback wrapped = () -> {
			completionCallback.onSuccess();
			setFacebookInfo();
		};
		final AddUserIdentityOutcomeCallback addUserIdentityOutcomeCallback = new AddUserIdentityOutcomeCallback() {
			@Override
			public void onSuccess() {
				_log.logInfoAndToast("Added Facebook identity successfully");
				wrapped.onSuccess();
			}

			@Override
			public void onFailure(final GetSocialError exception) {
				_log.logInfoAndToast("Adding Facebook identity failed : " + exception);
				disconnectFromFacebook();
			}

			@Override
			public void onConflictResolvedWithCurrent() {
				_log.logInfoAndToast("Conflict adding Facebook user identity resolved with current");
				disconnectFromFacebook();
			}

			@Override
			public void onConflictResolvedWithRemote() {
				_log.logInfoAndToast("Conflict adding Facebook user identity resolved with remote");
				wrapped.onSuccess();
			}
		};

		final AccessTokenTracker accessTokenTracker = new AccessTokenTracker() {
			@Override
			protected void onCurrentAccessTokenChanged(final AccessToken oldAccessToken, @Nullable final AccessToken newAccessToken) {

				if (newAccessToken == null) {
					addUserIdentityOutcomeCallback.onFailure(new GetSocialError(ErrorCode.UNKNOWN, "Facebook SDK did not provide an AccessToken"));
					return;
				}

				stopTracking();

				final String tokenString = newAccessToken.getToken();

				addIdentity(Identity.facebook(tokenString), addUserIdentityOutcomeCallback);
			}
		};
		accessTokenTracker.startTracking();
		LoginManager.getInstance().logInWithReadPermissions(getActivity(), FACEBOOK_PERMISSIONS);
	}

	protected void setFacebookInfo() {
		final Profile fbProfile = Profile.getCurrentProfile();

		final String userDisplayName = fbProfile.getFirstName() + " " + fbProfile.getLastName();
		final String profileImageUri = fbProfile.getProfilePictureUri(250, 250).toString();

		final UserUpdate userDetails = new UserUpdate();
		userDetails.updateAvatarUrl(profileImageUri);
		userDetails.updateDisplayName(userDisplayName);

		GetSocial.getCurrentUser().updateDetails(userDetails, () -> _activityListener.invalidateUi(), error -> {
			_log.logErrorAndToast(error.getMessage());
		});
	}

	protected void addCustomUserIdentity(final CompletionCallback completionCallback) {
		final String providerId = CUSTOM_PROVIDER;

		final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
		final View view = layoutInflater.inflate(R.layout.dialog_custom_identity, null, false);

		final EditText userIdEditText = view.findViewById(R.id.user_id);
		final EditText tokenEditText = view.findViewById(R.id.user_token);

		final UserManagementFragment.AddUserIdentityOutcomeCallback addUserIdentityOutcomeCallback = new UserManagementFragment.AddUserIdentityOutcomeCallback() {
			@Override
			public void onSuccess() {
				_log.logInfoAndToast("Added custom user identity successfully");
				completionCallback.onSuccess();
			}

			@Override
			public void onFailure(final GetSocialError exception) {
				_log.logInfoAndToast("Adding custom user identity failed : " + exception);
			}

			@Override
			public void onConflictResolvedWithCurrent() {
				_log.logInfoAndToast("Conflict adding custom user identity resolved with current");
			}

			@Override
			public void onConflictResolvedWithRemote() {
				_log.logInfoAndToast("Conflict adding custom user identity resolved with remote");
				completionCallback.onSuccess();
			}
		};

		final AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
						.setView(view)
						.setPositiveButton("Add", (dialog, which) -> {
											String userId = userIdEditText.getText().toString().trim();
											String token = tokenEditText.getText().toString().trim();
											addIdentity(Identity.custom(providerId, userId, token), addUserIdentityOutcomeCallback);
										}
						)
						.setNegativeButton("Cancel", (dialog, which) -> {
						});
		builder.show();
	}
	protected void addTrustedUserIdentity(final CompletionCallback completionCallback) {
		final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
		final View view = layoutInflater.inflate(R.layout.dialog_trusted_identity, null, false);

		final EditText providerIdEditText = view.findViewById(R.id.provider_id);
		final EditText tokenEditText = view.findViewById(R.id.user_token);

		final UserManagementFragment.AddUserIdentityOutcomeCallback addUserIdentityOutcomeCallback = new UserManagementFragment.AddUserIdentityOutcomeCallback() {
			@Override
			public void onSuccess() {
				_log.logInfoAndToast("Added trusted user identity successfully");
				completionCallback.onSuccess();
			}

			@Override
			public void onFailure(final GetSocialError exception) {
				_log.logInfoAndToast("Adding trusted user identity failed : " + exception);
			}

			@Override
			public void onConflictResolvedWithCurrent() {
				_log.logInfoAndToast("Conflict adding trusted user identity resolved with current");
			}

			@Override
			public void onConflictResolvedWithRemote() {
				_log.logInfoAndToast("Conflict adding trusted user identity resolved with remote");
				completionCallback.onSuccess();
			}
		};

		final AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
				.setView(view)
				.setPositiveButton("Add", (dialog, which) -> {
							String providerId = providerIdEditText.getText().toString().trim();
							String token = tokenEditText.getText().toString().trim();
							addIdentity(Identity.trusted(providerId, token), addUserIdentityOutcomeCallback);
						}
				)
				.setNegativeButton("Cancel", (dialog, which) -> {
				});
		builder.show();
	}

	protected void addIdentity(final Identity identity, final UserManagementFragment.AddUserIdentityOutcomeCallback addUserIdentityOutcomeCallback) {
		GetSocial.getCurrentUser().addIdentity(
						identity,
						addUserIdentityOutcomeCallback::onSuccess,
						conflictUser -> resolveConflictIdentities(conflictUser, addUserIdentityOutcomeCallback, identity),
						addUserIdentityOutcomeCallback::onFailure
		);
	}

	protected void resolveConflictIdentities(final ConflictUser conflictUser, final AddUserIdentityOutcomeCallback addUserIdentityOutcomeCallback, final Identity identity) {
		showDialogToSolveIdentityConflict(conflictUser, new ConflictResolution() {
			@Override
			public void resolveWithCurrentUser() {
				addUserIdentityOutcomeCallback.onConflictResolvedWithCurrent();
			}

			@Override
			public void resolveWithConflictUser() {
				GetSocial.switchUser(identity, new SafeCompletionCallback() {
					@Override
					public void onSafeSuccess() {
						addUserIdentityOutcomeCallback.onConflictResolvedWithRemote();
					}
				}, addUserIdentityOutcomeCallback::onFailure);
			}
		});
	}

	protected void showDialogToSolveIdentityConflict(final ConflictUser conflictUser, final ConflictResolution conflictResolution) {
		new AlertDialog.Builder(getContext())
						.setTitle("Conflict")
						.setMessage(String.format("The new identity is already linked to another user(%s). Which one do you want to continue using?", conflictUser.getDisplayName()))
						.setPositiveButton("Remote", (dialog, whichButton) ->
										conflictResolution.resolveWithConflictUser()
						)
						.setNegativeButton("Current", (dialog, whichButton) ->
										conflictResolution.resolveWithCurrentUser()
						)
						.show();
	}

	protected void getUserAndShowActionDialog(final String mention) {
		if (mention.equals(MentionClickListener.APP_SHORTCUT)) {
			Toast.makeText(getContext(), "Application mention clicked.", Toast.LENGTH_SHORT).show();
			return;
		}
		Communities.getUser(
						UserId.create(mention),
						this::showUserActionDialog,
						error -> _log.logErrorAndToast("Failed to get user: " + error.getMessage())
		);
	}

	protected void showUserActionDialog(final User user) {
		final ActionDialog actionDialog = new ActionDialog(getContext())
						.addAction(new ActionDialog.Action("Show User Feed") {
							@Override
							public void execute() {
								ActivityFeedViewBuilder
												.create(ActivitiesQuery.feedOf(UserId.create(user.getId())))
												.show();
							}
						});
		if (user.isApp()) {
			Toast.makeText(getContext(), "App user avatar clicked", Toast.LENGTH_SHORT).show();
		} else if (isCurrentUser(user)) {
			actionDialog.show();
		} else {
			checkIfFriendsAndShowDialog(user, actionDialog.setTitle("User " + user.getDisplayName()));
		}
	}

	private boolean isCurrentUser(final User user) {
		return user.getId().equals(GetSocial.getCurrentUser().getId());
	}

	private void checkIfFriendsAndShowDialog(final User user, final ActionDialog actionDialog) {
		final boolean[] sync = new boolean[1];
		final Runnable showOnLoad = () -> {
			if (sync[0]) {
				actionDialog.show();
			} else {
				sync[0] = true;
			}
		};
		actionDialog.addAction(new ActionDialog.Action("Open Chat") {
			@Override
			public void execute() {
				GetSocialUi.closeView();
				ChatMessagesFragment fragment = ChatMessagesFragment.chatWith(user.getId());
				addContentFragment(fragment);
			}
		});

		Communities.isFollowing(UserId.currentUser(), FollowQuery.users(UserIdList.create(user.getId())), followingMap -> {
			final boolean isFollowing = Boolean.TRUE.equals(followingMap.get(user.getId()));
			actionDialog.addAction(new ActionDialog.Action(isFollowing ? "Unfollow" : "Follow") {
				@Override
				public void execute() {
					if (isFollowing) {
						unfollow(user);
					} else {
						follow(user);
					}
				}
			});
			showOnLoad.run();
		}, error -> _log.logErrorAndToast("Failed to check if following: " + error));
		Communities.isFriend(UserId.create(user.getId()), isFriend -> {
			actionDialog.addAction(new ActionDialog.Action(isFriend ? "Remove from Friends" : "Add to Friends") {
				@Override
				public void execute() {
					if (isFriend) {
						removeFriend(user);
					} else {
						addFriend(user);
					}
				}
			});
			showOnLoad.run();
		}, error -> _log.logErrorAndToast("Failed to check if friend: " + error));
	}

	private void addFriend(final User user) {
		Communities.addFriends(
						UserIdList.create(user.getId()),
						integer -> Toast.makeText(getContext(), user.getDisplayName() + " is now your friend!", Toast.LENGTH_SHORT).show(),
						error -> _log.logErrorAndToast("Failed to add friend: " + error)
		);
	}

	private void removeFriend(final User user) {
		Communities.removeFriends(
						UserIdList.create(user.getId()),
						integer -> Toast.makeText(getContext(), user.getDisplayName() + " is not your friend anymore!", Toast.LENGTH_SHORT).show(),
						error -> _log.logErrorAndToast("Failed to remove friend: " + error)
		);
	}

	private void follow(final User user) {
		Communities.follow(
						FollowQuery.users(UserIdList.create(user.getId())),
						integer -> Toast.makeText(getContext(), "Following " + user.getDisplayName(), Toast.LENGTH_SHORT).show(),
						error -> _log.logErrorAndToast("Failed to add friend: " + error)
		);
	}

	private void unfollow(final User user) {
		Communities.unfollow(
						FollowQuery.users(UserIdList.create(user.getId())),
						integer -> Toast.makeText(getContext(), "Unfollowed " + user.getDisplayName(), Toast.LENGTH_SHORT).show(),
						error -> _log.logErrorAndToast("Failed to add friend: " + error)
		);
	}

	protected byte[] getBytesFromInputStream(InputStream is) throws IOException {
		ByteArrayOutputStream os = new ByteArrayOutputStream();
		byte[] buffer = new byte[0xFFFF];
		for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
			os.write(buffer, 0, len);
		}
		return os.toByteArray();
	}

	protected void disconnectFromFacebook() {

		if (AccessToken.getCurrentAccessToken() == null) {
			return; // already logged out
		}

		new GraphRequest(AccessToken.getCurrentAccessToken(), "/me/permissions/", null, HttpMethod.DELETE, new GraphRequest.Callback() {
			@Override
			public void onCompleted(final GraphResponse graphResponse) {
				LoginManager.getInstance().logOut();
			}
		}).executeAsync();
	}


	public interface ActivityListener {
		void invalidateUi();

		void addContentFragment(Fragment fragment);

		void putSessionValue(String key, @Nullable String value);

		String getSessionValue(String key);

		DependenciesContainer dependencies();

		void onBackPressed();
	}

	protected interface ConflictResolution {
		void resolveWithCurrentUser();

		void resolveWithConflictUser();
	}

	protected interface AddUserIdentityOutcomeCallback {
		void onSuccess();

		void onFailure(GetSocialError exception);

		void onConflictResolvedWithCurrent();

		void onConflictResolvedWithRemote();
	}

	protected abstract class SafeCompletionCallback implements CompletionCallback {

		@Override
		public void onSuccess() {
			if (getContext() != null) {
				onSafeSuccess();
			}
		}

		protected abstract void onSafeSuccess();
	}
} public class ChatMessagesFragment extends BaseFragment {
	public static final String TAG = "chat_fragment";

	private Button _sendBtn;
	private Button _refreshBtn;
	private RecyclerView _list;
	private EditText _messageInput;
	private EditText _messageImageUrl;
	private EditText _messageVideoUrl;
	private CheckBox _sendImage;
	private CheckBox _sendVideo;
	private MessageListAdapter _adapter;
	private ChatId _chatId;
	private String _title;
	private EndlessRecyclerViewScrollListener _scrollListener;
	private ChatMessagesQuery _query;
	private String _newMessagesCursor;
	private String _refreshCursor;
	private String _oldMessagesCursor;

	public SwipeRefreshLayout _swipeRefreshLayout;

	public static ChatMessagesFragment openChat(final String chatId) {
		final Bundle args = new Bundle();
		args.putString("CHATID", chatId);
		final ChatMessagesFragment fragment = new ChatMessagesFragment();
		fragment.setArguments(args);
		return fragment;
	}

	public static ChatMessagesFragment chatWith(final String userId) {
		final Bundle args = new Bundle();
		args.putString("USERID", userId);
		final ChatMessagesFragment fragment = new ChatMessagesFragment();
		fragment.setArguments(args);
		return fragment;
	}

	public static ChatMessagesFragment chatWith(final Chat chat) {
		final Bundle args = new Bundle();
		args.putString("CHATID", chat.getId());
		args.putString("TITLE", chat.getTitle());
		final ChatMessagesFragment fragment = new ChatMessagesFragment();
		fragment.setArguments(args);
		return fragment;
	}

	@Override
	public void onCreate(@Nullable final Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		if (savedInstanceState != null) {
			setArguments(savedInstanceState);
		}
	}

	@Nullable
	@Override
	public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
		final View view = inflater.inflate(R.layout.fragment_friend_chat, container, false);
		_swipeRefreshLayout = view.findViewById(R.id.swiperefresh);

		_sendBtn = view.findViewById(R.id.message_send);
		_list = view.findViewById(R.id.message_list);
		final DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL);
		_list.addItemDecoration(dividerItemDecoration);
		_messageInput = view.findViewById(R.id.message_text);
		_messageImageUrl = view.findViewById(R.id.message_image);
		_messageVideoUrl = view.findViewById(R.id.message_video);
		_sendImage = view.findViewById(R.id.send_image);
		_sendVideo = view.findViewById(R.id.send_video);
		_refreshBtn = view.findViewById(R.id.refresh_messages);

		final LinearLayoutManager manager = new LinearLayoutManager(getContext());
		_list.setLayoutManager(manager);
		_swipeRefreshLayout.setOnRefreshListener(() -> {
			loadOlderMessages();
			_swipeRefreshLayout.setRefreshing(false);
		});

		getValuesFromArguments(getArguments());

		initListeners();
		getMessages();

		return view;
	}

	private void getValuesFromArguments(Bundle savedBundle) {
		if (savedBundle.containsKey("CHATID")) {
			this._chatId = ChatId.create(savedBundle.getString("CHATID"));
			this._query = ChatMessagesQuery.messagesInChat(this._chatId);
			String title = savedBundle.getString("TITLE");
			if (title != null) {
				this._title = title;
			} else {
				Communities.getChat(this._chatId, (chat) -> {
					this._title = chat.getTitle();
					((ActivityListener)getActivity()).invalidateUi();
				}, (error) -> {
					this._title = "not found";
					((ActivityListener)getActivity()).invalidateUi();
				});
			}
		} else {
			String userIdStr = savedBundle.getString("USERID");
			UserId userId = UserId.create(userIdStr);
			this._chatId = ChatId.createWithUserId(userId);
			this._query = ChatMessagesQuery.messagesInChat(this._chatId);
			Communities.getUser(userId, (user) -> {
				this._title = user.getDisplayName();
				((ActivityListener)getActivity()).invalidateUi();
			}, (error) -> {
				System.out.println("Failed to load user, no title");
			});
		}
	}

	private void loadOlderMessages() {
		if (TextUtils.isEmpty(this._oldMessagesCursor)) {
			return;
		}
		ChatMessagesPagingQuery pagingQuery = new ChatMessagesPagingQuery(this._query).withPreviousMessagesCursor(this._oldMessagesCursor);
		showLoading("Wait", "Loading older messages");
		Communities.getChatMessages(pagingQuery, result -> {
			_oldMessagesCursor = result.previousMessagesCursor();
			if (result.getEntries().size() != 0) {
				_adapter.insertNewMessages(result.getEntries());
				_list.scrollToPosition(0);
			}
			hideLoading();
		}, exception -> {
			hideLoading();
			_log.logErrorAndToast(exception);
		});
	}


	private void loadMore() {
		ChatMessagesPagingQuery pagingQuery = new ChatMessagesPagingQuery(this._query).withNextMessagesCursor(this._refreshCursor);
		showLoading("Wait", "Loading new messages");
		Communities.getChatMessages(pagingQuery, result -> {
			_newMessagesCursor = result.nextMessagesCursor();
			_refreshCursor = result.refreshCursor();
			if (result.getEntries().size() != 0) {
				_adapter.appendNewMessages(result.getEntries());
				_list.scrollToPosition(_adapter.getItemCount() - 1);
			}
			hideLoading();
		}, exception -> {
			hideLoading();
			_log.logErrorAndToast(exception);
		});
	}

	private void getMessages() {
		ChatMessagesPagingQuery pagingQuery = new ChatMessagesPagingQuery(this._query);
		Communities.getChatMessages(pagingQuery, result -> {
			_newMessagesCursor = result.nextMessagesCursor();
			_refreshCursor = result.refreshCursor();
			_oldMessagesCursor = result.previousMessagesCursor();
			initList(result.getEntries());
		}, exception -> {
			_log.logErrorAndToast(exception);
		});
	}

	private void initListeners() {
		_sendBtn.setOnClickListener(view -> sendMessage());
		_refreshBtn.setOnClickListener(view -> loadMore());
	}

	private void initList(final List<ChatMessage> messages) {
		_list.setHasFixedSize(true);
		_list.setLayoutManager(new LinearLayoutManager(getActivity()));
		_adapter = new MessageListAdapter(messages);
		_adapter.clickListener = message -> showAlert("Details", message.toString());
		_list.setAdapter(_adapter);
		_list.scrollToPosition(messages.size() - 1);
	}

	@Override
	public String getFragmentTag() {
		return TAG;
	}

	@Override
	public String getTitle() {
		return this._title == null ? "Unknown" : this._title;
	}

	private void sendMessage() {
		if (!_messageInput.getText().toString().isEmpty() || _sendImage.isChecked() || _sendVideo.isChecked() || !TextUtils.isEmpty(_messageImageUrl.getText()) || !TextUtils.isEmpty(_messageVideoUrl.getText())) {
			final ChatMessageContent content = ChatMessageContent.createWithText(_messageInput.getText().toString());
			if (_sendImage.isChecked()) {
				content.addAttachment(MediaAttachment.image(BitmapFactory.decodeResource(getContext().getResources(),
						R.drawable.activity_image)));
			}
			if (_sendVideo.isChecked()) {
				try {
					InputStream inStream = getContext().getResources().openRawResource(R.raw.giphy);
					byte[] video = getBytesFromInputStream(inStream);
					content.addAttachment(MediaAttachment.video(video));
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (!TextUtils.isEmpty(_messageImageUrl.getText())) {
				content.addAttachment(MediaAttachment.imageUrl(_messageImageUrl.getText().toString()));
			}
			if (!TextUtils.isEmpty(_messageVideoUrl.getText())) {
				content.addAttachment(MediaAttachment.videoUrl(_messageVideoUrl.getText().toString()));
			}
			showLoading("Sending message", "Please wait...");
			Communities.sendChatMessage(content, this._chatId, result -> {
				hideLoading();
				_messageInput.getText().clear();
				_messageImageUrl.getText().clear();
				_messageVideoUrl.getText().clear();
				_adapter.appendNewMessage(result);
				_list.scrollToPosition(_adapter.getItemCount() - 1);
			}, exception -> hideLoading());
		} else {
			showAlert("Error", "Text, image or video is mandatory");
		}
	}
} public class ConsoleFragment extends BaseFragment {

	private ViewContainer _viewContainer;

	public ConsoleFragment() {
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		return inflater.inflate(R.layout.fragment_console, container, false);
	}

	@Override
	public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);

		_viewContainer = new ViewContainer(view);

		setHasOptionsMenu(true);

		populateConsoleContent();

		scrollToBottom();
	}

	@Override
	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
		menu.clear();
		inflater.inflate(R.menu.menu_console, menu);
		MenuItem item = menu.findItem(R.id.action_shareConsole);
		ShareActionProvider shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
		shareActionProvider.setShareIntent(getDefaultShareIntent());
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
			case R.id.action_cleanConsole:
				Console.clear();
				populateConsoleContent();
				return true;

			default:
				return super.onOptionsItemSelected(item);
		}
	}

	@Override
	public String getTitle() {
		return "Console";
	}

	private Intent getDefaultShareIntent() {
		Intent intent = new Intent(Intent.ACTION_SEND);
		intent.setType("text/plain")
				.putExtra(Intent.EXTRA_SUBJECT, getShareSubject())
				.putExtra(Intent.EXTRA_TEXT, getShareText());

		return intent;
	}

	private String getShareSubject() {
		return String.format("[%s] GetSocial Android Demo Logs", DateFormat.getDateTimeInstance().format(new Date()));
	}

	private String getShareText() {
		return "LOGS:\n" + CompatibilityUtils.fromHtml(Console.getFormattedOutput());
	}

	private void populateConsoleContent() {
		_viewContainer._consoleTextView.setText(CompatibilityUtils.fromHtml(Console.getFormattedOutput()));
	}

	private void scrollToBottom() {
		_viewContainer._scrollView.post(
				new Runnable() {
					public void run() {
						_viewContainer._scrollView.smoothScrollTo(0, _viewContainer._consoleTextView.getBottom());
					}
				}
		);
	}

	@Override
	public String getFragmentTag() {
		return "console";
	}

	static class ViewContainer {

		@BindView(R.id.console_textView)
		TextView _consoleTextView;
		@BindView(R.id.console_scrollView)
		ScrollView _scrollView;

		ViewContainer(View view) {
			ButterKnife.bind(this, view);
		}
	}
} public class FriendsFragment extends BaseUsersListFragment<FriendsQuery> {

	public static final String KEY_FRIENDS_COUNT = "GetSocial_FriendsCount_Key";

	@Override
	public String getFragmentTag() {
		return "friends";
	}

	@Override
	public String getTitle() {
		return "Friends";
	}

	@Override
	protected void load(final PagingQuery<FriendsQuery> query, final Callback<PagingResult<User>> success, final FailureCallback failure) {
		Communities.getFriends(query, success, failure);
	}

	@Override
	public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
		menu.add(Menu.NONE, 0x42, Menu.NONE, "Suggest Friends");
		menu.add(Menu.NONE, 0x43, Menu.NONE, "Add Friend");
		super.onCreateOptionsMenu(menu, inflater);
	}

	@Override
	public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);
		_query.setVisibility(View.GONE);
	}

	@Override
	public boolean onOptionsItemSelected(final android.view.MenuItem item) {
		if (item.getItemId() == 0x42) {
			suggestFriends();
			return true;
		}
		if (item.getItemId() == 0x43) {
			showAddFriendDialog();
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	protected FriendsQuery createQuery(final SearchObject searchObject) {
		return FriendsQuery.ofUser(UserId.currentUser());
	}

	private void showAddFriendDialog() {
		final EditText input = new EditText(getContext());
		new AlertDialog.Builder(getContext())
						.setTitle("Add friend")
						.setView(input)
						.setPositiveButton("Add", (dialog, which) -> {
							String userId = input.getText().toString();
							Communities.addFriends(
											UserIdList.create(userId),
											integer -> Toast.makeText(getContext(), userId + " is now your friend!", Toast.LENGTH_SHORT).show(),
											error -> _log.logErrorAndToast("Failed to add friend: " + error)
							);
						})
						.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss())
						.show();
	}

	private void suggestFriends() {
		showLoading("Loading suggested friends", "Wait please...");
		Communities.getSuggestedFriends(SimplePagingQuery.simple(10), result -> {
			hideLoading();
			if (result.getEntries().isEmpty()) {
				Toast.makeText(getContext(), "No more suggested friends available", Toast.LENGTH_SHORT).show();
			} else {
				showSuggestedFriends(result.getEntries());
			}
		}, error -> {
			hideLoading();
			_log.logErrorAndToast("Failed to get suggested friends: " + error);
		});
	}

	private void showSuggestedFriends(final List<SuggestedFriend> suggestedFriends) {
		final ListView friendsView = new ListView(getContext());
		final ArrayAdapter arrayAdapter = new SuggestedFriendsAdapter(friendsView, getContext(), suggestedFriends);
		friendsView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
		friendsView.setAdapter(arrayAdapter);
		friendsView.setOnItemClickListener((parent, view, position, id) -> arrayAdapter.notifyDataSetChanged());
		new AlertDialog.Builder(getContext())
						.setTitle("Suggested friends")
						.setView(friendsView)
						.setPositiveButton("Add Friends", (dialog, which) -> {
							final SparseBooleanArray checked = friendsView.getCheckedItemPositions();
							final List<SuggestedFriend> friendsToAdd = new ArrayList<>(checked.size());
							for (int i = 0; i < arrayAdapter.getCount(); i++) {
								if (checked.get(i)) {
									friendsToAdd.add(suggestedFriends.get(i));
								}
							}
							addFriends(friendsToAdd);
							dialog.dismiss();
						})
						.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss())
						.show();
	}

	private void addFriends(final List<SuggestedFriend> friendsToAdd) {
		if (friendsToAdd.isEmpty()) {
			return;
		}
		showLoading("Adding friends", "Wait please...");
		final List<String> userIds = new ArrayList<>();
		for (final SuggestedFriend friend : friendsToAdd) {
			userIds.add(friend.getId());
		}
		Communities.addFriends(UserIdList.create(userIds),
						integer -> {
							hideLoading();
							onFriendsUpdated();
						},
						error -> {
							hideLoading();
							Toast.makeText(getContext(), "Failed to add friends: " + error, Toast.LENGTH_SHORT).show();
						});
	}

	private void onFriendsUpdated() {
		loadItems();
	}

	private void removeFriend(final User user) {
		showLoading("Removing friend", "Wait...");
		Communities.removeFriends(UserIdList.create(user.getId()), numberOfFriends -> {
			Toast.makeText(getContext(), "User " + user.getDisplayName() + " was removed from your friends list.", Toast.LENGTH_SHORT).show();
			onFriendsUpdated();
		}, this::onFailure);
	}

	private void onFailure(final GetSocialError error) {
		Toast.makeText(getContext(), error.getMessage(), Toast.LENGTH_SHORT).show();
		hideLoading();
	}

	class FriendsAdapter extends ArrayAdapter<User> {

		FriendsAdapter(final Context context, final List<User> objects) {
			super(context, 0, objects);
		}

		@NonNull
		@Override
		public View getView(final int position, @Nullable View convertView, final ViewGroup parent) {
			final ViewHolder holder;
			if (convertView == null) {
				convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item_friend, null);
				convertView.setTag(holder = new ViewHolder(convertView));
			} else {
				holder = (ViewHolder) convertView.getTag();
			}
			holder.setUser(getItem(position));

			return convertView;
		}

		class ViewHolder {

			@BindView(R.id.user_avatar)
			ImageView _avatar;
			@BindView(R.id.user_name)
			TextView _userName;
			private User _user;

			ViewHolder(final View view) {
				ButterKnife.bind(this, view);
			}

			void setUser(final User user) {
				_user = user;
				populate();
			}

			private void populate() {
				_userName.setText(_user.getDisplayName());
				if (TextUtils.isEmpty(_user.getAvatarUrl())) {
					Picasso.with(getContext())
									.load(R.drawable.avatar_default)
									.transform(new CircleTransform())
									.into(_avatar);
					return;
				}
				Picasso.with(getContext())
								.load(_user.getAvatarUrl())
								.placeholder(R.drawable.avatar_default)
								.transform(new CircleTransform())
								.into(_avatar);
			}

			@OnClick(R.id.remove_friend_btn)
			void removeFriend() {
				FriendsFragment.this.removeFriend(_user);
			}

//			@OnClick(R.id.message_friend_btn)
//			void openChatDialog() {
//				final Bundle bundle = new Bundle();
//				bundle.putString(ChatMessagesFragment.KEY_RECIPIENT_ID, _user.getId());
//				bundle.putString(ChatMessagesFragment.KEY_RECIPIENT_NAME, _userName.getText().toString());
//
//				final ChatMessagesFragment chatFragment = new ChatMessagesFragment();
//				chatFragment.setArguments(bundle);
//
//				addContentFragment(chatFragment);
//			}
		}

	}

	class SuggestedFriendsAdapter extends ArrayAdapter<SuggestedFriend> {

		private final ListView _listView;

		SuggestedFriendsAdapter(final ListView listView, @NonNull final Context context, @NonNull final List<SuggestedFriend> objects) {
			super(context, 0, objects);
			_listView = listView;
		}

		@NonNull
		@Override
		public View getView(final int position, @Nullable View convertView, @NonNull final ViewGroup parent) {
			final ViewHolder holder;
			if (convertView == null) {
				convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item_suggested_friend, null);
				convertView.setTag(holder = new ViewHolder(convertView));
			} else {
				holder = (ViewHolder) convertView.getTag();
			}
			final SuggestedFriend user = getItem(position);
			holder.setUser(user);
			if (_listView.getCheckedItemPositions().get(position)) {
				convertView.setBackgroundColor(getResources().getColor(R.color.grey));
			} else {
				convertView.setBackgroundColor(getResources().getColor(R.color.transparent));
			}

			return convertView;
		}

		class ViewHolder {

			@BindView(R.id.user_avatar)
			ImageView _avatar;
			@BindView(R.id.user_name)
			TextView _userName;
			@BindView(R.id.mutual_friends)
			TextView _mutualFriends;
			private SuggestedFriend _user;

			ViewHolder(final View view) {
				ButterKnife.bind(this, view);
			}

			void setUser(final SuggestedFriend user) {
				_user = user;
				populate();
			}

			private void populate() {
				_userName.setText(_user.getDisplayName());
				_mutualFriends.setText(String.valueOf(_user.getMutualFriendsCount()));
				if (TextUtils.isEmpty(_user.getAvatarUrl())) {
					Picasso.with(getContext())
									.load(R.drawable.avatar_default)
									.transform(new CircleTransform())
									.into(_avatar);
					return;
				}
				Picasso.with(getContext())
								.load(_user.getAvatarUrl())
								.placeholder(R.drawable.avatar_default)
								.transform(new CircleTransform())
								.into(_avatar);
			}
		}
	}

} public interface HasFragmentTag {
	String getFragmentTag();
} public interface HasTitle {
	String getTitle();
} public class RootFragment extends BaseListFragment implements NotificationsManager.Listener {

	private NotificationsManager _notificationsManager;

	public RootFragment() {
	}

	@Override
	protected void inject(final DependenciesContainer dependencies) {
		_notificationsManager = dependencies.notificationsManager();
	}

	@Override
	public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);
		_notificationsManager.addListener(this);
	}

	@Override
	public void onResume() {
		super.onResume();
		GetSocial.addOnInitializeListener(_notificationsManager::sync);
		if (!GetSocial.isInitialized()) {
			return;
		}
		Communities.getFriendsCount(FriendsQuery.ofUser(UserId.currentUser()), friendsCount -> {
			_activityListener.putSessionValue(FriendsFragment.KEY_FRIENDS_COUNT, String.valueOf(friendsCount));
			invalidateList();
		}, error -> Console.logError(error.getMessage()));
	}

	@Override
	protected List<MenuItem> createListData() {
		return Arrays.asList(
						navigationListItem("User Management", UserManagementFragment.class, builder -> builder.withEnabledCheck(GetSocial::isInitialized)),
						navigationListItem("Friends", FriendsFragment.class, builder -> builder.withSubtitle(() -> {
							final String friendsCount = _activityListener.getSessionValue(FriendsFragment.KEY_FRIENDS_COUNT);
							final String count = TextUtils.isEmpty(friendsCount) ? "0" : friendsCount;
							return "You have " + count + " friends";
						})),
						navigationListItem("Invites", InvitesFragment.class),
						navigationListItem("Activities", ActivitiesFragment.class),
						navigationListItem("Topics", TopicsSearchFragment.class),
						navigationListItem("Tags", TagsListFragment.class),
						navigationListItem("Labels", LabelsListFragment.class),
						navigationListItem("Groups", GroupsFragment.class),
						navigationListItem("Chats", ChatsFragment.class),
						navigationListItem("Users", UsersSearchFragment.class),
						navigationListItem("Users by IDs", TestUsersByIdFragment.class),
						navigationListItem("Notifications", NotificationsFragment.class, builder -> builder.withSubtitle(() -> "You have " + _notificationsManager.getNewNotificationsCount() + " new notifications")),
						navigationListItem("Promo Codes", PromoCodesFragment.class),
						navigationListItem("UI Customization", UiCustomizationFragment.class, builder -> builder.withSubtitle(() -> {
							final String savedName = _activityListener.getSessionValue(UiCustomizationFragment.UI_CONFIGURATION_NAME_KEY);
							if (TextUtils.isEmpty(savedName)) {
								return "Current UI: default";
							} else {
								return "Current UI: " + savedName;
							}
						})),
						navigationListItem("Settings", SettingsFragment.class),
						navigationListItem("IAP", PurchaseFragment.class),
						navigationListItem("Custom Analytics Events", CustomAnalyticsEventsFragment.class),
						MenuItem.builder("Init with new anonymous user").withAction(() -> {
							if (GetSocial.isInitialized()) {
								_log.logInfoAndToast("Already initialized, call resetWithoutInit first");
								return;
							}
							GetSocial.init();
							GetSocial.addOnInitializeListener(() -> {
								_log.logInfoAndToast("Anonymous User logged in");
								invalidateUi();
							});
						}).withEnabledCheck(() -> !GetSocial.isInitialized()).build(),
						MenuItem.builder("Init with...").withAction(() -> loginWith())
										.withEnabledCheck(() -> !GetSocial.isInitialized()).build());

	}

	//region Presenter
	@Override
	public String getTitle() {
		return "";
	}

	@Override
	public String getFragmentTag() {
		return "root";
	}

	@Override
	public void onSync() {
		invalidateList();
	}


	private void loginWith() {
		final CompletionCallback callback = () -> {
			_log.logInfoAndToast("Successfully logged in");
			invalidateUi();
		};
		final FailureCallback failureCallback = exception -> {
			_log.logErrorAndToast(exception);
			invalidateUi();
		};
		new ActionDialog(getContext())
				.addAction(new ActionDialog.Action("Facebook") {
					@Override
					public void execute() {
						loginWithFacebook(callback, failureCallback);
					}
				})
				.addAction(new ActionDialog.Action("Custom Identity") {
					@Override
					public void execute() {
						loginWithCustom(callback, failureCallback);
					}
				})
				.addAction(new ActionDialog.Action("Trusted Identity") {
					@Override
					public void execute() {
						loginWithTrusted(callback, failureCallback);
					}
				})
				.setTitle("Login with...").show();
	}

	private void invalidateUi() {
		_activityListener.invalidateUi();
		invalidateList();
	}

	protected void loginWithFacebook(final CompletionCallback callback, final FailureCallback failureCallback) {
		final CompletionCallback wrapped = () -> {
			callback.onSuccess();
			setFacebookInfo();
		};
		final AccessTokenTracker accessTokenTracker = new AccessTokenTracker() {
			@Override
			protected void onCurrentAccessTokenChanged(final AccessToken oldAccessToken, @javax.annotation.Nullable final AccessToken newAccessToken) {

				if (newAccessToken == null) {
					failureCallback.onFailure(new GetSocialError(ErrorCode.UNKNOWN, "Facebook SDK did not provide an AccessToken"));
					return;
				}

				stopTracking();

				final String tokenString = newAccessToken.getToken();

				GetSocial.init(Identity.facebook(tokenString), wrapped, failureCallback);
			}
		};
		accessTokenTracker.startTracking();
		LoginManager.getInstance().logInWithReadPermissions(getActivity(), FACEBOOK_PERMISSIONS);
	}

	protected void loginWithCustom(final CompletionCallback callback, final FailureCallback failureCallback) {
		final String providerId = CUSTOM_PROVIDER;

		final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
		final View view = layoutInflater.inflate(R.layout.dialog_custom_identity, null, false);

		final EditText userIdEditText = view.findViewById(R.id.user_id);
		final EditText tokenEditText = view.findViewById(R.id.user_token);

		final AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
						.setView(view)
						.setPositiveButton("Add", new DialogInterface.OnClickListener() {
											@Override
											public void onClick(final DialogInterface dialog, final int which) {
												final String userId = userIdEditText.getText().toString().trim();
												final String token = tokenEditText.getText().toString().trim();
												GetSocial.init(Identity.custom(providerId, userId, token), callback, failureCallback);
											}
										}
						)
						.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
											@Override
											public void onClick(final DialogInterface dialog, final int which) {

											}
										}
						);
		builder.show();
	}

	protected void loginWithTrusted(final CompletionCallback callback, final FailureCallback failureCallback) {
		final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
		final View view = layoutInflater.inflate(R.layout.dialog_custom_identity, null, false);

		final EditText providerIdEditText = view.findViewById(R.id.user_id);
		final EditText tokenEditText = view.findViewById(R.id.user_token);

		final AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
				.setView(view)
				.setPositiveButton("Add", new DialogInterface.OnClickListener() {
							@Override
							public void onClick(final DialogInterface dialog, final int which) {
								final String providerId = providerIdEditText.getText().toString().trim();
								final String token = tokenEditText.getText().toString().trim();
								GetSocial.init(Identity.custom(providerId, null, token), callback, failureCallback);
							}
						}
				)
				.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
							@Override
							public void onClick(final DialogInterface dialog, final int which) {

							}
						}
				);
		builder.show();
	}
	//endregion
} public class FacebookSharePlugin extends InviteChannelPlugin {

	private final Activity _activity;
	private final CallbackManager _callbackManager;
	private final ConnectivityManager _connectivityManager;

	public FacebookSharePlugin(final Activity activity, final CallbackManager callbackManager) {
		_activity = activity;
		_callbackManager = callbackManager;
		_connectivityManager = (ConnectivityManager) activity.getSystemService(Context.CONNECTIVITY_SERVICE);
	}

	@Override
	public boolean isAvailableForDevice(final InviteChannel inviteChannel) {
		// always available, if Facebook app not installed, it opens webview.
		return true;
	}

	@Override
	public void presentChannelInterface(final InviteChannel inviteChannel, final Invite invite, final InviteCallback callback) {
		if (isConnected()) {
			final ShareLinkContent.Builder shareLinkBuilder = new ShareLinkContent.Builder();

			shareLinkBuilder.setContentUrl(Uri.parse(invite.getReferralUrl()));
			shareLinkBuilder.setQuote(invite.getText());

			final ShareLinkContent sharedContent = shareLinkBuilder.build();

			final ShareDialog shareDialog = new ShareDialog(_activity);
			shareDialog.registerCallback(_callbackManager, new FacebookCallback<ShareDialog.Result>() {
								@Override
								public void onSuccess(final ShareDialog.Result result) {
									callback.onComplete();
								}

								@Override
								public void onCancel() {
									callback.onCancel();
								}

								@Override
								public void onError(final FacebookException facebookException) {
									callback.onError(facebookException);
								}
							}
			);
			if (isFacebookAppInstalled()) {
				shareDialog.show(sharedContent, ShareDialog.Mode.NATIVE);
			} else {
				shareDialog.show(sharedContent, ShareDialog.Mode.WEB);
			}
		} else {
			onError("Can't reach Facebook. No internet connection.", callback);
		}
	}

	private boolean isFacebookAppInstalled() {
		final PackageManager pm = _activity.getPackageManager();
		boolean app_installed = false;
		try {
			PackageInfo info = pm.getPackageInfo("com.facebook.katana", PackageManager.GET_ACTIVITIES);
			app_installed = info.applicationInfo.enabled;
		} catch (final PackageManager.NameNotFoundException exception) {
			app_installed = false;
		}
		return app_installed;
	}

	private boolean isConnected() {
		final NetworkInfo networkInfo = _connectivityManager.getActiveNetworkInfo();
		return networkInfo != null && networkInfo.isConnected();
	}

	private void onError(final String message, final InviteCallback callback) {
		callback.onError(new Exception(message));
		showToastOnMainThread(getContext(), message);
	}

	private void showToastOnMainThread(final Context context, final String message) {
		_activity.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				Toast.makeText(context, message, Toast.LENGTH_LONG).show();
			}
		});
	}

} public class InstagramStoriesPlugin extends InviteChannelPlugin {

	private static final String PACKAGE_NAME = "com.instagram.android";

	public InstagramStoriesPlugin() {
	}

	/**
	 * Save remote resource to local FS and returns URI for saved file.
	 *
	 * @param context android context to use FS.
	 * @param url     url to file to be saved.
	 */
	private static void saveResourceToDisk(final Context context, final String url, final InstagramStoriesDownloadResourceListener listener) {
		final String fileName;
		final String fileUri;
		if (url.endsWith("gif")) {
			fileName = "tempgif.gif";
			fileUri = "invite-gif.gif";
		} else if (url.endsWith("mp4")) {
			fileName = "tempvideo.mp4";
			fileUri = "invite-video.mp4";
		} else {
			System.out.println("Invalid invite resource format.");
			return;
		}

		try {
			new InstagramStoriesDownloadResourceTask(new InstagramStoriesDownloadFileListener() {
				@Override
				public void onFileLoaded(@Nullable final String filePath) {
					final String uriBase = getUriBase(context);
					listener.onResourceLoaded(new Uri.Builder()
									.scheme("content")
									.authority(uriBase)
									.path(getUriForFileName(fileName))
									.build());
				}
			}).execute(context, url, fileName, fileUri);
		} catch (final Exception exception) {
			System.out.println("Could not writeRemoteResourceFileToDisk contents of url, returning null. error: " + exception.getMessage());
		}
	}

	private static boolean writeRemoteResourceFileToDisk(final File target, final String source) {
		FileOutputStream fileOutputStream = null;
		InputStream inputStream = null;

		try {
			final URL url = new URL(source);
			final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
			connection.setDoInput(true);
			connection.setUseCaches(true);
			connection.connect();
			inputStream = connection.getInputStream();

			if (!target.createNewFile()) {
				System.out.println("Couldn't create new file " + target.getPath());
				return false;
			}
			fileOutputStream = new FileOutputStream(target);
			final byte[] buffer = new byte[256];
			int readBytes;
			while (-1 != (readBytes = inputStream.read(buffer))) {
				fileOutputStream.write(buffer, 0, readBytes);
			}
			return true;
		} catch (final IOException ioException) {
			System.out.println("Could not save url content to the cache directory, returning null. error: " + ioException.getMessage());
			return false;
		} finally {
			close(fileOutputStream);
			close(inputStream);
		}
	}

	/**
	 * Safe close the closeable.
	 *
	 * @param closeable stream to close.
	 */
	private static void close(@Nullable final Closeable closeable) {
		if (closeable == null) {
			return;
		}
		try {
			closeable.close();
		} catch (final IOException exception) {
			System.out.println("Failed to close the stream: " + exception.getMessage());
		}
	}

	/**
	 * Get base image URI.
	 *
	 * @param context Android Context.
	 * @return Base image URI if ImageContentProvider is present, null otherwise.
	 */
	private static String getUriBase(final Context context) {
		final ProviderInfo imageContentProvider = getImageContentProvider(context);
		if (imageContentProvider != null) {
			return imageContentProvider.authority;
		}

		System.out.println("Can not create media content URI, %s is not found in the AndroidManifest.xml: " + ImageContentProvider.class.getSimpleName());
		return null;
	}

	/**
	 * Get sharable file name for uri construction.
	 *
	 * @param fileName Cached resource file name according to ImageContract.
	 * @return Uri path part for content sharing.
	 */
	@Nullable
	private static String getUriForFileName(final String fileName) {
		switch (fileName) {
			case "tempimage.jpg":
				return "invite-image.jpg";
			case "tempgif.gif":
				return "invite-gif.gif";
			case "tempsticker.png":
				return "invite-sticker.png";
			case "tempvideo.mp4":
				return "invite-video.mp4";
			default:
				return null;
		}
	}

	/**
	 * Save bitmap to local FS and returns URI for saved file.
	 *
	 * @param context android context to use FS.
	 * @param bitmap  image to be saved.
	 * @return URI for created file.
	 */
	@Nullable
	private static Uri saveBitmapToDisk(final Context context, final Bitmap bitmap, final boolean isPng, final String fileName) {
		final String uriBase = getUriBase(context);
		if (uriBase == null) {
			return null;
		}
		if (writeImageFileToDisk(context, bitmap, isPng, fileName)) {
			return new Uri.Builder()
							.scheme("content")
							.authority(uriBase)
							.path(getUriForFileName(fileName))
							.build();
		}
		return null;
	}

	private static boolean writeImageFileToDisk(final Context context, final Bitmap bitmap, final boolean isPng, final String fileName) {
		final File tempImageFile = new File(context.getCacheDir(), fileName);
		if (tempImageFile.exists() && !tempImageFile.delete()) {
			System.out.println("Couldn't delete the old file " + tempImageFile.getPath());
			return false;
		}
		FileOutputStream fileOutputStream = null;
		try {
			if (!tempImageFile.createNewFile()) {
				System.out.println("Couldn't create the new file " + tempImageFile.getPath());
				return false;
			}
			fileOutputStream = new FileOutputStream(tempImageFile);
			final Bitmap.CompressFormat saveAs = isPng ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG;
			bitmap.compress(saveAs, 100, fileOutputStream);
			return true;
		} catch (final IOException ioException) {
			System.out.println("Could not save image to the cache directory, returning null. error: " + ioException.getMessage());
			return false;
		} finally {
			close(fileOutputStream);
		}
	}

	/**
	 * Creates a rounded rectangular bitmap with text.
	 *
	 * @param stickerText text to be drawn.
	 * @return bitmap with text.
	 */
	private static Bitmap generateBitmapFromText(final String stickerText) {
		final int backgroundColor = Color.WHITE;
		final int textColor = Color.RED;

		final int paddingLeft = 15;
		final int paddingTop = 15;
		final int cornerRadius = 15;
		final int charsInLine = 40;
		//make sticker smaller if text is small
		final int width = stickerText.length() > charsInLine ? 400 : 200;
		final int bgWidth = width + (paddingLeft * 2);

		final Paint backgroundPaint = new Paint();
		backgroundPaint.setStyle(Paint.Style.FILL);
		backgroundPaint.setColor(backgroundColor);

		final TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
		textPaint.setTextSize(25);
		textPaint.setColor(textColor);
		textPaint.setAntiAlias(true);

		final Layout.Alignment alignment = stickerText.length() > charsInLine ? Layout.Alignment.ALIGN_NORMAL : Layout.Alignment.ALIGN_CENTER;
		final StaticLayout staticLayout = new StaticLayout(stickerText, textPaint, width, alignment, 1, 0, false);

		final RectF background = new RectF();
		background.bottom = staticLayout.getHeight() + (paddingTop * 2);
		background.right = bgWidth;
		background.left = 0;
		background.top = 0;

		final Bitmap image = Bitmap.createBitmap(bgWidth, staticLayout.getHeight() + paddingTop * 2, Bitmap.Config.ARGB_8888);
		final Canvas canvas = new Canvas(image);
		canvas.drawRoundRect(background, cornerRadius, cornerRadius, backgroundPaint);

		canvas.save();
		canvas.translate(paddingLeft, paddingTop);
		staticLayout.draw(canvas);

		return image;
	}

	/**
	 * Get info about GetSocial Image Content Provider.
	 *
	 * @param context Android context.
	 * @return info or null if not available.
	 */
	@Nullable
	private static ProviderInfo getImageContentProvider(final Context context) {
		final String providerName = ImageContentProvider.class.getName();
		try {
			final PackageManager pm = context.getPackageManager();
			final PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS);
			final ProviderInfo[] providers = packageInfo.providers;
			if (providers != null) {
				for (final ProviderInfo provider : providers) {
					if (providerName.equals(provider.name)) {
						return provider;
					}
				}
			}
		} catch (final PackageManager.NameNotFoundException exception) {
			System.out.println("Failed to check if " + providerName + " is declared in the AndroidManifest, error: " + exception.getMessage());
		}
		return null;
	}

	@Override
	public boolean isAvailableForDevice(final InviteChannel inviteChannel) {
		return hasInstagramInstalled(getContext());
	}

	@Override
	@SuppressWarnings({"PMD.ConfusingTernary"})
	public void presentChannelInterface(final InviteChannel inviteChannel, final Invite invite, final InviteCallback callback) {
		if (invite.getVideoUrl() != null) {
			saveResourceToDisk(getContext(), invite.getVideoUrl(), new InstagramStoriesDownloadResourceListener() {
				@Override
				public void onResourceLoaded(@Nullable final Uri uri) {
					if (uri != null) {
						sendContent(invite, uri, "video/*", callback);
					}
				}
			});
		} else if (invite.getImage() != null) {
			final Uri uri = saveBitmapToDisk(getContext(), invite.getImage(), false, "tempimage.jpg");
			if (uri != null) {
				sendContent(invite, uri, "image/*", callback);
			}
		} else {
			sendContent(invite, null, "image/*", callback);
		}
	}

	private void sendContent(final Invite invite, @Nullable final Uri fileUri, final String mimeType, final InviteCallback callback) {
		final Intent intent = new Intent("com.instagram.share.ADD_TO_STORY");
		if (fileUri == null) {
			intent.setType(mimeType);
			intent.putExtra("top_background_color", "#000000");
		} else {
			intent.setDataAndType(fileUri, mimeType);
		}
		if (invite.getText() != null) {
			final Uri stickerUri = saveBitmapToDisk(getContext(), generateBitmapFromText(invite.getText()), true, "tempsticker.png");
			intent.putExtra("interactive_asset_uri", stickerUri);
			getContext().grantUriPermission(PACKAGE_NAME, stickerUri, FLAG_GRANT_READ_URI_PERMISSION);
		}
		intent.setFlags(FLAG_GRANT_READ_URI_PERMISSION);
		intent.putExtra("content_url", invite.getReferralUrl());

		if (getContext().getPackageManager().resolveActivity(intent, 0) != null) {
			intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			try {
				getContext().startActivity(intent);
				callback.onComplete();
			} catch (final Exception exception) {
				callback.onError(exception);
			}
		}
	}

	private boolean hasInstagramInstalled(final Context context) {
		try {
			context.getPackageManager().getPackageInfo(PACKAGE_NAME, 0);
			return true;
		} catch (final PackageManager.NameNotFoundException exception) {
			return false;
		}
	}

	public interface InstagramStoriesDownloadResourceListener {
		void onResourceLoaded(@Nullable Uri uri);
	}

	private interface InstagramStoriesDownloadFileListener {
		void onFileLoaded(@Nullable String filePath);
	}

	private static class InstagramStoriesDownloadResourceTask extends AsyncTask<Object, Integer, String> {

		private final InstagramStoriesDownloadFileListener _listener;

		InstagramStoriesDownloadResourceTask(final InstagramStoriesDownloadFileListener listener) {
			_listener = listener;
		}

		@Override
		protected String doInBackground(final Object... params) {
			final Context context = (Context) params[0];
			final String uriBase = getUriBase(context);

			if (uriBase == null) {
				return null;
			}

			final String fileName = (String) params[2];
			final File tempImageFile = new File(context.getCacheDir(), fileName);
			if (tempImageFile.exists() && !tempImageFile.delete()) {
				System.out.println("Failed to delete file " + tempImageFile.getPath());
				return null;
			}

			final String resourceUrl = (String) params[1];
			if (!writeRemoteResourceFileToDisk(tempImageFile, resourceUrl)) {
				return null;
			}

			return String.format("file://%s/%s", context.getCacheDir(), fileName);
		}

		@Override
		protected void onPostExecute(final String filePath) {
			_listener.onFileLoaded(filePath);
		}
	}

} public class KakaoInvitePlugin extends InviteChannelPlugin {

	private static final int SHARED_IMAGE_WIDTH = 300;

	private static final String PACKAGE_NAME = "com.kakao.talk";

	private static boolean hasKakaoInstalled(final Context context) {
		try {
			context.getPackageManager().getPackageInfo(PACKAGE_NAME, 0);
			return true;
		} catch (final PackageManager.NameNotFoundException e) {
			return false;
		}
	}

	@Override
	public boolean isAvailableForDevice(final InviteChannel inviteChannel) {
		return hasKakaoInstalled(getContext());
	}

	@Override
	public void presentChannelInterface(final InviteChannel inviteChannel, final Invite invite, final InviteCallback callback) {
		final LinkObject linkObject = LinkObject.newBuilder().setMobileWebUrl(invite.getReferralUrl()).build();
		final ContentObject.Builder contentObject = ContentObject.newBuilder(invite.getText(), invite.getImageUrl(), linkObject);

		if (invite.getImageUrl() != null && invite.getImage() != null) {
			final Bitmap image = invite.getImage();
			final double ratio = (double) image.getHeight() / (double) image.getWidth();
			final int height = (int) (ratio * SHARED_IMAGE_WIDTH);
			contentObject.setImageHeight(height);
			contentObject.setImageWidth(SHARED_IMAGE_WIDTH);
		}

		final FeedTemplate.Builder templateBuilder = FeedTemplate.newBuilder(contentObject.build());
		KakaoLinkService.getInstance().sendDefault(getContext(), templateBuilder.build(), new ResponseCallback<KakaoLinkResponse>() {
			@Override
			public void onFailure(final ErrorResult errorResult) {
				callback.onError(new Exception(errorResult.getErrorMessage()));
			}

			@Override
			public void onSuccess(final KakaoLinkResponse kakaoLinkResponse) {
				callback.onComplete();
			}
		});
	}
} public class VKInvitePlugin extends InviteChannelPlugin implements VKCallback<VKAccessToken> {

	private final Activity _activity;
	private InviteCallback _inviteCallback;
	private Invite _invite;

	public VKInvitePlugin(final Activity activity) {
		_activity = activity;
	}

	@Override
	public boolean isAvailableForDevice(final InviteChannel inviteChannel) {
		return true;
	}

	@Override
	public void presentChannelInterface(final InviteChannel inviteChannel, final Invite invite, final InviteCallback callback) {
		_inviteCallback = callback;
		_invite = invite;
		if (!VKSdk.isLoggedIn()) {
			VKSdk.login(_activity, VKScope.PHOTOS, VKScope.WALL);
		} else {
			openShareDialog();
		}
	}

	private void openShareDialog() {
		final VKShareDialogBuilder shareDialogBuilder = new VKShareDialogBuilder();
		shareDialogBuilder.setText(_invite.getText());
		if (_invite.getImage() != null) {
			final VKUploadImage uploadImage = new VKUploadImage(_invite.getImage(), VKImageParameters.pngImage());
			final VKUploadImage[] uploadImages = new VKUploadImage[] {uploadImage};
			shareDialogBuilder.setAttachmentImages(uploadImages);
		}
		shareDialogBuilder.setShareDialogListener(new VKShareDialog.VKShareDialogListener() {
			@Override
			public void onVkShareComplete(final int result) {
				_inviteCallback.onComplete();
			}

			@Override
			public void onVkShareCancel() {
				_inviteCallback.onCancel();
			}

			@Override
			public void onVkShareError(final VKError vkError) {
				_inviteCallback.onError(new Exception(vkError.errorMessage));
			}
		});
		shareDialogBuilder.show(_activity.getFragmentManager(), "VK_SHARE");
	}

	// VKCallback
	@Override
	public void onResult(final VKAccessToken vkAccessToken) {
		openShareDialog();
	}

	@Override
	public void onError(final VKError vkError) {
		_inviteCallback.onError(new Exception(vkError == null ? "Can not sent VK invite" : vkError.errorMessage));
	}
} public class PickActionView extends LinearLayout {

	public static final String KEY_USER_NAME = "user_name";

	private static final Map<String, List<String>> PLACEHOLDERS = new HashMap<String, List<String>>() {
		{
			put(ActionTypes.OPEN_PROFILE, Collections.singletonList(ActionDataKeys.OpenProfile.USER_ID));
			put(ActionTypes.OPEN_ACTIVITY, Arrays.asList(ActionDataKeys.OpenActivity.ACTIVITY_ID, ActionDataKeys.OpenActivity.COMMENT_ID, ActionDataKeys.OpenActivity.TOPIC_ID, ActionDataKeys.OpenActivity.USER_ID, ActionDataKeys.OpenActivity.GROUP_ID));
			put(ActionTypes.OPEN_INVITES, Collections.emptyList());
			put(ActionTypes.OPEN_URL, Collections.singletonList(ActionDataKeys.OpenUrl.URL));
			put(ActionTypes.CLAIM_PROMO_CODE, Collections.singletonList(ActionDataKeys.ClaimPromoCode.PROMO_CODE));
			put(ActionTypes.ADD_FRIEND, Collections.singletonList(ActionDataKeys.AddFriend.USER_ID));
		}
	};
	private static final String DEFAULT_ACTION = "$$DEFAULT";
	private static final String CUSTOM_ACTION = "custom";

	final List<DynamicUi.DynamicInputHolder> _notificationData = new ArrayList<>();
	@BindView(R.id.spinner_select_notification_type)
	Spinner _selectNotificationType;
	@BindView(R.id.container_notification_data)
	LinearLayout _notificationDataContainer;

	public PickActionView(final Context context) {
		super(context);
		init(context);
	}

	public PickActionView(final Context context, @Nullable final AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}

	public PickActionView(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		init(context);
	}

	@TargetApi(Build.VERSION_CODES.LOLLIPOP)
	@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
	public PickActionView(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
		super(context, attrs, defStyleAttr, defStyleRes);
		init(context);
	}

	private void init(final Context context) {
		LayoutInflater.from(context).inflate(R.layout.view_pick_action, this, true);
		setOrientation(VERTICAL);

		ButterKnife.bind(this);
		_selectNotificationType.setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, NotificationAction.names()));
	}

	private String notificationAction() {
		return NotificationAction.ALL[_selectNotificationType.getSelectedItemPosition()]._action;
	}

	@Nullable
	public Action getAction() {
		if (notificationAction().equals(DEFAULT_ACTION)) {
			return null;
		}
		return Action.create(notificationAction(), actionData());
	}

	private Map<String, String> actionData() {
		final Map<String, String> actionData = new HashMap<>();
		for (final DynamicUi.DynamicInputHolder inputHolder : _notificationData) {
			actionData.put(inputHolder.getText(0), inputHolder.getText(1));
		}
		if (isAddFriendRequest()) {
			actionData.put(ActionDataKeys.AddFriend.USER_ID, GetSocial.getCurrentUser().getId());
			actionData.put(KEY_USER_NAME, GetSocial.getCurrentUser().getDisplayName());
		}
		return actionData;
	}

	public boolean isAddFriendRequest() {
		return notificationAction().equals(ActionTypes.ADD_FRIEND);
	}

	@OnClick(R.id.button_add_notification_data)
	public void addNotificationData() {
		final DynamicUi.DynamicInputHolder inputHolder = DynamicUi.createDynamicTextRow(getContext(), _notificationDataContainer, _notificationData, "Key", "Value");
		final EditText key = inputHolder.getView(0);
		final EditText val = inputHolder.getView(1);
		key.setOnLongClickListener(view -> showPlaceholders(key, val));
	}

	private void createRow(final String k, final String v) {
		final DynamicUi.DynamicInputHolder inputHolder = DynamicUi.createDynamicTextRow(getContext(), _notificationDataContainer, _notificationData, "Key", "Value");
		final EditText key = inputHolder.getView(0);
		final EditText val = inputHolder.getView(1);
		key.setOnLongClickListener(view -> showPlaceholders(key, val));
		key.setText(k);
		val.setText(v);
	}

	private boolean showPlaceholders(final EditText key, final EditText val) {
		final List<String> placeholders = PLACEHOLDERS.get(notificationAction());
		if (placeholders == null || placeholders.isEmpty()) {
			return false;
		}
		final AlertDialog dialog = new AlertDialog.Builder(getContext())
						.setCancelable(true)
						.setTitle("Select Placeholder")
						.setItems(placeholders.toArray(new String[placeholders.size()]), (dialog1, which) -> {
							key.setText(placeholders.get(which));
							val.requestFocus();
						})
						.create();

		dialog.setCanceledOnTouchOutside(true);
		dialog.show();
		return true;
	}

	public void setAction(final Action action) {
		_selectNotificationType.setSelection(position(action.getType()));
		for (final Map.Entry<String, String> property : action.getData().entrySet()) {
			createRow(property.getKey(), property.getValue());
		}
	}

	private static int position(final String type) {
		for (int i = 0; i < NotificationAction.ALL.length; i++) {
			final NotificationAction notificationAction = NotificationAction.ALL[i];
			if (notificationAction._action.equals(type)) {
				return i;
			}
		}

		// hardcode - custom
		return 1;
	}

	private static class NotificationAction {
		static final NotificationAction[] ALL = new NotificationAction[] {
						new NotificationAction("Default", DEFAULT_ACTION),
						new NotificationAction("Custom", CUSTOM_ACTION),
						new NotificationAction("Open Activity", ActionTypes.OPEN_ACTIVITY),
						new NotificationAction("Open Invites", ActionTypes.OPEN_INVITES),
						new NotificationAction("Open Profile", ActionTypes.OPEN_PROFILE),
						new NotificationAction("Open URL", ActionTypes.OPEN_URL),
						new NotificationAction("Add Friend", ActionTypes.ADD_FRIEND),
						new NotificationAction("Claim Promo Code", ActionTypes.CLAIM_PROMO_CODE),
		};
		final String _name;
		final String _action;

		private NotificationAction(final String name, final String action) {
			_name = name;
			_action = action;
		}

		static List<String> names() {
			final List<String> names = new ArrayList<>();
			for (final NotificationAction action : ALL) {
				names.add(action._name);
			}
			return names;
		}
	}
} public class UserInfoView extends RelativeLayout {

	@BindView(R.id.userInfo_displayName)
	TextView _displayNameTextView;
	@BindView(R.id.userInfo_extra)
	TextView _extraInfoTextView;
	@BindView(R.id.userInfo_avatar)
	ImageView _avatarImageView;

	public UserInfoView(final Context context) {
		super(context);
		init(null, 0);
	}

	public UserInfoView(final Context context, final AttributeSet attrs) {
		super(context, attrs);
		init(attrs, 0);
	}

	public UserInfoView(final Context context, final AttributeSet attrs, final int defStyle) {
		super(context, attrs, defStyle);
		init(attrs, defStyle);
	}

	public void updateView(@Nullable final CurrentUser user) {
		final Handler handler = getHandler();
		if (handler != null) {
			getHandler().post(() -> updateContent(user));
		}
	}

	@Override
	protected void onAttachedToWindow() {
		super.onAttachedToWindow();
		updateView(GetSocial.getCurrentUser());
	}

	private void init(final AttributeSet attrs, final int defStyle) {
		final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

		inflater.inflate(R.layout.view_user_info, this, true);

		ButterKnife.bind(this);

		updateContent(GetSocial.getCurrentUser());
	}

	private void updateContent(@Nullable final CurrentUser user) {
		if (user != null) {
			if (user.isAnonymous()) {
				_extraInfoTextView.setText("Anonymous");
			} else {
				_extraInfoTextView.setText(printProviders());
			}

			String displayName = user.getDisplayName();
			if (TextUtils.isEmpty(displayName)) {
				displayName = "id: " + GetSocial.getCurrentUser().getId();
			}
			_displayNameTextView.setText(displayName);
			final String avatarUrl = user.getAvatarUrl();
			if (TextUtils.isEmpty(avatarUrl)) {
				Picasso.with(getContext())
								.load(R.drawable.avatar_default)
								.transform(new CircleTransform())
								.into(_avatarImageView);
			} else {
				Picasso.with(getContext())
								.load(avatarUrl)
								.transform(new CircleTransform())
								.placeholder(R.drawable.avatar_default)
								.into(_avatarImageView);
			}
		} else {
			_displayNameTextView.setText(R.string.no_user);
			_extraInfoTextView.setText(R.string.probably_you_are_offline);

			Picasso.with(getContext())
							.load(R.drawable.avatar_default)
							.transform(new CircleTransform())
							.into(_avatarImageView);
		}
	}

	private String printProviders() {
		final StringBuilder sb = new StringBuilder();
		final Set<String> keySet = GetSocial.getCurrentUser().getIdentities().keySet();
		final ArrayList<String> providers = new ArrayList<>(keySet);
		for (int i = 0; i < keySet.size() && i < 3; i++) {
			sb.append(providers.get(i));
			if (i < providers.size() - 1) {
				sb.append(" / ");
			}
		}
		if (providers.size() > 3) {
			sb.append("…");
		}
		return sb.toString();
	}
} public final class CompatibilityUtils {
	/**
	 * Provide backward compatible interface to get a drawable resource.
	 *
	 * @param context current context
	 * @param id      id of resource
	 * @return {@link Drawable} object
	 */
	public static Drawable getDrawable(Context context, int id) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
			return context.getDrawable(id);
		} else {
			return context.getResources().getDrawable(id);
		}
	}

	/**
	 * Provide backward compatible interface to convert html string to Spanned text.
	 *
	 * @param html html string
	 * @return converted {@link Spanned}
	 */
	public static Spanned fromHtml(String html) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
			return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
		} else {
			return Html.fromHtml(html);
		}
	}
} public final class Console {

	private static final DateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault());

	private static final List<LogMessage> LOG_MESSAGES = new LinkedList<>();

	public static void logInfo(String message) {
		log(message, Level.INFO);
	}

	public static void logWarning(String message) {
		log(message, Level.WARNING);
	}

	public static void logError(String message) {
		log(message, Level.ERROR);
	}

	public static void clear() {
		LOG_MESSAGES.clear();
	}

	/**
	 * @return HTML formatted console output.
	 */
	public static String getFormattedOutput() {
		List<LogMessage> messages = getMessages();

		StringBuilder sb = new StringBuilder();

		for (LogMessage message : messages) {
			sb.append(String.format("<font color=\"%s\"> %s %s %s</font><br>",
					message._level._color,
					TIMESTAMP_FORMAT.format(message._timestamp),
					message._level.toString().substring(0, 1),
					message._message
					)
			);
		}

		return sb.toString();
	}

	private static List<LogMessage> getMessages() {
		return Collections.unmodifiableList(LOG_MESSAGES);
	}

	private static void log(String message, Level level) {
		LOG_MESSAGES.add(new LogMessage(message, level, new Date()));
	}

	public enum Level {
		INFO("#8BC34A"), WARNING("#FBC02D"), ERROR("#F44336");

		public final String _color;

		Level(String color) {
			this._color = color;
		}
	}

	private static class LogMessage {
		final String _message;
		final Level _level;
		final Date _timestamp;

		LogMessage(String message, Level level, Date timestamp) {
			this._message = message;
			this._level = level;
			this._timestamp = timestamp;
		}
	}
} public class SimpleLogger {

	private final Activity _activity;
	private final String _tag;

	public SimpleLogger(Activity activity, String tag) {
		_activity = activity;
		_tag = tag;
	}

	public void logInfo(Object message) {
		Log.i(_tag, message.toString());
		Console.logInfo(message.toString());
	}

	public void logInfoAndToast(Object message) {
		Log.i(_tag, message.toString());
		Console.logInfo(message.toString());
		toastOnUiThread(message.toString());
	}

	private void logWarningAndToast(Object message) {
		Log.w(_tag, message.toString());
		Console.logWarning(message.toString());
		toastOnUiThread(message.toString());
	}

	public void logErrorAndToast(Object message) {
		Log.e(_tag, message.toString());
		Console.logError(message.toString());

		if (message instanceof Throwable) {
			Throwable throwable = (Throwable) message;
			toastOnUiThread(throwable.getMessage());
		} else {
			toastOnUiThread(message.toString());
		}
	}

	private void toastOnUiThread(final String message) {
		_activity.runOnUiThread(
				new Runnable() {
					@Override
					public void run() {
						Toast.makeText(_activity, message, Toast.LENGTH_SHORT).show();
					}
				}
		);
	}
}
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.Fragment;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnLongClick;
import com.facebook.CallbackManager;
import com.vk.sdk.VKSdk;
import im.getsocial.demo.dependencies.DependenciesContainer;
import im.getsocial.demo.dependencies.components.NotificationsManager;
import im.getsocial.demo.dialog.CurrentUserInfoDialog;
import im.getsocial.demo.dialog.ReferralDataDialog;
import im.getsocial.demo.dialog.UserInfoDialog;
import im.getsocial.demo.fragment.BaseFragment;
import im.getsocial.demo.fragment.ChatMessagesFragment;
import im.getsocial.demo.fragment.ConsoleFragment;
import im.getsocial.demo.fragment.FriendsFragment;
import im.getsocial.demo.fragment.HasFragmentTag;
import im.getsocial.demo.fragment.HasTitle;
import im.getsocial.demo.fragment.RootFragment;
import im.getsocial.demo.plugin.FacebookSharePlugin;
import im.getsocial.demo.plugin.InstagramStoriesPlugin;
import im.getsocial.demo.plugin.KakaoInvitePlugin;
import im.getsocial.demo.plugin.VKInvitePlugin;
import im.getsocial.demo.ui.PickActionView;
import im.getsocial.demo.ui.UserInfoView;
import im.getsocial.demo.utils.CompatibilityUtils;
import im.getsocial.demo.utils.Console;
import im.getsocial.demo.utils.SimpleLogger;
import im.getsocial.sdk.Communities;
import im.getsocial.sdk.GetSocial;
import im.getsocial.sdk.Invites;
import im.getsocial.sdk.Notifications;
import im.getsocial.sdk.actions.Action;
import im.getsocial.sdk.actions.ActionDataKeys;
import im.getsocial.sdk.actions.ActionListener;
import im.getsocial.sdk.actions.ActionTypes;
import im.getsocial.sdk.communities.CurrentUser;
import im.getsocial.sdk.communities.FriendsQuery;
import im.getsocial.sdk.communities.OnCurrentUserChangedListener;
import im.getsocial.sdk.communities.UserId;
import im.getsocial.sdk.communities.UserIdList;
import im.getsocial.sdk.communities.UsersQuery;
import im.getsocial.sdk.invites.InviteChannelIds;
import im.getsocial.sdk.json.serializer.Getson;
import im.getsocial.sdk.notifications.Notification;
import im.getsocial.sdk.notifications.NotificationButton;
import im.getsocial.sdk.notifications.NotificationContext;
import im.getsocial.sdk.notifications.NotificationStatus;
import im.getsocial.sdk.notifications.OnNotificationClickedListener;
import im.getsocial.sdk.notifications.OnNotificationReceivedListener;
import im.getsocial.sdk.ui.GetSocialUi;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
			}
			Toast.makeText(MainActivity.this, notification.getText(), Toast.LENGTH_SHORT).show();
		}, (error) -> {
			Toast.makeText(MainActivity.this, "Failed to set notification status", Toast.LENGTH_SHORT).show();
		});
	}

	private void openChat(final String chatId) {
		ChatMessagesFragment fragment = ChatMessagesFragment.openChat(chatId);
		addContentFragment(fragment);
	}

	private void showAddFriendDialog(final Notification notification) {
		new AlertDialog.Builder(this)
						.setTitle(notification.getTitle())
						.setMessage(notification.getText())
						.setPositiveButton("Confirm", (dialog, which) -> {
							dialog.dismiss();
							handleCustomAction(notification, NotificationButton.CONSUME_ACTION);
						})
						.setNegativeButton("Delete", (dialog, which) -> {
							dialog.dismiss();
							handleCustomAction(notification, NotificationButton.IGNORE_ACTION);
						})
						.setNeutralButton("Dismiss", (dialog, which) -> dialog.dismiss())
						.create()
						.show();
	}

	private boolean handleCustomAction(final Notification notification, final String actionId) {
		final Action action = notification.getAction();
		if (actionId.equals(NotificationButton.CONSUME_ACTION)) {
			if (ActionTypes.ADD_FRIEND.equals(action.getType())) {
				final String userId = action.getData().get(ActionDataKeys.AddFriend.USER_ID);
				final String userName = action.getData().get(PickActionView.KEY_USER_NAME);
				addFriend(userId, userName);
			} else {
				GetSocial.handle(action);
			}
			_dependenciesContainer.notificationsManager().setStatus(notification.getId(), NotificationStatus.CONSUMED);
		} else {
			_dependenciesContainer.notificationsManager().setStatus(notification.getId(), NotificationStatus.IGNORED);
		}
		return false;
	}

	private void addFriend(final String userId, final String userName) {
		Communities.addFriends(
						UserIdList.create(userId),
						result -> Toast.makeText(MainActivity.this, userName + " is now your friend!", Toast.LENGTH_SHORT).show(),
						error -> Toast.makeText(MainActivity.this, "Failed to add " + userName + ", error: " + error, Toast.LENGTH_SHORT).show()
		);
	}

	protected void registerCustomInvitesChannelPlugins() {
		Invites.registerPlugin(new KakaoInvitePlugin(), InviteChannelIds.KAKAO);
		Invites.registerPlugin(new FacebookSharePlugin(this, _facebookCallbackManager), InviteChannelIds.FACEBOOK);
		Invites.registerPlugin(_vkInvitePlugin, InviteChannelIds.VK);
		Invites.registerPlugin(new InstagramStoriesPlugin(), InviteChannelIds.INSTAGRAM_STORIES);
	}

	private void showUserInfoDialog(final String userId) {
		Communities.getUser(
						UserId.create(userId),
						publicUser -> UserInfoDialog.show(getSupportFragmentManager(), publicUser),
						error -> _log.logErrorAndToast("Failed to get user: " + error.getMessage())
		);
	}

	protected String getDemoAppInfo() {
		return String.format(Locale.getDefault(), "GetSocial Android Demo\nSDK v%s. Build v%d", GetSocial.getSdkVersion(), BuildConfig.VERSION_CODE);
	}

	private void showUserDetails() {
		if (GetSocial.isInitialized()) {
			CurrentUserInfoDialog.show(getSupportFragmentManager());
		}
	}

	private void copyUserIdToClipboard() {
		final String userId = GetSocial.getCurrentUser().getId();

		final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
		final ClipData clip = ClipData.newPlainText("GetSocial User ID", userId);
		clipboard.setPrimaryClip(clip);
		vibrate();
		Toast.makeText(this, "Copied " + userId + " to clipboard.", Toast.LENGTH_LONG).show();
	}

	private void vibrate() {
		final Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);

		if (vibrator == null) {
			return;
		}
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
			vibrator.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
		} else {
			vibrator.vibrate(500);
		}
	}

	private RootFragment findRootFragment() {
		return findFragment("root");
	}

	private <T extends Fragment> T findFragment(final String tag) {
		return (T) getSupportFragmentManager().findFragmentByTag(tag);
	}

	//region ActivityListener

	@Override
	public void invalidateUi() {
		_viewContainer.updateView();
	}

	@Override
	public void addContentFragment(final Fragment fragment) {
		String tag = "";
		if (fragment instanceof HasFragmentTag) {