mirror of
https://github.com/M66B/FairEmail.git
synced 2025-12-12 18:00:28 +01:00
First public release
This commit is contained in:
35
app/src/main/java/eu/faircode/email/ActivityBase.java
Normal file
35
app/src/main/java/eu/faircode/email/ActivityBase.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
abstract class ActivityBase extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String theme = prefs.getString("theme", "light");
|
||||
setTheme("dark".equals(theme) ? R.style.AppThemeDark : R.style.AppThemeLight);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
}
|
||||
57
app/src/main/java/eu/faircode/email/ActivityCompose.java
Normal file
57
app/src/main/java/eu/faircode/email/ActivityCompose.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
|
||||
public class ActivityCompose extends ActivityBase implements FragmentManager.OnBackStackChangedListener {
|
||||
static final int LOADER_COMPOSE_GET = 1;
|
||||
static final int LOADER_COMPOSE_PUT = 2;
|
||||
static final int LOADER_COMPOSE_DELETE = 3;
|
||||
|
||||
static final int REQUEST_CONTACT = 1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_compose);
|
||||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
|
||||
if (getSupportFragmentManager().getFragments().size() == 0) {
|
||||
FragmentCompose fragment = new FragmentCompose();
|
||||
Bundle args = getIntent().getExtras();
|
||||
if (args == null)
|
||||
args = new Bundle();
|
||||
fragment.setArguments(args);
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("compose");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
|
||||
finish();
|
||||
}
|
||||
}
|
||||
46
app/src/main/java/eu/faircode/email/ActivitySetup.java
Normal file
46
app/src/main/java/eu/faircode/email/ActivitySetup.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
|
||||
public class ActivitySetup extends ActivityBase implements FragmentManager.OnBackStackChangedListener {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_setup);
|
||||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
|
||||
if (getSupportFragmentManager().getFragments().size() == 0) {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentSetup()).addToBackStack("setup");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
|
||||
finish();
|
||||
}
|
||||
}
|
||||
388
app/src/main/java/eu/faircode/email/ActivityView.java
Normal file
388
app/src/main/java/eu/faircode/email/ActivityView.java
Normal file
@@ -0,0 +1,388 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ActivityView extends ActivityBase implements FragmentManager.OnBackStackChangedListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private DrawerLayout drawerLayout;
|
||||
private ListView drawerList;
|
||||
private ActionBarDrawerToggle drawerToggle;
|
||||
|
||||
static final int LOADER_ACCOUNT_PUT = 1;
|
||||
static final int LOADER_IDENTITY_PUT = 2;
|
||||
static final int LOADER_FOLDER_PUT = 3;
|
||||
|
||||
static final int REQUEST_VIEW = 1;
|
||||
|
||||
static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES";
|
||||
static final String ACTION_VIEW_MESSAGE = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGE";
|
||||
static final String ACTION_EDIT_FOLDER = BuildConfig.APPLICATION_ID + ".EDIT_FOLDER";
|
||||
static final String ACTION_EDIT_ACCOUNT = BuildConfig.APPLICATION_ID + ".EDIT_ACCOUNT";
|
||||
static final String ACTION_EDIT_IDENTITY = BuildConfig.APPLICATION_ID + ".EDIT_IDENTITY";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_view);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
|
||||
drawerLayout = findViewById(R.id.drawer_layout);
|
||||
drawerLayout.setScrimColor(Helper.resolveColor(this, R.attr.colorDrawerScrim));
|
||||
|
||||
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.app_name, R.string.app_name) {
|
||||
public void onDrawerClosed(View view) {
|
||||
super.onDrawerClosed(view);
|
||||
getSupportActionBar().setTitle(getString(R.string.app_name));
|
||||
}
|
||||
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
super.onDrawerOpened(drawerView);
|
||||
getSupportActionBar().setTitle(getString(R.string.app_name));
|
||||
}
|
||||
};
|
||||
drawerLayout.addDrawerListener(drawerToggle);
|
||||
|
||||
drawerList = findViewById(R.id.drawer_list);
|
||||
drawerList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
DrawerItem item = (DrawerItem) parent.getAdapter().getItem(position);
|
||||
switch (item.getId()) {
|
||||
case R.string.menu_unified:
|
||||
onMenuUnified();
|
||||
break;
|
||||
case R.string.menu_folders:
|
||||
onMenuFolders();
|
||||
break;
|
||||
case R.string.menu_accounts:
|
||||
onMenuAccounts();
|
||||
break;
|
||||
case R.string.menu_identities:
|
||||
onMenuIdentities();
|
||||
break;
|
||||
case R.string.menu_theme:
|
||||
onMenuTheme();
|
||||
break;
|
||||
case R.string.menu_setup:
|
||||
onMenuSetup();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!item.isCheckable())
|
||||
drawerLayout.closeDrawer(drawerList);
|
||||
}
|
||||
});
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
|
||||
updateDrawer();
|
||||
|
||||
if (getSupportFragmentManager().getFragments().size() == 0)
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
drawerToggle.syncState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
|
||||
IntentFilter iff = new IntentFilter();
|
||||
iff.addAction(ACTION_VIEW_MESSAGES);
|
||||
iff.addAction(ACTION_VIEW_MESSAGE);
|
||||
iff.addAction(ACTION_EDIT_FOLDER);
|
||||
iff.addAction(ACTION_EDIT_ACCOUNT);
|
||||
iff.addAction(ACTION_EDIT_IDENTITY);
|
||||
lbm.registerReceiver(receiver, iff);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
|
||||
lbm.unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
drawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (drawerLayout.isDrawerOpen(drawerList))
|
||||
drawerLayout.closeDrawer(drawerList);
|
||||
else
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
if ("eula".equals(key))
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (drawerToggle.onOptionsItemSelected(item))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (prefs.getBoolean("eula", false)) {
|
||||
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
|
||||
|
||||
FragmentMessages fragment = new FragmentMessages();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("folder", -1);
|
||||
fragment.setArguments(args);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("unified");
|
||||
fragmentTransaction.commit();
|
||||
|
||||
Fragment eula = getSupportFragmentManager().findFragmentByTag("eula");
|
||||
if (eula != null)
|
||||
getSupportFragmentManager().beginTransaction().remove(eula).commit();
|
||||
|
||||
DB.getInstance(this).account().liveAccounts(true).observe(this, new Observer<List<EntityAccount>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityAccount> accounts) {
|
||||
if (accounts.size() == 0)
|
||||
startActivity(new Intent(ActivityView.this, ActivitySetup.class));
|
||||
else
|
||||
ServiceSynchronize.start(ActivityView.this);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentEula(), "eula");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDrawer() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
ArrayAdapterDrawer drawerArray = new ArrayAdapterDrawer(this, R.layout.item_drawer);
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_unified));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_folders));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_accounts));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_identities));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_theme, "dark".equals(prefs.getString("theme", "light"))));
|
||||
drawerArray.add(new DrawerItem(ActivityView.this, R.string.menu_setup));
|
||||
drawerList.setAdapter(drawerArray);
|
||||
}
|
||||
|
||||
private void onMenuUnified() {
|
||||
FragmentMessages fragment = new FragmentMessages();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("folder", -1);
|
||||
fragment.setArguments(args);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("unified");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuFolders() {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentFolders()).addToBackStack("folders");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuAccounts() {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentAccounts()).addToBackStack("accounts");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuIdentities() {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentIdentities()).addToBackStack("identities");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onMenuTheme() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String theme = prefs.getString("theme", "light");
|
||||
theme = ("dark".equals(theme) ? "light" : "dark");
|
||||
prefs.edit().putString("theme", theme).apply();
|
||||
recreate();
|
||||
}
|
||||
|
||||
private void onMenuSetup() {
|
||||
startActivity(new Intent(ActivityView.this, ActivitySetup.class));
|
||||
}
|
||||
|
||||
private class DrawerItem {
|
||||
private int id;
|
||||
private String title;
|
||||
private boolean checkable;
|
||||
private boolean checked;
|
||||
|
||||
DrawerItem(Context context, int title) {
|
||||
this.id = title;
|
||||
this.title = context.getString(title);
|
||||
this.checkable = false;
|
||||
this.checked = false;
|
||||
}
|
||||
|
||||
DrawerItem(Context context, int title, boolean checked) {
|
||||
this.id = title;
|
||||
this.title = context.getString(title);
|
||||
this.checkable = true;
|
||||
this.checked = checked;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
public boolean isCheckable() {
|
||||
return this.checkable;
|
||||
}
|
||||
|
||||
public boolean isChecked() {
|
||||
return this.checked;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ArrayAdapterDrawer extends ArrayAdapter<DrawerItem> {
|
||||
private int resource;
|
||||
|
||||
ArrayAdapterDrawer(@NonNull Context context, int resource) {
|
||||
super(context, resource);
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
|
||||
View row;
|
||||
if (null == convertView)
|
||||
row = LayoutInflater.from(getContext()).inflate(this.resource, null);
|
||||
else
|
||||
row = convertView;
|
||||
|
||||
DrawerItem item = getItem(position);
|
||||
|
||||
TextView tv = row.findViewById(R.id.tvItem);
|
||||
CheckBox cb = row.findViewById(R.id.cbItem);
|
||||
tv.setText(item.getTitle());
|
||||
cb.setVisibility(item.isCheckable() ? View.VISIBLE : View.GONE);
|
||||
cb.setChecked(item.isChecked());
|
||||
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (ACTION_VIEW_MESSAGES.equals(intent.getAction())) {
|
||||
FragmentMessages fragment = new FragmentMessages();
|
||||
fragment.setArguments(intent.getExtras());
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("messages");
|
||||
fragmentTransaction.commit();
|
||||
} else if (ACTION_VIEW_MESSAGE.equals(intent.getAction())) {
|
||||
FragmentMessage fragment = new FragmentMessage();
|
||||
fragment.setArguments(intent.getExtras());
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("message");
|
||||
fragmentTransaction.commit();
|
||||
} else if (ACTION_EDIT_FOLDER.equals(intent.getAction())) {
|
||||
FragmentFolder fragment = new FragmentFolder();
|
||||
fragment.setArguments(intent.getExtras());
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("folder");
|
||||
fragmentTransaction.commit();
|
||||
} else if (ACTION_EDIT_ACCOUNT.equals(intent.getAction())) {
|
||||
FragmentAccount fragment = new FragmentAccount();
|
||||
fragment.setArguments(intent.getExtras());
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("account");
|
||||
fragmentTransaction.commit();
|
||||
} else if (ACTION_EDIT_IDENTITY.equals(intent.getAction())) {
|
||||
FragmentIdentity fragment = new FragmentIdentity();
|
||||
fragment.setArguments(intent.getExtras());
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("identity");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
201
app/src/main/java/eu/faircode/email/AdapterAccount.java
Normal file
201
app/src/main/java/eu/faircode/email/AdapterAccount.java
Normal file
@@ -0,0 +1,201 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.util.DiffUtil;
|
||||
import android.support.v7.util.ListUpdateCallback;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class AdapterAccount extends RecyclerView.Adapter<AdapterAccount.ViewHolder> {
|
||||
private Context context;
|
||||
|
||||
private List<EntityAccount> all = new ArrayList<>();
|
||||
private List<EntityAccount> filtered = new ArrayList<>();
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
View itemView;
|
||||
ImageView ivPrimary;
|
||||
TextView tvName;
|
||||
ImageView ivSync;
|
||||
TextView tvHost;
|
||||
TextView tvUser;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView;
|
||||
ivPrimary = itemView.findViewById(R.id.ivPrimary);
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
ivSync = itemView.findViewById(R.id.ivSync);
|
||||
tvHost = itemView.findViewById(R.id.tvHost);
|
||||
tvUser = itemView.findViewById(R.id.tvUser);
|
||||
}
|
||||
|
||||
private void wire() {
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
private void unwire() {
|
||||
itemView.setOnClickListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
EntityAccount account = filtered.get(getLayoutPosition());
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_EDIT_ACCOUNT)
|
||||
.putExtra("id", account.id));
|
||||
}
|
||||
}
|
||||
|
||||
AdapterAccount(Context context) {
|
||||
this.context = context;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void set(List<EntityAccount> accounts) {
|
||||
Log.i(Helper.TAG, "Set accounts=" + accounts.size());
|
||||
|
||||
final Collator collator = Collator.getInstance(Locale.getDefault());
|
||||
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
|
||||
|
||||
Collections.sort(accounts, new Comparator<EntityAccount>() {
|
||||
@Override
|
||||
public int compare(EntityAccount a1, EntityAccount a2) {
|
||||
return collator.compare(a1.host, a2.host);
|
||||
}
|
||||
});
|
||||
|
||||
all.clear();
|
||||
all.addAll(accounts);
|
||||
|
||||
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all));
|
||||
|
||||
filtered.clear();
|
||||
filtered.addAll(all);
|
||||
|
||||
diff.dispatchUpdatesTo(new ListUpdateCallback() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
Log.i(Helper.TAG, "Inserted @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
Log.i(Helper.TAG, "Removed @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count, Object payload) {
|
||||
Log.i(Helper.TAG, "Changed @" + position + " #" + count);
|
||||
}
|
||||
});
|
||||
diff.dispatchUpdatesTo(AdapterAccount.this);
|
||||
}
|
||||
|
||||
private class MessageDiffCallback extends DiffUtil.Callback {
|
||||
private List<EntityAccount> prev;
|
||||
private List<EntityAccount> next;
|
||||
|
||||
MessageDiffCallback(List<EntityAccount> prev, List<EntityAccount> next) {
|
||||
this.prev = prev;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return prev.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return next.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityAccount f1 = prev.get(oldItemPosition);
|
||||
EntityAccount f2 = next.get(newItemPosition);
|
||||
return f1.id.equals(f2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityAccount f1 = prev.get(oldItemPosition);
|
||||
EntityAccount f2 = next.get(newItemPosition);
|
||||
return f1.equals(f2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return filtered.get(position).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return filtered.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_account, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.unwire();
|
||||
|
||||
EntityAccount account = filtered.get(position);
|
||||
|
||||
holder.ivPrimary.setVisibility(account.primary ? View.VISIBLE : View.GONE);
|
||||
holder.tvName.setText(account.name);
|
||||
holder.ivSync.setVisibility(account.synchronize ? View.VISIBLE : View.INVISIBLE);
|
||||
holder.tvHost.setText(String.format("%s:%d", account.host, account.port));
|
||||
holder.tvUser.setText(account.user);
|
||||
|
||||
holder.wire();
|
||||
}
|
||||
}
|
||||
238
app/src/main/java/eu/faircode/email/AdapterFolder.java
Normal file
238
app/src/main/java/eu/faircode/email/AdapterFolder.java
Normal file
@@ -0,0 +1,238 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.util.DiffUtil;
|
||||
import android.support.v7.util.ListUpdateCallback;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder> {
|
||||
private Context context;
|
||||
|
||||
private List<TupleFolderEx> all = new ArrayList<>();
|
||||
private List<TupleFolderEx> filtered = new ArrayList<>();
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder
|
||||
implements View.OnClickListener, View.OnLongClickListener {
|
||||
View itemView;
|
||||
TextView tvName;
|
||||
TextView tvAfter;
|
||||
ImageView ivSync;
|
||||
TextView tvCount;
|
||||
TextView tvType;
|
||||
TextView tvAccount;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView;
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
tvAfter = itemView.findViewById(R.id.tvAfter);
|
||||
ivSync = itemView.findViewById(R.id.ivSync);
|
||||
tvCount = itemView.findViewById(R.id.tvCount);
|
||||
tvType = itemView.findViewById(R.id.tvType);
|
||||
tvAccount = itemView.findViewById(R.id.tvAccount);
|
||||
}
|
||||
|
||||
private void wire() {
|
||||
itemView.setOnClickListener(this);
|
||||
itemView.setOnLongClickListener(this);
|
||||
}
|
||||
|
||||
private void unwire() {
|
||||
itemView.setOnClickListener(null);
|
||||
itemView.setOnLongClickListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
TupleFolderEx folder = filtered.get(getLayoutPosition());
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_VIEW_MESSAGES)
|
||||
.putExtra("folder", folder.id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
TupleFolderEx folder = filtered.get(getLayoutPosition());
|
||||
|
||||
if (!EntityFolder.TYPE_OUTBOX.equals(folder.type)) {
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_EDIT_FOLDER)
|
||||
.putExtra("id", folder.id));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AdapterFolder(Context context) {
|
||||
this.context = context;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void set(List<TupleFolderEx> folders) {
|
||||
Log.i(Helper.TAG, "Set folders=" + folders.size());
|
||||
|
||||
final Collator collator = Collator.getInstance(Locale.getDefault());
|
||||
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
|
||||
|
||||
Collections.sort(folders, new Comparator<TupleFolderEx>() {
|
||||
@Override
|
||||
public int compare(TupleFolderEx f1, TupleFolderEx f2) {
|
||||
if (f1.accountName == null)
|
||||
if (f2.accountName == null)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
else if (f2.accountName == null)
|
||||
return 1;
|
||||
else
|
||||
return collator.compare(f1.accountName, f2.accountName);
|
||||
}
|
||||
});
|
||||
|
||||
all.clear();
|
||||
all.addAll(folders);
|
||||
|
||||
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all));
|
||||
|
||||
filtered.clear();
|
||||
filtered.addAll(all);
|
||||
|
||||
diff.dispatchUpdatesTo(new ListUpdateCallback() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
Log.i(Helper.TAG, "Inserted @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
Log.i(Helper.TAG, "Removed @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count, Object payload) {
|
||||
Log.i(Helper.TAG, "Changed @" + position + " #" + count);
|
||||
}
|
||||
});
|
||||
diff.dispatchUpdatesTo(AdapterFolder.this);
|
||||
}
|
||||
|
||||
private class MessageDiffCallback extends DiffUtil.Callback {
|
||||
private List<TupleFolderEx> prev;
|
||||
private List<TupleFolderEx> next;
|
||||
|
||||
MessageDiffCallback(List<TupleFolderEx> prev, List<TupleFolderEx> next) {
|
||||
this.prev = prev;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return prev.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return next.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
TupleFolderEx f1 = prev.get(oldItemPosition);
|
||||
TupleFolderEx f2 = next.get(newItemPosition);
|
||||
return f1.id.equals(f2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
TupleFolderEx f1 = prev.get(oldItemPosition);
|
||||
TupleFolderEx f2 = next.get(newItemPosition);
|
||||
return f1.equals(f2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return filtered.get(position).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return filtered.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_folder, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.unwire();
|
||||
|
||||
TupleFolderEx folder = filtered.get(position);
|
||||
|
||||
String name = Helper.localizeFolderName(context, folder.name);
|
||||
if (folder.unseen > 0)
|
||||
holder.tvName.setText(context.getString(R.string.title_folder_unseen, name, folder.unseen));
|
||||
else
|
||||
holder.tvName.setText(name);
|
||||
|
||||
holder.tvName.setTypeface(null, folder.unseen > 0 ? Typeface.BOLD : Typeface.NORMAL);
|
||||
holder.tvAfter.setText(Integer.toString(folder.after));
|
||||
holder.tvAfter.setVisibility(folder.synchronize ? View.VISIBLE : View.INVISIBLE);
|
||||
holder.ivSync.setVisibility(folder.synchronize ? View.VISIBLE : View.INVISIBLE);
|
||||
holder.tvCount.setText(Integer.toString(folder.messages));
|
||||
holder.tvType.setText(folder.type);
|
||||
holder.tvAccount.setText(folder.accountName);
|
||||
|
||||
holder.wire();
|
||||
}
|
||||
}
|
||||
201
app/src/main/java/eu/faircode/email/AdapterIdentity.java
Normal file
201
app/src/main/java/eu/faircode/email/AdapterIdentity.java
Normal file
@@ -0,0 +1,201 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.util.DiffUtil;
|
||||
import android.support.v7.util.ListUpdateCallback;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHolder> {
|
||||
private Context context;
|
||||
|
||||
private List<EntityIdentity> all = new ArrayList<>();
|
||||
private List<EntityIdentity> filtered = new ArrayList<>();
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
View itemView;
|
||||
ImageView ivPrimary;
|
||||
TextView tvName;
|
||||
ImageView ivSync;
|
||||
TextView tvHost;
|
||||
TextView tvUser;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView;
|
||||
ivPrimary = itemView.findViewById(R.id.ivPrimary);
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
ivSync = itemView.findViewById(R.id.ivSync);
|
||||
tvHost = itemView.findViewById(R.id.tvHost);
|
||||
tvUser = itemView.findViewById(R.id.tvUser);
|
||||
}
|
||||
|
||||
private void wire() {
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
private void unwire() {
|
||||
itemView.setOnClickListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
EntityIdentity identity = filtered.get(getLayoutPosition());
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_EDIT_IDENTITY)
|
||||
.putExtra("id", identity.id));
|
||||
}
|
||||
}
|
||||
|
||||
AdapterIdentity(Context context) {
|
||||
this.context = context;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void set(List<EntityIdentity> identities) {
|
||||
Log.i(Helper.TAG, "Set identities=" + identities.size());
|
||||
|
||||
final Collator collator = Collator.getInstance(Locale.getDefault());
|
||||
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
|
||||
|
||||
Collections.sort(identities, new Comparator<EntityIdentity>() {
|
||||
@Override
|
||||
public int compare(EntityIdentity i1, EntityIdentity i2) {
|
||||
return collator.compare(i1.host, i2.host);
|
||||
}
|
||||
});
|
||||
|
||||
all.clear();
|
||||
all.addAll(identities);
|
||||
|
||||
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all));
|
||||
|
||||
filtered.clear();
|
||||
filtered.addAll(all);
|
||||
|
||||
diff.dispatchUpdatesTo(new ListUpdateCallback() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
Log.i(Helper.TAG, "Inserted @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
Log.i(Helper.TAG, "Removed @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count, Object payload) {
|
||||
Log.i(Helper.TAG, "Changed @" + position + " #" + count);
|
||||
}
|
||||
});
|
||||
diff.dispatchUpdatesTo(AdapterIdentity.this);
|
||||
}
|
||||
|
||||
private class MessageDiffCallback extends DiffUtil.Callback {
|
||||
private List<EntityIdentity> prev;
|
||||
private List<EntityIdentity> next;
|
||||
|
||||
MessageDiffCallback(List<EntityIdentity> prev, List<EntityIdentity> next) {
|
||||
this.prev = prev;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return prev.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return next.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityIdentity i1 = prev.get(oldItemPosition);
|
||||
EntityIdentity i2 = next.get(newItemPosition);
|
||||
return i1.id.equals(i2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
EntityIdentity i1 = prev.get(oldItemPosition);
|
||||
EntityIdentity i2 = next.get(newItemPosition);
|
||||
return i1.equals(i2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return filtered.get(position).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return filtered.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_identity, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.unwire();
|
||||
|
||||
EntityIdentity identity = filtered.get(position);
|
||||
|
||||
holder.ivPrimary.setVisibility(identity.primary ? View.VISIBLE : View.GONE);
|
||||
holder.tvName.setText(identity.name);
|
||||
holder.ivSync.setVisibility(identity.synchronize ? View.VISIBLE : View.INVISIBLE);
|
||||
holder.tvHost.setText(String.format("%s:%d", identity.host, identity.port));
|
||||
holder.tvUser.setText(identity.user);
|
||||
|
||||
holder.wire();
|
||||
}
|
||||
}
|
||||
242
app/src/main/java/eu/faircode/email/AdapterMessage.java
Normal file
242
app/src/main/java/eu/faircode/email/AdapterMessage.java
Normal file
@@ -0,0 +1,242 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.util.DiffUtil;
|
||||
import android.support.v7.util.ListUpdateCallback;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHolder> {
|
||||
private Context context;
|
||||
|
||||
private List<TupleMessageEx> all = new ArrayList<>();
|
||||
private List<TupleMessageEx> filtered = new ArrayList<>();
|
||||
|
||||
private ExecutorService executor = Executors.newCachedThreadPool();
|
||||
|
||||
enum ViewType {FOLDER, THREAD}
|
||||
|
||||
private ViewType viewType;
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder
|
||||
implements View.OnClickListener {
|
||||
View itemView;
|
||||
TextView tvAddress;
|
||||
TextView tvTime;
|
||||
TextView tvSubject;
|
||||
TextView tvCount;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.itemView = itemView;
|
||||
tvAddress = itemView.findViewById(R.id.tvAddress);
|
||||
tvTime = itemView.findViewById(R.id.tvTime);
|
||||
tvSubject = itemView.findViewById(R.id.tvSubject);
|
||||
tvCount = itemView.findViewById(R.id.tvCount);
|
||||
}
|
||||
|
||||
private void wire() {
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
private void unwire() {
|
||||
itemView.setOnClickListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
final TupleMessageEx message = filtered.get(getLayoutPosition());
|
||||
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (EntityFolder.TYPE_DRAFTS.equals(message.folderType))
|
||||
context.startActivity(
|
||||
new Intent(context, ActivityCompose.class)
|
||||
.putExtra("id", message.id));
|
||||
else {
|
||||
if (!message.seen && !message.ui_seen) {
|
||||
message.ui_seen = !message.ui_seen;
|
||||
DB.getInstance(context).message().updateMessage(message);
|
||||
EntityOperation.queue(context, message, EntityOperation.SEEN, message.ui_seen);
|
||||
}
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_VIEW_MESSAGE)
|
||||
.putExtra("folder", message.folder)
|
||||
.putExtra("id", message.id));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
AdapterMessage(Context context, ViewType viewType) {
|
||||
this.context = context;
|
||||
this.viewType = viewType;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void set(List<TupleMessageEx> messages) {
|
||||
Log.i(Helper.TAG, "Set messages=" + messages.size());
|
||||
|
||||
Collections.sort(messages, new Comparator<TupleMessageEx>() {
|
||||
@Override
|
||||
public int compare(TupleMessageEx m1, TupleMessageEx m2) {
|
||||
if (EntityFolder.isOutgoing(m1.folderType))
|
||||
return -Long.compare(m1.received, m2.received);
|
||||
else
|
||||
return -Long.compare(m1.sent, m2.sent);
|
||||
}
|
||||
});
|
||||
|
||||
all.clear();
|
||||
all.addAll(messages);
|
||||
|
||||
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new MessageDiffCallback(filtered, all));
|
||||
|
||||
filtered.clear();
|
||||
filtered.addAll(all);
|
||||
|
||||
diff.dispatchUpdatesTo(new ListUpdateCallback() {
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
Log.i(Helper.TAG, "Inserted @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
Log.i(Helper.TAG, "Removed @" + position + " #" + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
Log.i(Helper.TAG, "Moved " + fromPosition + ">" + toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count, Object payload) {
|
||||
Log.i(Helper.TAG, "Changed @" + position + " #" + count);
|
||||
}
|
||||
});
|
||||
diff.dispatchUpdatesTo(AdapterMessage.this);
|
||||
}
|
||||
|
||||
private class MessageDiffCallback extends DiffUtil.Callback {
|
||||
private List<TupleMessageEx> prev;
|
||||
private List<TupleMessageEx> next;
|
||||
|
||||
MessageDiffCallback(List<TupleMessageEx> prev, List<TupleMessageEx> next) {
|
||||
this.prev = prev;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOldListSize() {
|
||||
return prev.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize() {
|
||||
return next.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
TupleMessageEx m1 = prev.get(oldItemPosition);
|
||||
TupleMessageEx m2 = next.get(newItemPosition);
|
||||
return m1.id.equals(m2.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
TupleMessageEx m1 = prev.get(oldItemPosition);
|
||||
TupleMessageEx m2 = next.get(newItemPosition);
|
||||
return m1.equals(m2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return filtered.get(position).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return filtered.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_message, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.unwire();
|
||||
|
||||
TupleMessageEx message = filtered.get(position);
|
||||
|
||||
if (EntityFolder.isOutgoing(message.folderType)) {
|
||||
holder.tvAddress.setText(message.to == null ? null : MessageHelper.getFormattedAddresses(message.to));
|
||||
holder.tvTime.setText(DateUtils.getRelativeTimeSpanString(context, message.received));
|
||||
} else {
|
||||
holder.tvAddress.setText(message.from == null ? null : MessageHelper.getFormattedAddresses(message.from));
|
||||
holder.tvTime.setText(message.sent == null ? null : DateUtils.getRelativeTimeSpanString(context, message.sent));
|
||||
}
|
||||
|
||||
holder.tvSubject.setText(message.subject);
|
||||
if (viewType == ViewType.FOLDER) {
|
||||
holder.tvCount.setText(Integer.toString(message.count));
|
||||
holder.tvCount.setVisibility(message.count > 1 ? View.VISIBLE : View.GONE);
|
||||
} else
|
||||
holder.tvCount.setText(Helper.localizeFolderName(context, message.folderName));
|
||||
|
||||
boolean unseen = (message.thread == null ? !message.seen : message.unseen > 0);
|
||||
int visibility = (unseen ? Typeface.BOLD : Typeface.NORMAL);
|
||||
holder.tvAddress.setTypeface(null, visibility);
|
||||
holder.tvTime.setTypeface(null, visibility);
|
||||
holder.tvSubject.setTypeface(null, visibility);
|
||||
holder.tvCount.setTypeface(null, visibility);
|
||||
|
||||
holder.wire();
|
||||
}
|
||||
}
|
||||
25
app/src/main/java/eu/faircode/email/ApplicationEx.java
Normal file
25
app/src/main/java/eu/faircode/email/ApplicationEx.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
public class ApplicationEx extends Application {
|
||||
}
|
||||
94
app/src/main/java/eu/faircode/email/DB.java
Normal file
94
app/src/main/java/eu/faircode/email/DB.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.arch.persistence.room.Database;
|
||||
import android.arch.persistence.room.Room;
|
||||
import android.arch.persistence.room.RoomDatabase;
|
||||
import android.arch.persistence.room.TypeConverter;
|
||||
import android.arch.persistence.room.TypeConverters;
|
||||
import android.arch.persistence.room.migration.Migration;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
EntityFolder.class,
|
||||
EntityMessage.class,
|
||||
EntityOperation.class
|
||||
},
|
||||
version = 1,
|
||||
exportSchema = true
|
||||
)
|
||||
|
||||
@TypeConverters({DB.Converters.class})
|
||||
public abstract class DB extends RoomDatabase {
|
||||
public abstract DaoIdentity identity();
|
||||
|
||||
public abstract DaoAccount account();
|
||||
|
||||
public abstract DaoFolder folder();
|
||||
|
||||
public abstract DaoMessage message();
|
||||
|
||||
public abstract DaoOperation operation();
|
||||
|
||||
private static DB sInstance;
|
||||
|
||||
private static final String DB_NAME = "email.db";
|
||||
|
||||
public static synchronized DB getInstance(Context context) {
|
||||
if (sInstance == null)
|
||||
sInstance = migrate(Room.databaseBuilder(context.getApplicationContext(), DB.class, DB_NAME));
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private static DB migrate(RoomDatabase.Builder<DB> builder) {
|
||||
return builder
|
||||
//.addMigrations(MIGRATION_1_2)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE message ADD COLUMN error TEXT");
|
||||
}
|
||||
};
|
||||
|
||||
public static class Converters {
|
||||
@TypeConverter
|
||||
public static byte[] fromString(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String fromBytes(byte[] value) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
app/src/main/java/eu/faircode/email/DaoAccount.java
Normal file
65
app/src/main/java/eu/faircode/email/DaoAccount.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DaoAccount {
|
||||
@Query("SELECT * FROM account WHERE synchronize = :synchronize")
|
||||
List<EntityAccount> getAccounts(boolean synchronize);
|
||||
|
||||
@Query("SELECT * FROM account")
|
||||
LiveData<List<EntityAccount>> liveAccounts();
|
||||
|
||||
@Query("SELECT * FROM account WHERE synchronize = :synchronize")
|
||||
LiveData<List<EntityAccount>> liveAccounts(boolean synchronize);
|
||||
|
||||
@Query("SELECT * FROM account WHERE id = :id")
|
||||
EntityAccount getAccount(long id);
|
||||
|
||||
@Query("SELECT * FROM account WHERE id = :id")
|
||||
LiveData<EntityAccount> liveAccount(long id);
|
||||
|
||||
@Query("SELECT * FROM account ORDER BY id LIMIT 1")
|
||||
LiveData<EntityAccount> liveFirstAccount();
|
||||
|
||||
@Query("SELECT" +
|
||||
" (SELECT COUNT(*) FROM account WHERE synchronize) AS accounts," +
|
||||
" (SELECT COUNT(*) FROM operation JOIN message ON message.id = operation.message JOIN account ON account.id = message.account WHERE synchronize) AS operations")
|
||||
LiveData<TupleAccountStats> liveStats();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
long insertAccount(EntityAccount account);
|
||||
|
||||
@Update
|
||||
void updateAccount(EntityAccount account);
|
||||
|
||||
@Query("UPDATE account SET `primary` = 0")
|
||||
void resetPrimary();
|
||||
}
|
||||
|
||||
90
app/src/main/java/eu/faircode/email/DaoFolder.java
Normal file
90
app/src/main/java/eu/faircode/email/DaoFolder.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DaoFolder {
|
||||
@Query("SELECT * FROM folder WHERE account = :account AND synchronize = :synchronize")
|
||||
List<EntityFolder> getFolders(long account, boolean synchronize);
|
||||
|
||||
@Query("SELECT * FROM folder WHERE account = :account AND type = '" + EntityFolder.TYPE_USER + "'")
|
||||
List<EntityFolder> getUserFolders(long account);
|
||||
|
||||
@Query("SELECT folder.*, account.name AS accountName" +
|
||||
", COUNT(message.id) AS messages" +
|
||||
", 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" +
|
||||
" GROUP BY folder.id")
|
||||
LiveData<List<TupleFolderEx>> liveFolders();
|
||||
|
||||
@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.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")
|
||||
EntityFolder getFolder(Long id);
|
||||
|
||||
@Query("SELECT * FROM folder WHERE account = :account AND name = :name")
|
||||
EntityFolder getFolder(Long account, String name);
|
||||
|
||||
@Query("SELECT folder.* FROM folder" +
|
||||
" JOIN account ON account.id = folder.account" +
|
||||
" WHERE account.`primary` AND type = '" + EntityFolder.TYPE_DRAFTS + "' ")
|
||||
EntityFolder getPrimaryDraftFolder();
|
||||
|
||||
@Query("SELECT folder.* FROM folder" +
|
||||
" WHERE account = :account AND type = '" + EntityFolder.TYPE_ARCHIVE + "' ")
|
||||
EntityFolder getArchiveFolder(long account);
|
||||
|
||||
@Query("SELECT folder.* FROM folder" +
|
||||
" WHERE account = :account AND type = '" + EntityFolder.TYPE_JUNK + "' ")
|
||||
EntityFolder getSpamFolder(long account);
|
||||
|
||||
@Query("SELECT * FROM folder WHERE type = '" + EntityFolder.TYPE_OUTBOX + "'")
|
||||
EntityFolder getOutbox();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
long insertFolder(EntityFolder folder);
|
||||
|
||||
@Update
|
||||
void updateFolder(EntityFolder folder);
|
||||
|
||||
@Query("DELETE FROM folder WHERE account= :account AND name = :name")
|
||||
int deleteFolder(Long account, String name);
|
||||
}
|
||||
56
app/src/main/java/eu/faircode/email/DaoIdentity.java
Normal file
56
app/src/main/java/eu/faircode/email/DaoIdentity.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DaoIdentity {
|
||||
@Query("SELECT * FROM identity")
|
||||
LiveData<List<EntityIdentity>> liveIdentities();
|
||||
|
||||
@Query("SELECT * FROM identity WHERE synchronize = :synchronize")
|
||||
LiveData<List<EntityIdentity>> liveIdentities(boolean synchronize);
|
||||
|
||||
@Query("SELECT * FROM identity WHERE id = :id")
|
||||
EntityIdentity getIdentity(long id);
|
||||
|
||||
@Query("SELECT * FROM identity WHERE id = :id")
|
||||
LiveData<EntityIdentity> liveIdentity(long id);
|
||||
|
||||
@Query("SELECT * FROM identity ORDER BY id LIMIT 1")
|
||||
LiveData<EntityIdentity> liveFirstIdentity();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
long insertIdentity(EntityIdentity identity);
|
||||
|
||||
@Update
|
||||
void updateIdentity(EntityIdentity identity);
|
||||
|
||||
@Query("UPDATE identity SET `primary` = 0")
|
||||
void resetPrimary();
|
||||
}
|
||||
96
app/src/main/java/eu/faircode/email/DaoMessage.java
Normal file
96
app/src/main/java/eu/faircode/email/DaoMessage.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
import android.arch.persistence.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DaoMessage {
|
||||
@Query("SELECT message.*, folder.name as folderName, folder.type as folderType" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread) AS count" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_seen) AS unseen" +
|
||||
" FROM folder" +
|
||||
" JOIN message ON folder = folder.id" +
|
||||
" WHERE folder.type = '" + EntityFolder.TYPE_INBOX + "'" +
|
||||
" AND NOT ui_hide" +
|
||||
" AND received IN (SELECT MAX(m.received) FROM message m WHERE m.folder = message.folder GROUP BY m.thread)")
|
||||
LiveData<List<TupleMessageEx>> liveUnifiedInbox();
|
||||
|
||||
@Query("SELECT message.*, folder.name as folderName, folder.type as folderType" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread) AS count" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_seen) AS unseen" +
|
||||
" FROM folder" +
|
||||
" JOIN message ON folder = folder.id" +
|
||||
" WHERE folder.id = :folder" +
|
||||
" AND NOT ui_hide" +
|
||||
" AND received IN (SELECT MAX(m.received) FROM message m WHERE m.folder = message.folder GROUP BY m.thread)")
|
||||
LiveData<List<TupleMessageEx>> liveMessages(long folder);
|
||||
|
||||
@Query("SELECT message.*, folder.name as folderName, folder.type as folderType" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread) AS count" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_seen) AS unseen" +
|
||||
" FROM message" +
|
||||
" JOIN folder ON folder.id = message.folder" +
|
||||
" JOIN message m1 ON m1.id = :msgid AND m1.account = message.account AND m1.thread = message.thread" +
|
||||
" WHERE NOT message.ui_hide")
|
||||
LiveData<List<TupleMessageEx>> liveThread(long msgid);
|
||||
|
||||
@Query("SELECT * FROM message WHERE id = :id")
|
||||
EntityMessage getMessage(long id);
|
||||
|
||||
@Query("SELECT * FROM message WHERE folder = :folder AND uid = :uid")
|
||||
EntityMessage getMessage(long folder, long uid);
|
||||
|
||||
@Query("SELECT message.*, folder.name as folderName, folder.type as folderType" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread) AS count" +
|
||||
", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_seen) AS unseen" +
|
||||
" FROM message" +
|
||||
" JOIN folder ON folder.id = message.folder" +
|
||||
" WHERE message.id = :id")
|
||||
LiveData<TupleMessageEx> liveMessage(long id);
|
||||
|
||||
@Query("SELECT uid FROM message WHERE folder = :folder AND received >= :received AND NOT uid IS NULL")
|
||||
List<Long> getUids(long folder, long received);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
long insertMessage(EntityMessage message);
|
||||
|
||||
@Update
|
||||
void updateMessage(EntityMessage message);
|
||||
|
||||
@Query("DELETE FROM message WHERE id = :id")
|
||||
void deleteMessage(long id);
|
||||
|
||||
@Query("DELETE FROM message WHERE folder = :folder AND uid = :uid")
|
||||
void deleteMessage(long folder, long uid);
|
||||
|
||||
@Query("DELETE FROM message WHERE folder = :folder")
|
||||
int deleteMessages(long folder);
|
||||
|
||||
@Query("DELETE FROM message WHERE folder = :folder AND received < :received AND NOT uid IS NULL")
|
||||
int deleteMessagesBefore(long folder, long received);
|
||||
}
|
||||
45
app/src/main/java/eu/faircode/email/DaoOperation.java
Normal file
45
app/src/main/java/eu/faircode/email/DaoOperation.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Dao;
|
||||
import android.arch.persistence.room.Insert;
|
||||
import android.arch.persistence.room.OnConflictStrategy;
|
||||
import android.arch.persistence.room.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DaoOperation {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
long insertOperation(EntityOperation operation);
|
||||
|
||||
@Query("SELECT operation.*, message.uid FROM operation" +
|
||||
" JOIN message ON message.id = operation.message" +
|
||||
" WHERE folder = :folder" +
|
||||
" ORDER BY operation.id")
|
||||
List<TupleOperationEx> getOperations(long folder);
|
||||
|
||||
@Query("DELETE FROM operation WHERE id = :id")
|
||||
void deleteOperation(long id);
|
||||
|
||||
@Query("DELETE FROM operation WHERE message = :id AND name = :name")
|
||||
int deleteOperations(long id, String name);
|
||||
}
|
||||
64
app/src/main/java/eu/faircode/email/EntityAccount.java
Normal file
64
app/src/main/java/eu/faircode/email/EntityAccount.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityAccount.TABLE_NAME,
|
||||
indices = {
|
||||
}
|
||||
)
|
||||
public class EntityAccount {
|
||||
static final String TABLE_NAME = "account";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
public String name;
|
||||
@NonNull
|
||||
public String host; // IMAP
|
||||
@NonNull
|
||||
public Integer port;
|
||||
@NonNull
|
||||
public String user;
|
||||
@NonNull
|
||||
public String password;
|
||||
@NonNull
|
||||
public Boolean primary;
|
||||
@NonNull
|
||||
public Boolean synchronize;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityAccount) {
|
||||
EntityAccount other = (EntityAccount) obj;
|
||||
return (this.name == null ? other.name == null : this.name.equals(other.name) &&
|
||||
this.host.equals(other.host) &&
|
||||
this.port.equals(other.port) &&
|
||||
this.user.equals(other.user) &&
|
||||
this.password.equals(other.password) &&
|
||||
this.primary.equals(other.primary) &&
|
||||
this.synchronize.equals(other.synchronize));
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
50
app/src/main/java/eu/faircode/email/EntityAttachment.java
Normal file
50
app/src/main/java/eu/faircode/email/EntityAttachment.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityAttachment.TABLE_NAME,
|
||||
foreignKeys = {
|
||||
@ForeignKey(childColumns = "message", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE)
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"message"})
|
||||
}
|
||||
)
|
||||
public class EntityAttachment {
|
||||
static final String TABLE_NAME = "attachment";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
@NonNull
|
||||
public Long message;
|
||||
@NonNull
|
||||
public String type;
|
||||
public String name;
|
||||
public byte[] content;
|
||||
}
|
||||
100
app/src/main/java/eu/faircode/email/EntityFolder.java
Normal file
100
app/src/main/java/eu/faircode/email/EntityFolder.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityFolder.TABLE_NAME,
|
||||
foreignKeys = {
|
||||
@ForeignKey(childColumns = "account", entity = EntityAccount.class, parentColumns = "id", onDelete = CASCADE)
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"account", "name"}, unique = true),
|
||||
@Index(value = {"account"}),
|
||||
@Index(value = {"name"}),
|
||||
@Index(value = {"type"})
|
||||
}
|
||||
)
|
||||
public class EntityFolder {
|
||||
static final String TABLE_NAME = "folder";
|
||||
|
||||
static final String TYPE_INBOX = "Inbox";
|
||||
static final String TYPE_OUTBOX = "Outbox";
|
||||
static final String TYPE_ARCHIVE = "All";
|
||||
static final String TYPE_DRAFTS = "Drafts";
|
||||
static final String TYPE_TRASH = "Trash";
|
||||
static final String TYPE_JUNK = "Junk";
|
||||
static final String TYPE_SENT = "Sent";
|
||||
static final String TYPE_USER = "User";
|
||||
|
||||
static final List<String> STANDARD_FOLDER_ATTR = Arrays.asList(
|
||||
"All",
|
||||
"Drafts",
|
||||
"Trash",
|
||||
"Junk",
|
||||
"Sent"
|
||||
);
|
||||
static final List<String> STANDARD_FOLDER_TYPE = Arrays.asList(
|
||||
TYPE_ARCHIVE,
|
||||
TYPE_DRAFTS,
|
||||
TYPE_TRASH,
|
||||
TYPE_JUNK,
|
||||
TYPE_SENT
|
||||
); // Must match STANDARD_FOLDER_ATTR
|
||||
|
||||
static boolean isOutgoing(String type) {
|
||||
return (TYPE_OUTBOX.equals(type) || TYPE_DRAFTS.equals(type) || TYPE_SENT.equals(type));
|
||||
}
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
public Long account; // Outbox = null
|
||||
@NonNull
|
||||
public String name;
|
||||
@NonNull
|
||||
public String type;
|
||||
@NonNull
|
||||
public Boolean synchronize;
|
||||
@NonNull
|
||||
public Integer after; // days
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityFolder) {
|
||||
EntityFolder other = (EntityFolder) obj;
|
||||
return (this.account == null ? other.account == null : this.account.equals(other.account) &&
|
||||
this.name.equals(other.name) &&
|
||||
this.type.equals(other.type) &&
|
||||
this.synchronize.equals(other.synchronize) &&
|
||||
this.after.equals(other.after));
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
78
app/src/main/java/eu/faircode/email/EntityIdentity.java
Normal file
78
app/src/main/java/eu/faircode/email/EntityIdentity.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityIdentity.TABLE_NAME,
|
||||
indices = {
|
||||
}
|
||||
)
|
||||
public class EntityIdentity {
|
||||
static final String TABLE_NAME = "identity";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
@NonNull
|
||||
public String name;
|
||||
@NonNull
|
||||
public String email;
|
||||
public String replyto;
|
||||
@NonNull
|
||||
public String host; // SMTP
|
||||
@NonNull
|
||||
public Integer port;
|
||||
@NonNull
|
||||
public Boolean starttls;
|
||||
@NonNull
|
||||
public String user;
|
||||
@NonNull
|
||||
public String password;
|
||||
@NonNull
|
||||
public Boolean primary;
|
||||
@NonNull
|
||||
public Boolean synchronize;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityIdentity) {
|
||||
EntityIdentity other = (EntityIdentity) obj;
|
||||
return (this.name.equals(other.name) &&
|
||||
this.email.equals(other.email) &&
|
||||
this.replyto == null ? other.replyto == null : this.replyto.equals(other.replyto) &&
|
||||
this.host.equals(other.host) &&
|
||||
this.port.equals(other.port) &&
|
||||
this.starttls.equals(other.starttls) &&
|
||||
this.user.equals(other.user) &&
|
||||
this.password.equals(other.password) &&
|
||||
this.primary.equals(other.primary) &&
|
||||
this.synchronize.equals(other.synchronize));
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
111
app/src/main/java/eu/faircode/email/EntityMessage.java
Normal file
111
app/src/main/java/eu/faircode/email/EntityMessage.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
|
||||
// https://developer.android.com/training/data-storage/room/defining-data
|
||||
|
||||
@Entity(
|
||||
tableName = EntityMessage.TABLE_NAME,
|
||||
foreignKeys = {
|
||||
@ForeignKey(childColumns = "account", entity = EntityAccount.class, parentColumns = "id", onDelete = CASCADE),
|
||||
@ForeignKey(childColumns = "folder", entity = EntityFolder.class, parentColumns = "id", onDelete = CASCADE),
|
||||
@ForeignKey(childColumns = "identity", entity = EntityIdentity.class, parentColumns = "id", onDelete = CASCADE),
|
||||
@ForeignKey(childColumns = "replying", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE)
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"account"}),
|
||||
@Index(value = {"folder"}),
|
||||
@Index(value = {"identity"}),
|
||||
@Index(value = {"replying"}),
|
||||
@Index(value = {"folder", "uid"}, unique = true),
|
||||
@Index(value = {"thread"}),
|
||||
@Index(value = {"received"})
|
||||
// ui_seen? ui_seen?
|
||||
}
|
||||
)
|
||||
public class EntityMessage {
|
||||
static final String TABLE_NAME = "message";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
public Long account; // performance, compose = null
|
||||
@NonNull
|
||||
public Long folder;
|
||||
public Long identity;
|
||||
public Long replying;
|
||||
public Long uid; // compose = null
|
||||
public String msgid;
|
||||
public String references;
|
||||
public String inreplyto;
|
||||
public String thread; // compose = null
|
||||
public String from;
|
||||
public String to;
|
||||
public String cc;
|
||||
public String bcc;
|
||||
public String reply;
|
||||
public String subject;
|
||||
public String body;
|
||||
public Long sent; // compose = null
|
||||
@NonNull
|
||||
public Long received; // compose = stored
|
||||
@NonNull
|
||||
public Boolean seen;
|
||||
@NonNull
|
||||
public Boolean ui_seen;
|
||||
@NonNull
|
||||
public Boolean ui_hide;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityMessage) {
|
||||
EntityMessage other = (EntityMessage) obj;
|
||||
return (this.account == null ? other.account == null : this.account.equals(other.account) &&
|
||||
this.folder.equals(other.folder) &&
|
||||
this.identity == null ? other.identity == null : this.identity.equals(other.identity) &&
|
||||
this.replying == null ? other.replying == null : this.replying.equals(other.replying) &&
|
||||
this.uid == null ? other.uid == null : this.uid.equals(other.uid) &&
|
||||
this.msgid == null ? other.msgid == null : this.msgid.equals(other.msgid) &&
|
||||
this.references == null ? other.references == null : this.references.equals(other.references) &&
|
||||
this.inreplyto == null ? other.inreplyto == null : this.inreplyto.equals(other.inreplyto) &&
|
||||
this.thread == null ? other.thread == null : thread.equals(other.thread) &&
|
||||
this.from == null ? other.from == null : this.from.equals(other.from) &&
|
||||
this.to == null ? other.to == null : this.to.equals(other.to) &&
|
||||
this.cc == null ? other.cc == null : this.cc.equals(other.cc) &&
|
||||
this.bcc == null ? other.bcc == null : this.bcc.equals(other.bcc) &&
|
||||
this.reply == null ? other.reply == null : this.reply.equals(other.reply) &&
|
||||
this.subject == null ? other.subject == null : this.subject.equals(other.subject) &&
|
||||
this.body == null ? other.body == null : this.body.equals(other.body) &&
|
||||
this.sent == null ? other.sent == null : this.sent.equals(other.sent) &&
|
||||
this.received.equals(other.received) &&
|
||||
this.seen.equals(other.seen) &&
|
||||
this.ui_seen.equals(other.ui_seen) &&
|
||||
this.ui_hide.equals(other.ui_hide));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
95
app/src/main/java/eu/faircode/email/EntityOperation.java
Normal file
95
app/src/main/java/eu/faircode/email/EntityOperation.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.persistence.room.Entity;
|
||||
import android.arch.persistence.room.ForeignKey;
|
||||
import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import static android.arch.persistence.room.ForeignKey.CASCADE;
|
||||
|
||||
@Entity(
|
||||
tableName = EntityOperation.TABLE_NAME,
|
||||
foreignKeys = {
|
||||
@ForeignKey(childColumns = "message", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE)
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"message"})
|
||||
}
|
||||
)
|
||||
public class EntityOperation {
|
||||
static final String TABLE_NAME = "operation";
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
@NonNull
|
||||
public Long message;
|
||||
@NonNull
|
||||
public String name;
|
||||
public String args;
|
||||
|
||||
public static final String SEEN = "seen";
|
||||
public static final String ADD = "add";
|
||||
public static final String MOVE = "move";
|
||||
public static final String DELETE = "delete";
|
||||
public static final String SEND = "send";
|
||||
|
||||
static void queue(Context context, EntityMessage message, String name) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
queue(context, message, name, jsonArray);
|
||||
}
|
||||
|
||||
static void queue(Context context, EntityMessage message, String name, Object value) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
jsonArray.put(value);
|
||||
queue(context, message, name, jsonArray);
|
||||
}
|
||||
|
||||
private static void queue(Context context, EntityMessage message, String name, JSONArray jsonArray) {
|
||||
DaoOperation dao = DB.getInstance(context).operation();
|
||||
|
||||
int purged = 0;
|
||||
if (SEEN.equals(name))
|
||||
purged = dao.deleteOperations(message.id, name);
|
||||
|
||||
EntityOperation operation = new EntityOperation();
|
||||
operation.message = message.id;
|
||||
operation.name = name;
|
||||
operation.args = jsonArray.toString();
|
||||
operation.id = dao.insertOperation(operation);
|
||||
|
||||
Log.i(Helper.TAG, "Queued op=" + operation.id + "/" + name +
|
||||
" args=" + operation.args +
|
||||
" msg=" + message.folder + "/" + message.id + " uid=" + message.uid +
|
||||
" purged=" + purged);
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ServiceSynchronize.ACTION_PROCESS_OPERATIONS + message.folder));
|
||||
}
|
||||
}
|
||||
319
app/src/main/java/eu/faircode/email/FragmentAccount.java
Normal file
319
app/src/main/java/eu/faircode/email/FragmentAccount.java
Normal file
@@ -0,0 +1,319 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.sun.mail.imap.IMAPFolder;
|
||||
import com.sun.mail.imap.IMAPStore;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Session;
|
||||
|
||||
public class FragmentAccount extends Fragment {
|
||||
private List<Provider> providers;
|
||||
|
||||
private EditText etName;
|
||||
private Spinner spProfile;
|
||||
private EditText etHost;
|
||||
private EditText etPort;
|
||||
private EditText etUser;
|
||||
private EditText etPassword;
|
||||
private CheckBox cbPrimary;
|
||||
private CheckBox cbSynchronize;
|
||||
private Button btnOk;
|
||||
private ProgressBar pbCheck;
|
||||
|
||||
static final int DEFAULT_INBOX_SYNC = 30;
|
||||
static final int DEFAULT_STANDARD_SYNC = 7;
|
||||
|
||||
private static final List<String> standard_sync = Arrays.asList(
|
||||
EntityFolder.TYPE_DRAFTS,
|
||||
EntityFolder.TYPE_SENT
|
||||
);
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_account, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
final long id = args.getLong("id", -1);
|
||||
|
||||
// Get providers
|
||||
providers = Provider.loadProfiles(getContext());
|
||||
providers.add(0, new Provider(getString(R.string.title_custom)));
|
||||
|
||||
// Get controls
|
||||
spProfile = view.findViewById(R.id.spProvider);
|
||||
etName = view.findViewById(R.id.etName);
|
||||
etHost = view.findViewById(R.id.etHost);
|
||||
etPort = view.findViewById(R.id.etPort);
|
||||
etUser = view.findViewById(R.id.etUser);
|
||||
etPassword = view.findViewById(R.id.etPassword);
|
||||
cbPrimary = view.findViewById(R.id.cbPrimary);
|
||||
cbSynchronize = view.findViewById(R.id.cbSynchronize);
|
||||
btnOk = view.findViewById(R.id.btnOk);
|
||||
pbCheck = view.findViewById(R.id.pbCheck);
|
||||
|
||||
// Wire controls
|
||||
|
||||
spProfile.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
Provider provider = providers.get(position);
|
||||
if (provider.imap_port != 0) {
|
||||
etName.setText(provider.name);
|
||||
etHost.setText(provider.imap_host);
|
||||
etPort.setText(Integer.toString(provider.imap_port));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
ArrayAdapter<Provider> adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, providers);
|
||||
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
|
||||
spProfile.setAdapter(adapter);
|
||||
|
||||
pbCheck.setVisibility(View.GONE);
|
||||
|
||||
btnOk.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
btnOk.setEnabled(false);
|
||||
pbCheck.setVisibility(View.VISIBLE);
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putString("name", etName.getText().toString());
|
||||
args.putString("host", etHost.getText().toString());
|
||||
args.putString("port", etPort.getText().toString());
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", etPassword.getText().toString());
|
||||
args.putBoolean("primary", cbPrimary.isChecked());
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
|
||||
getLoaderManager().restartLoader(ActivityView.LOADER_ACCOUNT_PUT, args, putLoaderCallbacks).forceLoad();
|
||||
}
|
||||
});
|
||||
|
||||
DB.getInstance(getContext()).account().liveAccount(id).observe(this, new Observer<EntityAccount>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityAccount account) {
|
||||
etName.setText(account == null ? null : account.name);
|
||||
etHost.setText(account == null ? null : account.host);
|
||||
etPort.setText(account == null ? null : Long.toString(account.port));
|
||||
etUser.setText(account == null ? null : account.user);
|
||||
etPassword.setText(account == null ? null : account.password);
|
||||
cbPrimary.setChecked(account == null ? true : account.primary);
|
||||
cbSynchronize.setChecked(account == null ? true : account.synchronize);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_edit_account);
|
||||
}
|
||||
|
||||
private static class PutLoader extends AsyncTaskLoader<Throwable> {
|
||||
private Bundle args;
|
||||
|
||||
PutLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable loadInBackground() {
|
||||
try {
|
||||
String name = args.getString("name");
|
||||
String host = args.getString("host");
|
||||
String port = args.getString("port");
|
||||
String user = args.getString("user");
|
||||
|
||||
if (TextUtils.isEmpty(name))
|
||||
name = host + "/" + user;
|
||||
if (TextUtils.isEmpty(port))
|
||||
port = "0";
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityAccount account = db.account().getAccount(args.getLong("id"));
|
||||
boolean update = (account != null);
|
||||
if (account == null)
|
||||
account = new EntityAccount();
|
||||
account.name = name;
|
||||
account.host = host;
|
||||
account.port = Integer.parseInt(port);
|
||||
account.user = user;
|
||||
account.password = Objects.requireNonNull(args.getString("password"));
|
||||
account.primary = args.getBoolean("primary");
|
||||
account.synchronize = args.getBoolean("synchronize");
|
||||
|
||||
// Check IMAP server
|
||||
List<EntityFolder> folders = new ArrayList<>();
|
||||
if (account.synchronize) {
|
||||
Session isession = Session.getDefaultInstance(MessageHelper.getSessionProperties(), null);
|
||||
IMAPStore istore = null;
|
||||
try {
|
||||
istore = (IMAPStore) isession.getStore("imaps");
|
||||
istore.connect(account.host, account.port, account.user, account.password);
|
||||
|
||||
if (!istore.hasCapability("IDLE"))
|
||||
throw new MessagingException(getContext().getString(R.string.title_no_idle));
|
||||
|
||||
boolean drafts = false;
|
||||
for (Folder ifolder : istore.getDefaultFolder().list("*")) {
|
||||
String[] attrs = ((IMAPFolder) ifolder).getAttributes();
|
||||
for (String attr : attrs) {
|
||||
if (attr.startsWith("\\")) {
|
||||
int index = EntityFolder.STANDARD_FOLDER_ATTR.indexOf(attr.substring(1));
|
||||
if (index >= 0) {
|
||||
EntityFolder folder = new EntityFolder();
|
||||
folder.name = ifolder.getFullName();
|
||||
folder.type = EntityFolder.STANDARD_FOLDER_TYPE.get(index);
|
||||
folder.synchronize = standard_sync.contains(folder.type);
|
||||
folder.after = DEFAULT_STANDARD_SYNC;
|
||||
folders.add(folder);
|
||||
|
||||
Log.i(Helper.TAG, "Standard folder=" + folder.name +
|
||||
" type=" + folder.type + " attr=" + TextUtils.join(",", attrs));
|
||||
|
||||
if (EntityFolder.TYPE_DRAFTS.equals(folder.type))
|
||||
drafts = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!drafts)
|
||||
throw new MessagingException(getContext().getString(R.string.title_no_drafts));
|
||||
} finally {
|
||||
if (istore != null)
|
||||
istore.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (account.primary)
|
||||
db.account().resetPrimary();
|
||||
|
||||
if (update)
|
||||
db.account().updateAccount(account);
|
||||
else
|
||||
try {
|
||||
db.beginTransaction();
|
||||
account.id = db.account().insertAccount(account);
|
||||
|
||||
EntityFolder inbox = new EntityFolder();
|
||||
inbox.name = "INBOX";
|
||||
inbox.type = EntityFolder.TYPE_INBOX;
|
||||
inbox.synchronize = true;
|
||||
inbox.after = DEFAULT_INBOX_SYNC;
|
||||
folders.add(0, inbox);
|
||||
|
||||
for (EntityFolder folder : folders) {
|
||||
folder.account = account.id;
|
||||
Log.i(Helper.TAG, "Creating folder=" + folder.name + " (" + folder.type + ")");
|
||||
folder.id = db.folder().insertFolder(folder);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
ServiceSynchronize.restart(getContext(), "account");
|
||||
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Throwable> onCreateLoader(int id, Bundle args) {
|
||||
PutLoader loader = new PutLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
btnOk.setEnabled(true);
|
||||
pbCheck.setVisibility(View.GONE);
|
||||
|
||||
if (ex == null)
|
||||
getFragmentManager().popBackStack();
|
||||
else {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Throwable> loader) {
|
||||
}
|
||||
};
|
||||
}
|
||||
102
app/src/main/java/eu/faircode/email/FragmentAccounts.java
Normal file
102
app/src/main/java/eu/faircode/email/FragmentAccounts.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentAccounts extends Fragment {
|
||||
private RecyclerView rvAccount;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private AdapterAccount adapter;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_accounts, container, false);
|
||||
|
||||
// Get controls
|
||||
rvAccount = view.findViewById(R.id.rvAccount);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
|
||||
// Wire controls
|
||||
|
||||
rvAccount.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvAccount.setLayoutManager(llm);
|
||||
|
||||
adapter = new AdapterAccount(getContext());
|
||||
rvAccount.setAdapter(adapter);
|
||||
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
FragmentAccount fragment = new FragmentAccount();
|
||||
fragment.setArguments(new Bundle());
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("account");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
// Observe accounts
|
||||
DB.getInstance(getContext()).account().liveAccounts().observe(this, new Observer<List<EntityAccount>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityAccount> accounts) {
|
||||
adapter.set(accounts);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_list_accounts);
|
||||
}
|
||||
}
|
||||
585
app/src/main/java/eu/faircode/email/FragmentCompose.java
Normal file
585
app/src/main/java/eu/faircode/email/FragmentCompose.java
Normal file
@@ -0,0 +1,585 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.BottomNavigationView;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class FragmentCompose extends Fragment {
|
||||
private boolean once = false;
|
||||
private String thread = null;
|
||||
private long rid = -1;
|
||||
|
||||
private Spinner spFrom;
|
||||
private ImageView ivIdentyAdd;
|
||||
private EditText etTo;
|
||||
private ImageView ivContactAdd;
|
||||
private EditText etSubject;
|
||||
private EditText etBody;
|
||||
private BottomNavigationView bottom_navigation;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_compose, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
String action = args.getString("action");
|
||||
final long id = (TextUtils.isEmpty(action) ? args.getLong("id") : -1);
|
||||
|
||||
// Get controls
|
||||
spFrom = view.findViewById(R.id.spFrom);
|
||||
ivIdentyAdd = view.findViewById(R.id.ivIdentyAdd);
|
||||
etTo = view.findViewById(R.id.etTo);
|
||||
ivContactAdd = view.findViewById(R.id.ivContactAdd);
|
||||
etSubject = view.findViewById(R.id.etSubject);
|
||||
etBody = view.findViewById(R.id.etBody);
|
||||
bottom_navigation = view.findViewById(R.id.bottom_navigation);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
|
||||
etBody.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
// Wire controls
|
||||
|
||||
ivIdentyAdd.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", -1);
|
||||
FragmentIdentity fragment = new FragmentIdentity();
|
||||
fragment.setArguments(args);
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("identity");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
});
|
||||
|
||||
ivContactAdd.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI);
|
||||
startActivityForResult(intent, ActivityCompose.REQUEST_CONTACT);
|
||||
}
|
||||
});
|
||||
|
||||
bottom_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
bottom_navigation.setEnabled(false);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_delete:
|
||||
actionDelete(id);
|
||||
return true;
|
||||
case R.id.action_save:
|
||||
actionPut(id, false);
|
||||
return true;
|
||||
case R.id.action_send:
|
||||
actionPut(id, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
bottom_navigation.getMenu().findItem(R.id.action_delete).setEnabled(id > 0);
|
||||
bottom_navigation.setEnabled(false);
|
||||
|
||||
DB.getInstance(getContext()).identity().liveIdentities(true).observe(getActivity(), new Observer<List<EntityIdentity>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable final List<EntityIdentity> identities) {
|
||||
Collections.sort(identities, new Comparator<EntityIdentity>() {
|
||||
@Override
|
||||
public int compare(EntityIdentity i1, EntityIdentity i2) {
|
||||
return i1.name.compareTo(i2.name);
|
||||
}
|
||||
});
|
||||
|
||||
ArrayAdapter<EntityIdentity> adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, identities);
|
||||
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
|
||||
spFrom.setAdapter(adapter);
|
||||
|
||||
// Select primary identity, also for saved drafts
|
||||
for (int pos = 0; pos < identities.size(); pos++)
|
||||
if (identities.get(pos).primary) {
|
||||
spFrom.setSelection(pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
getLoaderManager().restartLoader(ActivityCompose.LOADER_COMPOSE_GET, getArguments(), getLoaderCallbacks).forceLoad();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_compose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == ActivityCompose.REQUEST_CONTACT && resultCode == RESULT_OK) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = getContext().getContentResolver().query(data.getData(),
|
||||
new String[]{
|
||||
ContactsContract.CommonDataKinds.Email.ADDRESS,
|
||||
ContactsContract.Contacts.DISPLAY_NAME
|
||||
},
|
||||
null, null, null);
|
||||
if (cursor.moveToFirst()) {
|
||||
int colEmail = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS);
|
||||
int colName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
|
||||
String email = cursor.getString(colEmail);
|
||||
String name = cursor.getString(colName);
|
||||
|
||||
InternetAddress address = new InternetAddress(email, name);
|
||||
StringBuilder sb = new StringBuilder(etTo.getText().toString());
|
||||
if (sb.length() > 0)
|
||||
sb.append("; ");
|
||||
sb.append(address.toString());
|
||||
etTo.setText(sb.toString());
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), ex.getMessage(), Toast.LENGTH_LONG).show();
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void actionDelete(final long id) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
getLoaderManager().restartLoader(ActivityCompose.LOADER_COMPOSE_DELETE, args, deleteLoaderCallbacks).forceLoad();
|
||||
}
|
||||
|
||||
private void actionPut(long id, boolean send) {
|
||||
bottom_navigation.setEnabled(false);
|
||||
|
||||
EntityIdentity identity = (EntityIdentity) spFrom.getSelectedItem();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putLong("iid", identity == null ? -1 : identity.id);
|
||||
args.putString("thread", FragmentCompose.this.thread);
|
||||
args.putLong("rid", FragmentCompose.this.rid);
|
||||
args.putString("to", etTo.getText().toString());
|
||||
args.putString("subject", etSubject.getText().toString());
|
||||
args.putString("body", etBody.getText().toString());
|
||||
args.putBoolean("send", send);
|
||||
|
||||
getLoaderManager().restartLoader(ActivityCompose.LOADER_COMPOSE_PUT, args, putLoaderCallbacks).forceLoad();
|
||||
}
|
||||
|
||||
private static class GetLoader extends AsyncTaskLoader<Bundle> {
|
||||
private Bundle args;
|
||||
|
||||
GetLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Bundle loadInBackground() {
|
||||
String action = args.getString("action");
|
||||
long id = args.getLong("id", -1);
|
||||
EntityMessage msg = DB.getInstance(getContext()).message().getMessage(id);
|
||||
|
||||
Bundle result = new Bundle();
|
||||
result.putString("action", action);
|
||||
|
||||
if (msg != null) {
|
||||
if (msg.identity != null)
|
||||
result.putLong("iid", msg.identity);
|
||||
if (msg.replying != null)
|
||||
result.putLong("rid", msg.replying);
|
||||
result.putString("thread", msg.thread);
|
||||
result.putString("subject", msg.subject);
|
||||
result.putString("body", msg.body);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(action)) {
|
||||
if (msg != null) {
|
||||
result.putString("from", msg.from);
|
||||
result.putString("to", msg.to);
|
||||
}
|
||||
} else if ("reply".equals(action)) {
|
||||
String to = null;
|
||||
if (msg != null)
|
||||
try {
|
||||
Address[] reply = MessageHelper.decodeAddresses(msg.reply);
|
||||
to = (reply.length == 0 ? msg.from : msg.reply);
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
result.putLong("rid", msg.id);
|
||||
result.putString("from", msg.to);
|
||||
result.putString("to", to);
|
||||
} else if ("reply_all".equals(action)) {
|
||||
String to = null;
|
||||
if (msg != null) {
|
||||
try {
|
||||
Address[] from = MessageHelper.decodeAddresses(msg.from);
|
||||
Address[] reply = MessageHelper.decodeAddresses(msg.reply);
|
||||
Address[] cc = MessageHelper.decodeAddresses(msg.cc);
|
||||
List<Address> addresses = new ArrayList<>();
|
||||
addresses.addAll(Arrays.asList(reply.length == 0 ? from : reply));
|
||||
addresses.addAll(Arrays.asList(cc));
|
||||
to = MessageHelper.encodeAddresses(addresses.toArray(new Address[0]));
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
result.putLong("rid", msg.id);
|
||||
result.putString("from", msg.to);
|
||||
result.putString("to", to);
|
||||
} else if ("forward".equals(action)) {
|
||||
result.putString("from", msg.to);
|
||||
result.putString("to", null);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks getLoaderCallbacks = new LoaderManager.LoaderCallbacks<Bundle>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Bundle> onCreateLoader(int id, @Nullable Bundle args) {
|
||||
GetLoader loader = new GetLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Bundle> loader, final Bundle result) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
long iid = result.getLong("iid", -1);
|
||||
long rid = result.getLong("rid", -1);
|
||||
String thread = result.getString("thread");
|
||||
String from = result.getString("from");
|
||||
String to = result.getString("to");
|
||||
String subject = result.getString("subject");
|
||||
String body = result.getString("body");
|
||||
String action = result.getString("action");
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
FragmentCompose.this.thread = thread;
|
||||
FragmentCompose.this.rid = rid;
|
||||
|
||||
ArrayAdapter adapter = (ArrayAdapter) spFrom.getAdapter();
|
||||
if (adapter != null)
|
||||
for (int pos = 0; pos < adapter.getCount(); pos++) {
|
||||
EntityIdentity identity = (EntityIdentity) adapter.getItem(pos);
|
||||
if (iid < 0 ? identity.primary : iid == identity.id) {
|
||||
spFrom.setSelection(pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!once) {
|
||||
// Prevent changed fields from being overwritten
|
||||
once = true;
|
||||
|
||||
if (action == null) {
|
||||
if (to != null)
|
||||
etTo.setText(TextUtils.join(", ", MessageHelper.decodeAddresses(to)));
|
||||
etSubject.setText(subject);
|
||||
if (body != null)
|
||||
etBody.setText(Html.fromHtml(HtmlHelper.sanitize(getContext(), body, false)));
|
||||
} else if ("reply".equals(action) || "reply_all".equals(action)) {
|
||||
etTo.setText(TextUtils.join(", ", MessageHelper.decodeAddresses(to)));
|
||||
String text = String.format("<br><br>%s %s:<br><br>%s",
|
||||
Html.escapeHtml(new Date().toString()),
|
||||
Html.escapeHtml(TextUtils.join(", ", MessageHelper.decodeAddresses(from))),
|
||||
HtmlHelper.sanitize(getContext(), body, true));
|
||||
etSubject.setText(getContext().getString(R.string.title_subject_reply, subject));
|
||||
etBody.setText(Html.fromHtml(text));
|
||||
} else if ("forward".equals(action)) {
|
||||
String text = String.format("<br><br>%s %s:<br><br>%s",
|
||||
Html.escapeHtml(new Date().toString()),
|
||||
Html.escapeHtml(TextUtils.join(", ", MessageHelper.decodeAddresses(from))),
|
||||
HtmlHelper.sanitize(getContext(), body, true));
|
||||
etSubject.setText(getContext().getString(R.string.title_subject_forward, subject));
|
||||
etBody.setText(Html.fromHtml(text));
|
||||
}
|
||||
}
|
||||
|
||||
bottom_navigation.setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Bundle> loader) {
|
||||
}
|
||||
};
|
||||
|
||||
private static class DeleteLoader extends AsyncTaskLoader<Throwable> {
|
||||
private Bundle args;
|
||||
|
||||
DeleteLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable loadInBackground() {
|
||||
long id = args.getLong("id");
|
||||
DaoMessage message = DB.getInstance(getContext()).message();
|
||||
EntityMessage draft = message.getMessage(id);
|
||||
if (draft != null) {
|
||||
draft.ui_hide = true;
|
||||
message.updateMessage(draft);
|
||||
EntityOperation.queue(getContext(), draft, EntityOperation.DELETE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks deleteLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Throwable> onCreateLoader(int id, @Nullable Bundle args) {
|
||||
DeleteLoader loader = new DeleteLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
if (ex == null) {
|
||||
getFragmentManager().popBackStack();
|
||||
Toast.makeText(getContext(), R.string.title_deleted, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Throwable> loader) {
|
||||
}
|
||||
};
|
||||
|
||||
private static class PutLoader extends AsyncTaskLoader<Throwable> {
|
||||
private Bundle args;
|
||||
|
||||
PutLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable loadInBackground() {
|
||||
long id = args.getLong("id");
|
||||
boolean send = args.getBoolean("send", false);
|
||||
Log.i(Helper.TAG, "Put load id=" + id + " send=" + send);
|
||||
try {
|
||||
DB db = DB.getInstance(getContext());
|
||||
DaoMessage message = db.message();
|
||||
DaoIdentity identity = db.identity();
|
||||
DaoFolder folder = db.folder();
|
||||
|
||||
// Get data
|
||||
EntityMessage draft = message.getMessage(id);
|
||||
EntityIdentity ident = identity.getIdentity(args.getLong("iid"));
|
||||
EntityFolder drafts = db.folder().getPrimaryDraftFolder();
|
||||
if (drafts == null)
|
||||
throw new Throwable(getContext().getString(R.string.title_no_primary_drafts));
|
||||
|
||||
long rid = args.getLong("rid", -1);
|
||||
String thread = args.getString("thread");
|
||||
String to = args.getString("to");
|
||||
String body = args.getString("body");
|
||||
String subject = args.getString("subject");
|
||||
|
||||
Address afrom = (ident == null ? null : new InternetAddress(ident.email, ident.name));
|
||||
Address ato[] = (TextUtils.isEmpty(to) ? new Address[0] : InternetAddress.parse(to));
|
||||
|
||||
// Build draft
|
||||
boolean update = (draft != null);
|
||||
if (draft == null)
|
||||
draft = new EntityMessage();
|
||||
draft.account = drafts.account;
|
||||
draft.folder = drafts.id;
|
||||
draft.identity = (ident == null ? null : ident.id);
|
||||
draft.replying = (rid < 0 ? null : rid);
|
||||
draft.thread = thread;
|
||||
draft.from = (afrom == null ? null : MessageHelper.encodeAddresses(new Address[]{afrom}));
|
||||
draft.to = (ato == null ? null : MessageHelper.encodeAddresses(ato));
|
||||
draft.subject = subject;
|
||||
draft.body = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
|
||||
draft.received = new Date().getTime();
|
||||
draft.seen = false;
|
||||
draft.ui_seen = false;
|
||||
draft.ui_hide = send;
|
||||
|
||||
// Store draft
|
||||
if (update)
|
||||
message.updateMessage(draft);
|
||||
else
|
||||
draft.id = message.insertMessage(draft);
|
||||
|
||||
// Check data
|
||||
if (send) {
|
||||
if (draft.identity == null)
|
||||
throw new MessagingException(getContext().getString(R.string.title_from_missing));
|
||||
if (draft.to == null)
|
||||
throw new MessagingException(getContext().getString(R.string.title_to_missing));
|
||||
|
||||
// Get outbox
|
||||
EntityFolder outbox = folder.getOutbox();
|
||||
if (outbox == null) {
|
||||
outbox = new EntityFolder();
|
||||
outbox.name = "OUTBOX";
|
||||
outbox.type = EntityFolder.TYPE_OUTBOX;
|
||||
outbox.synchronize = false;
|
||||
outbox.after = 0;
|
||||
outbox.id = folder.insertFolder(outbox);
|
||||
}
|
||||
|
||||
// Build outgoing message
|
||||
EntityMessage out = new EntityMessage();
|
||||
out.folder = outbox.id;
|
||||
out.identity = draft.identity;
|
||||
out.replying = draft.replying;
|
||||
out.thread = draft.thread;
|
||||
out.from = draft.from;
|
||||
out.to = draft.to;
|
||||
out.subject = draft.subject;
|
||||
out.body = draft.body;
|
||||
out.received = draft.received;
|
||||
out.seen = draft.seen;
|
||||
out.ui_seen = draft.ui_seen;
|
||||
out.ui_hide = false;
|
||||
out.id = message.insertMessage(out);
|
||||
|
||||
EntityOperation.queue(getContext(), out, EntityOperation.SEND);
|
||||
EntityOperation.queue(getContext(), draft, EntityOperation.DELETE);
|
||||
} else
|
||||
EntityOperation.queue(getContext(), draft, EntityOperation.ADD);
|
||||
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() {
|
||||
private Bundle args;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Throwable> onCreateLoader(int id, Bundle args) {
|
||||
this.args = args;
|
||||
PutLoader loader = new PutLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
boolean send = args.getBoolean("send", false);
|
||||
Log.i(Helper.TAG, "Put finished send=" + send + " ex=" + ex);
|
||||
|
||||
if (ex == null) {
|
||||
getFragmentManager().popBackStack();
|
||||
Toast.makeText(getContext(), send ? R.string.title_queued : R.string.title_saved, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Throwable> loader) {
|
||||
}
|
||||
};
|
||||
}
|
||||
60
app/src/main/java/eu/faircode/email/FragmentEula.java
Normal file
60
app/src/main/java/eu/faircode/email/FragmentEula.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
public class FragmentEula extends Fragment {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_eula, container, false);
|
||||
|
||||
Button btnAgree = view.findViewById(R.id.btnOk);
|
||||
Button btnDisagree = view.findViewById(R.id.btnCancel);
|
||||
|
||||
btnAgree.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
prefs.edit().putBoolean("eula", true).apply();
|
||||
}
|
||||
});
|
||||
|
||||
btnDisagree.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
160
app/src/main/java/eu/faircode/email/FragmentFolder.java
Normal file
160
app/src/main/java/eu/faircode/email/FragmentFolder.java
Normal file
@@ -0,0 +1,160 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class FragmentFolder extends Fragment {
|
||||
private CheckBox cbSynchronize;
|
||||
private EditText etAfter;
|
||||
private Button btnOk;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_folder, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
final long id = args.getLong("id");
|
||||
|
||||
// Get controls
|
||||
cbSynchronize = view.findViewById(R.id.cbSynchronize);
|
||||
etAfter = view.findViewById(R.id.etAfter);
|
||||
btnOk = view.findViewById(R.id.btnOk);
|
||||
|
||||
btnOk.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
btnOk.setEnabled(false);
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
args.putString("after", etAfter.getText().toString());
|
||||
|
||||
getLoaderManager().restartLoader(ActivityView.LOADER_FOLDER_PUT, args, putLoaderCallbacks).forceLoad();
|
||||
}
|
||||
});
|
||||
|
||||
DB.getInstance(getContext()).folder().liveFolder(id).observe(this, new Observer<EntityFolder>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityFolder folder) {
|
||||
if (folder != null) {
|
||||
cbSynchronize.setChecked(folder.synchronize);
|
||||
etAfter.setText(Integer.toString(folder.after));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_edit_folder);
|
||||
}
|
||||
|
||||
private static class PutLoader extends AsyncTaskLoader<Throwable> {
|
||||
private Bundle args;
|
||||
|
||||
PutLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable loadInBackground() {
|
||||
try {
|
||||
long id = args.getLong("id");
|
||||
boolean synchronize = args.getBoolean("synchronize");
|
||||
String after = args.getString("after");
|
||||
int days = (TextUtils.isEmpty(after) ? 7 : Integer.parseInt(after));
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
DaoFolder dao = db.folder();
|
||||
EntityFolder folder = dao.getFolder(id);
|
||||
folder.synchronize = synchronize;
|
||||
folder.after = days;
|
||||
dao.updateFolder(folder);
|
||||
|
||||
if (!folder.synchronize)
|
||||
db.message().deleteMessages(folder.id);
|
||||
|
||||
ServiceSynchronize.restart(getContext(), "folder");
|
||||
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Throwable> onCreateLoader(int id, Bundle args) {
|
||||
PutLoader loader = new PutLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
btnOk.setEnabled(true);
|
||||
|
||||
if (ex == null)
|
||||
getFragmentManager().popBackStack();
|
||||
else {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Throwable> loader) {
|
||||
}
|
||||
};
|
||||
}
|
||||
97
app/src/main/java/eu/faircode/email/FragmentFolders.java
Normal file
97
app/src/main/java/eu/faircode/email/FragmentFolders.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentFolders extends Fragment {
|
||||
private RecyclerView rvFolder;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private AdapterFolder adapter;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_folders, container, false);
|
||||
|
||||
// Get controls
|
||||
rvFolder = view.findViewById(R.id.rvFolder);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
|
||||
// Wire controls
|
||||
|
||||
rvFolder.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvFolder.setLayoutManager(llm);
|
||||
|
||||
adapter = new AdapterFolder(getContext());
|
||||
rvFolder.setAdapter(adapter);
|
||||
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
}
|
||||
});
|
||||
fab.setVisibility(View.GONE);
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
// Observe folders
|
||||
DB.getInstance(getContext()).folder().liveFolders().observe(this, new Observer<List<TupleFolderEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<TupleFolderEx> folders) {
|
||||
adapter.set(folders);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_list_folders);
|
||||
}
|
||||
}
|
||||
102
app/src/main/java/eu/faircode/email/FragmentIdentities.java
Normal file
102
app/src/main/java/eu/faircode/email/FragmentIdentities.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentIdentities extends Fragment {
|
||||
private RecyclerView rvIdentity;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private AdapterIdentity adapter;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_identities, container, false);
|
||||
|
||||
// Get controls
|
||||
rvIdentity = view.findViewById(R.id.rvIdentity);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
|
||||
// Wire controls
|
||||
|
||||
rvIdentity.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvIdentity.setLayoutManager(llm);
|
||||
|
||||
adapter = new AdapterIdentity(getContext());
|
||||
rvIdentity.setAdapter(adapter);
|
||||
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
FragmentIdentity fragment = new FragmentIdentity();
|
||||
fragment.setArguments(new Bundle());
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("identity");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
// Observe identities
|
||||
DB.getInstance(getContext()).identity().liveIdentities().observe(this, new Observer<List<EntityIdentity>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityIdentity> identities) {
|
||||
adapter.set(identities);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_list_identities);
|
||||
}
|
||||
}
|
||||
289
app/src/main/java/eu/faircode/email/FragmentIdentity.java
Normal file
289
app/src/main/java/eu/faircode/email/FragmentIdentity.java
Normal file
@@ -0,0 +1,289 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.Session;
|
||||
import javax.mail.Transport;
|
||||
|
||||
public class FragmentIdentity extends Fragment {
|
||||
private List<Provider> providers;
|
||||
|
||||
private Spinner spProfile;
|
||||
private EditText etName;
|
||||
private EditText etEmail;
|
||||
private EditText etHost;
|
||||
private CheckBox cbStartTls;
|
||||
private EditText etPort;
|
||||
private EditText etUser;
|
||||
private EditText etPassword;
|
||||
private CheckBox cbPrimary;
|
||||
private CheckBox cbSynchronize;
|
||||
private Button btnOk;
|
||||
private ProgressBar pbCheck;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_identity, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
final long id = args.getLong("id", -1);
|
||||
|
||||
// Get providers
|
||||
providers = Provider.loadProfiles(getContext());
|
||||
providers.add(0, new Provider(getString(R.string.title_custom)));
|
||||
|
||||
// Get controls
|
||||
spProfile = view.findViewById(R.id.spProvider);
|
||||
etName = view.findViewById(R.id.etName);
|
||||
etEmail = view.findViewById(R.id.etEmail);
|
||||
etHost = view.findViewById(R.id.etHost);
|
||||
cbStartTls = view.findViewById(R.id.cbStartTls);
|
||||
etPort = view.findViewById(R.id.etPort);
|
||||
etUser = view.findViewById(R.id.etUser);
|
||||
etPassword = view.findViewById(R.id.etPassword);
|
||||
cbPrimary = view.findViewById(R.id.cbPrimary);
|
||||
cbSynchronize = view.findViewById(R.id.cbSynchronize);
|
||||
btnOk = view.findViewById(R.id.btnOk);
|
||||
pbCheck = view.findViewById(R.id.pbCheck);
|
||||
|
||||
// Wire controls
|
||||
|
||||
etEmail.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
etUser.setText(s.toString());
|
||||
}
|
||||
});
|
||||
|
||||
cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
|
||||
etPort.setHint(checked ? "587" : "465");
|
||||
}
|
||||
});
|
||||
|
||||
spProfile.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
Provider provider = providers.get(position);
|
||||
if (provider.smtp_port != 0) {
|
||||
etHost.setText(provider.smtp_host);
|
||||
etPort.setText(Integer.toString(provider.smtp_port));
|
||||
cbStartTls.setChecked(provider.starttls);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
ArrayAdapter<Provider> adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, providers);
|
||||
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
|
||||
spProfile.setAdapter(adapter);
|
||||
|
||||
pbCheck.setVisibility(View.GONE);
|
||||
|
||||
btnOk.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
btnOk.setEnabled(false);
|
||||
pbCheck.setVisibility(View.VISIBLE);
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putString("name", etName.getText().toString());
|
||||
args.putString("email", etEmail.getText().toString());
|
||||
args.putString("host", etHost.getText().toString());
|
||||
args.putBoolean("starttls", cbStartTls.isChecked());
|
||||
args.putString("port", etPort.getText().toString());
|
||||
args.putString("user", etUser.getText().toString());
|
||||
args.putString("password", etPassword.getText().toString());
|
||||
args.putBoolean("primary", cbPrimary.isChecked());
|
||||
args.putBoolean("synchronize", cbSynchronize.isChecked());
|
||||
|
||||
getLoaderManager().restartLoader(ActivityView.LOADER_IDENTITY_PUT, args, putLoaderCallbacks).forceLoad();
|
||||
}
|
||||
});
|
||||
|
||||
DB.getInstance(getContext()).identity().liveIdentity(id).observe(this, new Observer<EntityIdentity>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityIdentity identity) {
|
||||
etName.setText(identity == null ? null : identity.name);
|
||||
etEmail.setText(identity == null ? null : identity.email);
|
||||
etHost.setText(identity == null ? null : identity.host);
|
||||
cbStartTls.setChecked(identity == null ? false : identity.starttls);
|
||||
etPort.setText(identity == null ? null : Long.toString(identity.port));
|
||||
etUser.setText(identity == null ? null : identity.user);
|
||||
etPassword.setText(identity == null ? null : identity.password);
|
||||
cbPrimary.setChecked(identity == null ? true : identity.primary);
|
||||
cbSynchronize.setChecked(identity == null ? true : identity.synchronize);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_edit_indentity);
|
||||
}
|
||||
|
||||
private static class PutLoader extends AsyncTaskLoader<Throwable> {
|
||||
private Bundle args;
|
||||
|
||||
PutLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
void setArgs(Bundle args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable loadInBackground() {
|
||||
try {
|
||||
long id = args.getLong("id");
|
||||
String host = args.getString("host");
|
||||
boolean starttls = args.getBoolean("starttls");
|
||||
String port = args.getString("port");
|
||||
if (TextUtils.isEmpty(port))
|
||||
port = "0";
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityIdentity identity = db.identity().getIdentity(id);
|
||||
boolean update = (identity != null);
|
||||
if (identity == null)
|
||||
identity = new EntityIdentity();
|
||||
identity.name = Objects.requireNonNull(args.getString("name"));
|
||||
identity.email = Objects.requireNonNull(args.getString("email"));
|
||||
identity.host = host;
|
||||
identity.port = Integer.parseInt(port);
|
||||
identity.starttls = starttls;
|
||||
identity.user = Objects.requireNonNull(args.getString("user"));
|
||||
identity.password = Objects.requireNonNull(args.getString("password"));
|
||||
identity.primary = args.getBoolean("primary");
|
||||
identity.synchronize = args.getBoolean("synchronize");
|
||||
|
||||
if (TextUtils.isEmpty(identity.name))
|
||||
throw new IllegalArgumentException(getContext().getString(R.string.title_no_name));
|
||||
|
||||
if (TextUtils.isEmpty(identity.email))
|
||||
throw new IllegalArgumentException(getContext().getString(R.string.title_no_email));
|
||||
|
||||
// Check SMTP server
|
||||
if (identity.synchronize) {
|
||||
Properties props = MessageHelper.getSessionProperties();
|
||||
Session isession = Session.getDefaultInstance(props, null);
|
||||
Transport itransport = isession.getTransport(identity.starttls ? "smtp" : "smtps");
|
||||
try {
|
||||
itransport.connect(identity.host, identity.port, identity.user, identity.password);
|
||||
} finally {
|
||||
itransport.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (identity.primary)
|
||||
db.identity().resetPrimary();
|
||||
|
||||
if (update)
|
||||
db.identity().updateIdentity(identity);
|
||||
else
|
||||
identity.id = db.identity().insertIdentity(identity);
|
||||
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public Loader<Throwable> onCreateLoader(int id, Bundle args) {
|
||||
PutLoader loader = new PutLoader(getActivity());
|
||||
loader.setArgs(args);
|
||||
return loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) {
|
||||
getLoaderManager().destroyLoader(loader.getId());
|
||||
|
||||
btnOk.setEnabled(true);
|
||||
pbCheck.setVisibility(View.GONE);
|
||||
|
||||
if (ex == null)
|
||||
getFragmentManager().popBackStack();
|
||||
else {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
Toast.makeText(getContext(), Helper.formatThrowable(ex), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<Throwable> loader) {
|
||||
}
|
||||
};
|
||||
}
|
||||
329
app/src/main/java/eu/faircode/email/FragmentMessage.java
Normal file
329
app/src/main/java/eu/faircode/email/FragmentMessage.java
Normal file
@@ -0,0 +1,329 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.BottomNavigationView;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class FragmentMessage extends Fragment {
|
||||
private TextView tvTime;
|
||||
private TextView tvFrom;
|
||||
private TextView tvSubject;
|
||||
private TextView tvCount;
|
||||
private BottomNavigationView top_navigation;
|
||||
private TextView tvBody;
|
||||
private BottomNavigationView bottom_navigation;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
|
||||
private LiveData<TupleFolderEx> liveFolder;
|
||||
|
||||
private ExecutorService executor = Executors.newCachedThreadPool();
|
||||
private DateFormat df = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_message, container, false);
|
||||
|
||||
// Get arguments
|
||||
Bundle args = getArguments();
|
||||
final long folder = args.getLong("folder");
|
||||
final long id = args.getLong("id");
|
||||
|
||||
// Get controls
|
||||
tvFrom = view.findViewById(R.id.tvAddress);
|
||||
tvTime = view.findViewById(R.id.tvTime);
|
||||
tvSubject = view.findViewById(R.id.tvSubject);
|
||||
tvCount = view.findViewById(R.id.tvCount);
|
||||
top_navigation = view.findViewById(R.id.top_navigation);
|
||||
tvBody = view.findViewById(R.id.tvBody);
|
||||
bottom_navigation = view.findViewById(R.id.bottom_navigation);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
|
||||
tvTime.setTextIsSelectable(true);
|
||||
tvFrom.setTextIsSelectable(true);
|
||||
tvSubject.setTextIsSelectable(true);
|
||||
tvBody.setTextIsSelectable(true);
|
||||
tvBody.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
// Wire controls
|
||||
|
||||
top_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_seen:
|
||||
onActionSeen(id);
|
||||
return true;
|
||||
case R.id.action_thread:
|
||||
onActionThread(id);
|
||||
return true;
|
||||
case R.id.action_move:
|
||||
onActionMove(id);
|
||||
return true;
|
||||
case R.id.action_forward:
|
||||
onActionForward(id);
|
||||
return true;
|
||||
case R.id.action_reply_all:
|
||||
onActionReplyAll(id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
bottom_navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_delete:
|
||||
onActionDelete(id);
|
||||
return true;
|
||||
case R.id.action_spam:
|
||||
onActionSpam(id);
|
||||
return true;
|
||||
case R.id.action_archive:
|
||||
onActionArchive(id);
|
||||
return true;
|
||||
case R.id.action_reply:
|
||||
onActionReply(id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
|
||||
// Observe folder
|
||||
liveFolder = db.folder().liveFolderEx(folder);
|
||||
|
||||
// Observe message
|
||||
db.message().liveMessage(id).observe(this, new Observer<TupleMessageEx>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable TupleMessageEx message) {
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
if (message == null || message.ui_hide) {
|
||||
// Message gone (moved, deleted)
|
||||
if (FragmentMessage.this.isVisible())
|
||||
getFragmentManager().popBackStack();
|
||||
} else {
|
||||
tvFrom.setText(message.from == null ? null : MessageHelper.getFormattedAddresses(message.from));
|
||||
tvTime.setText(message.sent == null ? null : df.format(new Date(message.sent)));
|
||||
tvSubject.setText(message.subject);
|
||||
tvCount.setText(Integer.toString(message.count));
|
||||
tvCount.setVisibility(message.count > 1 ? View.VISIBLE : View.GONE);
|
||||
|
||||
int visibility = (message.ui_seen ? Typeface.NORMAL : Typeface.BOLD);
|
||||
tvFrom.setTypeface(null, visibility);
|
||||
tvTime.setTypeface(null, visibility);
|
||||
tvSubject.setTypeface(null, visibility);
|
||||
tvCount.setTypeface(null, visibility);
|
||||
|
||||
MenuItem actionSeen = top_navigation.getMenu().findItem(R.id.action_seen);
|
||||
actionSeen.setIcon(message.ui_seen
|
||||
? R.drawable.baseline_visibility_off_24
|
||||
: R.drawable.baseline_visibility_24);
|
||||
actionSeen.setTitle(message.ui_seen ? R.string.title_unseen : R.string.title_seen);
|
||||
|
||||
tvBody.setText(message.body == null
|
||||
? null
|
||||
: Html.fromHtml(HtmlHelper.sanitize(getContext(), message.body, false)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
liveFolder.observe(this, folderObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
liveFolder.removeObservers(this);
|
||||
}
|
||||
|
||||
Observer<TupleFolderEx> folderObserver = new Observer<TupleFolderEx>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable TupleFolderEx folder) {
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(folder == null
|
||||
? null
|
||||
: Helper.localizeFolderName(getContext(), folder));
|
||||
}
|
||||
};
|
||||
|
||||
private void onActionSeen(final long id) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
message.ui_seen = !message.ui_seen;
|
||||
db.message().updateMessage(message);
|
||||
EntityOperation.queue(getContext(), message, EntityOperation.SEEN, message.ui_seen);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onActionThread(long id) {
|
||||
FragmentMessages fragment = new FragmentMessages();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("thread", id); // message ID
|
||||
fragment.setArguments(args);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("thread");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
|
||||
private void onActionMove(final long id) {
|
||||
Toast.makeText(getContext(), "Not implemented yet", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void onActionForward(long id) {
|
||||
startActivity(new Intent(getContext(), ActivityCompose.class)
|
||||
.putExtra("id", id)
|
||||
.putExtra("action", "forward"));
|
||||
}
|
||||
|
||||
private void onActionReplyAll(long id) {
|
||||
startActivity(new Intent(getContext(), ActivityCompose.class)
|
||||
.putExtra("id", id)
|
||||
.putExtra("action", "reply_all"));
|
||||
}
|
||||
|
||||
private void onActionDelete(final long id) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder
|
||||
.setMessage(R.string.title_ask_delete)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
message.ui_hide = true;
|
||||
db.message().updateMessage(message);
|
||||
|
||||
EntityOperation.queue(getContext(), message, EntityOperation.DELETE);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null).show();
|
||||
}
|
||||
|
||||
private void onActionSpam(final long id) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder
|
||||
.setMessage(R.string.title_ask_spam)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
EntityFolder spam = db.folder().getSpamFolder(message.account);
|
||||
if (spam == null) {
|
||||
Toast.makeText(getContext(), R.string.title_no_spam, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
message.ui_hide = true;
|
||||
db.message().updateMessage(message);
|
||||
|
||||
EntityOperation.queue(getContext(), message, EntityOperation.MOVE, spam.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null).show();
|
||||
}
|
||||
|
||||
private void onActionArchive(final long id) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DB db = DB.getInstance(getContext());
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
EntityFolder archive = db.folder().getArchiveFolder(message.account);
|
||||
if (archive == null) {
|
||||
Toast.makeText(getContext(), R.string.title_no_archive, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
message.ui_hide = true;
|
||||
db.message().updateMessage(message);
|
||||
|
||||
EntityOperation.queue(getContext(), message, EntityOperation.MOVE, archive.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onActionReply(long id) {
|
||||
startActivity(new Intent(getContext(), ActivityCompose.class)
|
||||
.putExtra("id", id)
|
||||
.putExtra("action", "reply"));
|
||||
}
|
||||
}
|
||||
152
app/src/main/java/eu/faircode/email/FragmentMessages.java
Normal file
152
app/src/main/java/eu/faircode/email/FragmentMessages.java
Normal file
@@ -0,0 +1,152 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.Group;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentMessages extends Fragment {
|
||||
private RecyclerView rvMessage;
|
||||
private TextView tvNoEmail;
|
||||
private ProgressBar pbWait;
|
||||
private Group grpReady;
|
||||
private FloatingActionButton fab;
|
||||
|
||||
private AdapterMessage adapter;
|
||||
private LiveData<TupleFolderEx> liveFolder;
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_messages, container, false);
|
||||
|
||||
// Get arguments
|
||||
long folder = getArguments().getLong("folder", -1);
|
||||
long thread = getArguments().getLong("thread", -1); // message ID
|
||||
|
||||
// Get controls
|
||||
rvMessage = view.findViewById(R.id.rvFolder);
|
||||
tvNoEmail = view.findViewById(R.id.tvNoEmail);
|
||||
pbWait = view.findViewById(R.id.pbWait);
|
||||
grpReady = view.findViewById(R.id.grpReady);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
|
||||
// Wire controls
|
||||
|
||||
rvMessage.setHasFixedSize(false);
|
||||
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||
rvMessage.setLayoutManager(llm);
|
||||
|
||||
adapter = new AdapterMessage(getContext(),
|
||||
thread < 0
|
||||
? AdapterMessage.ViewType.FOLDER
|
||||
: AdapterMessage.ViewType.THREAD);
|
||||
rvMessage.setAdapter(adapter);
|
||||
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startActivity(new Intent(getContext(), ActivityCompose.class));
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
tvNoEmail.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.GONE);
|
||||
pbWait.setVisibility(View.VISIBLE);
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
|
||||
// Observe folder
|
||||
liveFolder = (thread < 0 ? DB.getInstance(getContext()).folder().liveFolderEx(folder) : null);
|
||||
|
||||
// Observe messages
|
||||
if (thread < 0)
|
||||
if (folder < 0)
|
||||
db.message().liveUnifiedInbox().observe(this, messagesObserver);
|
||||
else
|
||||
db.message().liveMessages(folder).observe(this, messagesObserver);
|
||||
else {
|
||||
db.message().liveThread(thread).observe(this, messagesObserver);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (liveFolder == null)
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_folder_thread);
|
||||
else
|
||||
liveFolder.observe(this, folderObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (liveFolder != null)
|
||||
liveFolder.removeObservers(this);
|
||||
}
|
||||
|
||||
Observer<TupleFolderEx> folderObserver = new Observer<TupleFolderEx>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable TupleFolderEx folder) {
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(folder.name == null
|
||||
? getString(R.string.title_folder_unified)
|
||||
: Helper.localizeFolderName(getContext(), folder));
|
||||
}
|
||||
};
|
||||
|
||||
Observer<List<TupleMessageEx>> messagesObserver = new Observer<List<TupleMessageEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<TupleMessageEx> messages) {
|
||||
adapter.set(messages);
|
||||
|
||||
pbWait.setVisibility(View.GONE);
|
||||
grpReady.setVisibility(View.VISIBLE);
|
||||
|
||||
if (messages.size() == 0) {
|
||||
tvNoEmail.setVisibility(View.VISIBLE);
|
||||
rvMessage.setVisibility(View.GONE);
|
||||
} else {
|
||||
tvNoEmail.setVisibility(View.GONE);
|
||||
rvMessage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
192
app/src/main/java/eu/faircode/email/FragmentSetup.java
Normal file
192
app/src/main/java/eu/faircode/email/FragmentSetup.java
Normal file
@@ -0,0 +1,192 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.Manifest;
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FragmentSetup extends Fragment {
|
||||
private Button btnAccount;
|
||||
private Button btnIdentity;
|
||||
private Button btnPermissions;
|
||||
private ProgressBar pbAccount;
|
||||
private ProgressBar pbIdentity;
|
||||
private TextView tvAccountDone;
|
||||
private TextView tvIdentityDone;
|
||||
private TextView tvPermissionsDone;
|
||||
|
||||
private static final String[] permissions = new String[]{
|
||||
Manifest.permission.READ_CONTACTS
|
||||
};
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_setup, container, false);
|
||||
|
||||
// Get controls
|
||||
btnAccount = view.findViewById(R.id.btnAccount);
|
||||
btnIdentity = view.findViewById(R.id.btnIdentity);
|
||||
btnPermissions = view.findViewById(R.id.btnPermissions);
|
||||
pbAccount = view.findViewById(R.id.pbAccount);
|
||||
pbIdentity = view.findViewById(R.id.pbIdentity);
|
||||
tvAccountDone = view.findViewById(R.id.tvAccountDone);
|
||||
tvIdentityDone = view.findViewById(R.id.tvIdentityDone);
|
||||
tvPermissionsDone = view.findViewById(R.id.tvPermissionsDone);
|
||||
|
||||
// Wire controls
|
||||
|
||||
btnAccount.setOnClickListener(new View.OnClickListener() {
|
||||
private boolean once;
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
once = false;
|
||||
btnAccount.setEnabled(false);
|
||||
pbAccount.setVisibility(View.VISIBLE);
|
||||
|
||||
DB.getInstance(getContext()).account().liveFirstAccount().observe(getActivity(), new Observer<EntityAccount>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityAccount account) {
|
||||
btnAccount.setEnabled(true);
|
||||
pbAccount.setVisibility(View.GONE);
|
||||
|
||||
if (!once) {
|
||||
once = true;
|
||||
Bundle args = new Bundle();
|
||||
if (account != null)
|
||||
args.putLong("id", account.id);
|
||||
FragmentAccount fragment = new FragmentAccount();
|
||||
fragment.setArguments(args);
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("account");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
btnIdentity.setOnClickListener(new View.OnClickListener() {
|
||||
private boolean once;
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
once = false;
|
||||
btnIdentity.setEnabled(false);
|
||||
pbIdentity.setVisibility(View.VISIBLE);
|
||||
|
||||
DB.getInstance(getContext()).identity().liveFirstIdentity().observe(getActivity(), new Observer<EntityIdentity>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable EntityIdentity identity) {
|
||||
btnIdentity.setEnabled(true);
|
||||
pbIdentity.setVisibility(View.GONE);
|
||||
|
||||
if (!once) {
|
||||
once = true;
|
||||
Bundle args = new Bundle();
|
||||
if (identity != null)
|
||||
args.putLong("id", identity.id);
|
||||
FragmentIdentity fragment = new FragmentIdentity();
|
||||
fragment.setArguments(args);
|
||||
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("identity");
|
||||
fragmentTransaction.commit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
btnPermissions.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
requestPermissions(permissions, 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
|
||||
pbAccount.setVisibility(View.GONE);
|
||||
pbIdentity.setVisibility(View.GONE);
|
||||
tvAccountDone.setVisibility(View.INVISIBLE);
|
||||
tvIdentityDone.setVisibility(View.INVISIBLE);
|
||||
tvPermissionsDone.setVisibility(View.INVISIBLE);
|
||||
|
||||
DB db = DB.getInstance(getContext());
|
||||
|
||||
db.account().liveAccounts(true).observe(this, new Observer<List<EntityAccount>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityAccount> accounts) {
|
||||
tvAccountDone.setVisibility(accounts.size() > 0 ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
db.identity().liveIdentities(true).observe(this, new Observer<List<EntityIdentity>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<EntityIdentity> identities) {
|
||||
tvIdentityDone.setVisibility(identities.size() > 0 ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
int[] grantResults = new int[permissions.length];
|
||||
for (int i = 0; i < permissions.length; i++)
|
||||
grantResults[i] = ContextCompat.checkSelfPermission(getActivity(), permissions[i]);
|
||||
|
||||
onRequestPermissionsResult(0, permissions, grantResults);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle(R.string.title_setup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
boolean has = (grantResults.length > 0);
|
||||
for (int result : grantResults)
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
has = false;
|
||||
break;
|
||||
}
|
||||
|
||||
btnPermissions.setEnabled(!has);
|
||||
tvPermissionsDone.setVisibility(has ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
63
app/src/main/java/eu/faircode/email/Helper.java
Normal file
63
app/src/main/java/eu/faircode/email/Helper.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
|
||||
public class Helper {
|
||||
static final String TAG = BuildConfig.APPLICATION_ID;
|
||||
|
||||
static int resolveColor(Context context, int attr) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(attr, typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
|
||||
static String localizeFolderName(Context context, String name) {
|
||||
if ("INBOX".equals(name))
|
||||
return context.getString(R.string.title_folder_inbox);
|
||||
else if ("OUTBOX".equals(name))
|
||||
return context.getString(R.string.title_folder_outbox);
|
||||
else
|
||||
return name;
|
||||
}
|
||||
|
||||
static String localizeFolderName(Context context, TupleFolderEx folder) {
|
||||
if (TextUtils.isEmpty(folder.accountName))
|
||||
return localizeFolderName(context, folder.name);
|
||||
else
|
||||
return localizeFolderName(context, folder.name) + "/" + folder.accountName;
|
||||
}
|
||||
|
||||
static String formatThrowable(Throwable ex) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(ex.getMessage());
|
||||
Throwable cause = ex.getCause();
|
||||
while (cause != null) {
|
||||
sb.append(" ").append(cause.getMessage());
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
95
app/src/main/java/eu/faircode/email/HtmlHelper.java
Normal file
95
app/src/main/java/eu/faircode/email/HtmlHelper.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.helper.StringUtil;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Node;
|
||||
import org.jsoup.nodes.TextNode;
|
||||
import org.jsoup.select.NodeTraversor;
|
||||
import org.jsoup.select.NodeVisitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class HtmlHelper implements NodeVisitor {
|
||||
private Context context;
|
||||
private String newline;
|
||||
private List<String> links = new ArrayList<>();
|
||||
private StringBuilder sb = new StringBuilder();
|
||||
|
||||
private HtmlHelper(Context context, boolean reply) {
|
||||
this.context = context;
|
||||
this.newline = (reply ? "<br>> " : "<br>");
|
||||
}
|
||||
|
||||
public void head(Node node, int depth) {
|
||||
String name = node.nodeName();
|
||||
if (node instanceof TextNode)
|
||||
sb.append(((TextNode) node).text());
|
||||
else if (name.equals("li"))
|
||||
sb.append(newline).append(" * ");
|
||||
else if (name.equals("dt"))
|
||||
sb.append(" ");
|
||||
else if (StringUtil.in(name, "p", "h1", "h2", "h3", "h4", "h5", "tr", "div"))
|
||||
sb.append(newline);
|
||||
}
|
||||
|
||||
public void tail(Node node, int depth) {
|
||||
String name = node.nodeName();
|
||||
if (StringUtil.in(name, "br", "dd", "dt", "p", "h1", "h2", "h3", "h4", "h5", "div"))
|
||||
sb.append(newline);
|
||||
else if (name.equals("a")) {
|
||||
String link = node.absUrl("href");
|
||||
if (!TextUtils.isEmpty(link)) {
|
||||
if (!links.contains(link))
|
||||
links.add(link);
|
||||
sb.append(" ").append(context.getString(R.string.title_link, link, links.size()));
|
||||
}
|
||||
} else if (name.equals("img")) {
|
||||
String link = node.absUrl("src");
|
||||
if (!TextUtils.isEmpty(link)) {
|
||||
if (!links.contains(link))
|
||||
links.add(link);
|
||||
sb.append(" ").append(context.getString(R.string.title_image, link, links.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (links.size() > 0)
|
||||
sb.append(newline).append(newline);
|
||||
for (int i = 0; i < links.size(); i++)
|
||||
sb.append(String.format("[%d] %s ", i + 1, links.get(i))).append(newline);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String sanitize(Context context, String html, boolean reply) {
|
||||
Document document = Jsoup.parse(html);
|
||||
HtmlHelper visitor = new HtmlHelper(context, reply);
|
||||
NodeTraversor.traverse(visitor, document.body());
|
||||
return visitor.toString();
|
||||
}
|
||||
}
|
||||
282
app/src/main/java/eu/faircode/email/MessageHelper.java
Normal file
282
app/src/main/java/eu/faircode/email/MessageHelper.java
Normal file
@@ -0,0 +1,282 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.Flags;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Multipart;
|
||||
import javax.mail.Part;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
||||
public class MessageHelper {
|
||||
private MimeMessage imessage;
|
||||
private String raw = null;
|
||||
|
||||
static Properties getSessionProperties() {
|
||||
Properties props = new Properties();
|
||||
|
||||
// https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#properties
|
||||
props.put("mail.imaps.ssl.checkserveridentity", "true");
|
||||
props.put("mail.imaps.ssl.trust", "*");
|
||||
props.put("mail.imaps.starttls.enable", "false");
|
||||
props.put("mail.imaps.timeout", "20000");
|
||||
props.put("mail.imaps.connectiontimeout", "20000");
|
||||
|
||||
// https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html#properties
|
||||
props.put("mail.smtps.ssl.checkserveridentity", "true");
|
||||
props.put("mail.smtps.ssl.trust", "*");
|
||||
props.put("mail.smtps.starttls.enable", "false");
|
||||
props.put("mail.smtps.starttls.required", "false");
|
||||
props.put("mail.smtps.auth", "true");
|
||||
props.put("mail.smtps.timeout", "20000");
|
||||
props.put("mail.smtps.connectiontimeout", "20000");
|
||||
|
||||
props.put("mail.smtp.ssl.checkserveridentity", "true");
|
||||
props.put("mail.smtp.ssl.trust", "*");
|
||||
props.put("mail.smtp.starttls.enable", "true");
|
||||
props.put("mail.smtp.starttls.required", "true");
|
||||
props.put("mail.smtp.auth", "true");
|
||||
props.put("mail.smtp.timeout", "20000");
|
||||
props.put("mail.smtp.connectiontimeout", "20000");
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
static MimeMessage from(EntityMessage message, Session isession) throws MessagingException {
|
||||
MimeMessage imessage = new MimeMessage(isession);
|
||||
|
||||
if (message.from != null)
|
||||
imessage.setFrom(MessageHelper.decodeAddresses(message.from)[0]);
|
||||
|
||||
if (message.to != null)
|
||||
imessage.setRecipients(Message.RecipientType.TO, MessageHelper.decodeAddresses(message.to));
|
||||
|
||||
if (message.cc != null)
|
||||
imessage.setRecipients(Message.RecipientType.CC, MessageHelper.decodeAddresses(message.to));
|
||||
|
||||
if (message.subject != null)
|
||||
imessage.setSubject(message.subject);
|
||||
|
||||
if (message.body != null)
|
||||
imessage.setText(message.body, null, "html");
|
||||
|
||||
imessage.setSentDate(new Date());
|
||||
|
||||
return imessage;
|
||||
}
|
||||
|
||||
static MimeMessage from(EntityMessage message, EntityMessage reply, Session isession) throws MessagingException {
|
||||
MimeMessage imessage = from(message, isession);
|
||||
imessage.addHeader("In-Reply-To", reply.msgid);
|
||||
imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid);
|
||||
return imessage;
|
||||
}
|
||||
|
||||
MessageHelper(MimeMessage message) {
|
||||
this.imessage = message;
|
||||
}
|
||||
|
||||
MessageHelper(String raw, Session isession) throws MessagingException {
|
||||
byte[] bytes = Base64.decode(raw, Base64.URL_SAFE);
|
||||
InputStream is = new ByteArrayInputStream(bytes);
|
||||
this.imessage = new MimeMessage(isession, is);
|
||||
}
|
||||
|
||||
String getMessageID() throws MessagingException {
|
||||
return imessage.getHeader("Message-ID", null);
|
||||
}
|
||||
|
||||
String[] getReferences() throws MessagingException {
|
||||
String refs = imessage.getHeader("References", null);
|
||||
return (refs == null ? new String[0] : refs.split("\\s+"));
|
||||
}
|
||||
|
||||
String getInReplyTo() throws MessagingException {
|
||||
return imessage.getHeader("In-Reply-To", null);
|
||||
}
|
||||
|
||||
String getThreadId(long uid) throws MessagingException {
|
||||
for (String ref : getReferences())
|
||||
if (!TextUtils.isEmpty(ref))
|
||||
return ref;
|
||||
String msgid = getMessageID();
|
||||
return (TextUtils.isEmpty(msgid) ? Long.toString(uid) : msgid);
|
||||
}
|
||||
|
||||
String getFrom() throws MessagingException, JSONException {
|
||||
return encodeAddresses(imessage.getFrom());
|
||||
}
|
||||
|
||||
String getTo() throws MessagingException, JSONException {
|
||||
return encodeAddresses(imessage.getRecipients(Message.RecipientType.TO));
|
||||
}
|
||||
|
||||
String getCc() throws MessagingException, JSONException {
|
||||
return encodeAddresses(imessage.getRecipients(Message.RecipientType.CC));
|
||||
}
|
||||
|
||||
String getReply() throws MessagingException, JSONException {
|
||||
return encodeAddresses(imessage.getReplyTo());
|
||||
}
|
||||
|
||||
static String encodeAddresses(Address[] addresses) throws JSONException {
|
||||
JSONArray jaddresses = new JSONArray();
|
||||
if (addresses != null)
|
||||
for (Address address : addresses)
|
||||
if (address instanceof InternetAddress) {
|
||||
String a = ((InternetAddress) address).getAddress();
|
||||
String p = ((InternetAddress) address).getPersonal();
|
||||
JSONObject jaddress = new JSONObject();
|
||||
if (a != null)
|
||||
jaddress.put("address", a);
|
||||
if (p != null)
|
||||
jaddress.put("personal", p);
|
||||
jaddresses.put(jaddress);
|
||||
}
|
||||
return jaddresses.toString();
|
||||
}
|
||||
|
||||
static Address[] decodeAddresses(String json) {
|
||||
List<Address> result = new ArrayList<>();
|
||||
try {
|
||||
JSONArray jaddresses = new JSONArray(json);
|
||||
for (int i = 0; i < jaddresses.length(); i++) {
|
||||
JSONObject jaddress = (JSONObject) jaddresses.get(i);
|
||||
if (jaddress.has("personal"))
|
||||
result.add(new InternetAddress(
|
||||
jaddress.getString("address"),
|
||||
jaddress.getString("personal")));
|
||||
else
|
||||
result.add(new InternetAddress(
|
||||
jaddress.getString("address")));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
return result.toArray(new Address[0]);
|
||||
}
|
||||
|
||||
String getHtml() throws MessagingException {
|
||||
return getHtml(imessage);
|
||||
}
|
||||
|
||||
static String getFormattedAddresses(String json) {
|
||||
try {
|
||||
List<String> addresses = new ArrayList<>();
|
||||
for (Address address : decodeAddresses(json))
|
||||
if (address instanceof InternetAddress) {
|
||||
InternetAddress a = (InternetAddress) address;
|
||||
String personal = a.getPersonal();
|
||||
if (TextUtils.isEmpty(personal))
|
||||
addresses.add(address.toString());
|
||||
else
|
||||
addresses.add(personal);
|
||||
} else
|
||||
addresses.add(address.toString());
|
||||
return TextUtils.join(", ", addresses);
|
||||
} catch (Throwable ex) {
|
||||
return ex.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private String getHtml(Part part) throws MessagingException {
|
||||
if (part.isMimeType("text/*"))
|
||||
try {
|
||||
String s = (String) part.getContent();
|
||||
if (part.isMimeType("text/plain"))
|
||||
s = "<pre>" + s.replaceAll("\\r?\\n", "<br />") + "</pre>";
|
||||
return s;
|
||||
} catch (IOException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (part.isMimeType("multipart/alternative")) {
|
||||
String text = null;
|
||||
try {
|
||||
Multipart mp = (Multipart) part.getContent();
|
||||
for (int i = 0; i < mp.getCount(); i++) {
|
||||
Part bp = mp.getBodyPart(i);
|
||||
if (bp.isMimeType("text/plain")) {
|
||||
if (text == null)
|
||||
text = getHtml(bp);
|
||||
} else if (bp.isMimeType("text/html")) {
|
||||
String s = getHtml(bp);
|
||||
if (s != null)
|
||||
return s;
|
||||
} else
|
||||
return getHtml(bp);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
if (part.isMimeType("multipart/*")) {
|
||||
try {
|
||||
Multipart mp = (Multipart) part.getContent();
|
||||
for (int i = 0; i < mp.getCount(); i++) {
|
||||
String s = getHtml(mp.getBodyPart(i));
|
||||
if (s != null)
|
||||
return s;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean getSeen() throws MessagingException {
|
||||
return imessage.isSet(Flags.Flag.SEEN);
|
||||
}
|
||||
|
||||
String getRaw() throws IOException, MessagingException {
|
||||
if (raw == null) {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
imessage.writeTo(os);
|
||||
raw = Base64.encodeToString(os.toByteArray(), Base64.URL_SAFE);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
87
app/src/main/java/eu/faircode/email/Provider.java
Normal file
87
app/src/main/java/eu/faircode/email/Provider.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.util.Log;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Provider {
|
||||
public String name;
|
||||
public String imap_host;
|
||||
public int imap_port;
|
||||
public String smtp_host;
|
||||
public int smtp_port;
|
||||
public boolean starttls;
|
||||
|
||||
private Provider() {
|
||||
}
|
||||
|
||||
Provider(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
static List<Provider> loadProfiles(Context context) {
|
||||
List<Provider> result = null;
|
||||
try {
|
||||
XmlResourceParser xml = context.getResources().getXml(R.xml.providers);
|
||||
int eventType = xml.getEventType();
|
||||
Provider provider = null;
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
if ("providers".equals(xml.getName()))
|
||||
result = new ArrayList<>();
|
||||
else if ("provider".equals(xml.getName())) {
|
||||
provider = new Provider();
|
||||
provider.name = xml.getAttributeValue(null, "name");
|
||||
} else if ("imap".equals(xml.getName())) {
|
||||
provider.imap_host = xml.getAttributeValue(null, "host");
|
||||
provider.imap_port = xml.getAttributeIntValue(null, "port", 0);
|
||||
} else if ("smtp".equals(xml.getName())) {
|
||||
provider.smtp_host = xml.getAttributeValue(null, "host");
|
||||
provider.smtp_port = xml.getAttributeIntValue(null, "port", 0);
|
||||
provider.starttls = xml.getAttributeBooleanValue(null, "starttls", false);
|
||||
} else
|
||||
throw new IllegalAccessException(xml.getName());
|
||||
} else if (eventType == XmlPullParser.END_TAG) {
|
||||
if ("provider".equals(xml.getName())) {
|
||||
result.add(provider);
|
||||
provider = null;
|
||||
}
|
||||
}
|
||||
|
||||
eventType = xml.next();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(Helper.TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
33
app/src/main/java/eu/faircode/email/ReceiverAutostart.java
Normal file
33
app/src/main/java/eu/faircode/email/ReceiverAutostart.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class ReceiverAutostart extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) ||
|
||||
Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction()))
|
||||
ServiceSynchronize.start(context);
|
||||
}
|
||||
}
|
||||
1024
app/src/main/java/eu/faircode/email/ServiceSynchronize.java
Normal file
1024
app/src/main/java/eu/faircode/email/ServiceSynchronize.java
Normal file
File diff suppressed because it is too large
Load Diff
25
app/src/main/java/eu/faircode/email/TupleAccountStats.java
Normal file
25
app/src/main/java/eu/faircode/email/TupleAccountStats.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class TupleAccountStats {
|
||||
public Integer accounts;
|
||||
public Integer operations;
|
||||
}
|
||||
38
app/src/main/java/eu/faircode/email/TupleFolderEx.java
Normal file
38
app/src/main/java/eu/faircode/email/TupleFolderEx.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class TupleFolderEx extends EntityFolder {
|
||||
public String accountName;
|
||||
public int messages;
|
||||
public int unseen;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof TupleFolderEx) {
|
||||
TupleFolderEx other = (TupleFolderEx) obj;
|
||||
return (super.equals(obj) &&
|
||||
this.accountName == null ? other.accountName == null : accountName.equals(other.accountName) &&
|
||||
this.messages == other.messages &&
|
||||
this.unseen == other.unseen);
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
39
app/src/main/java/eu/faircode/email/TupleMessageEx.java
Normal file
39
app/src/main/java/eu/faircode/email/TupleMessageEx.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class TupleMessageEx extends EntityMessage {
|
||||
public String folderName;
|
||||
public String folderType;
|
||||
public int count;
|
||||
public int unseen;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof TupleMessageEx) {
|
||||
TupleMessageEx other = (TupleMessageEx) obj;
|
||||
return (super.equals(obj) &&
|
||||
this.folderType.equals(other.folderType) &&
|
||||
this.count == other.count &&
|
||||
this.unseen == other.unseen);
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
}
|
||||
24
app/src/main/java/eu/faircode/email/TupleOperationEx.java
Normal file
24
app/src/main/java/eu/faircode/email/TupleOperationEx.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of Safe email.
|
||||
|
||||
Safe email 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.
|
||||
|
||||
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class TupleOperationEx extends EntityOperation {
|
||||
public Long uid;
|
||||
}
|
||||
Reference in New Issue
Block a user