Compare commits

..

9 commits

26 changed files with 323 additions and 57 deletions

View file

@ -83,6 +83,7 @@ dependencies {
implementation 'com.github.singpolyma:TokenAutoComplete:bfa93780e0' implementation 'com.github.singpolyma:TokenAutoComplete:bfa93780e0'
implementation 'com.github.kizitonwose.colorpreference:core:1.1.0'
implementation 'com.github.kizitonwose.colorpreference:support:1.1.0' implementation 'com.github.kizitonwose.colorpreference:support:1.1.0'
implementation 'com.caverock:androidsvg-aar:1.4' implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.github.singpolyma:Better-Link-Movement-Method:4df081e1e4' implementation 'com.github.singpolyma:Better-Link-Movement-Method:4df081e1e4'

View file

@ -36,6 +36,7 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.ui.adapter.AccountAdapter; import eu.siacs.conversations.ui.adapter.AccountAdapter;
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection;
@ -44,8 +45,14 @@ import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.navigation.NavigationBarView; import com.google.android.material.navigation.NavigationBarView;
import com.kizitonwose.colorpreference.ColorDialog;
import com.kizitonwose.colorpreference.ColorShape;
public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState { public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate,
KeyChainAliasCallback,
XmppConnectionService.OnAccountCreated,
AccountAdapter.OnTglAccountState,
ColorDialog.OnColorSelectedListener {
private final String STATE_SELECTED_ACCOUNT = "selected_account"; private final String STATE_SELECTED_ACCOUNT = "selected_account";
@ -61,6 +68,18 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
protected Pair<Integer, Intent> mPostponedActivityResult = null; protected Pair<Integer, Intent> mPostponedActivityResult = null;
private AccountAdapter.ColorSelectorListener colorSelectorListener = new AccountAdapter.ColorSelectorListener() {
@Override
public void onColorPickerRequested(Jid accountJid, int currentColor) {
new ColorDialog.Builder(ManageAccountActivity.this)
.setColorShape(ColorShape.CIRCLE)
.setColorChoices(R.array.themeColorsOverride)
.setSelectedColor(currentColor)
.setTag(accountJid.asBareJid().toEscapedString())
.show();
}
};
@Override @Override
public void onAccountUpdate() { public void onAccountUpdate() {
refreshUi(); refreshUi();
@ -102,7 +121,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
} }
accountListView = findViewById(R.id.account_list); accountListView = findViewById(R.id.account_list);
this.mAccountAdapter = new AccountAdapter(this, accountList); this.mAccountAdapter = new AccountAdapter(this, accountList, colorSelectorListener);
accountListView.setAdapter(this.mAccountAdapter); accountListView.setAdapter(this.mAccountAdapter);
accountListView.setOnItemClickListener((arg0, view, position, arg3) -> switchToAccount(accountList.get(position))); accountListView.setOnItemClickListener((arg0, view, position, arg3) -> switchToAccount(accountList.get(position)));
registerForContextMenu(accountListView); registerForContextMenu(accountListView);
@ -158,6 +177,13 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
super.onSaveInstanceState(savedInstanceState); super.onSaveInstanceState(savedInstanceState);
} }
@Override
protected void onDestroy() {
super.onDestroy();
colorSelectorListener = null;
mAccountAdapter.colorSelectorListener = null;
}
@Override @Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo); super.onCreateContextMenu(menu, v, menuInfo);
@ -349,6 +375,12 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
} }
} }
@Override
public void onColorSelected(int newColor, String tag) {
UIHelper.overrideAccountColor(this, tag, newColor);
refreshUiReal();
}
private void addAccountFromKey() { private void addAccountFromKey() {
try { try {
KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);

View file

@ -352,7 +352,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
} }
public String replyId() { public String replyId() {
return conversation.getMode() == Conversation.MODE_MULTI || getRemoteMsgId() == null ? getServerMsgId() : getRemoteMsgId(); if (conversation.getMode() == Conversation.MODE_MULTI) return getServerMsgId();
final String remote = getRemoteMsgId();
if (remote == null && getStatus() > STATUS_RECEIVED) return getUuid();
return remote;
} }
public Message reply() { public Message reply() {

View file

@ -508,10 +508,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} }
} }
if (nextCounterpart != null && mXmppConnectionService.checkIsArchived(account, counterpart.asBareJid(), nextCounterpart)) {
return;
}
if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null) && !isMucStatusMessage) { if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null) && !isMucStatusMessage) {
final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), null, conversationIsProbablyMuc, nextCounterpart != null, false, nextCounterpart); final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), null, conversationIsProbablyMuc, nextCounterpart != null, false, nextCounterpart);
final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI; final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;

View file

@ -504,7 +504,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
&& contact.getLastseen() > 0 && contact.getLastseen() > 0
&& contact.getPresences().allOrNonSupport(Namespace.IDLE)) { && contact.getPresences().allOrNonSupport(Namespace.IDLE)) {
binding.detailsLastseen.setVisibility(View.VISIBLE); binding.detailsLastseen.setVisibility(View.VISIBLE);
binding.detailsLastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.isActive(), contact.getLastseen())); binding.detailsLastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.isActive(), contact.getLastseen(), false));
} else { } else {
binding.detailsLastseen.setVisibility(View.GONE); binding.detailsLastseen.setVisibility(View.GONE);
} }
@ -523,7 +523,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
AvatarWorkerTask.loadAvatar(contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size); AvatarWorkerTask.loadAvatar(contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size);
binding.detailsContactBadge.setOnClickListener(this::onBadgeClick); binding.detailsContactBadge.setOnClickListener(this::onBadgeClick);
binding.presenceIndicator.setStatus(contact.getShownStatus()); binding.presenceIndicator.setStatus(contact);
binding.detailsContactKeys.removeAllViews(); binding.detailsContactKeys.removeAllViews();
boolean hasKeys = false; boolean hasKeys = false;

View file

@ -590,7 +590,7 @@ public class ConversationFragment extends XmppFragment
public void onClick(View v) { public void onClick(View v) {
stopScrolling(); stopScrolling();
if (previousClickedReply != null) { /*if (previousClickedReply != null) {
int lastVisiblePosition = binding.messagesView.getLastVisiblePosition(); int lastVisiblePosition = binding.messagesView.getLastVisiblePosition();
Message lastVisibleMessage = messageListAdapter.getItem(lastVisiblePosition); Message lastVisibleMessage = messageListAdapter.getItem(lastVisiblePosition);
Message jump = previousClickedReply; Message jump = previousClickedReply;
@ -602,7 +602,7 @@ public class ConversationFragment extends XmppFragment
return; return;
} }
} }
} }*/
if (conversation.isInHistoryPart()) { if (conversation.isInHistoryPart()) {
conversation.jumpToLatest(); conversation.jumpToLatest();
@ -3610,7 +3610,7 @@ public class ConversationFragment extends XmppFragment
conversation.refreshSessions(); conversation.refreshSessions();
if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI) { if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI && conversation.getNextCounterpart() == null) {
String subject = conversation.getMucOptions().getSubject(); String subject = conversation.getMucOptions().getSubject();
Boolean hidden = conversation.getMucOptions().subjectHidden(); Boolean hidden = conversation.getMucOptions().subjectHidden();

View file

@ -42,10 +42,14 @@ import android.app.FragmentTransaction;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
@ -95,7 +99,9 @@ import eu.siacs.conversations.ui.util.PendingItem;
import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.ExceptionHelper;
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper; import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.SignupUtils;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import io.michaelrocks.libphonenumber.android.NumberParseException; import io.michaelrocks.libphonenumber.android.NumberParseException;
@ -135,6 +141,10 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
private boolean mActivityPaused = true; private boolean mActivityPaused = true;
private final AtomicBoolean mRedirectInProcess = new AtomicBoolean(false); private final AtomicBoolean mRedirectInProcess = new AtomicBoolean(false);
private final Handler handler = new Handler(Looper.getMainLooper());
private final Runnable refreshTitleRunnable = this::invalidateActionBarTitle;
private boolean showLastSeen = false;
private static boolean isViewOrShareIntent(Intent i) { private static boolean isViewOrShareIntent(Intent i) {
Log.d(Config.LOGTAG, "action: " + (i == null ? null : i.getAction())); Log.d(Config.LOGTAG, "action: " + (i == null ? null : i.getAction()));
return i != null && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION); return i != null && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION);
@ -660,6 +670,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
this.mSkipBackgroundBinding = false; this.mSkipBackgroundBinding = false;
} }
mRedirectInProcess.set(false); mRedirectInProcess.set(false);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
this.showLastSeen = preferences.getBoolean("last_activity", false);
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation); BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation);
bottomNavigationView.setSelectedItemId(R.id.chats); bottomNavigationView.setSelectedItemId(R.id.chats);
@ -735,6 +747,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
if (actionBar == null) { if (actionBar == null) {
return; return;
} }
final FragmentManager fragmentManager = getFragmentManager(); final FragmentManager fragmentManager = getFragmentManager();
final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
if (mainFragment instanceof ConversationFragment) { if (mainFragment instanceof ConversationFragment) {
@ -750,10 +763,38 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
binding.toolbar, binding.toolbar,
(v) -> openConversationDetails(conversation) (v) -> openConversationDetails(conversation)
); );
if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getNextCounterpart() == null) {
int usersCount = conversation.getMucOptions().getUserCount();
if (usersCount > 0) {
actionBar.setSubtitle(getResources().getQuantityString(R.plurals.x_participants, conversation.getMucOptions().getUserCount(), conversation.getMucOptions().getUserCount()));
} else {
actionBar.setSubtitle("");
}
handler.postDelayed(refreshTitleRunnable, 5000L);
} else if (conversation.getMode() == Conversation.MODE_SINGLE) {
Contact contact = conversation.getContact();
List<String> statuses = contact.getPresences().getStatusMessages();
if (!statuses.isEmpty() && !statuses.get(0).isBlank()) {
actionBar.setSubtitle(statuses.get(0));
handler.postDelayed(refreshTitleRunnable, 5000L);
} else {
actionBar.setSubtitle("");
handler.removeCallbacks(refreshTitleRunnable);
}
} else {
actionBar.setSubtitle("");
handler.removeCallbacks(refreshTitleRunnable);
}
return; return;
} }
} }
handler.removeCallbacks(refreshTitleRunnable);
actionBar.setTitle(R.string.app_name); actionBar.setTitle(R.string.app_name);
actionBar.setSubtitle("");
actionBar.setDisplayHomeAsUpEnabled(false); actionBar.setDisplayHomeAsUpEnabled(false);
ActionBarUtil.resetActionBarOnClickListeners(binding.toolbar); ActionBarUtil.resetActionBarOnClickListeners(binding.toolbar);
} }

View file

@ -9,6 +9,11 @@ import android.widget.ArrayAdapter;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import com.kizitonwose.colorpreference.ColorDialog;
import com.kizitonwose.colorpreference.ColorPreference;
import com.kizitonwose.colorpreference.ColorShape;
import com.kizitonwose.colorpreference.ColorUtils;
import java.util.List; import java.util.List;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
@ -19,22 +24,29 @@ import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.util.AvatarWorkerTask; import eu.siacs.conversations.ui.util.AvatarWorkerTask;
import eu.siacs.conversations.ui.util.StyledAttributes; import eu.siacs.conversations.ui.util.StyledAttributes;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid;
public class AccountAdapter extends ArrayAdapter<Account> { public class AccountAdapter extends ArrayAdapter<Account> {
private final XmppActivity activity; private final XmppActivity activity;
private final boolean showStateButton; private final boolean showStateButton;
private final boolean showColorSelector;
public ColorSelectorListener colorSelectorListener = null;
public AccountAdapter(XmppActivity activity, List<Account> objects, boolean showStateButton) { public AccountAdapter(XmppActivity activity, List<Account> objects, boolean showStateButton) {
super(activity, 0, objects); super(activity, 0, objects);
this.activity = activity; this.activity = activity;
this.showStateButton = showStateButton; this.showStateButton = showStateButton;
this.showColorSelector = false;
} }
public AccountAdapter(XmppActivity activity, List<Account> objects) { public AccountAdapter(XmppActivity activity, List<Account> objects, ColorSelectorListener listener) {
super(activity, 0, objects); super(activity, 0, objects);
this.activity = activity; this.activity = activity;
this.showStateButton = true; this.showStateButton = true;
this.showColorSelector = true;
colorSelectorListener = listener;
} }
@Override @Override
@ -77,18 +89,28 @@ public class AccountAdapter extends ArrayAdapter<Account> {
} else { } else {
viewHolder.binding.tglAccountStatus.setVisibility(View.GONE); viewHolder.binding.tglAccountStatus.setVisibility(View.GONE);
} }
viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener((compoundButton, b) -> { viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener((compoundButton, b) -> {
if (b == isDisabled && activity instanceof OnTglAccountState) { if (b == isDisabled && activity instanceof OnTglAccountState) {
((OnTglAccountState) activity).onClickTglAccountState(account, b); ((OnTglAccountState) activity).onClickTglAccountState(account, b);
} }
}); });
if (activity.xmppConnectionService.getAccounts().size() > 1) { if (this.showColorSelector && activity.xmppConnectionService.getAccounts().size() > 1 &&
viewHolder.binding.accountIndicator.setBackgroundColor(UIHelper.getColorForName(account.getJid().asBareJid().getEscapedLocal())); activity.xmppConnectionService.getPreferences().getBoolean("show_account_indicator", activity.getResources().getBoolean(R.bool.show_account_indicator))) {
int color = UIHelper.getAccountColor(activity, account.getJid());
viewHolder.binding.colorView.setVisibility(View.VISIBLE);
ColorUtils.setColorViewValue(viewHolder.binding.colorView, color, false, ColorShape.CIRCLE);
viewHolder.binding.colorView.setOnClickListener(v -> {
if (colorSelectorListener != null) {
colorSelectorListener.onColorPickerRequested(account.getJid(), color);
}
});
} else { } else {
viewHolder.binding.accountIndicator.setBackgroundColor(Color.TRANSPARENT); viewHolder.binding.colorView.setVisibility(View.GONE);
} }
return view; return view;
} }
@ -106,4 +128,7 @@ public class AccountAdapter extends ArrayAdapter<Account> {
void onClickTglAccountState(Account account, boolean state); void onClickTglAccountState(Account account, boolean state);
} }
public interface ColorSelectorListener {
void onColorPickerRequested(Jid accountJid, int currentColor);
}
} }

View file

@ -393,7 +393,7 @@ public class ConversationAdapter
Contact contact = conversation.getContact(); Contact contact = conversation.getContact();
if (contact != null) { if (contact != null) {
viewHolder.binding.presenceIndicator.setStatus(contact.getShownStatus()); viewHolder.binding.presenceIndicator.setStatus(contact);
} else { } else {
viewHolder.binding.presenceIndicator.setStatus(null); viewHolder.binding.presenceIndicator.setStatus(null);
} }
@ -401,7 +401,7 @@ public class ConversationAdapter
Account account = conversation.getAccount(); Account account = conversation.getAccount();
if (account != null && activity.xmppConnectionService.getAccounts().size() > 1) { if (account != null && activity.xmppConnectionService.getAccounts().size() > 1) {
viewHolder.binding.accountIndicator.setBackgroundColor(UIHelper.getColorForName(account.getJid().asBareJid().getEscapedLocal())); viewHolder.binding.accountIndicator.setBackgroundColor(UIHelper.getAccountColor(activity, account.getJid()));
} else { } else {
viewHolder.binding.accountIndicator.setBackgroundColor(Color.TRANSPARENT); viewHolder.binding.accountIndicator.setBackgroundColor(Color.TRANSPARENT);
} }

View file

@ -94,7 +94,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
AvatarWorkerTask.loadAvatar(item, viewHolder.avatar, R.dimen.avatar); AvatarWorkerTask.loadAvatar(item, viewHolder.avatar, R.dimen.avatar);
if (item instanceof Contact) { if (item instanceof Contact) {
viewHolder.presenceIndicator.setStatus(((Contact) item).getShownStatus()); viewHolder.presenceIndicator.setStatus(((Contact) item));
} else { } else {
viewHolder.presenceIndicator.setStatus(null); viewHolder.presenceIndicator.setStatus(null);
} }
@ -108,7 +108,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
} }
if (account != null && activity.xmppConnectionService.getAccounts().size() > 1) { if (account != null && activity.xmppConnectionService.getAccounts().size() > 1) {
viewHolder.accountIndicator.setBackgroundColor(UIHelper.getColorForName(account.getJid().asBareJid().getEscapedLocal())); viewHolder.accountIndicator.setBackgroundColor(UIHelper.getAccountColor(activity, account.getJid()));
} else { } else {
viewHolder.accountIndicator.setBackgroundColor(Color.TRANSPARENT); viewHolder.accountIndicator.setBackgroundColor(Color.TRANSPARENT);
} }

View file

@ -91,7 +91,7 @@ public class UserAdapter extends ListAdapter<MucOptions.User, UserAdapter.ViewHo
} else { } else {
viewHolder.binding.contactJid.setText(ConferenceDetailsActivity.getStatus(viewHolder.binding.getRoot().getContext(), user, advancedMode)); viewHolder.binding.contactJid.setText(ConferenceDetailsActivity.getStatus(viewHolder.binding.getRoot().getContext(), user, advancedMode));
} }
viewHolder.binding.presenceIndicator.setStatus(contact.getShownStatus()); viewHolder.binding.presenceIndicator.setStatus(contact);
} else { } else {
viewHolder.binding.contactDisplayName.setText(name == null ? "" : name); viewHolder.binding.contactDisplayName.setText(name == null ? "" : name);
viewHolder.binding.contactJid.setText(ConferenceDetailsActivity.getStatus(viewHolder.binding.getRoot().getContext(), user, advancedMode)); viewHolder.binding.contactJid.setText(ConferenceDetailsActivity.getStatus(viewHolder.binding.getRoot().getContext(), user, advancedMode));

View file

@ -38,7 +38,7 @@ public class UserPreviewAdapter extends ListAdapter<MucOptions.User, UserPreview
AvatarWorkerTask.loadAvatar(user, viewHolder.binding.avatar, R.dimen.media_size); AvatarWorkerTask.loadAvatar(user, viewHolder.binding.avatar, R.dimen.media_size);
Contact contact = user.getContact(); Contact contact = user.getContact();
if (contact != null) { if (contact != null) {
viewHolder.binding.presenceIndicator.setStatus(user.getContact().getShownStatus()); viewHolder.binding.presenceIndicator.setStatus(user.getContact());
} else { } else {
viewHolder.binding.presenceIndicator.setStatus(null); viewHolder.binding.presenceIndicator.setStatus(null);
} }

View file

@ -0,0 +1,37 @@
package eu.siacs.conversations.ui.widget
import android.content.Context
import android.util.AttributeSet
import android.view.View
import eu.siacs.conversations.R
import eu.siacs.conversations.ui.XmppActivity
class AccountIndicator : View {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
val enabled = (context as? XmppActivity)
?.xmppConnectionService?.preferences
?.getBoolean("show_account_indicator", context.resources.getBoolean(R.bool.show_account_indicator)) ?: false
visibility = if (enabled) {
VISIBLE
} else {
INVISIBLE
}
}
}

View file

@ -8,9 +8,13 @@ import android.graphics.Paint
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
import eu.siacs.conversations.R
import eu.siacs.conversations.entities.Contact
import eu.siacs.conversations.entities.Presence import eu.siacs.conversations.entities.Presence
import eu.siacs.conversations.ui.XmppActivity
import eu.siacs.conversations.ui.util.StyledAttributes import eu.siacs.conversations.ui.util.StyledAttributes
import eu.siacs.conversations.utils.UIHelper import eu.siacs.conversations.utils.UIHelper
import eu.siacs.conversations.xml.Namespace
class PresenceIndicator : View { class PresenceIndicator : View {
private var paint: Paint = Paint().also { private var paint: Paint = Paint().also {
@ -19,13 +23,9 @@ class PresenceIndicator : View {
it.strokeWidth = 1 * Resources.getSystem().displayMetrics.density it.strokeWidth = 1 * Resources.getSystem().displayMetrics.density
} }
var status: Presence.Status? = null private var status: Presence.Status? = null
set(value) {
if (field != value) { private var enabled = false
field = value
invalidate()
}
}
constructor(context: Context?) : super(context) constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
@ -51,7 +51,26 @@ class PresenceIndicator : View {
} }
} }
fun setStatus(contact: Contact?) {
val status = contact?.shownStatus
if (status != this.status) {
this.status = status
invalidate()
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
enabled = (context as? XmppActivity)
?.xmppConnectionService?.preferences
?.getBoolean("show_contact_status", context.resources.getBoolean(R.bool.show_contact_status)) ?: false
}
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
if (!enabled) {
return
}
super.onDraw(canvas) super.onDraw(canvas)
val color: Int? = UIHelper.getColorForStatus(status); val color: Int? = UIHelper.getColorForStatus(status);

View file

@ -1,6 +1,7 @@
package eu.siacs.conversations.utils; package eu.siacs.conversations.utils;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
@ -204,26 +205,60 @@ public class UIHelper {
.get(Calendar.DAY_OF_YEAR); .get(Calendar.DAY_OF_YEAR);
} }
public static String lastseen(Context context, boolean active, long time) { public static String lastseen(Context context, boolean active, long time, boolean shortText) {
long difference = (System.currentTimeMillis() - time) / 1000; long difference = (System.currentTimeMillis() - time) / 1000;
if (active) { if (active) {
return context.getString(R.string.online_right_now); if (shortText) {
return context.getString(R.string.online_right_now_short);
} else {
return context.getString(R.string.online_right_now);
}
} else if (difference < 60) { } else if (difference < 60) {
return context.getString(R.string.last_seen_now); if (shortText) {
return context.getString(R.string.last_seen_now_short);
} else {
return context.getString(R.string.last_seen_now);
}
} else if (difference < 60 * 2) { } else if (difference < 60 * 2) {
return context.getString(R.string.last_seen_min); if (shortText) {
return context.getString(R.string.last_seen_min_short);
} else {
return context.getString(R.string.last_seen_min);
}
} else if (difference < 60 * 60) { } else if (difference < 60 * 60) {
return context.getString(R.string.last_seen_mins, Math.round(difference / 60.0)); if (shortText) {
return context.getString(R.string.last_seen_mins_short, Math.round(difference / 60.0));
} else {
return context.getString(R.string.last_seen_mins, Math.round(difference / 60.0));
}
} else if (difference < 60 * 60 * 2) { } else if (difference < 60 * 60 * 2) {
return context.getString(R.string.last_seen_hour); if (shortText) {
return context.getString(R.string.last_seen_hour_short);
} else {
return context.getString(R.string.last_seen_hour);
}
} else if (difference < 60 * 60 * 24) { } else if (difference < 60 * 60 * 24) {
return context.getString(R.string.last_seen_hours, if (shortText) {
Math.round(difference / (60.0 * 60.0))); return context.getString(R.string.last_seen_hours_short,
Math.round(difference / (60.0 * 60.0)));
} else {
return context.getString(R.string.last_seen_hours,
Math.round(difference / (60.0 * 60.0)));
}
} else if (difference < 60 * 60 * 48) { } else if (difference < 60 * 60 * 48) {
return context.getString(R.string.last_seen_day); if (shortText) {
return context.getString(R.string.last_seen_day_short);
} else {
return context.getString(R.string.last_seen_day);
}
} else { } else {
return context.getString(R.string.last_seen_days, if (shortText) {
Math.round(difference / (60.0 * 60.0 * 24.0))); return context.getString(R.string.last_seen_days_short,
Math.round(difference / (60.0 * 60.0 * 24.0)));
} else {
return context.getString(R.string.last_seen_days,
Math.round(difference / (60.0 * 60.0 * 24.0)));
}
} }
} }
@ -231,6 +266,23 @@ public class UIHelper {
return getColorForName(name, false); return getColorForName(name, false);
} }
public static int getAccountColor(Context context, Jid accountJid) {
SharedPreferences prefs = context.getSharedPreferences("accountColorsPrefs", Context.MODE_PRIVATE);
String name = accountJid.asBareJid().toEscapedString();
int overrideColor = prefs.getInt(name, -1);
if (overrideColor != -1) {
return overrideColor;
} else {
return getColorForName(name);
}
}
public static void overrideAccountColor(Context context, String accountName, int color) {
SharedPreferences prefs = context.getSharedPreferences("accountColorsPrefs", Context.MODE_PRIVATE);
prefs.edit().putInt(accountName, color).apply();
}
public static int getColorForName(String name, boolean safe) { public static int getColorForName(String name, boolean safe) {
if (Config.XEP_0392) { if (Config.XEP_0392) {
return XEP0392Helper.rgbFromNick(name); return XEP0392Helper.rgbFromNick(name);

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#000000"
android:pathData="M160,760L160,680L560,680L560,760L160,760ZM160,600L160,520L800,520L800,600L160,600ZM160,440L160,360L800,360L800,440L160,440ZM160,280L160,200L800,200L800,280L160,280Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#ffffff"
android:pathData="M160,760L160,680L560,680L560,760L160,760ZM160,600L160,520L800,520L800,600L160,600ZM160,440L160,360L800,360L800,440L160,440ZM160,280L160,200L800,200L800,280L160,280Z"/>
</vector>

View file

@ -6,17 +6,10 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
android:clipToPadding="false"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:paddingTop="8dp"> android:paddingTop="8dp">
<View
android:id="@+id/account_indicator"
android:layout_width="@dimen/account_indicator_width"
android:layout_marginStart="-4dp"
android:layout_height="48dp" />
<eu.siacs.conversations.ui.widget.AvatarView <eu.siacs.conversations.ui.widget.AvatarView
android:id="@+id/account_image" android:id="@+id/account_image"
android:layout_width="48dp" android:layout_width="48dp"
@ -31,8 +24,8 @@
android:layout_toEndOf="@+id/account_image" android:layout_toEndOf="@+id/account_image"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="@dimen/avatar_item_distance" android:paddingLeft="@dimen/avatar_item_distance"
android:layout_toLeftOf="@+id/tgl_account_status" android:layout_toLeftOf="@+id/controls"
android:layout_toStartOf="@+id/tgl_account_status"> android:layout_toStartOf="@+id/controls">
<TextView <TextView
android:id="@+id/account_jid" android:id="@+id/account_jid"
@ -50,14 +43,31 @@
android:textAppearance="@style/TextAppearance.Conversations.Body2" /> android:textAppearance="@style/TextAppearance.Conversations.Body2" />
</LinearLayout> </LinearLayout>
<androidx.appcompat.widget.SwitchCompat <LinearLayout
android:id="@+id/tgl_account_status" android:id="@+id/controls"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:orientation="horizontal"
android:layout_centerVertical="true" android:gravity="center_vertical"
android:padding="16dp" android:padding="16dp"
android:focusable="false" /> android:layout_alignParentRight="true"
android:layout_centerVertical="true">
<ImageView
android:id="@+id/color_view"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:layout_marginEnd="12dp"
android:scaleType="fitXY" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/tgl_account_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false" />
</LinearLayout>
</RelativeLayout> </RelativeLayout>
</layout> </layout>

View file

@ -9,7 +9,7 @@
android:clipToPadding="false" android:clipToPadding="false"
android:padding="@dimen/list_padding"> android:padding="@dimen/list_padding">
<View <eu.siacs.conversations.ui.widget.AccountIndicator
android:id="@+id/account_indicator" android:id="@+id/account_indicator"
android:layout_width="@dimen/account_indicator_width" android:layout_width="@dimen/account_indicator_width"
android:layout_marginStart="-4dp" android:layout_marginStart="-4dp"

View file

@ -20,7 +20,7 @@
android:clipToPadding="false" android:clipToPadding="false"
android:padding="8dp"> android:padding="8dp">
<View <eu.siacs.conversations.ui.widget.AccountIndicator
android:id="@+id/account_indicator" android:id="@+id/account_indicator"
android:layout_width="@dimen/account_indicator_width" android:layout_width="@dimen/account_indicator_width"
android:layout_marginStart="-4dp" android:layout_marginStart="-4dp"

View file

@ -54,7 +54,7 @@
<ImageView <ImageView
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:src="?attr/ic_pin" android:src="?attr/ic_subject"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
/> />

View file

@ -40,6 +40,7 @@
<attr name="ic_send_voice_offline" format="reference" /> <attr name="ic_send_voice_offline" format="reference" />
<attr name="ic_pin" format="reference" /> <attr name="ic_pin" format="reference" />
<attr name="ic_subject" format="reference" />
<attr name="ic_close" format="reference" /> <attr name="ic_close" format="reference" />
<attr name="ic_attach_camera" format="reference" /> <attr name="ic_attach_camera" format="reference" />
<attr name="ic_attach_videocam" format="reference" /> <attr name="ic_attach_videocam" format="reference" />

View file

@ -55,4 +55,6 @@
<bool name="always_full_timestamps">false</bool> <bool name="always_full_timestamps">false</bool>
<bool name="skip_image_editor_screen">false</bool> <bool name="skip_image_editor_screen">false</bool>
<string name="avatar_shape">rounded_square</string> <string name="avatar_shape">rounded_square</string>
<bool name="show_contact_status">true</bool>
<bool name="show_account_indicator">true</bool>
</resources> </resources>

View file

@ -612,6 +612,10 @@
<string name="pref_skip_image_editor_screen_summary">Dont open image editor screen automatically for single image attachments</string> <string name="pref_skip_image_editor_screen_summary">Dont open image editor screen automatically for single image attachments</string>
<string name="unable_to_connect_to_keychain">Could not connect to OpenKeychain</string> <string name="unable_to_connect_to_keychain">Could not connect to OpenKeychain</string>
<string name="this_device_is_no_longer_in_use">This device is no longer in use</string> <string name="this_device_is_no_longer_in_use">This device is no longer in use</string>
<string name="pref_show_contact_presence">Contact presence</string>
<string name="pref_show_contact_presence_details">Show contact presence nearby contact avatar</string>
<string name="pref_show_account_indicator">Account indicator</string>
<string name="pref_show_account_indicator_details">Mark conversations with different colors in case of several account</string>
<string name="type_pc">Computer</string> <string name="type_pc">Computer</string>
<string name="type_phone">Mobile phone</string> <string name="type_phone">Mobile phone</string>
<string name="type_tablet">Tablet</string> <string name="type_tablet">Tablet</string>
@ -1108,6 +1112,19 @@
<string name="avater_shape_rounded_square">Rounded Square</string> <string name="avater_shape_rounded_square">Rounded Square</string>
<string name="avater_shape_square">Square</string> <string name="avater_shape_square">Square</string>
<plurals name="x_participants">
<item quantity="one">%1$d participant</item>
<item quantity="other">%1$d participants</item>
</plurals>
<string name="online_right_now_short">online</string>
<string name="last_seen_now_short">just now</string>
<string name="last_seen_min_short">one minute ago</string>
<string name="last_seen_mins_short">%d minutes ago</string>
<string name="last_seen_hour_short">one hour ago</string>
<string name="last_seen_hours_short">%d hours ago</string>
<string name="last_seen_day_short">one day ago</string>
<string name="last_seen_days_short">%d days ago</string>
<string name="clarendon" translatable="false">Clarendon</string> <string name="clarendon" translatable="false">Clarendon</string>
<string name="oldman" translatable="false">OldMan</string> <string name="oldman" translatable="false">OldMan</string>

View file

@ -59,6 +59,7 @@
<item name="ic_attach_videocam" type="reference">@drawable/ic_attach_videocam</item> <item name="ic_attach_videocam" type="reference">@drawable/ic_attach_videocam</item>
<item name="ic_attach_document" type="reference">@drawable/ic_attach_document</item> <item name="ic_attach_document" type="reference">@drawable/ic_attach_document</item>
<item name="ic_pin" type="reference">@drawable/ic_pin_black</item> <item name="ic_pin" type="reference">@drawable/ic_pin_black</item>
<item name="ic_subject" type="reference">@drawable/ic_subject_black</item>
<item name="ic_close" type="reference">@drawable/ic_close_24dp_black</item> <item name="ic_close" type="reference">@drawable/ic_close_24dp_black</item>
<item name="ic_attach_location" type="reference">@drawable/ic_attach_location</item> <item name="ic_attach_location" type="reference">@drawable/ic_attach_location</item>
<item name="ic_attach_photo" type="reference">@drawable/ic_attach_photo</item> <item name="ic_attach_photo" type="reference">@drawable/ic_attach_photo</item>
@ -239,6 +240,7 @@
<item name="ic_attach_videocam" type="reference">@drawable/ic_attach_videocam_white</item> <item name="ic_attach_videocam" type="reference">@drawable/ic_attach_videocam_white</item>
<item name="ic_attach_document" type="reference">@drawable/ic_attach_document_white</item> <item name="ic_attach_document" type="reference">@drawable/ic_attach_document_white</item>
<item name="ic_pin" type="reference">@drawable/ic_pin_white</item> <item name="ic_pin" type="reference">@drawable/ic_pin_white</item>
<item name="ic_subject" type="reference">@drawable/ic_subject_white</item>
<item name="ic_close" type="reference">@drawable/ic_close_24dp</item> <item name="ic_close" type="reference">@drawable/ic_close_24dp</item>
<item name="ic_attach_location" type="reference">@drawable/ic_attach_location_white</item> <item name="ic_attach_location" type="reference">@drawable/ic_attach_location_white</item>
<item name="ic_attach_photo" type="reference">@drawable/ic_attach_photo_white</item> <item name="ic_attach_photo" type="reference">@drawable/ic_attach_photo_white</item>

View file

@ -64,6 +64,16 @@
android:key="always_full_timestamps" android:key="always_full_timestamps"
android:summary="@string/pref_always_show_full_timestamps_summary" android:summary="@string/pref_always_show_full_timestamps_summary"
android:title="@string/pref_always_show_full_timestamps" /> android:title="@string/pref_always_show_full_timestamps" />
<CheckBoxPreference
android:defaultValue="@bool/use_green_background"
android:key="show_contact_status"
android:summary="@string/pref_show_contact_presence_details"
android:title="@string/pref_show_contact_presence" />
<CheckBoxPreference
android:defaultValue="@bool/use_green_background"
android:key="show_account_indicator"
android:summary="@string/pref_show_account_indicator_details"
android:title="@string/pref_show_account_indicator" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/pref_navigation"> <PreferenceCategory android:title="@string/pref_navigation">