Skip to content
Commits on Source (18)
......@@ -33,6 +33,7 @@ proguard/
# IDE and OS files
.idea/
*.iml
*.el
.DS_Store
.externalNativeBuild
\ No newline at end of file
......@@ -94,6 +94,7 @@ release:
only:
- master
before_script:
- apt-get update -qq && apt-get install -y -qq pandoc wget
- wget --quiet --output-document=/tmp/nvm-install.sh https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh
- bash /tmp/nvm-install.sh
- export NVM_DIR="$HOME/.nvm" &&. "$NVM_DIR/nvm.sh" && nvm install 8
......
......@@ -4,14 +4,15 @@ plugins:
- - "@semantic-release/changelog"
- changelogTitle: |-
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
- - "@semantic-release/exec"
- prepareCmd: './scripts/prepare.sh ${nextRelease.version}'
- prepareCmd: './scripts/prepare.sh ${nextRelease.version} "${nextRelease.notes}"'
- - "@semantic-release/git"
- assets:
- CHANGELOG.md
- metadata/**/*.txt
- app/build.gradle
message: "chore(release): v${nextRelease.version}\n\n${nextRelease.notes}"
- "@semantic-release/gitlab"
......@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.3.0](https://framagit.org/dystopia-project/simple-email/compare/v1.2.1...v1.3.0) (2018-12-08)
### Bug Fixes
* change app Invite to share because require google account ([8299d54](https://framagit.org/dystopia-project/simple-email/commit/8299d54))
* check error on open attachment ([87a22a9](https://framagit.org/dystopia-project/simple-email/commit/87a22a9)), closes [#10](https://framagit.org/dystopia-project/simple-email/issues/10)
* prevent flickering on message view ([d6294c6](https://framagit.org/dystopia-project/simple-email/commit/d6294c6))
### Features
* better layout for the message list ([73aa80d](https://framagit.org/dystopia-project/simple-email/commit/73aa80d)), closes [#8](https://framagit.org/dystopia-project/simple-email/issues/8)
* implement refresh messages list ([f5973df](https://framagit.org/dystopia-project/simple-email/commit/f5973df))
* move color picker account outside of advanced ([7fb625c](https://framagit.org/dystopia-project/simple-email/commit/7fb625c))
## [1.2.1](https://framagit.org/dystopia-project/simple-email/compare/v1.2.0...v1.2.1) (2018-11-23)
......
apply plugin: 'com.android.application'
apply plugin: 'changelog'
apply from: "${rootDir}/jdee.gradle"
android {
compileSdkVersion 28
......@@ -7,8 +7,8 @@ android {
applicationId "org.dystopia.email"
minSdkVersion 23
targetSdkVersion 28
versionCode 114
versionName "1.2.1"
versionCode 115
versionName "1.3.0"
archivesBaseName = "SimpleEmail-v$versionName"
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
......@@ -51,10 +51,6 @@ android {
}
}
changelog {
versionNum "v$android.defaultConfig.versionName"
}
repositories {
google()
jcenter()
......@@ -63,6 +59,10 @@ repositories {
}
}
configurations {
implementationDeps.extendsFrom implementation
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
......@@ -120,5 +120,5 @@ dependencies {
implementation "org.sufficientlysecure:openpgp-api:$openpgp_version"
// https://android.googlesource.com/platform/frameworks/opt/colorpicker
implementation project(path: ':colorpicker')
implementation project(path: ':colorpicker', configuration: 'default')
}
This diff is collapsed.
......@@ -58,6 +58,7 @@ import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.material.snackbar.Snackbar;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
......@@ -66,6 +67,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.FileNotFoundException;
import java.net.URL;
import java.text.Collator;
import java.util.ArrayList;
......@@ -104,7 +106,6 @@ public class ActivityView extends ActivityBase
static final int REQUEST_ERROR = 3;
static final int REQUEST_ATTACHMENT = 1;
static final int REQUEST_INVITE = 2;
static final int REQUEST_DECRYPT = 3;
static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES";
......@@ -284,14 +285,12 @@ public class ActivityView extends ActivityBase
drawerArray.add(new DrawerItem(R.layout.item_drawer_separator));
if (getIntentInvite().resolveActivity(getPackageManager()) != null) {
drawerArray.add(
new DrawerItem(
ActivityView.this,
R.layout.item_drawer,
R.drawable.baseline_people_24,
R.string.menu_invite));
}
drawerArray.add(
new DrawerItem(
ActivityView.this,
R.layout.item_drawer,
R.drawable.baseline_share_24,
R.string.menu_invite));
drawerList.setAdapter(drawerArray);
}
......@@ -714,18 +713,18 @@ public class ActivityView extends ActivityBase
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(
Uri.parse(
"https://framagit.org/dystopia-project/simple-email/blob/8f7296ddc2275471d4190df1dd55dee4025a5114/docs/FAQ.md"));
"https://framagit.org/dystopia-project/simple-email/blob/HEAD/docs/FAQ.md"));
return intent;
}
private Intent getIntentInvite() {
Intent intent = new Intent("com.google.android.gms.appinvite.ACTION_APP_INVITE");
intent.setPackage("com.google.android.gms");
intent.putExtra("com.google.android.gms.appinvite.TITLE", getString(R.string.menu_invite));
intent.putExtra("com.google.android.gms.appinvite.MESSAGE", getString(R.string.title_try));
intent.putExtra("com.google.android.gms.appinvite.BUTTON_TEXT", getString(R.string.title_try));
// com.google.android.gms.appinvite.DEEP_LINK_URL
return intent;
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.title_try_subject));
shareIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.title_try));
shareIntent.setType("text/plain");
return Intent.createChooser(shareIntent, getString(R.string.title_try_text));
}
private void onMenuFolders(long account) {
......@@ -787,7 +786,7 @@ public class ActivityView extends ActivityBase
}
private void onMenuInvite() {
startActivityForResult(getIntentInvite(), REQUEST_INVITE);
startActivity(getIntentInvite());
}
private class DrawerItem {
......@@ -1102,6 +1101,8 @@ public class ActivityView extends ActivityBase
while ((read = fis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
} catch (FileNotFoundException ex) {
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
} finally {
try {
if (pfd != null) {
......
......@@ -97,6 +97,9 @@ import java.util.List;
import java.util.Locale;
import javax.mail.Address;
import javax.mail.internet.InternetAddress;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.xml.sax.XMLReader;
public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMessage.ViewHolder> {
......@@ -134,6 +137,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
private ImageView ivFlagged;
private ImageView ivAvatar;
private TextView tvFrom;
private TextView tvSummary;
private ImageView ivAddContact;
private TextView tvSize;
private TextView tvTime;
......@@ -183,6 +187,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
ivFlagged = itemView.findViewById(R.id.ivFlagged);
ivAvatar = itemView.findViewById(R.id.ivAvatar);
tvFrom = itemView.findViewById(R.id.tvFrom);
tvSummary = itemView.findViewById(R.id.tvSummary);
ivAddContact = itemView.findViewById(R.id.ivAddContact);
tvSize = itemView.findViewById(R.id.tvSize);
tvTime = itemView.findViewById(R.id.tvTime);
......@@ -317,12 +322,12 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
if (EntityFolder.DRAFTS.equals(message.folderType)
|| EntityFolder.OUTBOX.equals(message.folderType)
|| EntityFolder.SENT.equals(message.folderType)) {
tvFrom.setText(MessageHelper.getFormattedAddresses(message.to, !compact));
tvFrom.setText(MessageHelper.getFormattedAddresses(message.to, show_expanded));
tvTime.setText(
DateUtils.getRelativeTimeSpanString(
context, message.sent == null ? message.received : message.sent));
} else {
tvFrom.setText(MessageHelper.getFormattedAddresses(message.from, !compact));
tvFrom.setText(MessageHelper.getFormattedAddresses(message.from, show_expanded));
tvTime.setText(DateUtils.getRelativeTimeSpanString(context, message.received));
}
......@@ -340,21 +345,40 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
tvAccount.setText(message.accountName);
tvAccount.setVisibility(View.VISIBLE);
} else {
tvFolder.setText(
message.folderDisplay == null
? Helper.localizeFolderName(context, message.folderName)
: message.folderDisplay);
tvFolder.setText(message.folderDisplay == null
? Helper.localizeFolderName(context, message.folderName)
: message.folderDisplay);
tvFolder.setVisibility(View.VISIBLE);
}
if (viewType == ViewType.THREAD) {
if (viewType == ViewType.THREAD || message.count == 1) {
tvCount.setVisibility(View.GONE);
ivThread.setVisibility(View.GONE);
} else {
tvCount.setText(Integer.toString(message.count));
tvCount.setVisibility(View.VISIBLE);
ivThread.setVisibility(View.VISIBLE);
}
if (!compact) {
tvFrom.setMaxLines(show_expanded ? Integer.MAX_VALUE : 1);
tvSubject.setMaxLines(show_expanded ? Integer.MAX_VALUE : 1);
}
tvSummary.setVisibility(View.GONE);
if (message.content && !show_expanded) {
try {
String body = message.read(context);
Document doc = Jsoup.parse(body);
String plainText = doc.body().text();
int limit = compact ? 60 : 120;
tvSummary.setText(plainText.substring(0, Math.min(plainText.length(), limit)) + "...");
tvSummary.setVisibility(View.VISIBLE);
} catch(IOException ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
}
}
if (debug) {
db.operation().getOperationsByMessage(message.id).removeObservers(owner);
db.operation()
......@@ -400,17 +424,17 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
tvError.setVisibility(message.error == null ? View.GONE : View.VISIBLE);
}
int typeface = (message.unseen > 0 ? Typeface.BOLD : Typeface.NORMAL);
int typeface = (message.unseen <= 0 || show_expanded ? Typeface.NORMAL : Typeface.BOLD);
tvFrom.setTypeface(null, typeface);
tvTime.setTypeface(null, typeface);
tvSubject.setTypeface(null, typeface);
tvCount.setTypeface(null, typeface);
int colorUnseen =
Helper.resolveColor(
context, message.unseen > 0 ? R.attr.colorUnread : android.R.attr.textColorSecondary);
int colorUnseen = Helper.resolveColor(context, R.attr.colorUnread);
tvSubject.setTextColor(colorUnseen);
tvFrom.setTextColor(colorUnseen);
tvTime.setTextColor(colorUnseen);
tvSummary.setTextColor(Helper.resolveColor(context, android.R.attr.textColorSecondary));
grpExpanded.setVisibility(
viewType == ViewType.THREAD && show_expanded ? View.VISIBLE : View.GONE);
......@@ -424,8 +448,8 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
pbHeaders.setVisibility(View.GONE);
grpHeaders.setVisibility(show_headers && show_expanded ? View.VISIBLE : View.GONE);
bnvActions.setVisibility(View.GONE);
vSeparatorBody.setVisibility(View.GONE);
bnvActions.setVisibility(show_expanded ? View.INVISIBLE : View.GONE);
vSeparatorBody.setVisibility(!show_expanded ? View.INVISIBLE : View.GONE);
btnImages.setVisibility(View.GONE);
pbBody.setVisibility(View.GONE);
grpAttachments.setVisibility(
......@@ -1599,14 +1623,15 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
private static final DiffUtil.ItemCallback<TupleMessageEx> DIFF_CALLBACK =
new DiffUtil.ItemCallback<TupleMessageEx>() {
@Override
public boolean areItemsTheSame(@NonNull TupleMessageEx prev, @NonNull TupleMessageEx next) {
public boolean areItemsTheSame(
@NonNull TupleMessageEx prev, @NonNull TupleMessageEx next) {
return prev.id.equals(next.id);
}
@Override
public boolean areContentsTheSame(
@NonNull TupleMessageEx prev, @NonNull TupleMessageEx next) {
return prev.equals(next);
return prev.shallowEquals(next);
}
};
......
......@@ -41,7 +41,7 @@ import org.json.JSONObject;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 24,
version = 25,
entities = {
EntityIdentity.class,
EntityAccount.class,
......@@ -349,11 +349,20 @@ public abstract class DB extends RoomDatabase {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i(
Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `message` ADD COLUMN `account_name` TEXT");
}
})
.build();
.addMigrations(
new Migration(24, 25) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i(
Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `folder` ADD COLUMN `sync_state` TEXT");
}
})
.build();
}
public static class Converters {
......
......@@ -17,6 +17,7 @@ package org.dystopia.email;
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018, Marcel Bokhorst (M66B)
Copyright 2018, Distopico (dystopia project) <distopico@riseup.net> and contributors
*/
import androidx.lifecycle.LiveData;
......@@ -27,76 +28,53 @@ import java.util.List;
@Dao
public interface DaoFolder {
@Query(
"SELECT * FROM folder"
+ " WHERE account = :account"
+ " ORDER BY CASE WHEN folder.type = '"
+ EntityFolder.USER
+ "' THEN 1 ELSE 0 END")
@Query("SELECT * FROM folder" + " WHERE account = :account"
+ " ORDER BY CASE WHEN folder.type = '" + EntityFolder.USER + "' THEN 1 ELSE 0 END")
List<EntityFolder> getFolders(long account);
@Query(
"SELECT * FROM folder"
+ " WHERE account = :account"
+ " AND synchronize = :synchronize"
+ " ORDER BY CASE WHEN folder.type = '"
+ EntityFolder.USER
+ "' THEN 1 ELSE 0 END")
@Query("SELECT * FROM folder" + " WHERE account = :account" + " AND synchronize = :synchronize"
+ " ORDER BY CASE WHEN folder.type = '" + EntityFolder.USER + "' THEN 1 ELSE 0 END")
List<EntityFolder> getFolders(long account, boolean synchronize);
@Query(
"SELECT * FROM folder"
+ " WHERE account = :account"
+ " AND type = '"
+ EntityFolder.USER
+ "'")
@Query("SELECT * FROM folder" + " WHERE account = :account" + " AND type = '" + EntityFolder.USER
+ "'")
List<EntityFolder> getUserFolders(long account);
@Query(
"SELECT folder.*, account.name AS accountName"
+ ", COUNT(message.id) AS messages"
+ ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
+ ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen"
+ " FROM folder"
+ " LEFT JOIN account ON account.id = folder.account"
+ " LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
+ " WHERE folder.account = :account OR folder.account IS NULL"
+ " GROUP BY folder.id")
@Query("SELECT * FROM folder WHERE unified")
List<EntityFolder> getUnifiedFolders();
@Query("SELECT folder.*, account.name AS accountName, account.state as accountState"
+ ", COUNT(message.id) AS messages"
+ ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
+ ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" + " FROM folder"
+ " LEFT JOIN account ON account.id = folder.account"
+ " LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
+ " WHERE folder.account = :account OR folder.account IS NULL" + " GROUP BY folder.id")
LiveData<List<TupleFolderEx>> liveFolders(long account);
@Query(
"SELECT * FROM folder"
+ " WHERE (:account < 0 OR folder.account = :account)"
+ " AND type <> '"
+ EntityFolder.USER
+ "'")
@Query("SELECT * FROM folder" + " WHERE (:account < 0 OR folder.account = :account)"
+ " AND type <> '" + EntityFolder.USER + "'")
LiveData<List<EntityFolder>> liveSystemFolders(long account);
@Query(
"SELECT folder.*, account.name AS accountName"
+ ", COUNT(message.id) AS messages"
+ ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
+ ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen"
+ " FROM folder"
+ " JOIN account ON account.id = folder.account"
+ " JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
+ " WHERE account.`synchronize`"
+ " AND folder.unified"
+ " GROUP BY folder.id")
@Query("SELECT folder.*, account.name AS accountName, account.state as accountState"
+ ", COUNT(message.id) AS messages"
+ ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
+ ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" + " FROM folder"
+ " JOIN account ON account.id = folder.account"
+ " JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
+ " WHERE account.`synchronize`" + " AND folder.unified" + " GROUP BY folder.id")
LiveData<List<TupleFolderEx>> liveUnified();
@Query("SELECT folder.* FROM folder WHERE folder.id = :id")
LiveData<EntityFolder> liveFolder(long id);
@Query(
"SELECT folder.*, account.name AS accountName"
+ ", COUNT(message.id) AS messages"
+ ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
+ ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen"
+ " FROM folder"
+ " LEFT JOIN account ON account.id = folder.account"
+ " LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
+ " WHERE folder.id = :id")
@Query("SELECT folder.*, account.name AS accountName, account.state as accountState"
+ ", COUNT(message.id) AS messages"
+ ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
+ ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" + " FROM folder"
+ " LEFT JOIN account ON account.id = folder.account"
+ " LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
+ " WHERE folder.id = :id")
LiveData<TupleFolderEx> liveFolderEx(long id);
@Query("SELECT * FROM folder WHERE id = :id")
......@@ -109,20 +87,12 @@ public interface DaoFolder {
EntityFolder getFolderByType(long account, String type);
// For debug/crash info
@Query(
"SELECT folder.* FROM folder"
+ " JOIN account ON account.id = folder.account"
+ " WHERE `primary` AND type = '"
+ EntityFolder.DRAFTS
+ "'")
@Query("SELECT folder.* FROM folder" + " JOIN account ON account.id = folder.account"
+ " WHERE `primary` AND type = '" + EntityFolder.DRAFTS + "'")
EntityFolder getPrimaryDrafts();
@Query(
"SELECT folder.* FROM folder"
+ " JOIN account ON account.id = folder.account"
+ " WHERE `primary` AND type = '"
+ EntityFolder.ARCHIVE
+ "'")
@Query("SELECT folder.* FROM folder" + " JOIN account ON account.id = folder.account"
+ " WHERE `primary` AND type = '" + EntityFolder.ARCHIVE + "'")
EntityFolder getPrimaryArchive();
@Query("SELECT * FROM folder WHERE type = '" + EntityFolder.OUTBOX + "'")
......@@ -134,6 +104,9 @@ public interface DaoFolder {
@Query("UPDATE folder SET state = :state WHERE id = :id")
int setFolderState(long id, String state);
@Query("UPDATE folder SET sync_state = :state WHERE id = :id")
int setFolderSyncState(long id, String state);
@Query("UPDATE folder SET error = :error WHERE id = :id")
int setFolderError(long id, String error);
......@@ -143,23 +116,11 @@ public interface DaoFolder {
@Query("UPDATE folder" + " SET type = '" + EntityFolder.USER + "'" + " WHERE account = :account")
int setFoldersUser(long account);
@Query(
"UPDATE folder"
+ " SET name = :name"
+ ", display = :display"
+ ", hide = :hide"
+ ", synchronize = :synchronize"
+ ", unified = :unified"
+ ", `after` = :after"
+ " WHERE id = :id")
int setFolderProperties(
long id,
String name,
String display,
boolean hide,
boolean synchronize,
boolean unified,
int after);
@Query("UPDATE folder" + " SET name = :name" + ", display = :display" + ", hide = :hide"
+ ", synchronize = :synchronize" + ", unified = :unified" + ", `after` = :after"
+ " WHERE id = :id")
int setFolderProperties(long id, String name, String display, boolean hide, boolean synchronize,
boolean unified, int after);
@Query("UPDATE folder SET name = :name WHERE account = :account AND name = :old")
int renameFolder(long account, String old, String name);
......
......@@ -36,8 +36,10 @@ public interface DaoOperation {
@Query("SELECT * FROM operation ORDER BY id")
LiveData<List<EntityOperation>> liveOperations();
@Query("SELECT COUNT(id) FROM operation WHERE folder = :folder")
int getOperationCount(long folder);
@Query("SELECT COUNT(id) FROM operation" +
" WHERE folder = :folder" +
" AND (:name IS NULL OR operation.name = :name)")
int getOperationCount(long folder, String name);
@Insert
long insertOperation(EntityOperation operation);
......
......@@ -66,6 +66,7 @@ public class EntityFolder implements Serializable {
@NonNull public Boolean hide = false;
@NonNull public Boolean unified = false;
public String state;
public String sync_state;
public String error;
static final String INBOX = "Inbox";
......@@ -112,6 +113,7 @@ public class EntityFolder implements Serializable {
&& this.hide == other.hide
&& this.unified == other.unified
&& (this.state == null ? other.state == null : this.state.equals(other.state))
&& (this.sync_state == null ? other.sync_state == null : this.sync_state.equals(other.sync_state))
&& (this.error == null ? other.error == null : this.error.equals(other.error)));
} else {
return false;
......
......@@ -83,6 +83,7 @@ import javax.mail.Address;
@Index(value = {"ui_found"}),
@Index(value = {"ui_ignored"})
})
public class EntityMessage implements Serializable {
static final String TABLE_NAME = "message";
......@@ -230,6 +231,32 @@ public class EntityMessage implements Serializable {
return false;
}
public boolean shallowEquals(Object obj) {
if (obj instanceof EntityMessage) {
EntityMessage other = (EntityMessage) obj;
return ((this.msgid == null ? other.msgid == null : this.msgid.equals(other.msgid))
&& (this.thread == null ? other.thread == null : this.thread.equals(other.thread))
&& (this.avatar == null ? other.avatar == null : this.avatar.equals(other.avatar))
&& equal(this.from, other.from)
&& equal(this.to, other.to)
&& equal(this.cc, other.cc)
&& equal(this.bcc, other.bcc)
&& equal(this.reply, other.reply)
&& (this.headers == null ? other.headers == null : this.headers.equals(other.headers))
&& (this.subject == null ? other.subject == null : this.subject.equals(other.subject))
&& (this.size == null ? other.size == null : this.size.equals(other.size))
&& this.content == other.content
&& this.received.equals(other.received)
&& this.ui_seen.equals(other.ui_seen)
&& this.ui_flagged.equals(other.ui_flagged)
&& this.ui_hide.equals(other.ui_hide)
&& this.ui_found.equals(other.ui_found)
&& this.ui_ignored.equals(other.ui_ignored)
&& (this.error == null ? other.error == null : this.error.equals(other.error)));
}
return false;
}
private static boolean equal(Address[] a1, Address[] a2) {
if (a1 == null && a2 == null) {
return true;
......
......@@ -71,6 +71,7 @@ public class EntityOperation {
public static final String BODY = "body";
public static final String ATTACHMENT = "attachment";
public static final String FLAG = "flag";
public static final String SYNC = "sync";
private static List<Intent> queue = new ArrayList<>();
......@@ -85,13 +86,6 @@ public class EntityOperation {
queue(db, message, name, jsonArray);
}
static void queue(DB db, EntityMessage message, String name, Object value1, Object value2) {
JSONArray jsonArray = new JSONArray();
jsonArray.put(value1);
jsonArray.put(value2);
queue(db, message, name, jsonArray);
}
private static void queue(DB db, EntityMessage message, String name, JSONArray jsonArray) {
EntityOperation operation = new EntityOperation();
operation.folder = message.folder;
......@@ -124,6 +118,20 @@ public class EntityOperation {
+ operation.args);
}
private static void queue(DB db, long folder, Long message, String name, JSONArray jargs) {
EntityOperation operation = new EntityOperation();
operation.folder = folder;
operation.message = message;
operation.name = name;
operation.args = jargs.toString();
operation.created = new Date().getTime();
operation.id = db.operation().insertOperation(operation);
Log.i(Helper.TAG, "Queued op=" + operation.id + "/" + operation.name +
" msg=" + operation.folder + "/" + operation.message +
" args=" + operation.args);
}
public static void process(Context context) {
// Processing needs to be done after committing to the database
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
......@@ -135,6 +143,13 @@ public class EntityOperation {
}
}
static void sync(DB db, long folder) {
if (db.operation().getOperationCount(folder, EntityOperation.SYNC) == 0) {
queue(db, folder, null, EntityOperation.SYNC, new JSONArray());
db.folder().setFolderSyncState(folder, "requested");
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof EntityOperation) {
......
......@@ -107,7 +107,6 @@ public class FragmentAccount extends FragmentEx {
private TextView tvName;
private EditText etName;
private Button btnColor;
private View vwColor;
private ImageView ibColorDefault;
private EditText etSignature;
......@@ -183,7 +182,6 @@ public class FragmentAccount extends FragmentEx {
etName = view.findViewById(R.id.etName);
tvName = view.findViewById(R.id.tvName);
btnColor = view.findViewById(R.id.btnColor);
vwColor = view.findViewById(R.id.vwColor);
ibColorDefault = view.findViewById(R.id.ibColorDefault);
etSignature = view.findViewById(R.id.etSignature);
......@@ -372,7 +370,7 @@ public class FragmentAccount extends FragmentEx {
});
vwColor.setBackgroundColor(color);
btnColor.setOnClickListener(
vwColor.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
......@@ -1164,7 +1162,7 @@ public class FragmentAccount extends FragmentEx {
GradientDrawable border = new GradientDrawable();
border.setColor(color);
border.setStroke(1, Helper.resolveColor(getContext(), R.attr.colorSeparator));
border.setStroke(5, Helper.resolveColor(getContext(), R.attr.colorSeparator));
vwColor.setBackground(border);
}
......
......@@ -2,18 +2,18 @@ package org.dystopia.email;
/*
* This file is part of FairEmail.
*
*
* FairEmail is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* FairEmail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License along with FairEmail. If not,
* see <http://www.gnu.org/licenses/>.
*
*
* Copyright 2018, Marcel Bokhorst (M66B)
* Copyright 2018, Distopico (dystopia project) <distopico@riseup.net> and contributors
*/
......@@ -59,6 +59,7 @@ import androidx.recyclerview.selection.StorageStrategy;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
......@@ -73,6 +74,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FragmentMessages extends FragmentEx {
private SwipeRefreshLayout swipeRefresh;
private ViewGroup view;
private View popupAnchor;
private ImageButton ibHintSupport;
......@@ -157,6 +159,7 @@ public class FragmentMessages extends FragmentEx {
setHasOptionsMenu(true);
// Get controls
swipeRefresh = view.findViewById(R.id.swipeRefresh);
popupAnchor = view.findViewById(R.id.popupAnchor);
ibHintSupport = view.findViewById(R.id.ibHintSupport);
ibHintSwipe = view.findViewById(R.id.ibHintSwipe);
......@@ -176,6 +179,16 @@ public class FragmentMessages extends FragmentEx {
// Wire controls
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
Bundle args = new Bundle();
args.putLong("account", account);
args.putLong("folder", folder);
onRefreshHandler(args);
}
});
ibHintSwipe.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
......@@ -275,10 +288,13 @@ public class FragmentMessages extends FragmentEx {
selectionTracker.addObserver(new SelectionTracker.SelectionObserver() {
@Override
public void onSelectionChanged() {
swipeRefresh.setEnabled(false);
if (selectionTracker.hasSelection()) {
fabMove.show();
} else {
fabMove.hide();
swipeRefresh.setEnabled(true);
}
}
});
......@@ -718,10 +734,12 @@ public class FragmentMessages extends FragmentEx {
});
// Initialize
swipeRefresh.setEnabled(viewType == AdapterMessage.ViewType.UNIFIED || viewType == AdapterMessage.ViewType.FOLDER);
tvNoEmail.setVisibility(View.GONE);
bottom_navigation.setVisibility(View.GONE);
grpReady.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE);
// TODO: implement in load more items
pbWait.setVisibility(View.GONE);
fab.hide();
fabMove.hide();
......@@ -803,6 +821,16 @@ public class FragmentMessages extends FragmentEx {
} else {
setSubtitle(name);
}
boolean isRefreshing = false;
for (TupleFolderEx folder : folders) {
if (folder.sync_state != null && "connected".equals(folder.accountState)) {
isRefreshing = true;
break;
}
}
swipeRefresh.setRefreshing(isRefreshing);
}
});
break;
......@@ -827,6 +855,10 @@ public class FragmentMessages extends FragmentEx {
outbox = EntityFolder.OUTBOX.equals(folder.type);
getActivity().invalidateOptionsMenu();
}
swipeRefresh.setRefreshing(folder != null && folder.sync_state != null &&
"connected".equals(EntityFolder.OUTBOX.equals(folder.type)
? folder.state : folder.accountState));
}
});
break;
......@@ -1014,6 +1046,54 @@ public class FragmentMessages extends FragmentEx {
}
}
private void onRefreshHandler(Bundle args) {
new SimpleTask<Boolean>() {
@Override
protected Boolean onLoad(Context context, Bundle args) {
long aid = args.getLong("account");
long fid = args.getLong("folder");
DB db = DB.getInstance(context);
boolean isConnected = false;
try {
db.beginTransaction();
List<EntityFolder> folders = new ArrayList<>();
if (aid < 0) {
folders.addAll(db.folder().getUnifiedFolders());
} else {
folders.add(db.folder().getFolder(fid));
}
for (EntityFolder folder : folders) {
EntityOperation.sync(db, folder.id);
if (folder.account == null) { // outbox
isConnected = "connected".equals(folder.state);
} else {
EntityAccount account = db.account().getAccount(folder.account);
isConnected = "connected".equals(account.state);
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return isConnected;
}
@Override
protected void onLoaded(Bundle args, Boolean isConnected) {
if (!isConnected) {
swipeRefresh.setRefreshing(false);
}
}
}.load(FragmentMessages.this, args);
}
private void onMenuFolders() {
getFragmentManager().popBackStack("unified", 0);
......@@ -1098,12 +1178,12 @@ public class FragmentMessages extends FragmentEx {
new BoundaryCallbackMessages.IBoundaryCallbackMessages() {
@Override
public void onLoading() {
pbWait.setVisibility(View.VISIBLE);
swipeRefresh.setRefreshing(true);
}
@Override
public void onLoaded() {
pbWait.setVisibility(View.GONE);
swipeRefresh.setRefreshing(false);
}
@Override
......@@ -1141,12 +1221,12 @@ public class FragmentMessages extends FragmentEx {
@Override
public void onLoading() {
tvNoEmail.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE);
swipeRefresh.setRefreshing(true);
}
@Override
public void onLoaded() {
pbWait.setVisibility(View.GONE);
swipeRefresh.setRefreshing(false);
if (messages.getValue() == null || messages.getValue().size() == 0) {
tvNoEmail.setVisibility(View.VISIBLE);
}
......@@ -1247,7 +1327,7 @@ public class FragmentMessages extends FragmentEx {
boolean searching = (searchCallback != null && searchCallback.isSearching());
if (!searching) {
pbWait.setVisibility(View.GONE);
swipeRefresh.setRefreshing(false);
}
grpReady.setVisibility(View.VISIBLE);
......
......@@ -2,18 +2,18 @@ package org.dystopia.email;
/*
* This file is part of FairEmail.
*
*
* FairEmail is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* FairEmail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License along with FairEmail. If not,
* see <http://www.gnu.org/licenses/>.
*
*
* Copyright 2018, Marcel Bokhorst (M66B)
* Copyright 2018, Distopico (dystopia project) <distopico@riseup.net> and contributors
*/
......@@ -1093,7 +1093,7 @@ public class ServiceSynchronize extends LifecycleService {
// Prevent unnecessary folder
// connections
if (ACTION_PROCESS_OPERATIONS.equals(intent.getAction())) {
if (db.operation().getOperationCount(fid) == 0) {
if (db.operation().getOperationCount(fid, null) == 0) {
return;
}
}
......@@ -1152,7 +1152,7 @@ public class ServiceSynchronize extends LifecycleService {
lbm.registerReceiver(processFolder, f);
for (EntityFolder folder : folders.keySet()) {
if (db.operation().getOperationCount(folder.id) > 0) {
if (db.operation().getOperationCount(folder.id, null) > 0) {
Intent intent = new Intent();
intent.setType("account/" + account.id);
intent.setAction(ServiceSynchronize.ACTION_PROCESS_OPERATIONS);
......
......@@ -21,6 +21,7 @@ package org.dystopia.email;
public class TupleFolderEx extends EntityFolder {
public String accountName;
public String accountState;
public int messages;
public int content;
public int unseen;
......@@ -33,6 +34,9 @@ public class TupleFolderEx extends EntityFolder {
&& (this.accountName == null
? other.accountName == null
: accountName.equals(other.accountName))
&& (this.accountState == null
? other.accountState == null
: accountState.equals(other.accountState))
&& this.messages == other.messages
&& this.content == other.content
&& this.unseen == other.unseen);
......
......@@ -31,6 +31,11 @@ public class TupleMessageEx extends EntityMessage {
public int unflagged;
public int attachments;
@Override
public boolean shallowEquals(Object obj) {
return super.shallowEquals(obj);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TupleMessageEx) {
......@@ -52,6 +57,6 @@ public class TupleMessageEx extends EntityMessage {
&& this.unflagged == other.unflagged
&& this.attachments == other.attachments);
}
return super.equals(obj);
return false;
}
}
File mode changed from 100755 to 100644