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:theme="@style/Theme.Session.DayNight.FlatActionBar"/>
<activity
android:name="org.thoughtcrime.securesms.loki.activities.RestoreActivity"
android:name="org.thoughtcrime.securesms.loki.activities.RecoveryPhraseRestoreActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize"
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.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.CreateProfileActivity;
import org.thoughtcrime.securesms.DeviceListFragment;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
@ -79,7 +78,6 @@ import network.loki.messenger.BuildConfig;
MultiDeviceGroupUpdateJob.class,
MultiDeviceReadUpdateJob.class,
MultiDeviceBlockedUpdateJob.class,
DeviceListFragment.class,
RefreshAttributesJob.class,
RequestGroupInfoJob.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.widget.Toast
import kotlinx.android.synthetic.main.activity_create_closed_group.*
import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView
import network.loki.messenger.R
import nl.komponents.kovenant.ui.successUi
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
@ -31,6 +30,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.session.libsignal.libsignal.util.guava.Optional
import java.lang.ref.WeakReference
//TODO Refactor to avoid using kotlinx.android.synthetic
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
private var isLoading = false
set(newValue) { field = newValue; invalidateOptionsMenu() }
@ -121,9 +121,9 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM
}
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
isLoading = true
loader.fadeIn()
loaderContainer.fadeIn()
ClosedGroupsProtocol.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
loader.fadeOut()
loaderContainer.fadeOut()
isLoading = false
val threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false))
if (!isFinishing) {

View File

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

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Intent
import android.os.AsyncTask
import android.os.Bundle
import android.widget.Toast
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.BaseActionBarActivity
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.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.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?) {
super.onCreate(savedInstanceState)
@ -44,109 +20,26 @@ class LandingActivity : BaseActionBarActivity(), LinkDeviceSlaveModeDialogDelega
setUpActionBarSessionLogo(true)
fakeChatView.startAnimating()
registerButton.setOnClickListener { register() }
restoreButton.setOnClickListener { restore() }
restoreBackupButton.setOnClickListener {
val intent = Intent(this, BackupRestoreActivity::class.java)
push(intent)
}
// linkButton.setOnClickListener { linkDevice() }
restoreButton.setOnClickListener { restoreFromRecoveryPhrase() }
restoreBackupButton.setOnClickListener { restoreFromBackup() }
if (TextSecurePreferences.getWasUnlinked(this)) {
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() {
val intent = Intent(this, RegisterActivity::class.java)
push(intent)
}
private fun restore() {
val intent = Intent(this, RestoreActivity::class.java)
private fun restoreFromRecoveryPhrase() {
val intent = Intent(this, RecoveryPhraseRestoreActivity::class.java)
push(intent)
}
private fun linkDevice() {
val intent = Intent(this, LinkDeviceActivity::class.java)
show(intent, true)
}
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 restoreFromBackup() {
val intent = Intent(this, BackupRestoreActivity::class.java)
push(intent)
}
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.view.View
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 org.thoughtcrime.securesms.BaseActionBarActivity
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.utilities.hexEncodedPublicKey
class RestoreActivity : BaseActionBarActivity() {
class RecoveryPhraseRestoreActivity : BaseActionBarActivity() {
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setUpActionBarSessionLogo()
setContentView(R.layout.activity_restore)
setContentView(R.layout.activity_recovery_phrase_restore)
mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard
restoreButton.setOnClickListener { restore() }
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.MenuItem
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.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.recyclerView
import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.mms.GlideApp
//TODO Refactor to avoid using kotlinx.android.synthetic
class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
private var members = listOf<String>()
set(value) { field = value; selectContactsAdapter.members = value }

View File

@ -288,11 +288,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
push(intent)
}
private fun showLinkedDevices() {
val intent = Intent(this, LinkedDevicesActivity::class.java)
push(intent)
}
private fun sendInvitation() {
val intent = Intent()
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>
<RelativeLayout
android:id="@+id/loader"
android:id="@+id/loaderContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#A4000000"

View File

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