Device link related views removed. General unused code cleanup.

This commit is contained in:
Anton Chekulaev 2020-12-04 16:00:52 +11:00
parent a94273fdfc
commit eafa7c7a77
34 changed files with 53 additions and 1729 deletions

View File

@ -101,7 +101,7 @@
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:theme="@style/Theme.Session.DayNight.FlatActionBar"/> android:theme="@style/Theme.Session.DayNight.FlatActionBar"/>
<activity <activity
android:name="org.thoughtcrime.securesms.loki.activities.RestoreActivity" android:name="org.thoughtcrime.securesms.loki.activities.RecoveryPhraseRestoreActivity"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.Session.DayNight.FlatActionBar" /> android:theme="@style/Theme.Session.DayNight.FlatActionBar" />

View File

@ -1,60 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import network.loki.messenger.R;
public class DeviceLinkFragment extends Fragment implements View.OnClickListener {
private LinearLayout container;
private LinkClickedListener linkClickedListener;
private Uri uri;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
this.container = (LinearLayout) inflater.inflate(R.layout.device_link_fragment, container, false);
this.container.findViewById(R.id.link_device).setOnClickListener(this);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
container.setOrientation(LinearLayout.HORIZONTAL);
} else {
container.setOrientation(LinearLayout.VERTICAL);
}
return this.container;
}
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
super.onConfigurationChanged(newConfiguration);
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
container.setOrientation(LinearLayout.HORIZONTAL);
} else {
container.setOrientation(LinearLayout.VERTICAL);
}
}
public void setLinkClickedListener(Uri uri, LinkClickedListener linkClickedListener) {
this.uri = uri;
this.linkClickedListener = linkClickedListener;
}
@Override
public void onClick(View v) {
if (linkClickedListener != null) {
linkClickedListener.onLink(uri);
}
}
public interface LinkClickedListener {
void onLink(Uri uri);
}
}

View File

@ -1,249 +0,0 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.ListFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import com.melnykov.fab.FloatingActionButton;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.devicelist.Device;
import org.thoughtcrime.securesms.loki.dialogs.DeviceEditingOptionsBottomSheet;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.session.libsignal.libsignal.util.guava.Function;
import java.util.List;
import java.util.Locale;
import kotlin.Pair;
import kotlin.Unit;
import network.loki.messenger.R;
import static org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt.toPx;
public class DeviceListFragment extends ListFragment
implements LoaderManager.LoaderCallbacks<List<Device>>,
ListView.OnItemClickListener, InjectableType, Button.OnClickListener
{
private static final String TAG = DeviceListFragment.class.getSimpleName();
private Locale locale;
private View empty;
private View progressContainer;
private FloatingActionButton addDeviceButton;
private Button.OnClickListener addDeviceButtonListener;
private Function<String, Void> handleDisconnectDevice;
private Function<Pair<String, String>, Void> handleDeviceNameChange;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
ApplicationContext.getInstance(activity).injectDependencies(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
View view = inflater.inflate(R.layout.device_list_fragment, container, false);
this.empty = view.findViewById(R.id.emptyStateTextView);
this.progressContainer = view.findViewById(R.id.activityIndicator);
this.addDeviceButton = ViewUtil.findById(view, R.id.addDeviceButton);
this.addDeviceButton.setOnClickListener(this);
updateAddDeviceButtonVisibility();
return view;
}
@Override
public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle);
getLoaderManager().initLoader(0, null, this);
getListView().setOnItemClickListener(this);
}
public void setAddDeviceButtonListener(Button.OnClickListener listener) {
this.addDeviceButtonListener = listener;
}
public void setHandleDisconnectDevice(Function<String, Void> handler) {
this.handleDisconnectDevice = handler;
}
public void setHandleDeviceNameChange(Function<Pair<String, String>, Void> handler) {
this.handleDeviceNameChange = handler;
}
@Override
public @NonNull Loader<List<Device>> onCreateLoader(int id, Bundle args) {
empty.setVisibility(View.GONE);
progressContainer.setVisibility(View.VISIBLE);
return new DeviceListLoader(getActivity());
}
@Override
public void onLoadFinished(@NonNull Loader<List<Device>> loader, List<Device> data) {
progressContainer.setVisibility(View.GONE);
if (data == null) {
handleLoaderFailed();
return;
}
setListAdapter(new DeviceListAdapter(getActivity(), R.layout.device_list_item_view, data, locale));
if (data.isEmpty()) {
empty.setVisibility(View.VISIBLE);
TextSecurePreferences.setMultiDevice(getActivity(), false);
} else {
empty.setVisibility(View.GONE);
}
}
@Override
public void onLoaderReset(@NonNull Loader<List<Device>> loader) {
setListAdapter(null);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final boolean hasDeviceName = ((DeviceListItem)view).hasDeviceName(); // Tells us whether the name is set to shortId or the device name
final String deviceName = ((DeviceListItem)view).getDeviceName();
final String deviceId = ((DeviceListItem)view).getDeviceId();
DeviceEditingOptionsBottomSheet bottomSheet = new DeviceEditingOptionsBottomSheet();
bottomSheet.setOnEditTapped(() -> {
bottomSheet.dismiss();
EditText deviceNameEditText = new EditText(getContext());
LinearLayout deviceNameEditTextContainer = new LinearLayout(getContext());
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
layoutParams.setMarginStart(toPx(18, getResources()));
layoutParams.setMarginEnd(toPx(18, getResources()));
deviceNameEditText.setLayoutParams(layoutParams);
deviceNameEditTextContainer.addView(deviceNameEditText);
deviceNameEditText.setText(hasDeviceName ? deviceName : "");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.DeviceListActivity_edit_device_name);
builder.setView(deviceNameEditTextContainer);
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (handleDeviceNameChange != null) { handleDeviceNameChange.apply(new Pair<>(deviceId, deviceNameEditText.getText().toString().trim())); }
}
});
builder.show();
return Unit.INSTANCE;
});
bottomSheet.setOnUnlinkTapped(() -> {
bottomSheet.dismiss();
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName));
builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (handleDisconnectDevice != null) { handleDisconnectDevice.apply(deviceId); }
}
});
builder.show();
return Unit.INSTANCE;
});
bottomSheet.show(getFragmentManager(), bottomSheet.getTag());
}
public void refresh() {
updateAddDeviceButtonVisibility();
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
}
private void updateAddDeviceButtonVisibility() {
if (addDeviceButton != null) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
boolean isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(getContext()).getDeviceLinks(userHexEncodedPublicKey).isEmpty();
addDeviceButton.setVisibility(isDeviceLinkingEnabled ? View.VISIBLE : View.INVISIBLE);
}
}
private void handleLoaderFailed() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.DeviceListActivity_network_connection_failed);
builder.setPositiveButton(R.string.DeviceListActivity_try_again,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
DeviceListFragment.this.getActivity().onBackPressed();
}
});
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
DeviceListFragment.this.getActivity().onBackPressed();
}
});
builder.show();
}
@Override
public void onClick(View v) {
if (addDeviceButtonListener != null) addDeviceButtonListener.onClick(v);
}
private static class DeviceListAdapter extends ArrayAdapter<Device> {
private final int resource;
private final Locale locale;
public DeviceListAdapter(Context context, int resource, List<Device> objects, Locale locale) {
super(context, resource, objects);
this.resource = resource;
this.locale = locale;
}
@Override
public @NonNull View getView(int position, View convertView, @NonNull ViewGroup parent) {
if (convertView == null) {
convertView = ((Activity)getContext()).getLayoutInflater().inflate(resource, parent, false);
}
((DeviceListItem)convertView).set(getItem(position), locale);
return convertView;
}
}
}

View File

@ -1,57 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.devicelist.Device;
import org.thoughtcrime.securesms.util.DateUtils;
import java.util.Locale;
import network.loki.messenger.R;
public class DeviceListItem extends LinearLayout {
private String deviceId;
private TextView name;
private TextView shortId;
public DeviceListItem(Context context) {
super(context);
}
public DeviceListItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
this.name = (TextView) findViewById(R.id.name);
this.shortId = (TextView) findViewById(R.id.shortId);
}
public void set(Device deviceInfo, Locale locale) {
this.deviceId = deviceInfo.getId();
boolean hasName = !TextUtils.isEmpty(deviceInfo.getName());
this.name.setText(hasName ? deviceInfo.getName() : deviceInfo.getShortId());
this.shortId.setText(deviceInfo.getShortId());
this.shortId.setVisibility(hasName ? VISIBLE : GONE);
}
public String getDeviceId() {
return deviceId;
}
public String getDeviceName() {
return name.getText().toString();
}
public boolean hasDeviceName() {
return shortId.getVisibility() == VISIBLE;
}
}

View File

@ -1,56 +0,0 @@
package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.devicelist.Device;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.AsyncLoader;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
public class DeviceListLoader extends AsyncLoader<List<Device>> {
private static final String TAG = DeviceListLoader.class.getSimpleName();
public DeviceListLoader(Context context) {
super(context);
}
@Override
public List<Device> loadInBackground() {
try {
String userPublicKey = TextSecurePreferences.getLocalNumber(getContext());
Set<String> slaveDevicePublicKeys = MultiDeviceProtocol.shared.getSlaveDevices(userPublicKey);
List<Device> devices = Stream.of(slaveDevicePublicKeys).map(this::mapToDevice).toList();
Collections.sort(devices, new DeviceComparator());
return devices;
} catch (Exception e) {
Log.w(TAG, e);
return null;
}
}
private Device mapToDevice(@NonNull String hexEncodedPublicKey) {
String shortId = "";
String name = DatabaseFactory.getLokiUserDatabase(getContext()).getDisplayName(hexEncodedPublicKey);
return new Device(hexEncodedPublicKey, shortId, name);
}
private static class DeviceComparator implements Comparator<Device> {
@Override
public int compare(Device lhs, Device rhs) {
return lhs.getName().compareTo(rhs.getName());
}
}
}

View File

@ -12,7 +12,6 @@ import org.session.libsignal.service.api.util.UptimeSleepTimer;
import org.session.libsignal.service.api.websocket.ConnectivityListener; import org.session.libsignal.service.api.websocket.ConnectivityListener;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.CreateProfileActivity; import org.thoughtcrime.securesms.CreateProfileActivity;
import org.thoughtcrime.securesms.DeviceListFragment;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
@ -79,7 +78,6 @@ import network.loki.messenger.BuildConfig;
MultiDeviceGroupUpdateJob.class, MultiDeviceGroupUpdateJob.class,
MultiDeviceReadUpdateJob.class, MultiDeviceReadUpdateJob.class,
MultiDeviceBlockedUpdateJob.class, MultiDeviceBlockedUpdateJob.class,
DeviceListFragment.class,
RefreshAttributesJob.class, RefreshAttributesJob.class,
RequestGroupInfoJob.class, RequestGroupInfoJob.class,
PushGroupUpdateJob.class, PushGroupUpdateJob.class,

View File

@ -1,20 +0,0 @@
package org.thoughtcrime.securesms.devicelist;
public class Device {
private final String id;
private final String shortId;
private final String name;
public Device(String id, String shortId, String name) {
this.id = id;
this.shortId = shortId;
this.name = name;
}
public String getId() {
return id;
}
public String getShortId() { return shortId; }
public String getName() { return name; }
}

View File

@ -13,7 +13,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import kotlinx.android.synthetic.main.activity_create_closed_group.* import kotlinx.android.synthetic.main.activity_create_closed_group.*
import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView
import network.loki.messenger.R import network.loki.messenger.R
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
@ -31,6 +30,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.libsignal.util.guava.Optional
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
//TODO Refactor to avoid using kotlinx.android.synthetic
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> { class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
private var isLoading = false private var isLoading = false
set(newValue) { field = newValue; invalidateOptionsMenu() } set(newValue) { field = newValue; invalidateOptionsMenu() }
@ -121,9 +121,9 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM
} }
val userPublicKey = TextSecurePreferences.getLocalNumber(this) val userPublicKey = TextSecurePreferences.getLocalNumber(this)
isLoading = true isLoading = true
loader.fadeIn() loaderContainer.fadeIn()
ClosedGroupsProtocol.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID -> ClosedGroupsProtocol.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
loader.fadeOut() loaderContainer.fadeOut()
isLoading = false isLoading = false
val threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false)) val threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false))
if (!isFinishing) { if (!isFinishing) {

View File

@ -3,25 +3,20 @@ package org.thoughtcrime.securesms.loki.activities
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.*
import androidx.appcompat.content.res.AppCompatResources import androidx.loader.app.LoaderManager
import kotlinx.android.synthetic.main.activity_create_closed_group.* import androidx.loader.content.Loader
import kotlinx.android.synthetic.main.activity_create_closed_group.emptyStateContainer import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_create_closed_group.mainContentContainer import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_edit_closed_group.*
import kotlinx.android.synthetic.main.activity_edit_closed_group.loader
import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView
import network.loki.messenger.R import network.loki.messenger.R
import nl.komponents.kovenant.ui.failUi import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
import org.session.libsignal.service.loki.utilities.toHexString
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
@ -32,10 +27,8 @@ import org.thoughtcrime.securesms.loki.utilities.fadeIn
import org.thoughtcrime.securesms.loki.utilities.fadeOut import org.thoughtcrime.securesms.loki.utilities.fadeOut
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.GroupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.ThemeUtil import org.thoughtcrime.securesms.util.ThemeUtil
import org.session.libsignal.service.loki.utilities.toHexString
import java.io.IOException import java.io.IOException
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
@ -60,6 +53,14 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
EditClosedGroupMembersAdapter(this, GlideApp.with(this), this::onMemberClick) EditClosedGroupMembersAdapter(this, GlideApp.with(this), this::onMemberClick)
} }
private lateinit var mainContentContainer: LinearLayout
private lateinit var cntGroupNameEdit: LinearLayout
private lateinit var cntGroupNameDisplay: LinearLayout
private lateinit var edtGroupName: EditText
private lateinit var emptyStateContainer: LinearLayout
private lateinit var lblGroupNameDisplay: TextView
private lateinit var loaderContainer: View
companion object { companion object {
@JvmStatic val groupIDKey = "groupIDKey" @JvmStatic val groupIDKey = "groupIDKey"
private val loaderID = 0 private val loaderID = 0
@ -79,15 +80,27 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
originalName = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get().title originalName = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get().title
name = originalName name = originalName
addMembersClosedGroupButton.setOnClickListener { onAddMembersClick() } mainContentContainer = findViewById(R.id.mainContentContainer)
cntGroupNameEdit = findViewById(R.id.cntGroupNameEdit)
cntGroupNameDisplay = findViewById(R.id.cntGroupNameDisplay)
edtGroupName = findViewById(R.id.edtGroupName)
emptyStateContainer = findViewById(R.id.emptyStateContainer)
lblGroupNameDisplay = findViewById(R.id.lblGroupNameDisplay)
loaderContainer = findViewById(R.id.loaderContainer)
recyclerView.adapter = memberListAdapter findViewById<View>(R.id.addMembersClosedGroupButton).setOnClickListener {
recyclerView.layoutManager = LinearLayoutManager(this) onAddMembersClick()
}
findViewById<RecyclerView>(R.id.rvUserList).apply {
adapter = memberListAdapter
layoutManager = LinearLayoutManager(this@EditClosedGroupActivity)
}
lblGroupNameDisplay.text = originalName lblGroupNameDisplay.text = originalName
cntGroupNameDisplay.setOnClickListener { isEditingName = true } cntGroupNameDisplay.setOnClickListener { isEditingName = true }
btnCancelGroupNameEdit.setOnClickListener { isEditingName = false } findViewById<Button>(R.id.btnCancelGroupNameEdit).setOnClickListener { isEditingName = false }
btnSaveGroupNameEdit.setOnClickListener { saveName() } findViewById<Button>(R.id.btnSaveGroupNameEdit).setOnClickListener { saveName() }
edtGroupName.setImeActionLabel(getString(R.string.save), EditorInfo.IME_ACTION_DONE) edtGroupName.setImeActionLabel(getString(R.string.save), EditorInfo.IME_ACTION_DONE)
edtGroupName.setOnEditorActionListener { _, actionId, _ -> edtGroupName.setOnEditorActionListener { _, actionId, _ ->
when (actionId) { when (actionId) {
@ -247,9 +260,9 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
if (isSSKBasedClosedGroup) { if (isSSKBasedClosedGroup) {
isLoading = true isLoading = true
loader.fadeIn() loaderContainer.fadeIn()
ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name).successUi { ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name).successUi {
loader.fadeOut() loaderContainer.fadeOut()
isLoading = false isLoading = false
finish() finish()
}.failUi { exception -> }.failUi { exception ->

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.loki.activities package org.thoughtcrime.securesms.loki.activities
import android.content.Intent import android.content.Intent
import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import kotlinx.android.synthetic.main.activity_landing.* import kotlinx.android.synthetic.main.activity_landing.*
@ -9,34 +8,11 @@ import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.IdentityDatabase
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialog
import org.thoughtcrime.securesms.loki.dialogs.LinkDeviceSlaveModeDialogDelegate
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceProtocol
import org.thoughtcrime.securesms.loki.utilities.push import org.thoughtcrime.securesms.loki.utilities.push
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.utilities.show
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.Hex
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.curve25519.Curve25519
import org.session.libsignal.libsignal.ecc.Curve
import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.libsignal.util.KeyHelper
import org.session.libsignal.service.loki.protocol.mentions.MentionsManager
import org.session.libsignal.service.loki.protocol.meta.SessionMetaProtocol
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol
import org.session.libsignal.service.loki.protocol.shelved.syncmessages.SyncMessagesProtocol
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.session.libsignal.service.loki.utilities.retryIfNeeded
import java.lang.UnsupportedOperationException
class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelegate { class LandingActivity : BaseActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -44,109 +20,26 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
setUpActionBarSessionLogo(true) setUpActionBarSessionLogo(true)
fakeChatView.startAnimating() fakeChatView.startAnimating()
registerButton.setOnClickListener { register() } registerButton.setOnClickListener { register() }
restoreButton.setOnClickListener { restore() } restoreButton.setOnClickListener { restoreFromRecoveryPhrase() }
restoreBackupButton.setOnClickListener { restoreBackupButton.setOnClickListener { restoreFromBackup() }
val intent = Intent(this, BackupRestoreActivity::class.java)
push(intent)
}
// linkButton.setOnClickListener { linkDevice() }
if (TextSecurePreferences.getWasUnlinked(this)) { if (TextSecurePreferences.getWasUnlinked(this)) {
Toast.makeText(this, R.string.activity_landing_device_unlinked_dialog_title, Toast.LENGTH_LONG).show() Toast.makeText(this, R.string.activity_landing_device_unlinked_dialog_title, Toast.LENGTH_LONG).show()
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
if (resultCode != RESULT_OK) { return }
val hexEncodedPublicKey = data!!.getStringExtra("hexEncodedPublicKey")
requestDeviceLink(hexEncodedPublicKey)
}
private fun register() { private fun register() {
val intent = Intent(this, RegisterActivity::class.java) val intent = Intent(this, RegisterActivity::class.java)
push(intent) push(intent)
} }
private fun restore() { private fun restoreFromRecoveryPhrase() {
val intent = Intent(this, RestoreActivity::class.java) val intent = Intent(this, RecoveryPhraseRestoreActivity::class.java)
push(intent) push(intent)
} }
private fun linkDevice() { private fun restoreFromBackup() {
val intent = Intent(this, LinkDeviceActivity::class.java) val intent = Intent(this, BackupRestoreActivity::class.java)
show(intent, true) push(intent)
}
private fun requestDeviceLink(hexEncodedPublicKey: String) {
var seed: ByteArray? = null
var keyPair: ECKeyPair? = null
//FIXME AC: Previously we used the modified version of the Signal's Curve25519 lib to generate the seed and key pair.
// If you need to restore this logic you should probably fork and patch the lib to support that method as well.
// https://github.com/signalapp/curve25519-java
fun generateKeyPair() {
throw UnsupportedOperationException("Generating device link key pair is not supported at the moment.")
// val seedCandidate = Curve25519.getInstance(Curve25519.BEST).generateSeed(16)
// try {
// keyPair = Curve.generateKeyPair(seedCandidate + seedCandidate) // Validate the seed
// } catch (exception: Exception) {
// return generateKeyPair()
// }
// seed = seedCandidate
}
generateKeyPair()
IdentityKeyUtil.save(this, IdentityKeyUtil.LOKI_SEED, Hex.toStringCondensed(seed))
IdentityKeyUtil.save(this, IdentityKeyUtil.IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(keyPair!!.publicKey.serialize()))
IdentityKeyUtil.save(this, IdentityKeyUtil.IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(keyPair!!.privateKey.serialize()))
val userHexEncodedPublicKey = keyPair!!.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false)
TextSecurePreferences.setLocalRegistrationId(this, registrationID)
DatabaseFactory.getIdentityDatabase(this).saveIdentity(Address.fromSerialized(userHexEncodedPublicKey),
IdentityKeyUtil.getIdentityKeyPair(this).publicKey, IdentityDatabase.VerifiedStatus.VERIFIED,
true, System.currentTimeMillis(), true)
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
TextSecurePreferences.setPromptedPushRegistration(this, true)
val deviceLink = DeviceLink(hexEncodedPublicKey, userHexEncodedPublicKey).sign(DeviceLink.Type.REQUEST, keyPair!!.privateKey.serialize())
if (deviceLink == null) {
Log.d("Loki", "Failed to sign device link request.")
reset()
return Toast.makeText(application, R.string.device_linking_failed, Toast.LENGTH_LONG).show()
}
val application = ApplicationContext.getInstance(this)
application.startPollingIfNeeded()
val apiDB = DatabaseFactory.getLokiAPIDatabase(this)
val threadDB = DatabaseFactory.getLokiThreadDatabase(this)
val userDB = DatabaseFactory.getLokiUserDatabase(this)
val sskDatabase = DatabaseFactory.getSSKDatabase(this)
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
val sessionResetImpl = SessionResetImplementation(this)
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey)
org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol.configureIfNeeded(apiDB)
SessionManagementProtocol.configureIfNeeded(sessionResetImpl, sskDatabase, application)
SyncMessagesProtocol.configureIfNeeded(apiDB, userPublicKey)
application.setUpP2PAPIIfNeeded()
application.setUpStorageAPIIfNeeded()
val linkDeviceDialog = LinkDeviceSlaveModeDialog()
linkDeviceDialog.delegate = this
linkDeviceDialog.show(supportFragmentManager, "Link Device Dialog")
AsyncTask.execute {
retryIfNeeded(8) {
MultiDeviceProtocol.sendDeviceLinkMessage(this@LandingActivity, deviceLink.masterPublicKey, deviceLink)
}
}
}
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
TextSecurePreferences.setMasterHexEncodedPublicKey(this, deviceLink.masterPublicKey)
val intent = Intent(this, HomeActivity::class.java)
show(intent)
finish()
}
override fun onDeviceLinkCanceled() {
reset()
} }
private fun reset() { private fun reset() {

View File

@ -1,104 +0,0 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_link_device.*
import kotlinx.android.synthetic.main.fragment_enter_session_id.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
import org.session.libsignal.service.loki.utilities.PublicKeyValidation
class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
private val adapter = LinkDeviceActivityAdapter(this)
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Set content view
setContentView(R.layout.activity_link_device)
// Set title
supportActionBar!!.title = resources.getString(R.string.activity_link_device_title)
// Set up view pager
viewPager.adapter = adapter
tabLayout.setupWithViewPager(viewPager)
}
// endregion
// region Interaction
override fun handleQRCodeScanned(hexEncodedPublicKey: String) {
requestDeviceLinkIfPossible(hexEncodedPublicKey)
}
fun requestDeviceLinkIfPossible(hexEncodedPublicKey: String) {
if (!PublicKeyValidation.isValid(hexEncodedPublicKey)) {
Toast.makeText(this, R.string.invalid_session_id, Toast.LENGTH_SHORT).show()
} else {
val intent = Intent()
intent.putExtra("hexEncodedPublicKey", hexEncodedPublicKey)
setResult(RESULT_OK, intent)
finish()
}
}
// endregion
}
// region Adapter
private class LinkDeviceActivityAdapter(val activity: LinkDeviceActivity) : FragmentPagerAdapter(activity.supportFragmentManager) {
override fun getCount(): Int {
return 2
}
override fun getItem(index: Int): Fragment {
return when (index) {
0 -> EnterSessionIDFragment()
1 -> {
val result = ScanQRCodeWrapperFragment()
result.delegate = activity
result.message = activity.resources.getString(R.string.activity_link_device_scan_qr_code_explanation)
result
}
else -> throw IllegalStateException()
}
}
override fun getPageTitle(index: Int): CharSequence? {
return when (index) {
0 -> activity.getString(R.string.activity_link_device_enter_session_id_tab_title)
1 -> activity.getString(R.string.activity_link_device_scan_qr_code_tab_title)
else -> throw IllegalStateException()
}
}
}
// endregion
// region Enter Session ID Fragment
class EnterSessionIDFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_enter_session_id, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sessionIDEditText.imeOptions = sessionIDEditText.imeOptions or 16777216 // Always use incognito keyboard
requestDeviceLinkButton.setOnClickListener { requestDeviceLinkIfPossible() }
}
private fun requestDeviceLinkIfPossible() {
val inputMethodManager = context!!.getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(sessionIDEditText.windowToken, 0)
val hexEncodedPublicKey = sessionIDEditText.text.trim().toString().toLowerCase()
(activity!! as LinkDeviceActivity).requestDeviceLinkIfPossible(hexEncodedPublicKey)
}
}
// endregion

View File

@ -1,178 +0,0 @@
package org.thoughtcrime.securesms.loki.activities
import android.os.Bundle
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_linked_devices.*
import network.loki.messenger.R
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.devicelist.Device
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.loki.dialogs.*
import org.thoughtcrime.securesms.loki.protocol.shelved.SyncMessagesProtocol
import org.thoughtcrime.securesms.loki.utilities.recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.session.libsignal.service.api.messages.SignalServiceDataMessage
import org.session.libsignal.service.api.push.SignalServiceAddress
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
import java.util.*
import kotlin.concurrent.schedule
class LinkedDevicesActivity : PassphraseRequiredActionBarActivity, LoaderManager.LoaderCallbacks<List<Device>>, DeviceClickListener, EditDeviceNameDialogDelegate, LinkDeviceMasterModeDialogDelegate {
private var devices = listOf<Device>()
set(value) { field = value; linkedDevicesAdapter.devices = value }
private val linkedDevicesAdapter by lazy {
val result = LinkedDevicesAdapter(this)
result.deviceClickListener = this
result
}
// region Lifecycle
constructor() : super()
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_linked_devices)
supportActionBar!!.title = resources.getString(R.string.activity_linked_devices_title)
recyclerView.adapter = linkedDevicesAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
linkDeviceButton.setOnClickListener { linkDevice() }
LoaderManager.getInstance(this).initLoader(0, null, this)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_linked_devices, menu)
return true
}
// endregion
// region Updating
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<List<Device>> {
return LinkedDevicesLoader(this)
}
override fun onLoadFinished(loader: Loader<List<Device>>, devices: List<Device>?) {
update(devices ?: listOf())
}
override fun onLoaderReset(loader: Loader<List<Device>>) {
update(listOf())
}
private fun update(devices: List<Device>) {
this.devices = devices
emptyStateContainer.visibility = if (devices.isEmpty()) View.VISIBLE else View.GONE
}
override fun handleDeviceNameChanged(device: Device) {
LoaderManager.getInstance(this).restartLoader(0, null, this)
}
// endregion
// region Interaction
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
when(id) {
R.id.linkDeviceButton -> linkDevice()
else -> { /* Do nothing */ }
}
return super.onOptionsItemSelected(item)
}
private fun linkDevice() {
if (devices.isEmpty()) {
val linkDeviceDialog = LinkDeviceMasterModeDialog()
linkDeviceDialog.delegate = this
linkDeviceDialog.show(supportFragmentManager, "Link Device Dialog")
} else {
val builder = AlertDialog.Builder(this)
builder.setTitle(resources.getString(R.string.activity_linked_devices_multi_device_limit_reached_dialog_title))
builder.setMessage(resources.getString(R.string.activity_linked_devices_multi_device_limit_reached_dialog_explanation))
builder.setPositiveButton(resources.getString(R.string.ok), { dialog, _ -> dialog.dismiss() })
builder.create().show()
}
}
override fun onDeviceClick(device: Device) {
val bottomSheet = DeviceEditingOptionsBottomSheet()
bottomSheet.onEditTapped = {
bottomSheet.dismiss()
val editDeviceNameDialog = EditDeviceNameDialog()
editDeviceNameDialog.device = device
editDeviceNameDialog.delegate = this
editDeviceNameDialog.show(supportFragmentManager, "Edit Device Name Dialog")
}
bottomSheet.onUnlinkTapped = {
bottomSheet.dismiss()
unlinkDevice(device.id)
}
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
}
private fun unlinkDevice(slaveDevicePublicKey: String) {
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
val apiDB = DatabaseFactory.getLokiAPIDatabase(this)
val deviceLinks = apiDB.getDeviceLinks(userPublicKey)
val deviceLink = deviceLinks.find { it.masterPublicKey == userPublicKey && it.slavePublicKey == slaveDevicePublicKey }
if (deviceLink == null) {
return Toast.makeText(this, R.string.activity_linked_devices_unlinking_failed_message, Toast.LENGTH_LONG).show()
}
FileServerAPI.shared.setDeviceLinks(setOf()).successUi {
DatabaseFactory.getLokiAPIDatabase(this).clearDeviceLinks(userPublicKey)
deviceLinks.forEach { deviceLink ->
// We don't use PushEphemeralMessageJob because want these messages to send before the pre key and
// session associated with the slave device have been deleted
val unlinkingRequest = SignalServiceDataMessage.newBuilder()
.withTimestamp(System.currentTimeMillis())
.asDeviceUnlinkingRequest(true)
val messageSender = ApplicationContext.getInstance(this@LinkedDevicesActivity).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(deviceLink.slavePublicKey)
try {
val udAccess = UnidentifiedAccessUtil.getAccessFor(this@LinkedDevicesActivity, recipient(this@LinkedDevicesActivity, deviceLink.slavePublicKey))
messageSender.sendMessage(0, address, udAccess, unlinkingRequest.build()) // The message ID doesn't matter
} catch (e: Exception) {
Log.d("Loki", "Failed to send unlinking request due to error: $e.")
throw e
}
DatabaseFactory.getLokiPreKeyBundleDatabase(this).removePreKeyBundle(deviceLink.slavePublicKey)
val sessionStore = TextSecureSessionStore(this@LinkedDevicesActivity)
sessionStore.deleteAllSessions(deviceLink.slavePublicKey)
}
LoaderManager.getInstance(this).restartLoader(0, null, this)
Toast.makeText(this, R.string.activity_linked_devices_unlinking_successful_message, Toast.LENGTH_LONG).show()
}.failUi {
Toast.makeText(this, R.string.activity_linked_devices_unlinking_failed_message, Toast.LENGTH_LONG).show()
}
}
override fun onDeviceLinkRequestAuthorized() {
SyncMessagesProtocol.syncAllClosedGroups(this)
SyncMessagesProtocol.syncAllOpenGroups(this)
Timer().schedule(4000) { // Not the best way to do this but the idea is to wait for the closed groups sync to go through first
SyncMessagesProtocol.syncAllContacts(this@LinkedDevicesActivity)
}
LoaderManager.getInstance(this).restartLoader(0, null, this)
}
override fun onDeviceLinkAuthorizationFailed() {
Toast.makeText(this, R.string.activity_linked_devices_linking_failed_message, Toast.LENGTH_LONG).show()
}
override fun onDeviceLinkCanceled() {
// Do nothing
}
// endregion
}

View File

@ -1,35 +0,0 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup
import org.thoughtcrime.securesms.devicelist.Device
import org.thoughtcrime.securesms.loki.views.DeviceView
class LinkedDevicesAdapter(private val context: Context) : RecyclerView.Adapter<LinkedDevicesAdapter.ViewHolder>() {
var devices = listOf<Device>()
set(value) { field = value; notifyDataSetChanged() }
var deviceClickListener: DeviceClickListener? = null
class ViewHolder(val view: DeviceView) : RecyclerView.ViewHolder(view)
override fun getItemCount(): Int {
return devices.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = DeviceView(context)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val device = devices[position]
viewHolder.view.setOnClickListener { deviceClickListener?.onDeviceClick(device) }
viewHolder.view.bind(device)
}
}
interface DeviceClickListener {
fun onDeviceClick(device: Device)
}

View File

@ -1,35 +0,0 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.devicelist.Device
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.util.AsyncLoader
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.session.libsignal.service.loki.crypto.MnemonicCodec
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import java.io.File
class LinkedDevicesLoader(context: Context) : AsyncLoader<List<Device>>(context) {
private val mnemonicCodec by lazy {
val loadFileContents: (String) -> String = { fileName ->
MnemonicUtilities.loadFileContents(context, fileName)
}
MnemonicCodec(loadFileContents)
}
override fun loadInBackground(): List<Device>? {
try {
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val slaveDevices = MultiDeviceProtocol.shared.getSlaveDevices(userPublicKey)
return slaveDevices.map { device ->
val shortID = MnemonicUtilities.getFirst3Words(mnemonicCodec, device)
val name = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(device)
Device(device, shortID, name)
}.sortedBy { it.name }
} catch (e: Exception) {
return null
}
}
}

View File

@ -11,7 +11,7 @@ import android.text.style.ClickableSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import kotlinx.android.synthetic.main.activity_restore.* import kotlinx.android.synthetic.main.activity_recovery_phrase_restore.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
@ -28,13 +28,13 @@ import org.session.libsignal.libsignal.util.KeyHelper
import org.session.libsignal.service.loki.crypto.MnemonicCodec import org.session.libsignal.service.loki.crypto.MnemonicCodec
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
class RestoreActivity : BaseActionBarActivity() { class RecoveryPhraseRestoreActivity : BaseActionBarActivity() {
// region Lifecycle // region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
setContentView(R.layout.activity_restore) setContentView(R.layout.activity_recovery_phrase_restore)
mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard
restoreButton.setOnClickListener { restore() } restoreButton.setOnClickListener { restore() }
val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy") val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy")

View File

@ -9,15 +9,15 @@ import androidx.recyclerview.widget.LinearLayoutManager
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import kotlinx.android.synthetic.main.activity_create_closed_group.*
import kotlinx.android.synthetic.main.activity_create_closed_group.emptyStateContainer import kotlinx.android.synthetic.main.activity_create_closed_group.emptyStateContainer
import kotlinx.android.synthetic.main.activity_create_closed_group.mainContentContainer import kotlinx.android.synthetic.main.activity_create_closed_group.mainContentContainer
import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView
import kotlinx.android.synthetic.main.activity_select_contacts.* import kotlinx.android.synthetic.main.activity_select_contacts.*
import kotlinx.android.synthetic.main.activity_select_contacts.recyclerView
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
//TODO Refactor to avoid using kotlinx.android.synthetic
class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> { class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
private var members = listOf<String>() private var members = listOf<String>()
set(value) { field = value; selectContactsAdapter.members = value } set(value) { field = value; selectContactsAdapter.members = value }

View File

@ -288,11 +288,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
push(intent) push(intent)
} }
private fun showLinkedDevices() {
val intent = Intent(this, LinkedDevicesActivity::class.java)
push(intent)
}
private fun sendInvitation() { private fun sendInvitation() {
val intent = Intent() val intent = Intent()
intent.action = Intent.ACTION_SEND intent.action = Intent.ACTION_SEND

View File

@ -1,42 +0,0 @@
package org.thoughtcrime.securesms.loki.dialogs
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import android.view.View
import kotlinx.android.synthetic.main.dialog_edit_device_name.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.devicelist.Device
class EditDeviceNameDialog : DialogFragment() {
private lateinit var contentView: View
var device: Device? = null
var delegate: EditDeviceNameDialogDelegate? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(context!!)
contentView = LayoutInflater.from(context!!).inflate(R.layout.dialog_edit_device_name, null)
contentView.cancelButton.setOnClickListener { dismiss() }
contentView.okButton.setOnClickListener { updateDeviceName() }
builder.setView(contentView)
val result = builder.create()
result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
return result
}
private fun updateDeviceName() {
DatabaseFactory.getLokiUserDatabase(context).setDisplayName(device!!.id, contentView.deviceNameEditText.text.toString())
delegate?.handleDeviceNameChanged(device!!)
dismiss()
}
}
interface EditDeviceNameDialogDelegate {
fun handleDeviceNameChanged(device: Device)
}

View File

@ -1,121 +0,0 @@
package org.thoughtcrime.securesms.loki.dialogs
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.dialog_link_device_master_mode.view.*
import network.loki.messenger.R
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceProtocol
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.loki.utilities.QRCodeUtilities
import org.thoughtcrime.securesms.loki.utilities.toPx
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.session.libsignal.service.loki.api.SnodeAPI
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
import org.session.libsignal.service.loki.crypto.MnemonicCodec
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLinkingSession
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLinkingSessionListener
class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListener {
private lateinit var contentView: View
private var deviceLink: DeviceLink? = null
var delegate: LinkDeviceMasterModeDialogDelegate? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext())
contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_link_device_master_mode, null)
val size = toPx(128, resources)
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(requireContext())
val qrCode = QRCodeUtilities.encode(hexEncodedPublicKey, size, false, false)
contentView.qrCodeImageView.setImageBitmap(qrCode)
contentView.cancelButton.setOnClickListener { onDeviceLinkCanceled() }
contentView.authorizeButton.setOnClickListener { authorizeDeviceLink() }
builder.setView(contentView)
DeviceLinkingSession.shared.startListeningForLinkingRequests() // FIXME: This flag is named poorly as it's actually also used for authorizations
DeviceLinkingSession.shared.addListener(this)
val result = builder.create()
result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
return result
}
override fun requestUserAuthorization(deviceLink: DeviceLink) {
if (deviceLink.type != DeviceLink.Type.REQUEST || deviceLink.masterPublicKey != TextSecurePreferences.getLocalNumber(requireContext()) || this.deviceLink != null) { return }
Util.runOnMain {
this.deviceLink = deviceLink
contentView.qrCodeImageView.visibility = View.GONE
val titleTextViewLayoutParams = contentView.titleTextView.layoutParams as LinearLayout.LayoutParams
titleTextViewLayoutParams.topMargin = toPx(8, resources)
contentView.titleTextView.layoutParams = titleTextViewLayoutParams
contentView.titleTextView.text = resources.getString(R.string.dialog_link_device_master_mode_title_2)
contentView.explanationTextView.text = resources.getString(R.string.dialog_link_device_master_mode_explanation_2)
contentView.mnemonicTextView.visibility = View.VISIBLE
val loadFileContents: (String) -> String = { fileName ->
MnemonicUtilities.loadFileContents(requireContext(), fileName)
}
contentView.mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(loadFileContents), deviceLink.slavePublicKey)
contentView.authorizeButton.visibility = View.VISIBLE
}
}
private fun authorizeDeviceLink() {
val deviceLink = this.deviceLink ?: return
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
DeviceLinkingSession.shared.removeListener(this)
Util.runOnMain {
contentView.qrCodeImageViewContainer.visibility = View.GONE
contentView.spinner.visibility = View.VISIBLE
val titleTextViewLayoutParams = contentView.titleTextView.layoutParams as LinearLayout.LayoutParams
titleTextViewLayoutParams.topMargin = toPx(24, resources)
contentView.titleTextView.layoutParams = titleTextViewLayoutParams
contentView.titleTextView.text = resources.getString(R.string.dialog_link_device_master_mode_title_3)
contentView.explanationTextView.text = resources.getString(R.string.dialog_link_device_master_mode_explanation_3)
contentView.mnemonicTextView.visibility = View.GONE
contentView.buttonContainer.visibility = View.GONE
contentView.cancelButton.visibility = View.GONE
contentView.authorizeButton.visibility = View.GONE
}
FileServerAPI.shared.addDeviceLink(deviceLink).bind(SnodeAPI.sharedContext) {
MultiDeviceProtocol.signAndSendDeviceLinkMessage(requireContext(), deviceLink)
}.success {
TextSecurePreferences.setMultiDevice(requireContext(), true)
}.successUi {
delegate?.onDeviceLinkRequestAuthorized()
dismiss()
}.fail {
FileServerAPI.shared.removeDeviceLink(deviceLink) // If this fails we have a problem
DatabaseFactory.getLokiPreKeyBundleDatabase(requireContext()).removePreKeyBundle(deviceLink.slavePublicKey)
}.failUi {
delegate?.onDeviceLinkAuthorizationFailed()
dismiss()
}
}
private fun onDeviceLinkCanceled() {
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
DeviceLinkingSession.shared.removeListener(this)
if (deviceLink != null) {
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(deviceLink!!.slavePublicKey)
}
dismiss()
delegate?.onDeviceLinkCanceled()
}
}
interface LinkDeviceMasterModeDialogDelegate {
fun onDeviceLinkRequestAuthorized()
fun onDeviceLinkAuthorizationFailed()
fun onDeviceLinkCanceled()
}

View File

@ -1,78 +0,0 @@
package org.thoughtcrime.securesms.loki.dialogs
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.Handler
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.dialog_link_device_slave_mode.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.session.libsignal.service.loki.crypto.MnemonicCodec
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLinkingSession
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLinkingSessionListener
class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener {
private lateinit var contentView: View
private var deviceLink: DeviceLink? = null
var delegate: LinkDeviceSlaveModeDialogDelegate? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext())
contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_link_device_slave_mode, null)
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
val loadFileContents: (String) -> String = { fileName ->
MnemonicUtilities.loadFileContents(requireContext(), fileName)
}
contentView.mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(loadFileContents), hexEncodedPublicKey)
contentView.cancelButton.setOnClickListener { onDeviceLinkCanceled() }
builder.setView(contentView)
DeviceLinkingSession.shared.startListeningForLinkingRequests()
DeviceLinkingSession.shared.addListener(this)
val result = builder.create()
result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
return result
}
override fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) {
if (deviceLink.type != DeviceLink.Type.AUTHORIZATION || deviceLink.slavePublicKey != TextSecurePreferences.getLocalNumber(requireContext()) || this.deviceLink != null) { return }
Util.runOnMain {
this.deviceLink = deviceLink
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
DeviceLinkingSession.shared.removeListener(this)
contentView.spinner.visibility = View.GONE
val titleTextViewLayoutParams = contentView.titleTextView.layoutParams as LinearLayout.LayoutParams
titleTextViewLayoutParams.topMargin = 0
contentView.titleTextView.layoutParams = titleTextViewLayoutParams
contentView.titleTextView.text = resources.getString(R.string.dialog_link_device_slave_mode_title_2)
contentView.explanationTextView.text = resources.getString(R.string.dialog_link_device_slave_mode_explanation_2)
contentView.mnemonicTextView.visibility = View.GONE
contentView.cancelButton.visibility = View.GONE
Handler().postDelayed({
dismiss()
delegate?.onDeviceLinkRequestAuthorized(deviceLink)
}, 4000)
}
}
private fun onDeviceLinkCanceled() {
DeviceLinkingSession.shared.stopListeningForLinkingRequests()
DeviceLinkingSession.shared.removeListener(this)
dismiss()
delegate?.onDeviceLinkCanceled()
}
}
interface LinkDeviceSlaveModeDialogDelegate {
fun onDeviceLinkRequestAuthorized(authorization: DeviceLink)
fun onDeviceLinkCanceled()
}

View File

@ -1,49 +0,0 @@
package org.thoughtcrime.securesms.loki.views
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_device.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.devicelist.Device
import org.thoughtcrime.securesms.loki.utilities.toPx
class DeviceView : LinearLayout {
var device: Device? = null
// region Lifecycle
constructor(context: Context) : super(context) {
setUpViewHierarchy()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
setUpViewHierarchy()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
setUpViewHierarchy()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
setUpViewHierarchy()
}
private fun setUpViewHierarchy() {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val contentView = inflater.inflate(R.layout.view_device, null)
addView(contentView)
}
// endregion
// region Updating
fun bind(device: Device) {
titleTextView.text = if (!device.name.isNullOrBlank()) device.name else "Unnamed Device"
// FIXME: Hacky way of getting the view to be screen width
val titleTextViewLayoutParams = titleTextView.layoutParams
titleTextViewLayoutParams.width = resources.displayMetrics.widthPixels - toPx(32, resources)
titleTextView.layoutParams = titleTextViewLayoutParams
subtitleTextView.text = device.shortId
}
// endregion
}

View File

@ -66,7 +66,7 @@
</LinearLayout> </LinearLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/loader" android:id="@+id/loaderContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#A4000000" android:background="#A4000000"

View File

@ -129,7 +129,7 @@
android:background="?android:dividerHorizontal" /> android:background="?android:dividerHorizontal" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/rvUserList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:listitem="@layout/view_user"/> tools:listitem="@layout/view_user"/>
@ -147,7 +147,7 @@
</LinearLayout> </LinearLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/loader" android:id="@+id/loaderContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#A4000000" android:background="#A4000000"

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.viewpager.widget.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.google.android.material.tabs.TabLayout
style="@style/Widget.Session.TabLayout"
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="@dimen/tab_bar_height" />
</androidx.viewpager.widget.ViewPager>

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/emptyStateContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_centerInParent="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:text="@string/activity_linked_devices_empty_state_message" />
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"
android:id="@+id/linkDeviceButton"
android:layout_width="196dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="@dimen/medium_spacing"
android:text="@string/activity_linked_devices_empty_state_button_title" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<org.thoughtcrime.securesms.components.camera.CameraView
android:id="@+id/scanner"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:camera="0"/>
<LinearLayout android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="2">
<org.thoughtcrime.securesms.components.ShapeScrim
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:background="?android:windowBackground"
android:gravity="center">
<ImageView android:id="@+id/devices"
android:src="@drawable/ic_devices_white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/gray27"
android:transitionName="devices"
android:layout_marginBottom="16dp"/>
<TextView android:text="@string/device_add_fragment__scan_the_qr_code_displayed_on_the_device_to_link"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorSecondary"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_devices_white"
android:transitionName="devices"
android:tint="@color/gray27"
android:layout_marginBottom="25dp"
android:contentDescription="@string/device_link_fragment__link_device"/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp">
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?device_link_item_card_background">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:textStyle="bold"
android:text="@string/DeviceProvisioningActivity_link_this_device"
android:textSize="16sp"/>
<View android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#1E000000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@string/DeviceProvisioningActivity_content_intro"
android:textSize="14sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@string/DeviceProvisioningActivity_content_bullets"
android:textSize="14sp"/>
<View android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#1E000000"/>
<LinearLayout android:id="@+id/link_device"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:gravity="center_vertical"
android:clickable="true">
<ImageView android:id="@+id/check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_check_white_24dp"
android:tint="@color/blue_400"
android:clickable="false"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="@color/blue_400"
android:text="@string/device_link_fragment__link_device"
android:clickable="false"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:fab="http://schemas.android.com/apk/res-auto">
<ProgressBar
android:id="@+id/activityIndicator"
android:indeterminate="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone" />
<TextView
android:id="@+id/emptyStateTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="20sp"
android:visibility="gone"
android:text="@string/device_list_fragment__no_devices_linked"
android:paddingStart="16dip"
android:paddingEnd="16dip"
tools:visibility="visible"/>
<ListView
android:id="@id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="false" />
<com.melnykov.fab.FloatingActionButton
android:id="@+id/addDeviceButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="16dp"
android:src="@drawable/ic_add_white_original_24dp"
android:focusable="true"
android:contentDescription="@string/device_list_fragment__link_new_device"
fab:fab_colorNormal="?fab_color"
fab:fab_colorPressed="@color/textsecure_primary_dark"
fab:fab_colorRipple="@color/textsecure_primary_dark" />
</RelativeLayout>

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.DeviceListItem
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:text="Name"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?attr/conversation_list_item_contact_color"
android:textSize="18sp" />
<TextView
android:id="@+id/shortId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="marquee"
android:singleLine="true"
android:text="shortId"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#A2A2A2"
android:textSize="14sp" />
</org.thoughtcrime.securesms.DeviceListItem>

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/default_dialog_background_inset"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingLeft="32dp"
android:paddingTop="@dimen/medium_spacing"
android:paddingRight="32dp"
android:paddingBottom="@dimen/medium_spacing">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Change Device Name"
android:textStyle="bold"
android:textSize="@dimen/medium_font_size" />
<EditText
style="@style/SessionEditText"
android:id="@+id/deviceNameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/medium_spacing"
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginRight="@dimen/medium_spacing"
android:textAlignment="center"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:hint="@string/dialog_edit_device_name_edit_text_hint" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:orientation="horizontal">
<Button
style="@style/Widget.Session.Button.Dialog.Unimportant"
android:id="@+id/cancelButton"
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
android:layout_weight="1"
android:text="@string/cancel" />
<Button
style="@style/Widget.Session.Button.Dialog.Unimportant"
android:id="@+id/okButton"
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
android:layout_weight="1"
android:layout_marginLeft="@dimen/medium_spacing"
android:text="@string/ok" />
</LinearLayout>
</LinearLayout>

View File

@ -1,96 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@drawable/default_dialog_background_inset"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingLeft="32dp"
android:paddingTop="@dimen/medium_spacing"
android:paddingRight="32dp"
android:paddingBottom="@dimen/medium_spacing">
<RelativeLayout
android:id="@+id/qrCodeImageViewContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/small_spacing"
android:gravity="center">
<ImageView
android:id="@+id/qrCodeImageView"
android:layout_width="128dp"
android:layout_height="128dp" />
</RelativeLayout>
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.DoubleBounce"
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:visibility="gone"
app:SpinKit_Color="@color/text" />
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:text="@string/dialog_link_device_master_mode_title_1"
android:textColor="@color/text"
android:textStyle="bold"
android:textSize="@dimen/medium_font_size" />
<TextView
android:id="@+id/explanationTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:text="@string/dialog_link_device_master_mode_explanation_1"
android:textColor="@color/text"
android:textSize="@dimen/small_font_size"
android:textAlignment="center" />
<TextView
android:id="@+id/mnemonicTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="puffin circle idled"
android:textColor="@color/text"
android:textSize="@dimen/small_font_size"
android:textAlignment="center"
android:visibility="gone" />
<LinearLayout
android:id="@+id/buttonContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:orientation="horizontal">
<Button
style="@style/Widget.Session.Button.Dialog.Unimportant"
android:id="@+id/cancelButton"
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
android:layout_weight="1"
android:text="@string/cancel" />
<Button
style="@style/Widget.Session.Button.Dialog.Prominent"
android:id="@+id/authorizeButton"
android:layout_width="0dp"
android:layout_height="@dimen/small_button_height"
android:layout_weight="1"
android:layout_marginStart="@dimen/medium_spacing"
android:text="@string/dialog_link_device_master_mode_authorize_button_title"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>

View File

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@drawable/default_dialog_background_inset"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingLeft="32dp"
android:paddingTop="@dimen/medium_spacing"
android:paddingRight="32dp"
android:paddingBottom="@dimen/medium_spacing">
<com.github.ybq.android.spinkit.SpinKitView
style="@style/SpinKitView.DoubleBounce"
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:SpinKit_Color="@color/text" />
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:text="@string/dialog_link_device_slave_mode_title_1"
android:textColor="@color/text"
android:textStyle="bold"
android:textSize="@dimen/medium_font_size" />
<TextView
android:id="@+id/explanationTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:text="@string/dialog_link_device_slave_mode_explanation_1"
android:textColor="@color/text"
android:textSize="@dimen/small_font_size"
android:textAlignment="center" />
<TextView
android:id="@+id/mnemonicTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:text="puffin circle idled"
android:textColor="@color/text"
android:textSize="@dimen/small_font_size"
android:textAlignment="center" />
<Button
style="@style/Widget.Session.Button.Dialog.Unimportant"
android:id="@+id/cancelButton"
android:layout_width="match_parent"
android:layout_height="@dimen/small_button_height"
android:layout_marginTop="@dimen/large_spacing"
android:text="@string/cancel" />
</LinearLayout>