Added conversation actions

pull/177/head
M66B 5 years ago
parent 21c97c1cc7
commit b89f6bd48d

@ -241,6 +241,7 @@ dependencies {
def work_version = "2.3.3" def work_version = "2.3.3"
def exif_version = "1.3.0-alpha01" def exif_version = "1.3.0-alpha01"
def biometric_version = "1.0.1" def biometric_version = "1.0.1"
def textclassifier_version = "1.0.0-alpha03"
def billingclient_version = "2.1.0" def billingclient_version = "2.1.0"
def javamail_version = "1.6.5" def javamail_version = "1.6.5"
def jsoup_version = "1.12.1" def jsoup_version = "1.12.1"
@ -323,6 +324,10 @@ dependencies {
// https://developer.android.com/jetpack/androidx/releases/biometric // https://developer.android.com/jetpack/androidx/releases/biometric
implementation "androidx.biometric:biometric:$biometric_version" implementation "androidx.biometric:biometric:$biometric_version"
// https://mvnrepository.com/artifact/androidx.textclassifier/textclassifier
// https://developer.android.com/jetpack/androidx/releases/textclassifier
//implementation "androidx.textclassifier:textclassifier:$textclassifier_version"
// https://developer.android.com/google/play/billing/billing_library_releases_notes // https://developer.android.com/google/play/billing/billing_library_releases_notes
implementation "com.android.billingclient:billing:$billingclient_version" implementation "com.android.billingclient:billing:$billingclient_version"

@ -27,6 +27,7 @@ import android.app.Dialog;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.RemoteAction;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ContentResolver; import android.content.ContentResolver;
@ -88,6 +89,9 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.textclassifier.ConversationAction;
import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.TextClassificationManager;
import android.webkit.WebView; import android.webkit.WebView;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
@ -95,12 +99,14 @@ import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.PopupMenu;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
@ -142,6 +148,8 @@ import java.security.NoSuchAlgorithmException;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -167,6 +175,7 @@ import biweekly.property.Organizer;
import biweekly.util.ICalDate; import biweekly.util.ICalDate;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHolder> { public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHolder> {
private Fragment parentFragment; private Fragment parentFragment;
@ -408,6 +417,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private ImageButton ibDownloading; private ImageButton ibDownloading;
private Group grpDownloading; private Group grpDownloading;
private ImageButton ibSeen; private ImageButton ibSeen;
private LinearLayout llAction;
private TextView tvCalendarSummary; private TextView tvCalendarSummary;
private TextView tvCalendarDescription; private TextView tvCalendarDescription;
@ -596,6 +606,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ibDownloading = vsBody.findViewById(R.id.ibDownloading); ibDownloading = vsBody.findViewById(R.id.ibDownloading);
grpDownloading = vsBody.findViewById(R.id.grpDownloading); grpDownloading = vsBody.findViewById(R.id.grpDownloading);
ibSeen = vsBody.findViewById(R.id.ibSeen); ibSeen = vsBody.findViewById(R.id.ibSeen);
llAction = vsBody.findViewById(R.id.llAction);
rvImage = vsBody.findViewById(R.id.rvImage); rvImage = vsBody.findViewById(R.id.rvImage);
rvImage.setHasFixedSize(false); rvImage.setHasFixedSize(false);
@ -1183,6 +1194,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
tvNoInternetBody.setVisibility(View.GONE); tvNoInternetBody.setVisibility(View.GONE);
grpDownloading.setVisibility(View.GONE); grpDownloading.setVisibility(View.GONE);
ibSeen.setVisibility(View.GONE); ibSeen.setVisibility(View.GONE);
llAction.setVisibility(View.GONE);
llAction.removeAllViews();
} }
private void clearCalendar() { private void clearCalendar() {
@ -1814,11 +1827,17 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
document.body().appendChild(pre); document.body().appendChild(pre);
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
args.putParcelable("actions", getConversationActions(message, document));
return document.html(); return document.html();
} else { } else {
// Cleanup message // Cleanup message
document = HtmlHelper.sanitize(context, document, show_images, true, true); document = HtmlHelper.sanitize(context, document, show_images, true, true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
args.putParcelable("actions", getConversationActions(message, document));
// Collapse quotes // Collapse quotes
if (!show_quotes) { if (!show_quotes) {
for (Element quote : document.select("blockquote")) for (Element quote : document.select("blockquote"))
@ -1912,6 +1931,47 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
} else } else
throw new IllegalStateException("Result=" + result); throw new IllegalStateException("Result=" + result);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ConversationActions cactions = args.getParcelable("actions");
if (cactions != null) {
List<ConversationAction> actions = cactions.getConversationActions();
for (ConversationAction action : actions) {
String type = action.getType();
if (ConversationAction.TYPE_OPEN_URL.equals(type) ||
ConversationAction.TYPE_SEND_EMAIL.equals(type))
continue;
final RemoteAction raction = action.getAction();
final CharSequence title = (raction == null ? action.getTextReply() : raction.getTitle());
Button button = new Button(context, null, android.R.attr.buttonStyleSmall);
button.setId(View.generateViewId());
button.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
button.setText(title);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
if (raction == null) {
Intent reply = new Intent(context, ActivityCompose.class)
.putExtra("action", "reply")
.putExtra("reference", message.id)
.putExtra("text", title);
context.startActivity(reply);
} else
raction.getActionIntent().send();
} catch (Throwable ex) {
Log.e(ex);
}
}
});
llAction.addView(button);
}
if (llAction.getChildCount() > 0)
llAction.setVisibility(View.VISIBLE);
}
}
// Show attachments // Show attachments
cowner.start(); cowner.start();
@ -1955,6 +2015,30 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
else else
Log.unexpectedError(parentFragment.getParentFragmentManager(), ex); Log.unexpectedError(parentFragment.getParentFragmentManager(), ex);
} }
@RequiresApi(api = Build.VERSION_CODES.Q)
private ConversationActions getConversationActions(TupleMessageEx message, Document document) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean conversation_actions = prefs.getBoolean("conversation_actions", true);
if (!conversation_actions)
return null;
TextClassificationManager tcm = (TextClassificationManager) context.getSystemService(Context.TEXT_CLASSIFICATION_SERVICE);
if (tcm == null)
return null;
ZonedDateTime dt = new Date(message.received).toInstant().atZone(ZoneId.systemDefault());
ConversationActions.Message cmessage =
new ConversationActions.Message.Builder(ConversationActions.Message
.PERSON_USER_OTHERS)
.setReferenceTime(dt)
.setText(document.text())
.build();
ConversationActions.Request crequest =
new ConversationActions.Request.Builder(Arrays.asList(cmessage)).build();
return tcm.getTextClassifier().suggestConversationActions(crequest);
}
}.execute(context, owner, args, "message:body"); }.execute(context, owner, args, "message:body");
} }
@ -3681,7 +3765,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
} }
}); });
PopupWindow pw = new PopupWindow(dview, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); PopupWindow pw = new PopupWindow(dview, WRAP_CONTENT, WRAP_CONTENT);
pw.setFocusable(true); pw.setFocusable(true);
pw.setOnDismissListener(new PopupWindow.OnDismissListener() { pw.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override @Override

@ -970,6 +970,7 @@ public class FragmentCompose extends FragmentBase {
args.putString("bcc", a.getString("bcc")); args.putString("bcc", a.getString("bcc"));
args.putString("subject", a.getString("subject")); args.putString("subject", a.getString("subject"));
args.putString("body", a.getString("body")); args.putString("body", a.getString("body"));
args.putString("text", a.getString("text"));
args.putParcelableArrayList("attachments", a.getParcelableArrayList("attachments")); args.putParcelableArrayList("attachments", a.getParcelableArrayList("attachments"));
draftLoader.execute(this, args, "compose:new"); draftLoader.execute(this, args, "compose:new");
} else { } else {
@ -3046,6 +3047,18 @@ public class FragmentCompose extends FragmentBase {
for (String re : Helper.getStrings(context, R.string.title_subject_reply, "")) for (String re : Helper.getStrings(context, R.string.title_subject_reply, ""))
subject = unprefix(subject, re); subject = unprefix(subject, re);
data.draft.subject = context.getString(R.string.title_subject_reply, subject); data.draft.subject = context.getString(R.string.title_subject_reply, subject);
String t = args.getString("text");
if (t != null) {
Element div = document.createElement("div");
for (String line : t.split("\\r?\\n")) {
Element span = document.createElement("span");
span.text(line);
div.appendChild(span);
div.appendElement("br");
}
document.body().appendChild(div);
}
} else if ("forward".equals(action)) { } else if ("forward".equals(action)) {
if (prefix_once) if (prefix_once)
for (String fwd : Helper.getStrings(context, R.string.title_subject_forward, "")) for (String fwd : Helper.getStrings(context, R.string.title_subject_forward, ""))

@ -27,6 +27,7 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Paint; import android.graphics.Paint;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -52,6 +53,7 @@ import io.requery.android.database.sqlite.SQLiteDatabase;
public class FragmentOptionsMisc extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener { public class FragmentOptionsMisc extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener {
private SwitchCompat swExternalSearch; private SwitchCompat swExternalSearch;
private SwitchCompat swConversationActions;
private SwitchCompat swFts; private SwitchCompat swFts;
private TextView tvFtsIndexed; private TextView tvFtsIndexed;
private TextView tvFtsPro; private TextView tvFtsPro;
@ -77,7 +79,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private Group grpDebug; private Group grpDebug;
private final static String[] RESET_OPTIONS = new String[]{ private final static String[] RESET_OPTIONS = new String[]{
"fts", "english", "watchdog", "auto_optimize", "updates", "experiments", "crash_reports", "debug" "conversation_actions", "fts", "english", "watchdog", "auto_optimize", "updates", "experiments", "crash_reports", "debug"
}; };
private final static String[] RESET_QUESTIONS = new String[]{ private final static String[] RESET_QUESTIONS = new String[]{
@ -98,6 +100,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
// Get controls // Get controls
swExternalSearch = view.findViewById(R.id.swExternalSearch); swExternalSearch = view.findViewById(R.id.swExternalSearch);
swConversationActions = view.findViewById(R.id.swConversationActions);
swFts = view.findViewById(R.id.swFts); swFts = view.findViewById(R.id.swFts);
tvFtsIndexed = view.findViewById(R.id.tvFtsIndexed); tvFtsIndexed = view.findViewById(R.id.tvFtsIndexed);
tvFtsPro = view.findViewById(R.id.tvFtsPro); tvFtsPro = view.findViewById(R.id.tvFtsPro);
@ -141,6 +144,13 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
} }
}); });
swConversationActions.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("conversation_actions", checked).apply();
}
});
swFts.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { swFts.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@ -379,6 +389,8 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
int state = pm.getComponentEnabledSetting(new ComponentName(getContext(), ActivitySearch.class)); int state = pm.getComponentEnabledSetting(new ComponentName(getContext(), ActivitySearch.class));
swExternalSearch.setChecked(state != PackageManager.COMPONENT_ENABLED_STATE_DISABLED); swExternalSearch.setChecked(state != PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
swConversationActions.setChecked(prefs.getBoolean("conversation_actions", true));
swConversationActions.setVisibility(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? View.VISIBLE : View.GONE);
swFts.setChecked(prefs.getBoolean("fts", false)); swFts.setChecked(prefs.getBoolean("fts", false));
swEnglish.setChecked(prefs.getBoolean("english", false)); swEnglish.setChecked(prefs.getBoolean("english", false));
swWatchdog.setChecked(prefs.getBoolean("watchdog", true)); swWatchdog.setChecked(prefs.getBoolean("watchdog", true));

@ -101,6 +101,15 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvBody" /> app:layout_constraintTop_toBottomOf="@id/tvBody" />
<LinearLayout
android:id="@+id/llAction"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/wvBody" />
<ImageButton <ImageButton
android:id="@+id/ibSeen" android:id="@+id/ibSeen"
android:layout_width="36dp" android:layout_width="36dp"
@ -110,7 +119,7 @@
android:padding="6dp" android:padding="6dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/wvBody" app:layout_constraintTop_toBottomOf="@id/llAction"
app:srcCompat="@drawable/baseline_visibility_24" /> app:srcCompat="@drawable/baseline_visibility_24" />
<include <include

@ -426,6 +426,7 @@
<string name="title_advanced_aes_key_size" translatable="false">Max AES key size: %1$d</string> <string name="title_advanced_aes_key_size" translatable="false">Max AES key size: %1$d</string>
<string name="title_advanced_external_search">Allow other apps to search in messages</string> <string name="title_advanced_external_search">Allow other apps to search in messages</string>
<string name="title_advanced_conversation_actions">Show conversation actions</string>
<string name="title_advanced_fts">Build search index</string> <string name="title_advanced_fts">Build search index</string>
<string name="title_advanced_fts_indexed">%1$d / %2$d messages indexed (%3$s)</string> <string name="title_advanced_fts_indexed">%1$d / %2$d messages indexed (%3$s)</string>
<string name="title_advanced_english">Force English language</string> <string name="title_advanced_english">Force English language</string>

@ -398,6 +398,8 @@
<style name="buttonStyleSmall" parent="Base.Widget.AppCompat.Button.Small"> <style name="buttonStyleSmall" parent="Base.Widget.AppCompat.Button.Small">
<item name="android:textAppearance">@style/TextAppearance.AppCompat.Small</item> <item name="android:textAppearance">@style/TextAppearance.AppCompat.Small</item>
<item name="android:minHeight">0dp</item>
<item name="android:minWidth">0dp</item>
</style> </style>
<style name="buttonStyleToggle" parent="Base.Widget.AppCompat.Button"> <style name="buttonStyleToggle" parent="Base.Widget.AppCompat.Button">

Loading…
Cancel
Save