mirror of
https://github.com/etesync/android
synced 2025-01-11 00:01:12 +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">
|
||||
</activity>
|
||||
<activity android:name=".ui.ViewCollectionActivity"/>
|
||||
<activity android:name=".ui.CollectionMembersActivity"/>
|
||||
<activity android:name=".ui.importlocal.ImportActivity"/>
|
||||
<activity android:name=".ui.AccountSettingsActivity"/>
|
||||
<activity android:name=".ui.CreateCollectionActivity"/>
|
||||
|
@ -115,6 +115,12 @@ public class Crypto {
|
||||
public static byte[] getKeyFingerprint(byte[] 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 {
|
||||
@ -125,6 +131,7 @@ public class Crypto {
|
||||
private final byte version;
|
||||
private byte[] cipherKey;
|
||||
private byte[] hmacKey;
|
||||
private byte[] derivedKey;
|
||||
|
||||
private void setDerivedKey(byte[] derivedKey) {
|
||||
cipherKey = hmac256("aes".getBytes(Charsets.UTF_8), derivedKey);
|
||||
@ -133,14 +140,13 @@ public class Crypto {
|
||||
|
||||
public CryptoManager(int version, AsymmetricKeyPair keyPair, byte[] encryptedKey) {
|
||||
Crypto.AsymmetricCryptoManager cryptoManager = new Crypto.AsymmetricCryptoManager(keyPair);
|
||||
byte[] derivedKey = cryptoManager.decrypt(encryptedKey);
|
||||
derivedKey = cryptoManager.decrypt(encryptedKey);
|
||||
|
||||
this.version = (byte) version;
|
||||
setDerivedKey(derivedKey);
|
||||
}
|
||||
|
||||
public CryptoManager(int version, @NonNull String keyBase64, @NonNull String salt) throws Exceptions.IntegrityException, Exceptions.VersionTooNewException {
|
||||
byte[] derivedKey;
|
||||
if (version > Byte.MAX_VALUE) {
|
||||
throw new Exceptions.IntegrityException("Version is out of range.");
|
||||
} else if (version > Constants.CURRENT_VERSION) {
|
||||
@ -238,6 +244,11 @@ public class Crypto {
|
||||
hmac.doFinal(ret, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public byte[] getEncryptedKey(AsymmetricKeyPair keyPair, byte[] publicKey) {
|
||||
AsymmetricCryptoManager cryptoManager = new AsymmetricCryptoManager(keyPair);
|
||||
return cryptoManager.encrypt(publicKey, derivedKey);
|
||||
}
|
||||
}
|
||||
|
||||
static String sha256(String base) {
|
||||
|
@ -144,6 +144,12 @@ public class JournalManager extends BaseManager {
|
||||
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) {
|
||||
super(crypto, content, uid);
|
||||
hmac = calculateHmac(crypto);
|
||||
|
@ -217,9 +217,7 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
|
||||
AccountSettings settings = null;
|
||||
try {
|
||||
settings = new AccountSettings(this, account);
|
||||
byte[] fingerprint = Crypto.AsymmetricCryptoManager.getKeyFingerprint(settings.getKeyPair().getPublicKey());
|
||||
String fingerprintString = Hex.toHexString(fingerprint).toLowerCase();
|
||||
return fingerprintString.replaceAll("(.{4})", "$1 ");
|
||||
return Crypto.AsymmetricCryptoManager.getPrettyKeyFingerprint(settings.getKeyPair().getPublicKey());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
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));
|
||||
}
|
||||
|
||||
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 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:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<include
|
||||
layout="@layout/collection_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
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>
|
||||
android:layout_margin="@dimen/activity_margin" />
|
||||
|
||||
<TextView
|
||||
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"
|
||||
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"
|
||||
android:onClick="onImport"
|
||||
app:showAsAction="never"/>
|
||||
|
@ -103,6 +103,20 @@
|
||||
<!-- ViewCollection -->
|
||||
<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="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 -->
|
||||
<string name="permissions_title">EteSync permissions</string>
|
||||
@ -215,6 +229,7 @@
|
||||
<string name="create_collection_description">Description (optional):</string>
|
||||
<string name="view_collection_edit">Edit</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="delete_collection">Delete</string>
|
||||
<string name="delete_collection_confirm_title">Are you sure?</string>
|
||||
|
Loading…
Reference in New Issue
Block a user