Signature per identity

This commit is contained in:
M66B 2018-11-14 14:51:50 +01:00
parent 560eb1204b
commit 2b6b1ec3a6
10 changed files with 1209 additions and 78 deletions

1
FAQ.md
View File

@ -25,7 +25,6 @@ For:
* Notifications per account
* Fixed action bar conversations
* Password protected export file
* Signature per identity
* Keep conversations open (for previous/next navigation)
* Microsoft OAuth

File diff suppressed because it is too large Load Diff

View File

@ -46,7 +46,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 2,
version = 3,
entities = {
EntityIdentity.class,
EntityAccount.class,
@ -129,6 +129,15 @@ public abstract class DB extends RoomDatabase {
db.execSQL("UPDATE `folder` SET keep_days = sync_days");
}
})
.addMigrations(new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `identity` ADD COLUMN `signature` TEXT");
db.execSQL("UPDATE `identity` SET signature =" +
" (SELECT account.signature FROM account WHERE account.id = identity.account)");
}
})
.build();
}

View File

@ -37,7 +37,7 @@ public class EntityAccount {
@PrimaryKey(autoGenerate = true)
public Long id;
public String name;
public String signature;
public String signature; // obsolete
@NonNull
public String host; // IMAP
@NonNull
@ -66,7 +66,6 @@ public class EntityAccount {
public JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put("name", name);
json.put("signature", signature);
json.put("host", host);
json.put("starttls", starttls);
json.put("insecure", insecure);
@ -89,8 +88,6 @@ public class EntityAccount {
EntityAccount account = new EntityAccount();
if (json.has("name"))
account.name = json.getString("name");
if (json.has("signature"))
account.signature = json.getString("signature");
account.host = json.getString("host");
account.starttls = (json.has("starttls") && json.getBoolean("starttls"));
account.insecure = (json.has("insecure") && json.getBoolean("insecure"));
@ -111,7 +108,6 @@ public class EntityAccount {
if (obj instanceof EntityAccount) {
EntityAccount other = (EntityAccount) obj;
return ((this.name == null ? other.name == null : this.name.equals(other.name)) &&
(this.signature == null ? other.signature == null : this.signature.equals(other.signature)) &&
this.host.equals(other.host) &&
this.starttls == other.starttls &&
this.insecure == other.insecure &&

View File

@ -69,6 +69,7 @@ public class EntityIdentity {
@NonNull
public Boolean primary;
public Integer color;
public String signature;
@NonNull
public Boolean synchronize;
@NonNull
@ -93,6 +94,7 @@ public class EntityIdentity {
json.put("primary", primary);
if (color != null)
json.put("color", color);
json.put("signature", signature);
json.put("synchronize", false);
json.put("store_sent", store_sent);
if (sent_folder != null)
@ -118,6 +120,8 @@ public class EntityIdentity {
identity.primary = json.getBoolean("primary");
if (json.has("color"))
identity.color = json.getInt("color");
if (json.has("signature"))
identity.signature = json.getString("signature");
identity.synchronize = json.getBoolean("synchronize");
identity.store_sent = json.getBoolean("store_sent");
if (json.has("sent_folder"))
@ -141,6 +145,7 @@ public class EntityIdentity {
this.password.equals(other.password) &&
this.primary.equals(other.primary) &&
(this.color == null ? other.color == null : this.color.equals(other.color)) &&
(this.signature == null ? other.signature == null : this.signature.equals(other.signature)) &&
this.synchronize.equals(other.synchronize) &&
this.store_sent.equals(other.store_sent) &&
(this.sent_folder == null ? other.sent_folder == null : this.sent_folder.equals(other.sent_folder)) &&

View File

@ -37,7 +37,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.Html;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
@ -116,8 +115,6 @@ public class FragmentAccount extends FragmentEx {
private Button btnColor;
private View vwColor;
private ImageView ibColorDefault;
private EditText etSignature;
private ImageButton ibPro;
private CheckBox cbSynchronize;
private CheckBox cbPrimary;
@ -190,8 +187,6 @@ public class FragmentAccount extends FragmentEx {
btnColor = view.findViewById(R.id.btnColor);
vwColor = view.findViewById(R.id.vwColor);
ibColorDefault = view.findViewById(R.id.ibColorDefault);
etSignature = view.findViewById(R.id.etSignature);
ibPro = view.findViewById(R.id.ibPro);
cbSynchronize = view.findViewById(R.id.cbSynchronize);
cbPrimary = view.findViewById(R.id.cbPrimary);
@ -397,16 +392,6 @@ public class FragmentAccount extends FragmentEx {
}
});
ibPro.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.hide(FragmentAccount.this);
fragmentTransaction.add(R.id.content_frame, new FragmentPro()).addToBackStack("pro");
fragmentTransaction.commit();
}
});
cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@ -617,7 +602,6 @@ public class FragmentAccount extends FragmentEx {
args.putString("name", etName.getText().toString());
args.putInt("color", color);
args.putString("signature", Html.toHtml(etSignature.getText()));
args.putBoolean("synchronize", cbSynchronize.isChecked());
args.putBoolean("primary", cbPrimary.isChecked());
@ -643,7 +627,6 @@ public class FragmentAccount extends FragmentEx {
String name = args.getString("name");
Integer color = args.getInt("color");
String signature = args.getString("signature");
boolean synchronize = args.getBoolean("synchronize");
boolean primary = args.getBoolean("primary");
@ -723,7 +706,6 @@ public class FragmentAccount extends FragmentEx {
account.name = name;
account.color = color;
account.signature = signature;
account.synchronize = synchronize;
account.primary = (account.synchronize && primary);
@ -965,7 +947,6 @@ public class FragmentAccount extends FragmentEx {
tilPassword.getEditText().setText(account == null ? null : account.password);
etName.setText(account == null ? null : account.name);
etSignature.setText(account == null || account.signature == null ? null : Html.fromHtml(account.signature));
cbSynchronize.setChecked(account == null ? true : account.synchronize);
cbPrimary.setChecked(account == null ? true : account.primary);
@ -999,17 +980,6 @@ public class FragmentAccount extends FragmentEx {
Helper.setViewsEnabled(view, true);
setColor(color);
boolean pro = Helper.isPro(getContext());
etSignature.setHint(pro ? R.string.title_optional : R.string.title_pro_feature);
etSignature.setEnabled(pro);
if (pro) {
ViewGroup.LayoutParams lp = ibPro.getLayoutParams();
lp.height = 0;
lp.width = 0;
ibPro.setLayoutParams(lp);
}
cbPrimary.setEnabled(cbSynchronize.isChecked());
// Consider previous check/save/delete as cancelled

View File

@ -37,6 +37,7 @@ import android.os.Looper;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.OpenableColumns;
import android.text.Editable;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
@ -44,6 +45,7 @@ import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Log;
@ -71,6 +73,7 @@ import com.google.android.material.snackbar.Snackbar;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import org.xml.sax.XMLReader;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@ -177,6 +180,19 @@ public class FragmentCompose extends FragmentEx {
int at = (identity == null ? -1 : identity.email.indexOf('@'));
tvExtraPrefix.setText(at < 0 ? null : identity.email.substring(0, at) + "+");
tvExtraSuffix.setText(at < 0 ? null : identity.email.substring(at));
String signature = (identity == null ? null : identity.signature);
if (TextUtils.isEmpty(signature))
signature = "&nbsp;";
String html = Html.toHtml(etBody.getText());
Log.i(Helper.TAG, html);
int cstart = html.indexOf("<tt>");
int cend = html.indexOf("</tt>");
if (cstart >= 0 && cend > cstart) {
html = html.substring(0, cstart + 4) + signature + html.substring(cend);
etBody.setText(Html.fromHtml(html));
}
}
@Override
@ -1126,8 +1142,8 @@ public class FragmentCompose extends FragmentEx {
else
body = body.replaceAll("\\r?\\n", "<br />");
if (pro && !TextUtils.isEmpty(result.account.signature))
body += "<br><br>" + result.account.signature;
if (pro)
body += "<p>&nbsp;</p><p><tt>&nbsp;</tt></p>";
} else {
result.draft.thread = ref.thread;
@ -1175,8 +1191,8 @@ public class FragmentCompose extends FragmentEx {
HtmlHelper.sanitize(ref.read(context)));
}
if (pro && !TextUtils.isEmpty(result.account.signature))
body = result.account.signature + body;
if (pro)
body = "<p><tt>&nbsp;</tt></p>" + body;
if (answer > 0 && ("reply".equals(action) || "reply_all".equals(action))) {
String text = db.answer().getAnswer(answer).text;
@ -1192,7 +1208,7 @@ public class FragmentCompose extends FragmentEx {
body = text + body;
} else
body = "<br><br>" + body;
body = "<p>&nbsp;</p>" + body;
}
result.draft.content = true;
@ -1686,6 +1702,42 @@ public class FragmentCompose extends FragmentEx {
}
};
private Html.TagHandler tagHandler = new Html.TagHandler() {
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if (tag.equalsIgnoreCase("tt"))
processTt(opening, output);
}
private void processTt(boolean opening, Editable output) {
Log.i(Helper.TAG, "Handling tt");
int len = output.length();
if (opening)
output.setSpan(new TypefaceSpan("monospace"), len, len, Spannable.SPAN_MARK_MARK);
else {
Object span = getLast(output, TypefaceSpan.class);
if (span != null) {
int pos = output.getSpanStart(span);
output.removeSpan(span);
if (pos != len)
output.setSpan(new TypefaceSpan("monospace"), pos, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private Object getLast(Editable text, Class kind) {
Object[] spans = text.getSpans(0, text.length(), kind);
if (spans.length == 0)
return null;
for (int i = spans.length; i > 0; i--)
if (text.getSpanFlags(spans[i - 1]) == Spannable.SPAN_MARK_MARK)
return spans[i - 1];
return null;
}
};
private class DraftAccount {
EntityMessage draft;
EntityAccount account;

View File

@ -27,6 +27,7 @@ import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@ -90,12 +91,17 @@ public class FragmentIdentity extends FragmentEx {
private EditText etPort;
private EditText etUser;
private TextInputLayout tilPassword;
private Button btnColor;
private View vwColor;
private ImageView ibColorDefault;
private EditText etSignature;
private ImageButton ibPro;
private CheckBox cbSynchronize;
private CheckBox cbPrimary;
private Spinner spSent;
private Button btnSave;
private ProgressBar pbSave;
private ImageButton ibDelete;
@ -149,6 +155,8 @@ public class FragmentIdentity extends FragmentEx {
btnColor = view.findViewById(R.id.btnColor);
vwColor = view.findViewById(R.id.vwColor);
ibColorDefault = view.findViewById(R.id.ibColorDefault);
etSignature = view.findViewById(R.id.etSignature);
ibPro = view.findViewById(R.id.ibPro);
cbSynchronize = view.findViewById(R.id.cbSynchronize);
cbPrimary = view.findViewById(R.id.cbPrimary);
@ -342,6 +350,16 @@ public class FragmentIdentity extends FragmentEx {
}
});
ibPro.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.hide(FragmentIdentity.this);
fragmentTransaction.add(R.id.content_frame, new FragmentPro()).addToBackStack("pro");
fragmentTransaction.commit();
}
});
cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@ -372,6 +390,7 @@ public class FragmentIdentity extends FragmentEx {
args.putString("user", etUser.getText().toString());
args.putString("password", tilPassword.getEditText().getText().toString());
args.putInt("color", color);
args.putString("signature", Html.toHtml(etSignature.getText()));
args.putBoolean("synchronize", cbSynchronize.isChecked());
args.putBoolean("primary", cbPrimary.isChecked());
args.putSerializable("sent", (EntityFolder) spSent.getSelectedItem());
@ -391,6 +410,7 @@ public class FragmentIdentity extends FragmentEx {
String user = args.getString("user");
String password = args.getString("password");
Integer color = args.getInt("color");
String signature = args.getString("signature");
int auth_type = args.getInt("auth_type");
boolean synchronize = args.getBoolean("synchronize");
boolean primary = args.getBoolean("primary");
@ -462,6 +482,7 @@ public class FragmentIdentity extends FragmentEx {
identity.user = user;
identity.password = password;
identity.color = color;
identity.signature = signature;
identity.auth_type = auth_type;
identity.synchronize = synchronize;
identity.primary = (identity.synchronize && primary);
@ -607,6 +628,7 @@ public class FragmentIdentity extends FragmentEx {
etPort.setText(identity == null ? null : Long.toString(identity.port));
etUser.setText(identity == null ? null : identity.user);
tilPassword.getEditText().setText(identity == null ? null : identity.password);
etSignature.setText(identity == null || identity.signature == null ? null : Html.fromHtml(identity.signature));
cbSynchronize.setChecked(identity == null ? true : identity.synchronize);
cbPrimary.setChecked(identity == null ? true : identity.primary);
@ -635,6 +657,17 @@ public class FragmentIdentity extends FragmentEx {
Helper.setViewsEnabled(view, true);
setColor(color);
boolean pro = Helper.isPro(getContext());
etSignature.setHint(pro ? R.string.title_optional : R.string.title_pro_feature);
etSignature.setEnabled(pro);
if (pro) {
ViewGroup.LayoutParams lp = ibPro.getLayoutParams();
lp.height = 0;
lp.width = 0;
ibPro.setLayoutParams(lp);
}
cbPrimary.setEnabled(cbSynchronize.isChecked());
// Consider previous save/delete as cancelled

View File

@ -289,38 +289,6 @@
app:layout_constraintStart_toEndOf="@id/vwColor"
app:layout_constraintTop_toBottomOf="@id/etName" />
<TextView
android:id="@+id/tvSignature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_account_signature"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnColor" />
<EditText
android:id="@+id/etSignature"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:hint="@string/title_optional"
android:inputType="textCapSentences|textMultiLine"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toStartOf="@+id/ibPro"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvSignature" />
<ImageButton
android:id="@+id/ibPro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:src="@drawable/baseline_info_24"
app:layout_constraintBottom_toBottomOf="@id/etSignature"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/etSignature" />
<CheckBox
android:id="@+id/cbSynchronize"
android:layout_width="wrap_content"
@ -328,7 +296,7 @@
android:layout_marginTop="12dp"
android:text="@string/title_synchronize_account"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etSignature" />
app:layout_constraintTop_toBottomOf="@id/btnColor" />
<CheckBox
android:id="@+id/cbPrimary"
@ -552,7 +520,7 @@
android:id="@+id/grpAdvanced"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="tvName,etName,btnColor,vwColor,ibColorDefault,tvSignature,etSignature,ibPro,cbSynchronize,cbPrimary,tvInterval,etInterval" />
app:constraint_referenced_ids="tvName,etName,btnColor,vwColor,ibColorDefault,cbSynchronize,cbPrimary,tvInterval,etInterval" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpFolders"

View File

@ -333,6 +333,38 @@
app:layout_constraintStart_toEndOf="@id/vwColor"
app:layout_constraintTop_toBottomOf="@id/tilPassword" />
<TextView
android:id="@+id/tvSignature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_account_signature"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnColor" />
<EditText
android:id="@+id/etSignature"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:hint="@string/title_optional"
android:inputType="textCapSentences|textMultiLine"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toStartOf="@+id/ibPro"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvSignature" />
<ImageButton
android:id="@+id/ibPro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:src="@drawable/baseline_info_24"
app:layout_constraintBottom_toBottomOf="@id/etSignature"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/etSignature" />
<CheckBox
android:id="@+id/cbSynchronize"
android:layout_width="wrap_content"
@ -340,7 +372,7 @@
android:layout_marginTop="12dp"
android:text="@string/title_synchronize_identity"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnColor" />
app:layout_constraintTop_toBottomOf="@id/etSignature" />
<CheckBox
android:id="@+id/cbPrimary"
@ -412,6 +444,6 @@
android:id="@+id/grpAdvanced"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="tvEmail,etEmail,tvReplyTo,etReplyTo,tvProvider,spProvider,tvDomain,etDomain,btnAutoConfig,tvSmtp,tvInsecure,tvHost,etHost,cbStartTls,tvPort,etPort,tvUser,etUser,tvPassword,tilPassword,btnColor,vwColor,ibColorDefault,cbSynchronize,cbPrimary,tvSent,spSent" />
app:constraint_referenced_ids="tvEmail,etEmail,tvReplyTo,etReplyTo,tvProvider,spProvider,tvDomain,etDomain,btnAutoConfig,tvSmtp,tvInsecure,tvHost,etHost,cbStartTls,tvPort,etPort,tvUser,etUser,tvPassword,tilPassword,btnColor,vwColor,ibColorDefault,tvSignature,etSignature,ibPro,cbSynchronize,cbPrimary,tvSent,spSent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>