mirror of
https://github.com/etesync/android
synced 2025-07-04 22:02:37 +00:00
Add UI to add/remove/list journal members.
Only owners of a journal are allowed to control and view its members.
This commit is contained in:
parent
4246ae7ede
commit
656dad3615
@ -184,6 +184,7 @@
|
|||||||
android:parentActivityName=".ui.AccountsActivity">
|
android:parentActivityName=".ui.AccountsActivity">
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".ui.ViewCollectionActivity"/>
|
<activity android:name=".ui.ViewCollectionActivity"/>
|
||||||
|
<activity android:name=".ui.CollectionMembersActivity"/>
|
||||||
<activity android:name=".ui.importlocal.ImportActivity"/>
|
<activity android:name=".ui.importlocal.ImportActivity"/>
|
||||||
<activity android:name=".ui.AccountSettingsActivity"/>
|
<activity android:name=".ui.AccountSettingsActivity"/>
|
||||||
<activity android:name=".ui.CreateCollectionActivity"/>
|
<activity android:name=".ui.CreateCollectionActivity"/>
|
||||||
|
@ -115,6 +115,12 @@ public class Crypto {
|
|||||||
public static byte[] getKeyFingerprint(byte[] pubkey) {
|
public static byte[] getKeyFingerprint(byte[] pubkey) {
|
||||||
return sha256(pubkey);
|
return sha256(pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getPrettyKeyFingerprint(byte[] pubkey) {
|
||||||
|
byte[] fingerprint = Crypto.AsymmetricCryptoManager.getKeyFingerprint(pubkey);
|
||||||
|
String fingerprintString = Hex.toHexString(fingerprint).toLowerCase();
|
||||||
|
return fingerprintString.replaceAll("(.{4})", "$1 ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CryptoManager {
|
public static class CryptoManager {
|
||||||
@ -125,6 +131,7 @@ public class Crypto {
|
|||||||
private final byte version;
|
private final byte version;
|
||||||
private byte[] cipherKey;
|
private byte[] cipherKey;
|
||||||
private byte[] hmacKey;
|
private byte[] hmacKey;
|
||||||
|
private byte[] derivedKey;
|
||||||
|
|
||||||
private void setDerivedKey(byte[] derivedKey) {
|
private void setDerivedKey(byte[] derivedKey) {
|
||||||
cipherKey = hmac256("aes".getBytes(Charsets.UTF_8), derivedKey);
|
cipherKey = hmac256("aes".getBytes(Charsets.UTF_8), derivedKey);
|
||||||
@ -133,14 +140,13 @@ public class Crypto {
|
|||||||
|
|
||||||
public CryptoManager(int version, AsymmetricKeyPair keyPair, byte[] encryptedKey) {
|
public CryptoManager(int version, AsymmetricKeyPair keyPair, byte[] encryptedKey) {
|
||||||
Crypto.AsymmetricCryptoManager cryptoManager = new Crypto.AsymmetricCryptoManager(keyPair);
|
Crypto.AsymmetricCryptoManager cryptoManager = new Crypto.AsymmetricCryptoManager(keyPair);
|
||||||
byte[] derivedKey = cryptoManager.decrypt(encryptedKey);
|
derivedKey = cryptoManager.decrypt(encryptedKey);
|
||||||
|
|
||||||
this.version = (byte) version;
|
this.version = (byte) version;
|
||||||
setDerivedKey(derivedKey);
|
setDerivedKey(derivedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CryptoManager(int version, @NonNull String keyBase64, @NonNull String salt) throws Exceptions.IntegrityException, Exceptions.VersionTooNewException {
|
public CryptoManager(int version, @NonNull String keyBase64, @NonNull String salt) throws Exceptions.IntegrityException, Exceptions.VersionTooNewException {
|
||||||
byte[] derivedKey;
|
|
||||||
if (version > Byte.MAX_VALUE) {
|
if (version > Byte.MAX_VALUE) {
|
||||||
throw new Exceptions.IntegrityException("Version is out of range.");
|
throw new Exceptions.IntegrityException("Version is out of range.");
|
||||||
} else if (version > Constants.CURRENT_VERSION) {
|
} else if (version > Constants.CURRENT_VERSION) {
|
||||||
@ -238,6 +244,11 @@ public class Crypto {
|
|||||||
hmac.doFinal(ret, 0);
|
hmac.doFinal(ret, 0);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getEncryptedKey(AsymmetricKeyPair keyPair, byte[] publicKey) {
|
||||||
|
AsymmetricCryptoManager cryptoManager = new AsymmetricCryptoManager(keyPair);
|
||||||
|
return cryptoManager.encrypt(publicKey, derivedKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String sha256(String base) {
|
static String sha256(String base) {
|
||||||
|
@ -144,6 +144,12 @@ public class JournalManager extends BaseManager {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Journal fakeWithUid(String uid) {
|
||||||
|
Journal ret = new Journal();
|
||||||
|
ret.setUid(uid);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public Journal(Crypto.CryptoManager crypto, String content, String uid) {
|
public Journal(Crypto.CryptoManager crypto, String content, String uid) {
|
||||||
super(crypto, content, uid);
|
super(crypto, content, uid);
|
||||||
hmac = calculateHmac(crypto);
|
hmac = calculateHmac(crypto);
|
||||||
|
@ -217,9 +217,7 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
|
|||||||
AccountSettings settings = null;
|
AccountSettings settings = null;
|
||||||
try {
|
try {
|
||||||
settings = new AccountSettings(this, account);
|
settings = new AccountSettings(this, account);
|
||||||
byte[] fingerprint = Crypto.AsymmetricCryptoManager.getKeyFingerprint(settings.getKeyPair().getPublicKey());
|
return Crypto.AsymmetricCryptoManager.getPrettyKeyFingerprint(settings.getKeyPair().getPublicKey());
|
||||||
String fingerprintString = Hex.toHexString(fingerprint).toLowerCase();
|
|
||||||
return fingerprintString.replaceAll("(.{4})", "$1 ");
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
package com.etesync.syncadapter.ui;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
|
||||||
|
import com.etesync.syncadapter.AccountSettings;
|
||||||
|
import com.etesync.syncadapter.Constants;
|
||||||
|
import com.etesync.syncadapter.HttpClient;
|
||||||
|
import com.etesync.syncadapter.InvalidAccountException;
|
||||||
|
import com.etesync.syncadapter.R;
|
||||||
|
import com.etesync.syncadapter.journalmanager.Crypto;
|
||||||
|
import com.etesync.syncadapter.journalmanager.JournalManager;
|
||||||
|
import com.etesync.syncadapter.journalmanager.UserInfoManager;
|
||||||
|
import com.etesync.syncadapter.model.CollectionInfo;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
public class AddMemberFragment extends DialogFragment {
|
||||||
|
final static private String KEY_MEMBER = "memberEmail";
|
||||||
|
private Account account;
|
||||||
|
private AccountSettings settings;
|
||||||
|
private OkHttpClient httpClient;
|
||||||
|
private HttpUrl remote;
|
||||||
|
private CollectionInfo info;
|
||||||
|
private String memberEmail;
|
||||||
|
private byte[] memberPubKey;
|
||||||
|
|
||||||
|
public static AddMemberFragment newInstance(Account account, CollectionInfo info, String email) {
|
||||||
|
AddMemberFragment frag = new AddMemberFragment();
|
||||||
|
Bundle args = new Bundle(1);
|
||||||
|
args.putParcelable(Constants.KEY_ACCOUNT, account);
|
||||||
|
args.putSerializable(Constants.KEY_COLLECTION_INFO, info);
|
||||||
|
args.putString(KEY_MEMBER, email);
|
||||||
|
frag.setArguments(args);
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
account = getArguments().getParcelable(Constants.KEY_ACCOUNT);
|
||||||
|
info = (CollectionInfo) getArguments().getSerializable(Constants.KEY_COLLECTION_INFO);
|
||||||
|
memberEmail = getArguments().getString(KEY_MEMBER);
|
||||||
|
try {
|
||||||
|
settings = new AccountSettings(getContext(), account);
|
||||||
|
httpClient = HttpClient.create(getContext(), account);
|
||||||
|
} catch (InvalidAccountException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
remote = HttpUrl.get(settings.getUri());
|
||||||
|
|
||||||
|
new MemberAdd().execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
ProgressDialog progress = new ProgressDialog(getContext());
|
||||||
|
progress.setTitle(R.string.collection_members_adding);
|
||||||
|
progress.setMessage(getString(R.string.please_wait));
|
||||||
|
progress.setIndeterminate(true);
|
||||||
|
progress.setCanceledOnTouchOutside(false);
|
||||||
|
setCancelable(false);
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemberAdd extends AsyncTask<Void, Void, MemberAdd.AddResult> {
|
||||||
|
@Override
|
||||||
|
protected AddResult doInBackground(Void... voids) {
|
||||||
|
try {
|
||||||
|
UserInfoManager userInfoManager = new UserInfoManager(httpClient, remote);
|
||||||
|
|
||||||
|
Crypto.CryptoManager crypto = new Crypto.CryptoManager(info.version, settings.password(), info.uid);
|
||||||
|
|
||||||
|
memberPubKey = userInfoManager.get(crypto, memberEmail).getPubkey();
|
||||||
|
return new AddResult(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new AddResult(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(AddResult result) {
|
||||||
|
if (result.throwable == null) {
|
||||||
|
String fingerprint = Crypto.AsymmetricCryptoManager.getPrettyKeyFingerprint(memberPubKey);
|
||||||
|
|
||||||
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setIcon(R.drawable.ic_info_dark)
|
||||||
|
.setTitle(R.string.trust_fingerprint_title)
|
||||||
|
.setMessage(fingerprint)
|
||||||
|
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
new MemberAddSecond().execute();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
} else {
|
||||||
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setIcon(R.drawable.ic_error_dark)
|
||||||
|
.setTitle(R.string.collection_members_add_error)
|
||||||
|
.setMessage(result.throwable.getMessage())
|
||||||
|
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
class AddResult {
|
||||||
|
final Throwable throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemberAddSecond extends AsyncTask<Void, Void, MemberAddSecond.AddResultSecond> {
|
||||||
|
@Override
|
||||||
|
protected AddResultSecond doInBackground(Void... voids) {
|
||||||
|
try {
|
||||||
|
JournalManager journalsManager = new JournalManager(httpClient, remote);
|
||||||
|
|
||||||
|
JournalManager.Journal journal = JournalManager.Journal.fakeWithUid(info.uid);
|
||||||
|
Crypto.CryptoManager crypto = new Crypto.CryptoManager(info.version, settings.password(), info.uid);
|
||||||
|
|
||||||
|
byte[] encryptedKey = crypto.getEncryptedKey(settings.getKeyPair(), memberPubKey);
|
||||||
|
JournalManager.Member member = new JournalManager.Member(memberEmail, encryptedKey);
|
||||||
|
journalsManager.addMember(journal, member);
|
||||||
|
return new AddResultSecond(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new AddResultSecond(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(AddResultSecond result) {
|
||||||
|
if (result.throwable == null) {
|
||||||
|
((Refreshable) getActivity()).refresh();
|
||||||
|
} else {
|
||||||
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setIcon(R.drawable.ic_error_dark)
|
||||||
|
.setTitle(R.string.collection_members_add_error)
|
||||||
|
.setMessage(result.throwable.getMessage())
|
||||||
|
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
class AddResultSecond {
|
||||||
|
final Throwable throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
package com.etesync.syncadapter.ui;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.etesync.syncadapter.App;
|
||||||
|
import com.etesync.syncadapter.R;
|
||||||
|
import com.etesync.syncadapter.model.CollectionInfo;
|
||||||
|
import com.etesync.syncadapter.model.JournalEntity;
|
||||||
|
import com.etesync.syncadapter.resource.LocalCalendar;
|
||||||
|
|
||||||
|
import io.requery.Persistable;
|
||||||
|
import io.requery.sql.EntityDataStore;
|
||||||
|
|
||||||
|
public class CollectionMembersActivity extends AppCompatActivity implements Refreshable {
|
||||||
|
public final static String EXTRA_ACCOUNT = "account",
|
||||||
|
EXTRA_COLLECTION_INFO = "collectionInfo";
|
||||||
|
|
||||||
|
private Account account;
|
||||||
|
private JournalEntity journalEntity;
|
||||||
|
private CollectionMembersListFragment listFragment;
|
||||||
|
protected CollectionInfo info;
|
||||||
|
|
||||||
|
public static Intent newIntent(Context context, Account account, CollectionInfo info) {
|
||||||
|
Intent intent = new Intent(context, CollectionMembersActivity.class);
|
||||||
|
intent.putExtra(CollectionMembersActivity.EXTRA_ACCOUNT, account);
|
||||||
|
intent.putExtra(CollectionMembersActivity.EXTRA_COLLECTION_INFO, info);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
EntityDataStore<Persistable> data = ((App) getApplicationContext()).getData();
|
||||||
|
|
||||||
|
journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid);
|
||||||
|
if ((journalEntity == null) || journalEntity.isDeleted()) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info = journalEntity.getInfo();
|
||||||
|
|
||||||
|
setTitle(R.string.collection_members_title);
|
||||||
|
|
||||||
|
final View colorSquare = findViewById(R.id.color);
|
||||||
|
if (info.type == CollectionInfo.Type.CALENDAR) {
|
||||||
|
if (info.color != null) {
|
||||||
|
colorSquare.setBackgroundColor(info.color);
|
||||||
|
} else {
|
||||||
|
colorSquare.setBackgroundColor(LocalCalendar.defaultColor);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
colorSquare.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
findViewById(R.id.progressBar).setVisibility(View.GONE);
|
||||||
|
|
||||||
|
final TextView title = (TextView) findViewById(R.id.display_name);
|
||||||
|
title.setText(info.displayName);
|
||||||
|
|
||||||
|
final TextView desc = (TextView) findViewById(R.id.description);
|
||||||
|
desc.setText(info.description);
|
||||||
|
|
||||||
|
if (listFragment != null) {
|
||||||
|
listFragment.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.view_collection_members);
|
||||||
|
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
account = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
|
||||||
|
info = (CollectionInfo) getIntent().getExtras().getSerializable(EXTRA_COLLECTION_INFO);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
// We refresh before this, so we don't refresh the list before it was fully created.
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
listFragment = CollectionMembersListFragment.newInstance(account, info);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.list_entries_container, listFragment)
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAddMemberClicked(View v) {
|
||||||
|
final EditText input = new EditText(this);
|
||||||
|
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
|
||||||
|
AlertDialog.Builder dialog = new AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.collection_members_add)
|
||||||
|
.setIcon(R.drawable.ic_account_add_dark)
|
||||||
|
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
DialogFragment frag = AddMemberFragment.newInstance(account, info, input.getText().toString());
|
||||||
|
frag.show(getSupportFragmentManager(), null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.setView(input);
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == android.R.id.home) {
|
||||||
|
if (!getSupportFragmentManager().popBackStackImmediate()) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
App app = (App) getApplicationContext();
|
||||||
|
if (app.getCertManager() != null)
|
||||||
|
app.getCertManager().appInForeground = true;
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
|
||||||
|
App app = (App) getApplicationContext();
|
||||||
|
if (app.getCertManager() != null)
|
||||||
|
app.getCertManager().appInForeground = false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
package com.etesync.syncadapter.ui;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v4.app.ListFragment;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.etesync.syncadapter.AccountSettings;
|
||||||
|
import com.etesync.syncadapter.App;
|
||||||
|
import com.etesync.syncadapter.Constants;
|
||||||
|
import com.etesync.syncadapter.HttpClient;
|
||||||
|
import com.etesync.syncadapter.R;
|
||||||
|
import com.etesync.syncadapter.journalmanager.JournalManager;
|
||||||
|
import com.etesync.syncadapter.model.CollectionInfo;
|
||||||
|
import com.etesync.syncadapter.model.EntryEntity;
|
||||||
|
import com.etesync.syncadapter.model.JournalEntity;
|
||||||
|
import com.etesync.syncadapter.model.JournalModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.requery.Persistable;
|
||||||
|
import io.requery.sql.EntityDataStore;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
public class CollectionMembersListFragment extends ListFragment implements AdapterView.OnItemClickListener, Refreshable {
|
||||||
|
private EntityDataStore<Persistable> data;
|
||||||
|
private Account account;
|
||||||
|
private CollectionInfo info;
|
||||||
|
private JournalEntity journalEntity;
|
||||||
|
|
||||||
|
private TextView emptyTextView;
|
||||||
|
|
||||||
|
public static CollectionMembersListFragment newInstance(Account account, CollectionInfo info) {
|
||||||
|
CollectionMembersListFragment frag = new CollectionMembersListFragment();
|
||||||
|
Bundle args = new Bundle(1);
|
||||||
|
args.putParcelable(Constants.KEY_ACCOUNT, account);
|
||||||
|
args.putSerializable(Constants.KEY_COLLECTION_INFO, info);
|
||||||
|
frag.setArguments(args);
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
data = ((App) getContext().getApplicationContext()).getData();
|
||||||
|
account = getArguments().getParcelable(Constants.KEY_ACCOUNT);
|
||||||
|
info = (CollectionInfo) getArguments().getSerializable(Constants.KEY_COLLECTION_INFO);
|
||||||
|
journalEntity = JournalModel.Journal.fetch(data, info.getServiceEntity(data), info.uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.collection_members_list, container, false);
|
||||||
|
|
||||||
|
//This is instead of setEmptyText() function because of Google bug
|
||||||
|
//See: https://code.google.com/p/android/issues/detail?id=21742
|
||||||
|
emptyTextView = (TextView) view.findViewById(android.R.id.empty);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
new JournalMembersFetch().execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
getListView().setOnItemClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
final JournalManager.Member member = (JournalManager.Member) getListAdapter().getItem(position);
|
||||||
|
|
||||||
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setIcon(R.drawable.ic_info_dark)
|
||||||
|
.setTitle(R.string.collection_members_remove_title)
|
||||||
|
.setMessage(getString(R.string.collection_members_remove, member.getUser()))
|
||||||
|
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
DialogFragment frag = RemoveMemberFragment.newInstance(account, info, member.getUser());
|
||||||
|
frag.show(getFragmentManager(), null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MembersListAdapter extends ArrayAdapter<JournalManager.Member> {
|
||||||
|
MembersListAdapter(Context context) {
|
||||||
|
super(context, R.layout.collection_members_list_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public View getView(int position, View v, @NonNull ViewGroup parent) {
|
||||||
|
if (v == null)
|
||||||
|
v = LayoutInflater.from(getContext()).inflate(R.layout.collection_members_list_item, parent, false);
|
||||||
|
|
||||||
|
JournalManager.Member member = getItem(position);
|
||||||
|
|
||||||
|
TextView tv = (TextView) v.findViewById(R.id.title);
|
||||||
|
tv.setText(member.getUser());
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class JournalMembersFetch extends AsyncTask<Void, Void, JournalMembersFetch.MembersResult> {
|
||||||
|
@Override
|
||||||
|
protected MembersResult doInBackground(Void... voids) {
|
||||||
|
try {
|
||||||
|
OkHttpClient httpClient = HttpClient.create(getContext(), account);
|
||||||
|
AccountSettings settings = new AccountSettings(getContext(), account);
|
||||||
|
JournalManager journalsManager = new JournalManager(httpClient, HttpUrl.get(settings.getUri()));
|
||||||
|
|
||||||
|
JournalManager.Journal journal = JournalManager.Journal.fakeWithUid(journalEntity.getUid());
|
||||||
|
return new MembersResult(journalsManager.listMembers(journal), null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new MembersResult(null, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(MembersResult result) {
|
||||||
|
if (result.throwable == null) {
|
||||||
|
MembersListAdapter listAdapter = new MembersListAdapter(getContext());
|
||||||
|
setListAdapter(listAdapter);
|
||||||
|
|
||||||
|
listAdapter.addAll(result.members);
|
||||||
|
|
||||||
|
emptyTextView.setText(R.string.collection_members_list_empty);
|
||||||
|
} else {
|
||||||
|
emptyTextView.setText(result.throwable.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
class MembersResult {
|
||||||
|
final List<JournalManager.Member> members;
|
||||||
|
final Throwable throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
package com.etesync.syncadapter.ui;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
|
||||||
|
import com.etesync.syncadapter.AccountSettings;
|
||||||
|
import com.etesync.syncadapter.Constants;
|
||||||
|
import com.etesync.syncadapter.HttpClient;
|
||||||
|
import com.etesync.syncadapter.InvalidAccountException;
|
||||||
|
import com.etesync.syncadapter.R;
|
||||||
|
import com.etesync.syncadapter.journalmanager.Crypto;
|
||||||
|
import com.etesync.syncadapter.journalmanager.JournalManager;
|
||||||
|
import com.etesync.syncadapter.journalmanager.UserInfoManager;
|
||||||
|
import com.etesync.syncadapter.model.CollectionInfo;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.Charsets;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
public class RemoveMemberFragment extends DialogFragment {
|
||||||
|
final static private String KEY_MEMBER = "memberEmail";
|
||||||
|
private AccountSettings settings;
|
||||||
|
private OkHttpClient httpClient;
|
||||||
|
private HttpUrl remote;
|
||||||
|
private CollectionInfo info;
|
||||||
|
private String memberEmail;
|
||||||
|
|
||||||
|
public static RemoveMemberFragment newInstance(Account account, CollectionInfo info, String email) {
|
||||||
|
RemoveMemberFragment frag = new RemoveMemberFragment();
|
||||||
|
Bundle args = new Bundle(1);
|
||||||
|
args.putParcelable(Constants.KEY_ACCOUNT, account);
|
||||||
|
args.putSerializable(Constants.KEY_COLLECTION_INFO, info);
|
||||||
|
args.putString(KEY_MEMBER, email);
|
||||||
|
frag.setArguments(args);
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
Account account = getArguments().getParcelable(Constants.KEY_ACCOUNT);
|
||||||
|
info = (CollectionInfo) getArguments().getSerializable(Constants.KEY_COLLECTION_INFO);
|
||||||
|
memberEmail = getArguments().getString(KEY_MEMBER);
|
||||||
|
try {
|
||||||
|
settings = new AccountSettings(getContext(), account);
|
||||||
|
httpClient = HttpClient.create(getContext(), account);
|
||||||
|
} catch (InvalidAccountException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
remote = HttpUrl.get(settings.getUri());
|
||||||
|
|
||||||
|
new MemberRemove().execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
ProgressDialog progress = new ProgressDialog(getContext());
|
||||||
|
progress.setTitle(R.string.collection_members_removing);
|
||||||
|
progress.setMessage(getString(R.string.please_wait));
|
||||||
|
progress.setIndeterminate(true);
|
||||||
|
progress.setCanceledOnTouchOutside(false);
|
||||||
|
setCancelable(false);
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemberRemove extends AsyncTask<Void, Void, MemberRemove.RemoveResult> {
|
||||||
|
@Override
|
||||||
|
protected RemoveResult doInBackground(Void... voids) {
|
||||||
|
try {
|
||||||
|
JournalManager journalsManager = new JournalManager(httpClient, remote);
|
||||||
|
JournalManager.Journal journal = JournalManager.Journal.fakeWithUid(info.uid);
|
||||||
|
|
||||||
|
JournalManager.Member member = new JournalManager.Member(memberEmail, "placeholder".getBytes(Charsets.UTF_8));
|
||||||
|
journalsManager.deleteMember(journal, member);
|
||||||
|
|
||||||
|
return new RemoveResult(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new RemoveResult(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(RemoveResult result) {
|
||||||
|
if (result.throwable == null) {
|
||||||
|
((Refreshable) getActivity()).refresh();
|
||||||
|
} else {
|
||||||
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setIcon(R.drawable.ic_error_dark)
|
||||||
|
.setTitle(R.string.collection_members_remove_error)
|
||||||
|
.setMessage(result.throwable.getMessage())
|
||||||
|
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
class RemoveResult {
|
||||||
|
final Throwable throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -168,6 +168,24 @@ public class ViewCollectionActivity extends AppCompatActivity implements Refresh
|
|||||||
startActivity(ImportActivity.newIntent(ViewCollectionActivity.this, account, info));
|
startActivity(ImportActivity.newIntent(ViewCollectionActivity.this, account, info));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onManageMembers(MenuItem item) {
|
||||||
|
if (isOwner) {
|
||||||
|
startActivity(CollectionMembersActivity.newIntent(this, account, info));
|
||||||
|
} else {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||||
|
.setIcon(R.drawable.ic_info_dark)
|
||||||
|
.setTitle(R.string.not_allowed_title)
|
||||||
|
.setMessage(getString(R.string.members_owner_only, journalEntity.getOwner()))
|
||||||
|
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}).create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class LoadCountTask extends AsyncTask<Void, Void, Long> {
|
private class LoadCountTask extends AsyncTask<Void, Void, Long> {
|
||||||
private int entryCount;
|
private int entryCount;
|
||||||
|
|
||||||
|
10
app/src/main/res/drawable/ic_account_add_dark.xml
Normal file
10
app/src/main/res/drawable/ic_account_add_dark.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:alpha="0.54" >
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M15,14C12.33,14 7,15.33 7,18V20H23V18C23,15.33 17.67,14 15,14M6,10V7H4V10H1V12H4V15H6V12H9V10M15,12A4,4 0 0,0 19,8A4,4 0 0,0 15,4A4,4 0 0,0 11,8A4,4 0 0,0 15,12Z" />
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_members_dark.xml
Normal file
5
app/src/main/res/drawable/ic_members_dark.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:alpha="0.54" android:height="24dp"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M16,13C15.71,13 15.38,13 15.03,13.05C16.19,13.89 17,15 17,16.5V19H23V16.5C23,14.17 18.33,13 16,13M8,13C5.67,13 1,14.17 1,16.5V19H15V16.5C15,14.17 10.33,13 8,13M8,11A3,3 0 0,0 11,8A3,3 0 0,0 8,5A3,3 0 0,0 5,8A3,3 0 0,0 8,11M16,11A3,3 0 0,0 19,8A3,3 0 0,0 16,5A3,3 0 0,0 13,8A3,3 0 0,0 16,11Z"/>
|
||||||
|
</vector>
|
36
app/src/main/res/layout/collection_header.xml
Normal file
36
app/src/main/res/layout/collection_header.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/display_name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="@string/create_calendar_display_name_hint"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/color"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:background="@color/orangeA700" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
</LinearLayout>
|
21
app/src/main/res/layout/collection_members_list.xml
Normal file
21
app/src/main/res/layout/collection_members_list.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@id/android:list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@id/android:empty"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/collection_members_list_loading"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
|
||||||
|
</LinearLayout>
|
24
app/src/main/res/layout/collection_members_list_item.xml
Normal file
24
app/src/main/res/layout/collection_members_list_item.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<LinearLayout android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
tools:text="Title"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
@ -5,42 +5,11 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<include
|
||||||
|
layout="@layout/collection_header"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_margin="@dimen/activity_margin" />
|
||||||
android:padding="@dimen/activity_margin">
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/display_name"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:hint="@string/create_calendar_display_name_hint"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"/>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/color"
|
|
||||||
android:layout_width="32dp"
|
|
||||||
android:layout_height="32dp"
|
|
||||||
android:layout_marginLeft="16dp"
|
|
||||||
android:background="@color/orangeA700"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/description"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
61
app/src/main/res/layout/view_collection_members.xml
Normal file
61
app/src/main/res/layout/view_collection_members.xml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include
|
||||||
|
layout="@layout/collection_header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/activity_margin" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="@dimen/activity_margin">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="right" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/add_member"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/activity_margin"
|
||||||
|
android:onClick="onAddMemberClicked"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/collection_members_add"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_account_add_dark" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/list_entries_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -15,6 +15,11 @@
|
|||||||
android:onClick="onEditCollection"
|
android:onClick="onEditCollection"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
<item android:title="@string/view_collection_members"
|
||||||
|
android:icon="@drawable/ic_members_dark"
|
||||||
|
android:onClick="onManageMembers"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
<item android:title="@string/view_collection_import"
|
<item android:title="@string/view_collection_import"
|
||||||
android:onClick="onImport"
|
android:onClick="onImport"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never"/>
|
||||||
|
@ -103,6 +103,20 @@
|
|||||||
<!-- ViewCollection -->
|
<!-- ViewCollection -->
|
||||||
<string name="change_journal_title">Change Journal</string>
|
<string name="change_journal_title">Change Journal</string>
|
||||||
<string name="account_showcase_import">In order to import contacts and calendars into EteSync, you need to click on the menu, and choose \"Import\".</string>
|
<string name="account_showcase_import">In order to import contacts and calendars into EteSync, you need to click on the menu, and choose \"Import\".</string>
|
||||||
|
<string name="members_owner_only">Only the owner of this collection (%s) is allowed to view its members.</string>
|
||||||
|
|
||||||
|
<!-- CollectionMembers -->
|
||||||
|
<string name="collection_members_title">Members</string>
|
||||||
|
<string name="collection_members_list_loading">Loading members...</string>
|
||||||
|
<string name="collection_members_list_empty">No members</string>
|
||||||
|
<string name="collection_members_add">Add member</string>
|
||||||
|
<string name="collection_members_add_error">Error adding member</string>
|
||||||
|
<string name="collection_members_adding">Adding member</string>
|
||||||
|
<string name="trust_fingerprint_title">Trust fingerprint?</string>
|
||||||
|
<string name="collection_members_removing">Removing member</string>
|
||||||
|
<string name="collection_members_remove_error">Error removing member</string>
|
||||||
|
<string name="collection_members_remove_title">Remove member</string>
|
||||||
|
<string name="collection_members_remove">Would you like to revoke %s\'s access?\nPlease be advised that a malicious user would potentially be able to retain access to encryption keys. Please refer to the FAQ for more information.</string>
|
||||||
|
|
||||||
<!-- PermissionsActivity -->
|
<!-- PermissionsActivity -->
|
||||||
<string name="permissions_title">EteSync permissions</string>
|
<string name="permissions_title">EteSync permissions</string>
|
||||||
@ -215,6 +229,7 @@
|
|||||||
<string name="create_collection_description">Description (optional):</string>
|
<string name="create_collection_description">Description (optional):</string>
|
||||||
<string name="view_collection_edit">Edit</string>
|
<string name="view_collection_edit">Edit</string>
|
||||||
<string name="view_collection_import">Import</string>
|
<string name="view_collection_import">Import</string>
|
||||||
|
<string name="view_collection_members">Manage Members</string>
|
||||||
<string name="create_collection_create">Save</string>
|
<string name="create_collection_create">Save</string>
|
||||||
<string name="delete_collection">Delete</string>
|
<string name="delete_collection">Delete</string>
|
||||||
<string name="delete_collection_confirm_title">Are you sure?</string>
|
<string name="delete_collection_confirm_title">Are you sure?</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user