Added setting to poll instead of synchronize

pull/146/head
M66B 6 years ago
parent 17bfeceb03
commit 3aa89cae97

@ -323,18 +323,19 @@ If you are using a VPN, the VPN provider might block the connection because it i
<a name="faq23"></a>
**(23) Why do I get 'Too many simultaneous connections' ?**
The message *Too many simultaneous connections* is sent by the email server when there are too many connections to the same email account at the same time.
The message *Too many simultaneous connections* is sent by the email server
when there are too many folder connections for the same email account at the same time.
Possible causes are:
* There are multiple email clients connected to the same account
* The same email client is connected multiple times to the same account
* The previous connection was terminated abruptly for example by losing internet connectivity
* The previous connection was terminated abruptly for example by abruptly losing internet connectivity, for example when turning on flight mode
If only FairEmail is connecting to the email server, first try to wait half an hour to see if the problem resolves itself,
else try to reduce the number of folders to synchronize.
else enable the folder settings '*Poll instead of synchronize*' for some folders.
The maximum number of simultaneous connections for Gmail is 15,
The maximum number of simultaneous folder connections for Gmail is 15,
so you can synchronize at most 15 folders simultaneously on *all* your devices at the same time.
See [here](https://support.google.com/mail/answer/7126229) for details.

File diff suppressed because it is too large Load Diff

@ -46,7 +46,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 15,
version = 16,
entities = {
EntityIdentity.class,
EntityAccount.class,
@ -241,6 +241,13 @@ public abstract class DB extends RoomDatabase {
db.execSQL("ALTER TABLE `folder` ADD COLUMN `sync_state` TEXT");
}
})
.addMigrations(new Migration(15, 16) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `folder` ADD COLUMN `poll` INTEGER NOT NULL DEFAULT 0");
}
})
.build();
}

@ -149,6 +149,7 @@ public interface DaoFolder {
", display = :display" +
", hide = :hide" +
", synchronize = :synchronize" +
", poll = :poll" +
", unified = :unified" +
", `sync_days` = :sync_days" +
", `keep_days` = :keep_days" +
@ -158,6 +159,7 @@ public interface DaoFolder {
String name, String display,
boolean hide,
boolean synchronize,
boolean poll,
boolean unified,
int sync_days, int keep_days);

@ -69,6 +69,8 @@ public class EntityFolder implements Serializable {
@NonNull
public Boolean synchronize;
@NonNull
public Boolean poll = false;
@NonNull
public Integer sync_days;
@NonNull
public Integer keep_days;
@ -168,6 +170,9 @@ public class EntityFolder implements Serializable {
this.type.equals(other.type) &&
this.level.equals(other.level) &&
this.synchronize.equals(other.synchronize) &&
this.poll.equals(other.poll) &&
this.sync_days.equals(other.sync_days) &&
this.keep_days.equals(other.keep_days) &&
(this.display == null ? other.display == null : this.display.equals(other.display)) &&
this.hide == other.hide &&
this.unified == other.unified &&
@ -189,6 +194,7 @@ public class EntityFolder implements Serializable {
json.put("type", type);
json.put("level", level);
json.put("synchronize", synchronize);
json.put("poll", poll);
json.put("sync_days", sync_days);
json.put("keep_days", keep_days);
json.put("display", display);
@ -208,6 +214,10 @@ public class EntityFolder implements Serializable {
folder.level = 0;
folder.synchronize = json.getBoolean("synchronize");
if (json.has("poll"))
folder.poll = json.getBoolean("poll");
else
folder.poll = false;
if (json.has("after"))
folder.sync_days = json.getInt("after");

@ -29,6 +29,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ProgressBar;
@ -52,6 +53,7 @@ public class FragmentFolder extends FragmentEx {
private EditText etDisplay;
private CheckBox cbHide;
private CheckBox cbSynchronize;
private CheckBox cbPoll;
private CheckBox cbUnified;
private EditText etSyncDays;
private EditText etKeepDays;
@ -85,6 +87,7 @@ public class FragmentFolder extends FragmentEx {
etDisplay = view.findViewById(R.id.etDisplay);
cbHide = view.findViewById(R.id.cbHide);
cbSynchronize = view.findViewById(R.id.cbSynchronize);
cbPoll = view.findViewById(R.id.cbPoll);
cbUnified = view.findViewById(R.id.cbUnified);
etSyncDays = view.findViewById(R.id.etSyncDays);
etKeepDays = view.findViewById(R.id.etKeepDays);
@ -93,6 +96,13 @@ public class FragmentFolder extends FragmentEx {
pbSave = view.findViewById(R.id.pbSave);
pbWait = view.findViewById(R.id.pbWait);
cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
cbPoll.setEnabled(isChecked);
}
});
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -109,6 +119,7 @@ public class FragmentFolder extends FragmentEx {
args.putBoolean("hide", cbHide.isChecked());
args.putBoolean("unified", cbUnified.isChecked());
args.putBoolean("synchronize", cbSynchronize.isChecked());
args.putBoolean("poll", cbPoll.isChecked());
args.putString("sync", etSyncDays.getText().toString());
args.putString("keep", etKeepDays.getText().toString());
@ -122,6 +133,7 @@ public class FragmentFolder extends FragmentEx {
boolean hide = args.getBoolean("hide");
boolean unified = args.getBoolean("unified");
boolean synchronize = args.getBoolean("synchronize");
boolean poll = args.getBoolean("poll");
String sync = args.getString("sync");
String keep = args.getString("keep");
@ -132,7 +144,8 @@ public class FragmentFolder extends FragmentEx {
if (keep_days < sync_days)
keep_days = sync_days;
EntityFolder folder = null;
boolean reload = false;
EntityFolder folder;
IMAPStore istore = null;
DB db = DB.getInstance(getContext());
@ -167,6 +180,7 @@ public class FragmentFolder extends FragmentEx {
create.type = EntityFolder.USER;
create.unified = unified;
create.synchronize = synchronize;
create.poll = poll;
create.sync_days = sync_days;
create.keep_days = keep_days;
db.folder().insertFolder(create);
@ -182,6 +196,10 @@ public class FragmentFolder extends FragmentEx {
}
if (folder != null) {
reload = (!folder.name.equals(name) ||
!folder.synchronize.equals(synchronize) ||
!folder.poll.equals(poll));
Calendar cal_keep = Calendar.getInstance();
cal_keep.add(Calendar.DAY_OF_MONTH, -keep_days);
cal_keep.set(Calendar.HOUR_OF_DAY, 0);
@ -194,7 +212,12 @@ public class FragmentFolder extends FragmentEx {
keep_time = 0;
Log.i(Helper.TAG, "Updating folder=" + name);
db.folder().setFolderProperties(id, name, display, hide, synchronize, unified, sync_days, keep_days);
db.folder().setFolderProperties(id,
name, display,
hide,
synchronize, poll,
unified,
sync_days, keep_days);
db.message().deleteMessagesBefore(id, keep_time, true);
@ -210,7 +233,7 @@ public class FragmentFolder extends FragmentEx {
istore.close();
}
if (folder == null || !folder.name.equals(name))
if (folder == null || !folder.name.equals(name) || reload)
ServiceSynchronize.reload(getContext(), "save folder");
else
EntityOperation.sync(db, folder.id);
@ -350,6 +373,7 @@ public class FragmentFolder extends FragmentEx {
cbHide.setChecked(folder == null ? false : folder.hide);
cbUnified.setChecked(folder == null ? false : folder.unified);
cbSynchronize.setChecked(folder == null || folder.synchronize);
cbPoll.setChecked(folder == null ? false : folder.poll);
etSyncDays.setText(Integer.toString(folder == null ? EntityFolder.DEFAULT_USER_SYNC : folder.sync_days));
etKeepDays.setText(Integer.toString(folder == null ? EntityFolder.DEFAULT_USER_SYNC : folder.keep_days));
}
@ -358,6 +382,7 @@ public class FragmentFolder extends FragmentEx {
pbWait.setVisibility(View.GONE);
Helper.setViewsEnabled(view, true);
etRename.setEnabled(folder == null || EntityFolder.USER.equals(folder.type));
cbPoll.setEnabled(cbSynchronize.isChecked());
btnSave.setEnabled(true);
ibDelete.setVisibility(folder == null || !EntityFolder.USER.equals(folder.type) ? View.GONE : View.VISIBLE);
}

@ -886,53 +886,134 @@ public class ServiceSynchronize extends LifecycleService {
// Update folder list
synchronizeFolders(account, istore, state);
// Open folders
for (final EntityFolder folder : db.folder().getFolders(account.id, true)) {
Log.i(Helper.TAG, account.name + " sync folder " + folder.name);
// Open synchronizing folders
for (final EntityFolder folder : db.folder().getFolders(account.id)) {
if (folder.synchronize && !folder.poll && capIdle) {
Log.i(Helper.TAG, account.name + " sync folder " + folder.name);
db.folder().setFolderState(folder.id, "connecting");
db.folder().setFolderState(folder.id, "connecting");
final IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name);
try {
ifolder.open(Folder.READ_WRITE);
} catch (Throwable ex) {
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
throw ex;
}
folders.put(folder, ifolder);
final IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name);
try {
ifolder.open(Folder.READ_WRITE);
} catch (Throwable ex) {
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
throw ex;
}
folders.put(folder, ifolder);
db.folder().setFolderState(folder.id, "connected");
db.folder().setFolderError(folder.id, null);
db.folder().setFolderState(folder.id, "connected");
db.folder().setFolderError(folder.id, null);
Log.i(Helper.TAG, account.name + " folder " + folder.name + " flags=" + ifolder.getPermanentFlags());
Log.i(Helper.TAG, account.name + " folder " + folder.name + " flags=" + ifolder.getPermanentFlags());
// Listen for new and deleted messages
ifolder.addMessageCountListener(new MessageCountAdapter() {
@Override
public void messagesAdded(MessageCountEvent e) {
try {
wlAccount.acquire();
Log.i(Helper.TAG, folder.name + " messages added");
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(FetchProfile.Item.FLAGS);
fp.add(FetchProfile.Item.CONTENT_INFO); // body structure
fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.HEADERS);
fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
fp.add(FetchProfile.Item.SIZE);
fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
ifolder.fetch(e.getMessages(), fp);
for (Message imessage : e.getMessages())
// Listen for new and deleted messages
ifolder.addMessageCountListener(new MessageCountAdapter() {
@Override
public void messagesAdded(MessageCountEvent e) {
try {
wlAccount.acquire();
Log.i(Helper.TAG, folder.name + " messages added");
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(FetchProfile.Item.FLAGS);
fp.add(FetchProfile.Item.CONTENT_INFO); // body structure
fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.HEADERS);
fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
fp.add(FetchProfile.Item.SIZE);
fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
ifolder.fetch(e.getMessages(), fp);
for (Message imessage : e.getMessages())
try {
long id;
try {
db.beginTransaction();
id = synchronizeMessage(
ServiceSynchronize.this,
folder, ifolder, (IMAPMessage) imessage,
false, false, false);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
try {
db.beginTransaction();
downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) imessage, id);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
} catch (IOException ex) {
if (ex.getCause() instanceof MessageRemovedException)
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
else
throw ex;
}
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
}
@Override
public void messagesRemoved(MessageCountEvent e) {
try {
wlAccount.acquire();
Log.i(Helper.TAG, folder.name + " messages removed");
for (Message imessage : e.getMessages())
try {
long uid = ifolder.getUID(imessage);
DB db = DB.getInstance(ServiceSynchronize.this);
int count = db.message().deleteMessage(folder.id, uid);
Log.i(Helper.TAG, "Deleted uid=" + uid + " count=" + count);
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
}
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
}
});
// Flags (like "seen") at the remote could be changed while synchronizing
// Listen for changed messages
ifolder.addMessageChangedListener(new MessageChangedListener() {
@Override
public void messageChanged(MessageChangedEvent e) {
try {
wlAccount.acquire();
try {
Log.i(Helper.TAG, folder.name + " message changed");
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.FLAGS);
ifolder.fetch(new Message[]{e.getMessage()}, fp);
long id;
try {
db.beginTransaction();
id = synchronizeMessage(
ServiceSynchronize.this,
folder, ifolder, (IMAPMessage) imessage,
folder, ifolder, (IMAPMessage) e.getMessage(),
false, false, false);
db.setTransactionSuccessful();
} finally {
@ -941,7 +1022,7 @@ public class ServiceSynchronize extends LifecycleService {
try {
db.beginTransaction();
downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) imessage, id);
downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) e.getMessage(), id);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
@ -954,99 +1035,18 @@ public class ServiceSynchronize extends LifecycleService {
else
throw ex;
}
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
}
@Override
public void messagesRemoved(MessageCountEvent e) {
try {
wlAccount.acquire();
Log.i(Helper.TAG, folder.name + " messages removed");
for (Message imessage : e.getMessages())
try {
long uid = ifolder.getUID(imessage);
DB db = DB.getInstance(ServiceSynchronize.this);
int count = db.message().deleteMessage(folder.id, uid);
Log.i(Helper.TAG, "Deleted uid=" + uid + " count=" + count);
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
}
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
}
});
// Flags (like "seen") at the remote could be changed while synchronizing
// Listen for changed messages
ifolder.addMessageChangedListener(new MessageChangedListener() {
@Override
public void messageChanged(MessageChangedEvent e) {
try {
wlAccount.acquire();
try {
Log.i(Helper.TAG, folder.name + " message changed");
FetchProfile fp = new FetchProfile();
fp.add(UIDFolder.FetchProfileItem.UID);
fp.add(IMAPFolder.FetchProfileItem.FLAGS);
ifolder.fetch(new Message[]{e.getMessage()}, fp);
long id;
try {
db.beginTransaction();
id = synchronizeMessage(
ServiceSynchronize.this,
folder, ifolder, (IMAPMessage) e.getMessage(),
false, false, false);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
try {
db.beginTransaction();
downloadMessage(ServiceSynchronize.this, folder, ifolder, (IMAPMessage) e.getMessage(), id);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} catch (MessageRemovedException ex) {
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
} catch (IOException ex) {
if (ex.getCause() instanceof MessageRemovedException)
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
else
throw ex;
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
} catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
wlAccount.release();
}
}
});
});
// Idle folder
if (capIdle) {
// Idle folder
Thread idler = new Thread(new Runnable() {
@Override
public void run() {
@ -1068,13 +1068,12 @@ public class ServiceSynchronize extends LifecycleService {
}, "idler." + folder.id);
idler.start();
idlers.add(idler);
}
EntityOperation.sync(db, folder.id);
}
EntityOperation.sync(db, folder.id);
} else
folders.put(folder, null);
// Observe folder operations
for (final EntityFolder folder : db.folder().getFolders(account.id)) {
// Observe operations
Handler handler = new Handler(getMainLooper()) {
private List<Long> handling = new ArrayList<>();
private final PowerManager.WakeLock wlFolder = pm.newWakeLock(
@ -1109,55 +1108,50 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, folder.name + " process");
// Get folder
EntityFolder ofolder = null;
IMAPFolder ifolder = null;
for (EntityFolder f : folders.keySet())
if (f.id.equals(folder.id)) {
ofolder = f;
ifolder = folders.get(f);
ifolder = folders.get(f); // null when polling
break;
}
final boolean shouldClose = (ofolder == null);
final boolean shouldClose = (ifolder == null);
try {
if (ofolder == null)
ofolder = db.folder().getFolder(folder.id);
Log.i(Helper.TAG, ofolder.name + " run " + (shouldClose ? "offline" : "online"));
Log.i(Helper.TAG, folder.name + " run " + (shouldClose ? "offline" : "online"));
if (ifolder == null) {
// Prevent unnecessary folder connections
if (db.operation().getOperationCount(ofolder.id, null) == 0)
if (db.operation().getOperationCount(folder.id, null) == 0)
return;
db.folder().setFolderState(ofolder.id, "connecting");
db.folder().setFolderState(folder.id, "connecting");
ifolder = (IMAPFolder) istore.getFolder(ofolder.name);
ifolder = (IMAPFolder) istore.getFolder(folder.name);
ifolder.open(Folder.READ_WRITE);
db.folder().setFolderState(ofolder.id, "connected");
db.folder().setFolderError(ofolder.id, null);
db.folder().setFolderState(folder.id, "connected");
db.folder().setFolderError(folder.id, null);
}
processOperations(account, ofolder, isession, istore, ifolder, state);
processOperations(account, folder, isession, istore, ifolder, state);
} catch (Throwable ex) {
Log.e(Helper.TAG, ofolder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, ofolder, ex);
db.folder().setFolderError(ofolder.id, Helper.formatThrowable(ex));
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account, folder, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.error();
} finally {
if (shouldClose) {
if (ifolder != null && ifolder.isOpen()) {
db.folder().setFolderState(ofolder.id, "closing");
db.folder().setFolderState(folder.id, "closing");
try {
ifolder.close(false);
} catch (MessagingException ex) {
Log.w(Helper.TAG, ofolder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
}
}
db.folder().setFolderState(ofolder.id, null);
db.folder().setFolderState(folder.id, null);
}
}
} finally {
@ -1170,6 +1164,8 @@ public class ServiceSynchronize extends LifecycleService {
});
}
};
// Start watching for operations
handler.sendEmptyMessage(1);
handlers.add(handler);
}
@ -1197,11 +1193,12 @@ public class ServiceSynchronize extends LifecycleService {
throw new StoreClosedException(istore);
for (EntityFolder folder : folders.keySet())
if (capIdle) {
if (!folders.get(folder).isOpen())
throw new FolderClosedException(folders.get(folder));
} else
synchronizeMessages(account, folder, folders.get(folder), state);
if (folder.synchronize)
if (!folder.poll && capIdle) {
if (!folders.get(folder).isOpen())
throw new FolderClosedException(folders.get(folder));
} else
EntityOperation.sync(db, folder.id);
// Successfully connected: reset back off time
backoff = CONNECT_BACKOFF_START;
@ -1230,10 +1227,6 @@ public class ServiceSynchronize extends LifecycleService {
// Cleanup
am.cancel(pi);
unregisterReceiver(alarm);
for (Handler handler : handlers)
handler.sendEmptyMessage(0);
handlers.clear();
}
Log.i(Helper.TAG, account.name + " done state=" + state);
@ -1244,10 +1237,16 @@ public class ServiceSynchronize extends LifecycleService {
EntityLog.log(ServiceSynchronize.this, account.name + " " + Helper.formatThrowable(ex));
db.account().setAccountError(account.id, Helper.formatThrowable(ex));
} finally {
// Stop watching for operations
for (Handler handler : handlers)
handler.sendEmptyMessage(0);
handlers.clear();
EntityLog.log(this, account.name + " closing");
db.account().setAccountState(account.id, "closing");
for (EntityFolder folder : folders.keySet())
db.folder().setFolderState(folder.id, "closing");
if (folder.synchronize && !folder.poll)
db.folder().setFolderState(folder.id, "closing");
// Close store
try {
@ -1267,7 +1266,8 @@ public class ServiceSynchronize extends LifecycleService {
idlers.clear();
for (EntityFolder folder : folders.keySet())
db.folder().setFolderState(folder.id, null);
if (folder.synchronize && !folder.poll)
db.folder().setFolderState(folder.id, null);
}
if (state.running())

@ -80,6 +80,15 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbUnified" />
<CheckBox
android:id="@+id/cbPoll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_poll_folder"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbSynchronize" />
<!-- after -->
<TextView
@ -90,7 +99,7 @@
android:text="@string/title_sync_days"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbSynchronize" />
app:layout_constraintTop_toBottomOf="@id/cbPoll" />
<EditText
android:id="@+id/etSyncDays"

@ -180,6 +180,7 @@
<string name="title_hide_folders">Hide hidden folders</string>
<string name="title_show_folders">Show hidden folders</string>
<string name="title_synchronize_folder">Synchronize (receive messages)</string>
<string name="title_poll_folder">Poll instead of synchronize</string>
<string name="title_unified_folder">Show in unified inbox</string>
<string name="title_sync_days">Synchronize messages (days)</string>
<string name="title_keep_days">Keep messages (days)</string>

Loading…
Cancel
Save