Merge branch 'develop'

This commit is contained in:
houxg
2017-02-14 14:50:06 +08:00
47 changed files with 1499 additions and 514 deletions

View File

@@ -10,6 +10,7 @@ repositories {
mavenCentral()
jcenter()
maven { url "https://www.jitpack.io" }
maven { url "http://dl.bintray.com/piasy/maven" }
}
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
@@ -17,7 +18,7 @@ apply plugin: 'com.neenbedankt.android-apt'
def dbflow_version = "4.0.0-beta2"
def ciName = isEmpty(System.getenv("TRAVIS_TAG")) ? "Staging" : System.getenv("TRAVIS_TAG")
def ciCode = isEmpty(System.getenv("TRAVIS_BUILD_NUMBER")) ? 1000 : Integer.valueOf(System.getenv("TRAVIS_BUILD_NUMBER"))
def buglyPrdKey = isEmpty(System.getenv('BUGLY_PRD')) ? "" : System.getenv('BUGLY_PRD')
def isEmpty(String str) {
return str == null || "".equals(str);
}
@@ -56,6 +57,7 @@ android {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
buildConfigField "String", "BUGLY_KEY", String.format("\"%s\"", buglyPrdKey)
}
}
@@ -63,11 +65,8 @@ android {
def properties = new Properties();
if (buildType.isDebuggable()) {
properties.load(new FileInputStream(new File(projectDir.absolutePath + "/staging.properties")))
} else {
properties.load(new FileInputStream(new File(projectDir.absolutePath + "/production.properties")))
buildType.buildConfigField "String", "BUGLY_KEY", properties['BUGLY_KEY']
}
buildType.buildConfigField "String", "FLURRY_KEY", properties['FLURRY_KEY']
buildType.buildConfigField "String", "BUGLY_KEY", properties['BUGLY_KEY']
}
}
@@ -111,5 +110,6 @@ dependencies {
compile 'net.danlew:android.joda:2.9.5'
compile group: 'com.tencent.bugly', name: 'crashreport_upgrade', version: '1.2.1'
compile 'com.elvishew:xlog:1.3.0'
compile 'com.github.piasy:BigImageViewer:1.2.5'
compile 'com.github.piasy:GlideImageLoader:1.2.5'
}

View File

@@ -1,2 +0,0 @@
FLURRY_KEY="M6HD6WJPT9Y274MG2FSF"
BUGLY_KEY="cf1aa1ccff"

View File

@@ -45,6 +45,8 @@
<activity
android:name=".ui.AboutActivity"
android:label="@string/about" />
<activity
android:name=".ui.PictureViewerActivity" />
<service
android:name=".background.NoteSyncService"

View File

@@ -32,6 +32,10 @@
height: 100%;
}
img {
max-width: 100%;
}
</style>
</head>
@@ -184,6 +188,10 @@
var title = target.innerHTML;
e.preventDefault();
nativeCallbackHandler.linkTo(link);
} else if (target.tagName === 'IMG') {
var src = target.getAttribute('src');
e.preventDefault();
nativeCallbackHandler.onClickedImage(src);
}
}

View File

@@ -4,10 +4,13 @@ package org.houxg.leamonax;
import android.app.Application;
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
import com.elvishew.xlog.LogLevel;
import com.elvishew.xlog.XLog;
import com.facebook.stetho.Stetho;
import com.github.piasy.biv.BigImageViewer;
import com.github.piasy.biv.loader.glide.GlideImageLoader;
import com.raizlabs.android.dbflow.config.FlowConfig;
import com.raizlabs.android.dbflow.config.FlowManager;
import com.tencent.bugly.Bugly;
@@ -31,16 +34,20 @@ public class Leamonax extends Application {
super.onCreate();
mContext = this;
XLog.init(BuildConfig.DEBUG ? LogLevel.ALL : LogLevel.NONE);
initBugly();
if (!TextUtils.isEmpty(BuildConfig.BUGLY_KEY)) {
initBugly();
}
BigImageViewer.initialize(GlideImageLoader.with(this));
EventBus.builder()
.logNoSubscriberMessages(false)
.sendNoSubscriberEvent(false)
.throwSubscriberException(true)
.installDefaultEventBus();
FlowManager.init(new FlowConfig.Builder(this).build());
Stetho.initializeWithDefaults(this);
JodaTimeAndroid.init(this);
if (BuildConfig.DEBUG) {
Stetho.initializeWithDefaults(this);
}
}
private void initBugly() {

View File

@@ -0,0 +1,125 @@
package org.houxg.leamonax.adapter;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import org.houxg.leamonax.R;
import org.houxg.leamonax.model.Account;
import org.houxg.leamonax.model.Note;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import jp.wasabeef.glide.transformations.CropCircleTransformation;
public class AccountAdapter extends RecyclerView.Adapter<AccountAdapter.AccountHolder> {
private static final int TYPE_ADD = 345;
private static final int TYPE_ACCOUNT = 592;
private List<Account> mData;
private AccountAdapterListener mListener;
public AccountAdapter(AccountAdapterListener listener) {
mListener = listener;
}
public void load(List<Account> source) {
mData = source;
notifyDataSetChanged();
}
@Override
public AccountAdapter.AccountHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int layoutId = viewType == TYPE_ADD ? R.layout.item_add_account : R.layout.item_account;
View view = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
return new AccountHolder(view);
}
@Override
public int getItemViewType(int position) {
return position != getItemCount() - 1 ? TYPE_ACCOUNT : TYPE_ADD;
}
@Override
public void onBindViewHolder(AccountAdapter.AccountHolder holder, int position) {
if (getItemViewType(position) == TYPE_ADD) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onClickAddAccount();
}
}
});
} else {
final Account account = mData.get(position);
holder.emailTv.setText(account.getEmail());
holder.hostTv.setText(account.getHost());
if (!TextUtils.isEmpty(account.getAvatar())) {
Glide.with(holder.avatarIv.getContext())
.load(account.getAvatar())
.centerCrop()
.bitmapTransform(new CropCircleTransformation(holder.avatarIv.getContext()))
.into(holder.avatarIv);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mListener != null ) {
mListener.onClickAccount(v, account);
}
}
});
}
}
@Override
public int getItemCount() {
return mData == null ? 1 : mData.size() + 1;
}
public void delete(Note note) {
int index = mData.indexOf(note);
if (index >= 0) {
mData.remove(index);
notifyItemRemoved(index);
}
}
static class AccountHolder extends RecyclerView.ViewHolder {
View itemView;
@Nullable
@BindView(R.id.tv_email)
TextView emailTv;
@Nullable
@BindView(R.id.tv_host)
TextView hostTv;
@Nullable
@BindView(R.id.iv_avatar)
ImageView avatarIv;
public AccountHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
this.itemView = itemView;
}
}
public interface AccountAdapterListener {
void onClickAccount(View itemView, Account note);
void onClickAddAccount();
}
}

View File

@@ -22,6 +22,7 @@ import org.houxg.leamonax.service.AccountService;
import org.houxg.leamonax.utils.TimeUtils;
import org.houxg.leamonax.widget.NoteList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -34,12 +35,12 @@ import butterknife.ButterKnife;
public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.NoteHolder> {
private List<Note> mData;
private Map<String, String> mNotebookId2TitleMaps;
private NoteAdapterListener mListener;
private Pattern mTitleHighlight;
private int mCurrentType = NoteList.TYPE_SIMPLE;
private List<Long> mSelectedNotes = new ArrayList<>();
public NoteAdapter(NoteAdapterListener listener) {
mListener = listener;
@@ -63,6 +64,21 @@ public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.NoteHolder> {
mCurrentType = type;
}
public void setSelected(Note note, boolean isSelected) {
if (!isSelected ) {
mSelectedNotes.remove(note.getId());
notifyDataSetChanged();
} else if (!mSelectedNotes.contains(note.getId())){
mSelectedNotes.add(note.getId());
notifyDataSetChanged();
}
}
public void invalidateAllSelected() {
mSelectedNotes.clear();
notifyDataSetChanged();
}
@Override
public NoteAdapter.NoteHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
@@ -95,6 +111,7 @@ public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.NoteHolder> {
} else {
renderSimple(holder, note);
}
holder.container.setSelected(mSelectedNotes.contains(note.getId()));
}
private void renderDetail(NoteHolder holder, final Note note) {
@@ -146,7 +163,7 @@ public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.NoteHolder> {
});
}
private void renderSimple(NoteHolder holder, final Note note) {
private void renderSimple(final NoteHolder holder, final Note note) {
if (TextUtils.isEmpty(note.getTitle())) {
holder.titleTv.setText(R.string.untitled);
} else {
@@ -228,6 +245,8 @@ public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.NoteHolder> {
TextView updateTimeTv;
@BindView(R.id.tv_dirty)
TextView dirtyTv;
@BindView(R.id.container)
View container;
public NoteHolder(View itemView) {
super(itemView);

View File

@@ -32,22 +32,6 @@ public class TagAdapter extends RecyclerView.Adapter<TagAdapter.TagHolder> {
notifyDataSetChanged();
}
public void toggle() {
int size = getItemCount();
if (size == 0) {
mData = AppDataBase.getAllTags(AccountService.getCurrent().getUserId());
int newSize = getItemCount();
if (newSize != 0) {
notifyItemRangeInserted(0, newSize);
} else {
notifyDataSetChanged();
}
} else {
mData = new ArrayList<>();
notifyItemRangeRemoved(0, size);
}
}
@Override
public TagHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;

View File

@@ -32,7 +32,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Database(name = "leanote_db", version = 3)
@Database(name = "leanote_db", version = 4)
public class AppDataBase {
private static final String TAG = "AppDataBase:";
@@ -127,6 +127,48 @@ public class AppDataBase {
}
}
@Migration(version = 4, priority = 1, database = AppDataBase.class)
public static class AddColLastUseTime extends AlterTableMigration<Account> {
public AddColLastUseTime(Class<Account> table) {
super(table);
}
@Override
public void onPreMigrate() {
super.onPreMigrate();
addColumn(SQLiteType.INTEGER, "lastUseTime");
}
}
@Migration(version = 4, priority = 0, database = AppDataBase.class)
public static class UpdateLastUseTime extends BaseMigration {
@Override
public void migrate(DatabaseWrapper database) {
Cursor cursor = SQLite.select()
.from(Account.class)
.where(Account_Table.token.notEq(""))
.query(database);
if (cursor == null) {
return;
}
int idIndex = cursor.getColumnIndex("id");
while (cursor.moveToNext()) {
int id = cursor.getInt(idIndex);
Account account = SQLite.select()
.from(Account.class)
.where(Account_Table.id.eq(id))
.querySingle(database);
if (account != null) {
account.updateLastUseTime();
account.update(database);
}
}
cursor.close();
}
}
public static void deleteNoteByLocalId(long localId) {
SQLite.delete().from(Note.class)
.where(Note_Table.id.eq(localId))
@@ -196,21 +238,19 @@ public class AppDataBase {
}
public static Notebook getRecentNoteBook(String userId) {
List<Note> recentNotes = SQLite.select()
Note recentNotes = SQLite.select()
.from(Note.class)
.where(Note_Table.userId.eq(userId))
.and(Note_Table.notebookId.notEq(""))
.orderBy(Note_Table.updatedTime, false)
.queryList();
if (CollectionUtils.isNotEmpty(recentNotes)) {
for (Note note : recentNotes) {
Notebook notebook = getNotebookByServerId(note.getNoteBookId());
if (!notebook.isDeleted()) {
return notebook;
}
.querySingle();
if (recentNotes != null) {
Notebook notebook = getNotebookByServerId(recentNotes.getNoteBookId());
if (notebook != null && !notebook.isDeleted()) {
return notebook;
}
}
return SQLite.select()
.from(Notebook.class)
.where(Notebook_Table.userId.eq(userId))
@@ -286,9 +326,18 @@ public class AppDataBase {
return SQLite.select()
.from(Account.class)
.where(Account_Table.token.notEq(""))
.orderBy(Account_Table.lastUseTime, false)
.querySingle();
}
public static List<Account> getAccountListWithToken() {
return SQLite.select()
.from(Account.class)
.where(Account_Table.token.notEq(""))
.orderBy(Account_Table.lastUseTime, false)
.queryList();
}
public static List<Tag> getTagByNoteLocalId(long noteLocalId) {
return SQLite.select()
.from(Tag.class).as("T")

View File

@@ -80,6 +80,7 @@ public abstract class Editor {
void onFormatChanged(Map<Format, Object> enabledFormats);
void onCursorChanged(Map<Format, Object> enabledFormats);
void linkTo(String url);
void onClickedImage(String url);
}
protected class EditorClient extends WebViewClient {

View File

@@ -156,6 +156,11 @@ public class RichTextEditor extends Editor implements TinnyMceCallback.TinnyMceL
mListener.linkTo(url);
}
@Override
public void onClickedImage(String url) {
mListener.onClickedImage(url);
}
@Override
public void onCursorChanged(Map<Format, Object> enabledFormats) {
mListener.onCursorChanged(enabledFormats);

View File

@@ -25,6 +25,8 @@ public class TinnyMceCallback {
void linkTo(String url);
void onClickedImage(String url);
void onCursorChanged(Map<Editor.Format, Object> enabledFormats);
}
@@ -43,6 +45,13 @@ public class TinnyMceCallback {
}
}
@JavascriptInterface
public void onClickedImage(String url) {
if (mListener != null) {
mListener.onClickedImage(url);
}
}
@JavascriptInterface
public void onCursorChanged(String data) {
XLog.i(TAG + data);

View File

@@ -21,7 +21,6 @@ public class Account extends BaseModel {
@Column(name = "id")
@PrimaryKey(autoincrement = true)
@SerializedName("LocalUserId")
long localUserId;
@Column(name = "userId")
@SerializedName("UserId")
@@ -50,6 +49,8 @@ public class Account extends BaseModel {
int noteUsn;
@Column(name = "notebookUsn")
int notebookUsn;
@Column(name = "lastUseTime")
long lastUseTime;
@Deprecated
@Column(name = "lastUsn")
@@ -161,6 +162,10 @@ public class Account extends BaseModel {
this.notebookUsn = notebookUsn;
}
public void updateLastUseTime() {
lastUseTime = System.currentTimeMillis() / 1000;
}
@Override
public String toString() {
return "Account{" +

View File

@@ -63,7 +63,7 @@ public class ApiProvider {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
XLog.i(message);
XLog.d(message);
}
});
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

View File

@@ -1,13 +1,18 @@
package org.houxg.leamonax.service;
import com.raizlabs.android.dbflow.sql.language.Select;
import org.houxg.leamonax.database.AppDataBase;
import org.houxg.leamonax.model.Account;
import org.houxg.leamonax.model.Account_Table;
import org.houxg.leamonax.model.Authentication;
import org.houxg.leamonax.model.BaseResponse;
import org.houxg.leamonax.model.User;
import org.houxg.leamonax.network.ApiProvider;
import org.houxg.leamonax.utils.RetrofitUtils;
import java.util.List;
import rx.Observable;
public class AccountService {
@@ -24,7 +29,7 @@ public class AccountService {
return RetrofitUtils.create(ApiProvider.getInstance().getUserApi().getInfo(userId));
}
public static void saveToAccount(Authentication authentication, String host) {
public static long saveToAccount(Authentication authentication, String host) {
Account localAccount = AppDataBase.getAccount(authentication.getEmail(), host);
if (localAccount == null) {
localAccount = new Account();
@@ -35,6 +40,7 @@ public class AccountService {
localAccount.setUserId(authentication.getUserId());
localAccount.setUserName(authentication.getUserName());
localAccount.save();
return localAccount.getLocalUserId();
}
public static void saveToAccount(User user, String host) {
@@ -69,6 +75,17 @@ public class AccountService {
return AppDataBase.getAccountWithToken();
}
public static List<Account> getAccountList() {
return AppDataBase.getAccountListWithToken();
}
public static Account getAccountById(long id) {
return new Select()
.from(Account.class)
.where(Account_Table.id.eq(id))
.querySingle();
}
public static boolean isSignedIn() {
return getCurrent() != null;
}

View File

@@ -58,6 +58,20 @@ public class NoteFileService {
return SCHEME.equals(uri.getScheme()) && IMAGE_PATH_WITH_SLASH.equals(uri.getPath());
}
public static String getImagePath(Uri uri) {
String localId = uri.getQueryParameter("id");
NoteFile noteFile = AppDataBase.getNoteFileByLocalId(localId);
if (noteFile == null) {
return null;
}
if (!TextUtils.isEmpty(noteFile.getLocalPath())) {
File file = new File(noteFile.getLocalPath());
return file.isFile() ? noteFile.getLocalPath() : null;
} else {
return null;
}
}
public static InputStream getImage(String localId) {
NoteFile noteFile = AppDataBase.getNoteFileByLocalId(localId);
if (noteFile == null) {

View File

@@ -408,11 +408,11 @@ public class NoteService {
return localIds;
}
public static Observable<Void> deleteNote(final Note note) {
public static Observable<Note> deleteNote(final Note note) {
return Observable.create(
new Observable.OnSubscribe<Void>() {
new Observable.OnSubscribe<Note>() {
@Override
public void call(Subscriber<? super Void> subscriber) {
public void call(Subscriber<? super Note> subscriber) {
if (!subscriber.isUnsubscribed()) {
if (TextUtils.isEmpty(note.getNoteId())) {
AppDataBase.deleteNoteByLocalId(note.getId());
@@ -426,7 +426,7 @@ public class NoteService {
throw new IllegalStateException(response.getMsg());
}
}
subscriber.onNext(null);
subscriber.onNext(note);
subscriber.onCompleted();
}
}

View File

@@ -13,7 +13,7 @@ public class NotebookService {
private static final String TAG = "NotebookService:";
public static void addNotebook(String title, String parentNotebookId) {
public static Notebook addNotebook(String title, String parentNotebookId) {
Notebook notebook = RetrofitUtils.excute(ApiProvider.getInstance().getNotebookApi().addNotebook(title, parentNotebookId));
if (notebook == null) {
throw new IllegalStateException("Network error");
@@ -26,6 +26,7 @@ public class NotebookService {
account.save();
}
notebook.insert();
return notebook;
} else {
throw new IllegalStateException(notebook.getMsg());
}

View File

@@ -2,81 +2,57 @@ package org.houxg.leamonax.ui;
import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.elvishew.xlog.XLog;
import com.tencent.bugly.crashreport.CrashReport;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.houxg.leamonax.R;
import org.houxg.leamonax.adapter.NotebookAdapter;
import org.houxg.leamonax.adapter.TagAdapter;
import org.houxg.leamonax.background.NoteSyncService;
import org.houxg.leamonax.database.AppDataBase;
import org.houxg.leamonax.model.Account;
import org.houxg.leamonax.model.Note;
import org.houxg.leamonax.model.Notebook;
import org.houxg.leamonax.model.SyncEvent;
import org.houxg.leamonax.model.Tag;
import org.houxg.leamonax.model.User;
import org.houxg.leamonax.service.AccountService;
import org.houxg.leamonax.service.NotebookService;
import org.houxg.leamonax.ui.edit.NoteEditActivity;
import org.houxg.leamonax.utils.NetworkUtils;
import org.houxg.leamonax.utils.ToastUtils;
import java.util.Collections;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import jp.wasabeef.glide.transformations.CropCircleTransformation;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
public class MainActivity extends BaseActivity implements NotebookAdapter.NotebookAdapterListener, TagAdapter.TagAdapterListener, NoteFragment.OnSyncFinishListener {
public class MainActivity extends BaseActivity implements Navigation.Callback {
private static final String EXT_SHOULD_RELOAD = "ext_should_reload";
private static final String TAG_NOTE_FRAGMENT = "tag_note_fragment";
NoteFragment mNoteFragment;
NotebookAdapter mNotebookAdapter;
@BindView(R.id.rv_notebook)
RecyclerView mNotebookRv;
@BindView(R.id.drawer)
DrawerLayout mDrawerLayout;
@BindView(R.id.tv_email)
TextView mEmailTv;
@BindView(R.id.iv_avatar)
ImageView mAvatarIv;
@BindView(R.id.tv_user_name)
TextView mUserNameTv;
@BindView(R.id.iv_notebook_triangle)
View mNotebookTriangle;
@BindView(R.id.rl_notebook_list)
View mNotebookPanel;
@BindView(R.id.rv_tag)
RecyclerView mTagRv;
@BindView(R.id.iv_tag_triangle)
View mTagTriangle;
View mNavigationView;
@BindView(R.id.swiperefresh)
SwipeRefreshLayout mSwipeRefresh;
Navigation mNavigation;
public static Intent getOpenIntent(Context context, boolean shouldReload) {
Intent intent = new Intent(context, MainActivity.class);
@@ -93,42 +69,47 @@ public class MainActivity extends BaseActivity implements NotebookAdapter.Notebo
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu_white);
CrashReport.setUserId(AccountService.getCurrent().getUserId());
mNavigation = new Navigation(this);
mNavigation.init(this, mNavigationView);
boolean shouldReload = false;
if (savedInstanceState == null) {
mNoteFragment = NoteFragment.newInstance(getIntent().getBooleanExtra(EXT_SHOULD_RELOAD, false));
shouldReload = getIntent().getBooleanExtra(EXT_SHOULD_RELOAD, false);
mNoteFragment = NoteFragment.newInstance();
getSupportFragmentManager().beginTransaction().add(R.id.container, mNoteFragment, TAG_NOTE_FRAGMENT).commit();
} else {
mNoteFragment = (NoteFragment) getSupportFragmentManager().findFragmentByTag(TAG_NOTE_FRAGMENT);
}
mNoteFragment.setSyncFinishListener(this);
mEmailTv.setText(AccountService.getCurrent().getEmail());
initNotebookPanel();
initTagPanel();
refreshInfo();
fetchInfo();
mSwipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
syncNotes();
}
});
EventBus.getDefault().register(this);
if (shouldReload) {
mSwipeRefresh.postDelayed(new Runnable() {
@Override
public void run() {
XLog.i("fetching notes");
mSwipeRefresh.setRefreshing(true);
syncNotes();
}
}, 200);
}
}
private void initTagPanel() {
mTagRv.setLayoutManager(new LinearLayoutManager(this));
TagAdapter tagAdapter = new TagAdapter();
tagAdapter.setListener(this);
mTagRv.setAdapter(tagAdapter);
mTagTriangle.setTag(false);
}
private void initNotebookPanel() {
mNotebookRv.setLayoutManager(new LinearLayoutManager(this));
mNotebookAdapter = new NotebookAdapter();
mNotebookAdapter.setListener(this);
mNotebookRv.setAdapter(mNotebookAdapter);
mNotebookAdapter.refresh();
mNotebookTriangle.setTag(false);
}
@Override
protected void onResume() {
super.onResume();
refreshNotebookAndNotes();
private void syncNotes() {
if (!NetworkUtils.isNetworkAvailable()) {
ToastUtils.showNetworkUnavailable(MainActivity.this);
mSwipeRefresh.setRefreshing(false);
return;
}
NoteSyncService.startServiceForNote(MainActivity.this);
}
@Override
@@ -137,14 +118,16 @@ public class MainActivity extends BaseActivity implements NotebookAdapter.Notebo
return super.onCreateOptionsMenu(menu);
}
@Override
protected void onResume() {
super.onResume();
mNavigation.onResume();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.closeDrawer(GravityCompat.START, true);
} else {
mDrawerLayout.openDrawer(GravityCompat.START, true);
}
mNavigation.toggle();
return true;
} else if (item.getItemId() == R.id.action_search) {
Intent intent = new Intent(this, SearchActivity.class);
@@ -153,106 +136,21 @@ public class MainActivity extends BaseActivity implements NotebookAdapter.Notebo
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mNavigation.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onBackPressed() {
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.closeDrawer(GravityCompat.START);
if (mNavigation.isOpen()) {
mNavigation.close();
} else {
super.onBackPressed();
}
}
private void fetchInfo() {
AccountService.getInfo(AccountService.getCurrent().getUserId())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(User user) {
AccountService.saveToAccount(user, AccountService.getCurrent().getHost());
refreshInfo();
}
});
}
private void refreshInfo() {
Account account = AccountService.getCurrent();
mUserNameTv.setText(account.getUserName());
mEmailTv.setText(account.getEmail());
if (!TextUtils.isEmpty(account.getAvatar())) {
Glide.with(this)
.load(account.getAvatar())
.centerCrop()
.bitmapTransform(new CropCircleTransformation(this))
.into(mAvatarIv);
}
}
@Override
public void onClickedNotebook(Notebook notebook) {
mNoteFragment.loadFromNotebook(notebook.getId());
mDrawerLayout.closeDrawer(GravityCompat.START, true);
}
@Override
public void onClickedAddNotebook(final String parentNotebookId) {
View view = LayoutInflater.from(this).inflate(R.layout.dialog_sigle_edittext, null);
final EditText mEdit = (EditText) view.findViewById(R.id.edit);
new AlertDialog.Builder(this)
.setTitle(R.string.add_notebook)
.setView(view)
.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
addNotebook(mEdit.getText().toString(), parentNotebookId);
}
})
.show();
}
private void addNotebook(final String title, final String parentNotebookId) {
Observable.create(
new Observable.OnSubscribe<Void>() {
@Override
public void call(Subscriber<? super Void> subscriber) {
if (!subscriber.isUnsubscribed()) {
NotebookService.addNotebook(title, parentNotebookId);
subscriber.onNext(null);
subscriber.onCompleted();
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Void>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(Void isSucceed) {
mNotebookAdapter.refresh();
}
});
}
@OnClick(R.id.fab)
void clickedFab() {
Account account = AccountService.getCurrent();
@@ -268,78 +166,62 @@ public class MainActivity extends BaseActivity implements NotebookAdapter.Notebo
startActivity(intent);
}
@OnClick(R.id.rl_recent_notes)
void showRecentNote() {
mNoteFragment.loadRecentNotes();
mDrawerLayout.closeDrawer(GravityCompat.START, true);
@Override
public boolean onChangeAccount(Account account) {
account.updateLastUseTime();
account.update();
mNavigation.refresh();
mSwipeRefresh.setRefreshing(true);
syncNotes();
return true;
}
@OnClick(R.id.rl_about)
void clickedAbout() {
Intent intent = new Intent(this, AboutActivity.class);
startActivity(intent);
@Override
public boolean onShowNotes(Navigation.Mode mode) {
List<Note> notes;
switch (mode) {
case RECENT_NOTES:
notes = AppDataBase.getAllNotes(AccountService.getCurrent().getUserId());
break;
case NOTEBOOK:
notes = AppDataBase.getNotesFromNotebook(AccountService.getCurrent().getUserId(), mode.notebookId);
break;
case TAG:
notes = AppDataBase.getNotesByTagText(mode.tagText, AccountService.getCurrent().getUserId());
break;
default:
return false;
}
mNoteFragment.setNotes(notes);
return true;
}
@OnClick(R.id.rl_settings)
void clickedSettings() {
@Override
public void onClickSetting() {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
}
@OnClick(R.id.rl_notebook)
void toggleNotebook() {
boolean shouldShowNotebook = (boolean) mNotebookTriangle.getTag();
shouldShowNotebook = !shouldShowNotebook;
animateTriangle(mNotebookTriangle, shouldShowNotebook);
mNotebookPanel.setVisibility(shouldShowNotebook ? View.VISIBLE : View.GONE);
mNotebookTriangle.setTag(shouldShowNotebook);
@Override
public void onClickAbout() {
Intent intent = new Intent(this, AboutActivity.class);
startActivity(intent);
}
@OnClick(R.id.rl_tag)
void toggleTag() {
boolean shouldShowTag = (boolean) mTagTriangle.getTag();
shouldShowTag = !shouldShowTag;
animateTriangle(mTagTriangle, shouldShowTag);
((TagAdapter) mTagRv.getAdapter()).toggle();
mTagTriangle.setTag(shouldShowTag);
}
private void animateTriangle(View triangle, boolean isOpen) {
if (isOpen) {
triangle.animate()
.rotation(-180)
.setDuration(200)
.setInterpolator(new AccelerateDecelerateInterpolator())
.start();
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(SyncEvent event) {
XLog.i("RequestNotes rcv: isSucceed=" + event.isSucceed());
mSwipeRefresh.setRefreshing(false);
if (event.isSucceed()) {
mNavigation.refresh();
} else {
triangle.animate()
.rotation(0)
.setDuration(200)
.setInterpolator(new AccelerateDecelerateInterpolator())
.start();
ToastUtils.show(this, R.string.sync_notes_failed);
}
}
@Override
public void onClickedTag(Tag tag) {
mNoteFragment.loadFromTag(tag.getText());
mDrawerLayout.closeDrawer(GravityCompat.START, true);
}
@Override
public void onSyncFinish(SyncEvent event) {
refreshNotebookAndNotes();
}
private void refreshNotebookAndNotes() {
mNotebookAdapter.refresh();
if (mNoteFragment.getCurrentMode() == NoteFragment.Mode.NOTEBOOK) {
if (TextUtils.isEmpty(mNotebookAdapter.getCurrentParentId())) {
mNoteFragment.loadRecentNotes();
} else {
Notebook notebook = AppDataBase.getNotebookByServerId(mNotebookAdapter.getCurrentParentId());
mNoteFragment.loadFromNotebook(notebook.getId());
}
}
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}

View File

@@ -0,0 +1,487 @@
package org.houxg.leamonax.ui;
import android.animation.Animator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewGroupCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.elvishew.xlog.XLog;
import org.houxg.leamonax.R;
import org.houxg.leamonax.adapter.AccountAdapter;
import org.houxg.leamonax.adapter.NotebookAdapter;
import org.houxg.leamonax.adapter.TagAdapter;
import org.houxg.leamonax.model.Account;
import org.houxg.leamonax.model.Notebook;
import org.houxg.leamonax.model.Tag;
import org.houxg.leamonax.model.User;
import org.houxg.leamonax.service.AccountService;
import org.houxg.leamonax.service.NotebookService;
import org.houxg.leamonax.utils.DisplayUtils;
import org.houxg.leamonax.widget.TriangleView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import jp.wasabeef.glide.transformations.CropCircleTransformation;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import static android.app.Activity.RESULT_OK;
public class Navigation {
public static final int REQ_ADD_ACCOUNT = 55;
@BindView(R.id.drawer)
DrawerLayout mDrawerLayout;
@BindView(R.id.rl_info)
View mInfoPanel;
@BindView(R.id.tv_email)
TextView mEmailTv;
@BindView(R.id.iv_avatar)
ImageView mAvatarIv;
@BindView(R.id.tv_user_name)
TextView mUserNameTv;
@BindView(R.id.tr_account)
TriangleView mAccountTr;
@BindView(R.id.rv_account)
RecyclerView mAccountRv;
@BindView(R.id.rv_notebook)
RecyclerView mNotebookRv;
@BindView(R.id.tr_notebook)
TriangleView mNotebookTr;
@BindView(R.id.rv_tag)
RecyclerView mTagRv;
@BindView(R.id.tr_tag)
TriangleView mTagTr;
Drawable mAccountRipple;
private Callback mCallback;
private Activity mActivity;
private NotebookAdapter mNotebookAdapter;
private AccountAdapter mAccountAdapter;
private TagAdapter mTagAdapter;
private Mode mCurrentMode = Mode.RECENT_NOTES;
public Navigation(Callback callback) {
mCallback = callback;
}
public void init(Activity activity, View view) {
ButterKnife.bind(this, view);
mActivity = activity;
initAccountPanel();
initNotebookPanel();
initTagPanel();
fetchInfo();
}
private void fetchInfo() {
AccountService.getInfo(AccountService.getCurrent().getUserId())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(User user) {
AccountService.saveToAccount(user, AccountService.getCurrent().getHost());
refreshUserInfo(AccountService.getCurrent());
}
});
}
private void initAccountPanel() {
mAccountRipple = mInfoPanel.getBackground();
mAccountRipple.mutate();
mInfoPanel.setBackground(null);
mAccountRv.setLayoutManager(new LinearLayoutManager(mActivity));
mAccountAdapter = new AccountAdapter(new AccountAdapter.AccountAdapterListener() {
@Override
public void onClickAccount(View v, final Account account) {
animateChangeAccount(v, account);
}
@Override
public void onClickAddAccount() {
Intent intent = new Intent(mActivity, SignInActivity.class);
mActivity.startActivityForResult(intent, REQ_ADD_ACCOUNT);
}
}
);
mAccountRv.setAdapter(mAccountAdapter);
mAccountTr.setOnToggleListener(
new TriangleView.OnToggleListener() {
@Override
public void onToggle(boolean isChecked) {
mAccountRv.setVisibility(isChecked ? View.VISIBLE : View.GONE);
}
}
);
mAccountAdapter.load(AccountService.getAccountList());
}
private void animateChangeAccount(View v, final Account account) {
ImageView itemAvatar = (ImageView) v.findViewById(R.id.iv_avatar);
final ViewGroup rootView = (ViewGroup) mActivity.getWindow().getDecorView().getRootView();
float preSize = DisplayUtils.dp2px(30);
float postSize = DisplayUtils.dp2px(40);
int[] position = new int[2];
itemAvatar.getLocationInWindow(position);
int preLeft = position[0];
int preTop = position[1];
mAvatarIv.getLocationInWindow(position);
int postLeft = position[0];
int postTop = position[1];
final ImageView animateView = new ImageView(mActivity);
Drawable drawable = itemAvatar.getDrawable();
drawable.mutate();
animateView.setImageDrawable(drawable);
animateView.setLayoutParams(new ViewGroup.LayoutParams((int) preSize, (int) preSize));
animateView.setX(preLeft);
animateView.setY(preTop);
animateView.setPivotX(0);
animateView.setPivotY(0);
animateView.setAlpha(0.7f);
rootView.addView(animateView);
animateView.animate()
.scaleX(postSize / preSize)
.scaleY(postSize / preSize)
.translationX(postLeft)
.translationY(postTop)
.setDuration(350)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
//trigger quick ripple effect
mInfoPanel.setBackground(mAccountRipple);
mAccountRipple.setHotspot(mAvatarIv.getLeft() + mAvatarIv.getWidth() / 2, mAvatarIv.getTop() + mAvatarIv.getHeight() / 2);
mAccountRipple.setHotspotBounds(0, 0, mInfoPanel.getWidth(), mInfoPanel.getHeight());
mInfoPanel.setPressed(true);
mInfoPanel.setPressed(false);
refreshUserInfo(account);
rootView.removeView(animateView);
mInfoPanel.postDelayed(new Runnable() {
@Override
public void run() {
mInfoPanel.setBackground(null);
changeAccount(account);
}
}, 200);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
)
.start();
}
private void changeAccount(Account account) {
if (mCallback != null) {
if (mCallback.onChangeAccount(account)) {
mAccountTr.setChecked(false);
mTagTr.setChecked(false);
mNotebookTr.setChecked(false);
close();
}
}
}
private void initTagPanel() {
mTagRv.setLayoutManager(new LinearLayoutManager(mActivity));
mTagAdapter = new TagAdapter();
mTagAdapter.setListener(new TagAdapter.TagAdapterListener() {
@Override
public void onClickedTag(Tag tag) {
mCurrentMode = Mode.TAG;
mCurrentMode.setTagText(tag.getText());
if (mCallback != null) {
if (mCallback.onShowNotes(mCurrentMode)) {
close();
}
}
}
});
mTagRv.setAdapter(mTagAdapter);
mTagTr.setOnToggleListener(new TriangleView.OnToggleListener() {
@Override
public void onToggle(boolean isChecked) {
mTagRv.setVisibility(isChecked ? View.VISIBLE : View.GONE);
}
});
}
private void initNotebookPanel() {
mNotebookRv.setLayoutManager(new LinearLayoutManager(mActivity));
mNotebookAdapter = new NotebookAdapter();
mNotebookAdapter.setListener(new NotebookAdapter.NotebookAdapterListener() {
@Override
public void onClickedNotebook(Notebook notebook) {
mCurrentMode = Mode.NOTEBOOK;
mCurrentMode.setNotebookId(notebook.getId());
if (mCallback != null) {
if (mCallback.onShowNotes(mCurrentMode)) {
close();
}
}
}
@Override
public void onClickedAddNotebook(final String parentNotebookId) {
View view = LayoutInflater.from(mActivity).inflate(R.layout.dialog_sigle_edittext, null);
final EditText mEdit = (EditText) view.findViewById(R.id.edit);
new AlertDialog.Builder(mActivity)
.setTitle(R.string.add_notebook)
.setView(view)
.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
addNotebook(mEdit.getText().toString(), parentNotebookId);
}
})
.show();
}
});
mNotebookRv.setAdapter(mNotebookAdapter);
mNotebookAdapter.refresh();
mNotebookTr.setOnToggleListener(new TriangleView.OnToggleListener() {
@Override
public void onToggle(boolean isChecked) {
mNotebookRv.setVisibility(isChecked ? View.VISIBLE : View.GONE);
}
});
}
private void addNotebook(final String title, final String parentNotebookId) {
Observable.create(
new Observable.OnSubscribe<Notebook>() {
@Override
public void call(Subscriber<? super Notebook> subscriber) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(NotebookService.addNotebook(title, parentNotebookId));
subscriber.onCompleted();
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Notebook>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(Notebook isSucceed) {
mNotebookAdapter.refresh();
}
});
}
private void refreshUserInfo(Account account) {
mUserNameTv.setText(account.getUserName());
mEmailTv.setText(account.getEmail());
if (!TextUtils.isEmpty(account.getAvatar())) {
Glide.with(mActivity)
.load(account.getAvatar())
.centerCrop()
.bitmapTransform(new CropCircleTransformation(mActivity))
.into(mAvatarIv);
}
}
public void refresh() {
refreshUserInfo(AccountService.getCurrent());
mAccountAdapter.load(AccountService.getAccountList());
mTagAdapter.refresh();
mNotebookAdapter.refresh();
if (mCurrentMode == Mode.NOTEBOOK && TextUtils.isEmpty(mNotebookAdapter.getCurrentParentId())) {
mCurrentMode = Mode.RECENT_NOTES;
}
if (mCallback != null) {
if (mCallback.onShowNotes(mCurrentMode)) {
close();
}
}
}
public void toggle() {
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.closeDrawer(GravityCompat.START, true);
} else {
mDrawerLayout.openDrawer(GravityCompat.START, true);
}
}
public boolean isOpen() {
return mDrawerLayout.isDrawerOpen(GravityCompat.START);
}
public void open() {
if (!isOpen()) {
mDrawerLayout.openDrawer(GravityCompat.START, true);
}
}
public void close() {
if (isOpen()) {
mDrawerLayout.closeDrawer(GravityCompat.START, true);
}
}
public void onResume() {
refresh();
}
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQ_ADD_ACCOUNT) {
if (resultCode == RESULT_OK) {
Account account = AccountService.getAccountById(SignInActivity.getAccountIdFromData(data));
if (account != null) {
changeAccount(account);
}
}
return true;
}
return false;
}
@OnClick(R.id.rl_notebook)
void clickedNotebook() {
mNotebookTr.toggle();
}
@OnClick(R.id.rl_tag)
void clickedTag() {
mTagTr.toggle();
}
@OnClick(R.id.rl_settings)
void clickedSettings() {
if (mCallback != null) {
mCallback.onClickSetting();
}
}
@OnClick(R.id.iv_avatar)
void clickedAvatar() {
mAccountTr.toggle();
}
@OnClick(R.id.rl_about)
void clickedAbout() {
if (mCallback != null) {
mCallback.onClickAbout();
}
}
@OnClick(R.id.rl_recent_notes)
void clickedRecent() {
mCurrentMode = Mode.RECENT_NOTES;
if (mCallback != null) {
if (mCallback.onShowNotes(mCurrentMode)) {
close();
}
}
}
public interface Callback {
boolean onChangeAccount(Account account);
/**
* @param mode
* @return true if processed
*/
boolean onShowNotes(Mode mode);
void onClickSetting();
void onClickAbout();
}
public enum Mode {
RECENT_NOTES,
NOTEBOOK,
TAG;
long notebookId;
String tagText;
public void setNotebookId(long notebookId) {
this.notebookId = notebookId;
}
public void setTagText(String tagText) {
this.tagText = tagText;
}
@Override
public String toString() {
return name() + "{" +
"notebookId=" + notebookId +
", tagText='" + tagText + '\'' +
'}';
}
}
}

View File

@@ -7,10 +7,9 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -31,55 +30,46 @@ import org.houxg.leamonax.model.Note;
import org.houxg.leamonax.model.SyncEvent;
import org.houxg.leamonax.service.AccountService;
import org.houxg.leamonax.service.NoteService;
import org.houxg.leamonax.utils.DisplayUtils;
import org.houxg.leamonax.utils.ActionModeHandler;
import org.houxg.leamonax.utils.CollectionUtils;
import org.houxg.leamonax.utils.NetworkUtils;
import org.houxg.leamonax.utils.ToastUtils;
import org.houxg.leamonax.widget.DividerDecoration;
import org.houxg.leamonax.widget.NoteList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import butterknife.BindView;
import butterknife.ButterKnife;
import rx.Observable;
import rx.Observer;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
public class NoteFragment extends Fragment implements NoteAdapter.NoteAdapterListener {
import static org.houxg.leamonax.R.menu.note;
public class NoteFragment extends Fragment implements NoteAdapter.NoteAdapterListener, ActionModeHandler.Callback<Note> {
private static final String TAG = "NoteFragment:";
private static final String EXT_SCROLL_POSITION = "ext_scroll_position";
private static final String EXT_SHOULD_FETCH_NOTES = "ext_should_fetch_notes";
private Mode mCurrentMode = Mode.RECENT_NOTES;
@BindView(R.id.recycler_view)
RecyclerView mNoteListView;
@BindView(R.id.swiperefresh)
SwipeRefreshLayout mSwipeRefresh;
NoteList mNoteList;
List<Note> mNotes;
private OnSyncFinishListener mSyncFinishListener;
ActionModeHandler<Note> mActionModeHandler;
NoteList mNoteList;
public NoteFragment() {
}
public static NoteFragment newInstance(boolean shouldFetchNotes) {
public static NoteFragment newInstance() {
NoteFragment fragment = new NoteFragment();
Bundle bundle = new Bundle();
bundle.putBoolean(EXT_SHOULD_FETCH_NOTES, shouldFetchNotes);
fragment.setArguments(bundle);
return fragment;
}
public void setSyncFinishListener(OnSyncFinishListener listener) {
mSyncFinishListener = listener;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -89,7 +79,7 @@ public class NoteFragment extends Fragment implements NoteAdapter.NoteAdapterLis
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.note, menu);
inflater.inflate(note, menu);
}
@Override
@@ -106,43 +96,16 @@ public class NoteFragment extends Fragment implements NoteAdapter.NoteAdapterLis
View view = inflater.inflate(R.layout.fragment_note, container, false);
ButterKnife.bind(this, view);
mNoteList = new NoteList(container.getContext(), view, this);
mSwipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
syncNotes();
}
});
return view;
}
private void syncNotes() {
if (!NetworkUtils.isNetworkAvailable(getActivity())) {
ToastUtils.showNetworkUnavailable(getActivity());
mSwipeRefresh.setRefreshing(false);
return;
}
NoteSyncService.startServiceForNote(getActivity());
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
EventBus.getDefault().register(this);
refreshNotes();
if (savedInstanceState == null) {
if (getArguments().getBoolean(EXT_SHOULD_FETCH_NOTES, false)) {
mSwipeRefresh.postDelayed(new Runnable() {
@Override
public void run() {
XLog.i(TAG + "fetch notes");
mSwipeRefresh.setRefreshing(true);
syncNotes();
}
}, 200);
}
} else {
if (savedInstanceState != null) {
mNoteList.setScrollPosition(savedInstanceState.getInt(EXT_SCROLL_POSITION, 0));
}
mActionModeHandler = new ActionModeHandler<>(getActivity(), this, R.menu.delete);
}
@Override
@@ -151,81 +114,50 @@ public class NoteFragment extends Fragment implements NoteAdapter.NoteAdapterLis
outState.putInt(EXT_SCROLL_POSITION, mNoteList.getScrollPosition());
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
public void loadRecentNotes() {
mCurrentMode = Mode.RECENT_NOTES;
refreshNotes();
}
public void loadFromNotebook(long notebookId) {
mCurrentMode = Mode.NOTEBOOK;
mCurrentMode.notebookId = notebookId;
refreshNotes();
}
public void loadFromTag(String tagText) {
mCurrentMode = Mode.TAG;
mCurrentMode.tagText = tagText;
refreshNotes();
}
public Mode getCurrentMode() {
return mCurrentMode;
}
private void refreshNotes() {
XLog.i(TAG + "refresh:" + mCurrentMode);
switch (mCurrentMode) {
case RECENT_NOTES:
mNotes = AppDataBase.getAllNotes(AccountService.getCurrent().getUserId());
break;
case NOTEBOOK:
mNotes = AppDataBase.getNotesFromNotebook(AccountService.getCurrent().getUserId(), mCurrentMode.notebookId);
break;
case TAG:
mNotes = AppDataBase.getNotesByTagText(mCurrentMode.tagText, AccountService.getCurrent().getUserId());
}
public void setNotes(List<Note> notes) {
mNotes = notes;
Collections.sort(mNotes, new Note.UpdateTimeComparetor());
mNoteList.render(mNotes);
}
@Override
public void onClickNote(Note note) {
startActivity(NotePreviewActivity.getOpenIntent(getActivity(), note.getId()));
if (mActionModeHandler.isActionMode()) {
boolean isSelected = mActionModeHandler.chooseItem(note);
mNoteList.setSelected(note, isSelected);
} else {
startActivity(NotePreviewActivity.getOpenIntent(getActivity(), note.getId()));
}
}
@Override
public void onLongClickNote(final Note note) {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.delete_note)
.setMessage(String.format(Locale.US, getString(R.string.are_you_sure_to_delete_note), TextUtils.isEmpty(note.getTitle()) ? "this note" : note.getTitle()))
.setCancelable(true)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
deleteNote(note);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
boolean isSelected = mActionModeHandler.chooseItem(note);
mNoteList.setSelected(note, isSelected);
}
private void deleteNote(final Note note) {
NoteService.deleteNote(note)
private void deleteNote(final List<Note> notes) {
Observable.from(notes)
.flatMap(new Func1<Note, rx.Observable<Note>>() {
@Override
public rx.Observable<Note> call(Note note) {
return NoteService.deleteNote(note);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Void>() {
.subscribe(new Observer<Note>() {
@Override
public void onCompleted() {
@@ -234,56 +166,56 @@ public class NoteFragment extends Fragment implements NoteAdapter.NoteAdapterLis
@Override
public void onError(Throwable e) {
ToastUtils.show(getActivity(), R.string.delete_note_failed);
mNoteList.invalidateAllSelected();
}
@Override
public void onNext(Void aVoid) {
public void onNext(Note note) {
mNoteList.remove(note);
}
});
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(SyncEvent event) {
XLog.i(TAG + "RequestNotes rcv: isSucceed=" + event.isSucceed());
if (isAdded()) {
mSwipeRefresh.setRefreshing(false);
if (mSyncFinishListener != null) {
mSyncFinishListener.onSyncFinish(event);
}
refreshNotes();
if (!event.isSucceed()) {
ToastUtils.show(getActivity(), R.string.sync_notes_failed);
}
@Override
public boolean onAction(int actionId, List<Note> pendingItems) {
if (CollectionUtils.isEmpty(pendingItems)) {
ToastUtils.show(getActivity(), R.string.no_note_was_selected);
return false;
}
final List<Note> waitToDelete = new ArrayList<>();
for (int i = 0; i < pendingItems.size(); i++) {
waitToDelete.add(pendingItems.get(i));
}
new AlertDialog.Builder(getActivity())
.setTitle(R.string.delete_note)
.setMessage(R.string.are_you_sure_to_delete_note)
.setCancelable(true)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mActionModeHandler.dismiss();
deleteNote(waitToDelete);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mActionModeHandler.dismiss();
mNoteList.invalidateAllSelected();
}
})
.show();
return true;
}
@Override
public void onDestroy(List<Note> pendingItems) {
if (CollectionUtils.isNotEmpty(pendingItems)) {
mNoteList.invalidateAllSelected();
}
}
public interface OnSyncFinishListener {
void onSyncFinish(SyncEvent event);
}
public enum Mode {
RECENT_NOTES,
NOTEBOOK,
TAG;
long notebookId;
String tagText;
public void setNotebookId(long notebookId) {
this.notebookId = notebookId;
}
public void setTagText(String tagText) {
this.tagText = tagText;
}
@Override
public String toString() {
return name() + "{" +
"notebookId=" + notebookId +
", tagText='" + tagText + '\'' +
'}';
}
}
}

View File

@@ -166,7 +166,7 @@ public class NotePreviewActivity extends BaseActivity implements EditorFragment.
@OnClick(R.id.tv_revert)
void revert() {
if (!NetworkUtils.isNetworkAvailable(this)) {
if (!NetworkUtils.isNetworkAvailable()) {
ToastUtils.showNetworkUnavailable(this);
return;
}

View File

@@ -0,0 +1,52 @@
package org.houxg.leamonax.ui;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import com.github.piasy.biv.view.BigImageView;
import org.houxg.leamonax.R;
import java.io.File;
import butterknife.BindView;
import butterknife.ButterKnife;
public class PictureViewerActivity extends BaseActivity {
private static final String EXTRA_FILE_PATH = "extra.filePath";
@BindView(R.id.big_image)
BigImageView mBigImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_picture_viewer);
ButterKnife.bind(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(getResources().getColor(R.color.black));
}
String path = getIntent().getStringExtra(EXTRA_FILE_PATH);
if (TextUtils.isEmpty(path)) {
finish();
return;
}
Uri uri = Uri.fromFile(new File(path));
mBigImageView.showImage(uri);
}
public static Intent getOpenIntent(Context context, String path) {
Intent intent = new Intent(context, PictureViewerActivity.class);
intent.putExtra(EXTRA_FILE_PATH, path);
return intent;
}
}

View File

@@ -14,6 +14,7 @@ import android.transition.Slide;
import android.transition.Transition;
import android.transition.TransitionInflater;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
@@ -21,6 +22,8 @@ import org.houxg.leamonax.R;
import org.houxg.leamonax.adapter.NoteAdapter;
import org.houxg.leamonax.model.Note;
import org.houxg.leamonax.service.NoteService;
import org.houxg.leamonax.utils.ActionModeHandler;
import org.houxg.leamonax.utils.CollectionUtils;
import org.houxg.leamonax.utils.DisplayUtils;
import org.houxg.leamonax.utils.ToastUtils;
import org.houxg.leamonax.widget.DividerDecoration;
@@ -32,11 +35,13 @@ import java.util.Locale;
import butterknife.BindView;
import butterknife.ButterKnife;
import rx.Observable;
import rx.Observer;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
public class SearchActivity extends BaseActivity implements NoteAdapter.NoteAdapterListener {
public class SearchActivity extends BaseActivity implements NoteAdapter.NoteAdapterListener, ActionModeHandler.Callback<Note> {
private static final String EXT_SCROLL_POSITION = "ext_scroll_position";
@@ -49,7 +54,7 @@ public class SearchActivity extends BaseActivity implements NoteAdapter.NoteAdap
List<Note> mNotes = new ArrayList<>();
private NoteAdapter mAdapter;
private ActionModeHandler<Note> mActionModeHandler;
private float mScrollPosition;
@Override
@@ -62,12 +67,12 @@ public class SearchActivity extends BaseActivity implements NoteAdapter.NoteAdap
ButterKnife.bind(this);
initToolBar(mToolbar, true);
setTitle("");
mActionModeHandler = new ActionModeHandler<>(this, this, R.menu.delete);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
mNoteListView.setLayoutManager(layoutManager);
mNoteListView.setItemAnimator(new DefaultItemAnimator());
mNoteListView.addItemDecoration(new DividerDecoration(DisplayUtils.dp2px(8)));
mAdapter = new NoteAdapter(this);
mNoteListView.setAdapter(mAdapter);
mNoteListView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@@ -139,36 +144,31 @@ public class SearchActivity extends BaseActivity implements NoteAdapter.NoteAdap
@Override
public void onClickNote(Note note) {
startActivity(NotePreviewActivity.getOpenIntent(this, note.getId()));
if (mActionModeHandler.isActionMode()) {
boolean isSelected = mActionModeHandler.chooseItem(note);
mAdapter.setSelected(note, isSelected);
} else {
startActivity(NotePreviewActivity.getOpenIntent(this, note.getId()));
}
}
@Override
public void onLongClickNote(final Note note) {
new AlertDialog.Builder(this)
.setTitle(R.string.delete_note)
.setMessage(String.format(Locale.US, getString(R.string.are_you_sure_to_delete_note), TextUtils.isEmpty(note.getTitle()) ? "this note" : note.getTitle()))
.setCancelable(true)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
deleteNote(note);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
boolean isSelected = mActionModeHandler.chooseItem(note);
mAdapter.setSelected(note, isSelected);
}
private void deleteNote(final Note note) {
NoteService.deleteNote(note)
private void deleteNote(final List<Note> notes) {
Observable.from(notes)
.flatMap(new Func1<Note, Observable<Note>>() {
@Override
public rx.Observable<Note> call(Note note) {
return NoteService.deleteNote(note);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Void>() {
.subscribe(new Observer<Note>() {
@Override
public void onCompleted() {
@@ -180,10 +180,50 @@ public class SearchActivity extends BaseActivity implements NoteAdapter.NoteAdap
}
@Override
public void onNext(Void aVoid) {
public void onNext(Note note) {
mAdapter.delete(note);
}
});
}
@Override
public boolean onAction(int actionId, List<Note> pendingItems) {
if (CollectionUtils.isEmpty(pendingItems)) {
ToastUtils.show(this, R.string.no_note_was_selected);
return false;
}
final List<Note> waitToDelete = new ArrayList<>();
for (int i = 0; i < pendingItems.size(); i++) {
waitToDelete.add(pendingItems.get(i));
}
new AlertDialog.Builder(this)
.setTitle(R.string.delete_note)
.setMessage(R.string.are_you_sure_to_delete_note)
.setCancelable(true)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mActionModeHandler.dismiss();
deleteNote(waitToDelete);
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mActionModeHandler.dismiss();
mAdapter.invalidateAllSelected();
}
})
.show();
return true;
}
@Override
public void onDestroy(List<Note> pendingItems) {
if (CollectionUtils.isNotEmpty(pendingItems)) {
mAdapter.invalidateAllSelected();
}
}
}

View File

@@ -31,6 +31,8 @@ import org.houxg.leamonax.model.Tag_Table;
import org.houxg.leamonax.service.AccountService;
import org.houxg.leamonax.utils.ToastUtils;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
@@ -99,9 +101,12 @@ public class SettingsActivity extends BaseActivity {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
AccountService.logout();
Intent intent = new Intent(SettingsActivity.this, SignInActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
List<Account> remainingAccount = AccountService.getAccountList();
if (remainingAccount.size() == 0) {
Intent intent = new Intent(SettingsActivity.this, SignInActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
finish();
}
})

View File

@@ -37,6 +37,8 @@ import rx.schedulers.Schedulers;
public class SignInActivity extends BaseActivity implements TextWatcher {
public static final String ACTION_ADD_ACCOUNT = "action.addAccount:";
private static final String EXTRA_ACCOUNT_LOCAL_ID = "extra.account.LocalId";
private static final String TAG = "SignInActivity:";
private static final String LEANOTE_HOST = "https://leanote.com";
@@ -66,12 +68,13 @@ public class SignInActivity extends BaseActivity implements TextWatcher {
@BindView(R.id.rl_sign_up)
View mSignUpPanel;
@BindView(R.id.tv_example)
TextView mEampleTv;
TextView mExampleTv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_signin);
setResult(RESULT_CANCELED);
ButterKnife.bind(this);
mEmailEt.addTextChangedListener(this);
mPasswordEt.addTextChangedListener(this);
@@ -103,7 +106,7 @@ public class SignInActivity extends BaseActivity implements TextWatcher {
@Override
public void afterTextChanged(Editable s) {
mEampleTv.setText(String.format(Locale.US, "For example, login api will be:\n%s/api/login", s.toString()));
mExampleTv.setText(String.format(Locale.US, "For example, login api will be:\n%s/api/login", s.toString()));
}
});
}
@@ -126,7 +129,7 @@ public class SignInActivity extends BaseActivity implements TextWatcher {
void switchHost() {
boolean isCustomHost = !(boolean) mCustomHostBtn.getTag();
mCustomHostBtn.setTag(isCustomHost);
mEampleTv.setVisibility(isCustomHost ? View.VISIBLE : View.GONE);
mExampleTv.setVisibility(isCustomHost ? View.VISIBLE : View.GONE);
if (isCustomHost) {
mCustomHostBtn.setText(R.string.use_leanote_host);
mHostEt.animate()
@@ -215,11 +218,17 @@ public class SignInActivity extends BaseActivity implements TextWatcher {
@Override
public void onNext(Authentication authentication) {
if (authentication.isOk()) {
AccountService.saveToAccount(authentication, host);
Intent intent = MainActivity.getOpenIntent(SignInActivity.this, true);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
long localId = AccountService.saveToAccount(authentication, host);
if (ACTION_ADD_ACCOUNT.equals(getIntent().getAction())) {
Intent intent = new Intent();
intent.putExtra(EXTRA_ACCOUNT_LOCAL_ID, localId);
setResult(RESULT_OK, intent);
} else {
Intent intent = MainActivity.getOpenIntent(SignInActivity.this, true);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
}
} else {
ToastUtils.show(SignInActivity.this, R.string.email_or_password_incorrect);
}
@@ -294,6 +303,19 @@ public class SignInActivity extends BaseActivity implements TextWatcher {
});
}
/**
*
* @param data
* @return account local id or -1
*/
public static long getAccountIdFromData(Intent data) {
if (data == null) {
return -1;
} else {
return data.getLongExtra(EXTRA_ACCOUNT_LOCAL_ID, -1);
}
}
private Observable<String> initHost() {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override

View File

@@ -34,11 +34,14 @@ import org.houxg.leamonax.R;
import org.houxg.leamonax.editor.Editor;
import org.houxg.leamonax.editor.MarkdownEditor;
import org.houxg.leamonax.editor.RichTextEditor;
import org.houxg.leamonax.service.NoteFileService;
import org.houxg.leamonax.ui.PictureViewerActivity;
import org.houxg.leamonax.utils.CollectionUtils;
import org.houxg.leamonax.utils.DialogUtils;
import org.houxg.leamonax.utils.OpenUtils;
import org.houxg.leamonax.widget.ToggleImageButton;
import java.io.File;
import java.util.List;
import java.util.Map;
@@ -459,6 +462,14 @@ public class EditorFragment extends Fragment implements Editor.EditorListener {
OpenUtils.openUrl(getActivity(), url);
}
@Override
public void onClickedImage(String url) {
String path = NoteFileService.getImagePath(Uri.parse(url));
if (!TextUtils.isEmpty(path)) {
startActivity(PictureViewerActivity.getOpenIntent(getActivity(), path));
}
}
private void refreshFormatStatus(Map<Editor.Format, Object> formatStatus) {
for (Map.Entry<Editor.Format, Object> entry : formatStatus.entrySet()) {
switch (entry.getKey()) {

View File

@@ -0,0 +1,106 @@
package org.houxg.leamonax.utils;
import android.app.Activity;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.MenuRes;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import org.houxg.leamonax.R;
import java.util.ArrayList;
import java.util.List;
public class ActionModeHandler<T> {
private List<T> mPendingItems;
private int mMenuId;
private Callback<T> mCallback;
private ActionMode mActionMode;
private Activity mContext;
private ActionMode.Callback mActionCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(mMenuId, menu);
Drawable drawable = menu.findItem(R.id.action_delete).getIcon();
if (drawable != null) {
// If we don't mutate the drawable, then all drawable's with this id will have a color
// filter applied to it.
drawable.mutate();
drawable.setColorFilter(mContext.getResources().getColor(R.color.colorPrimaryDark), PorterDuff.Mode.SRC_ATOP);
drawable.setAlpha(255);
}
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
boolean isProceed = mCallback.onAction(item.getItemId(), mPendingItems);
if (isProceed) {
mPendingItems.clear();
}
return isProceed;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mContext.getWindow().setStatusBarColor(mContext.getResources().getColor(R.color.colorPrimary));
}
mCallback.onDestroy(mPendingItems);
}
};
public ActionModeHandler(Activity activity, Callback<T> callback, @MenuRes int menuId) {
mContext = activity;
mMenuId = menuId;
mCallback = callback;
}
/**
*
* @param item
* @return true if the item is in pending list, false for others.
*/
public boolean chooseItem(T item) {
if (isActionMode()) {
if (mPendingItems.contains(item)) {
mPendingItems.remove(item);
return false;
} else {
mPendingItems.add(item);
return true;
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mContext.getWindow().setStatusBarColor(mContext.getResources().getColor(R.color.colorPrimaryDark));
}
mActionMode = mContext.startActionMode(mActionCallback);
mPendingItems = new ArrayList<>();
mPendingItems.add(item);
return true;
}
}
public void dismiss() {
mActionMode.finish();
}
public boolean isActionMode() {
return mActionMode != null;
}
public interface Callback<T> {
boolean onAction(int actionId, List<T> pendingItems);
void onDestroy(List<T> pendingItems);
}
}

View File

@@ -22,13 +22,13 @@ public class NetworkUtils {
return cm.getActiveNetworkInfo();
}
public static boolean isNetworkAvailable(Context context) {
NetworkInfo info = getActiveNetworkInfo(context);
public static boolean isNetworkAvailable() {
NetworkInfo info = getActiveNetworkInfo(Leamonax.getContext());
return (info != null && info.isConnected());
}
public static void checkNetwork() throws NetworkUnavailableException {
if (!isNetworkAvailable(Leamonax.getContext())) {
if (!isNetworkAvailable()) {
throw new NetworkUnavailableException();
}
}

View File

@@ -74,10 +74,18 @@ public class NoteList {
mAdapter.delete(note);
}
public void setSelected(Note note, boolean isSelected) {
mAdapter.setSelected(note, isSelected);
}
public int getScrollPosition() {
return mScrollPosition;
}
public void invalidateAllSelected() {
mAdapter.invalidateAllSelected();
}
public void setScrollPosition(int position) {
mScrollPosition = position;
mNoteListView.scrollTo(0, position);

View File

@@ -0,0 +1,65 @@
package org.houxg.leamonax.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.ImageView;
import org.houxg.leamonax.R;
public class TriangleView extends ImageView {
private static final int DURATION = 200;
private boolean mIsChecked = false;
OnToggleListener mListener;
public TriangleView(Context context, AttributeSet attrs) {
super(context, attrs);
setImageResource(R.drawable.ic_triangle);
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mIsChecked = !mIsChecked;
if (mIsChecked) {
animate().rotation(-180)
.setDuration(DURATION)
.setInterpolator(new AccelerateDecelerateInterpolator())
.start();
} else {
animate().rotation(0)
.setDuration(DURATION)
.setInterpolator(new AccelerateDecelerateInterpolator())
.start();
}
if (mListener != null) {
mListener.onToggle(mIsChecked);
}
}
});
}
public void setOnToggleListener(OnToggleListener listener) {
mListener = listener;
}
public void toggle() {
performClick();
}
public void setChecked(boolean isChecked) {
if (mIsChecked == isChecked) {
return;
}
int rotate = isChecked ? -180 : 0;
setRotation(rotate);
mIsChecked = isChecked;
if (mListener != null) {
mListener.onToggle(mIsChecked);
}
}
public interface OnToggleListener {
void onToggle(boolean isChecked);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@color/divider_light"/>
<item android:drawable="@color/transparent"/>
</selector>

View File

@@ -73,7 +73,7 @@
style="@style/SettingsStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GNU GENERAL PUBLIC LICENSE V3" />
android:text="@string/gnu_general_public_license_v3" />
</LinearLayout>
@@ -93,13 +93,13 @@
style="@style/SettingsStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Leanote" />
android:text="@string/leanote" />
<TextView
style="@style/SettingsStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="https://material.io/icons/" />
android:text="@string/https_material_io_icons" />
</LinearLayout>
@@ -114,7 +114,7 @@
style="@style/SettingsSecondaryText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Github" />
android:text="@string/github" />
</LinearLayout>
@@ -151,7 +151,7 @@
style="@style/SettingsSecondaryText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Generate random note" />
android:text="@string/generate_random_note" />
</LinearLayout>

View File

@@ -17,10 +17,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/container"
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swiperefresh"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_about"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
tools:context="org.houxg.leamonax.ui.AboutActivity">
<com.github.piasy.biv.view.BigImageView
android:id="@+id/big_image"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@@ -5,16 +5,9 @@
android:orientation="vertical"
android:background="#EEEEEE">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swiperefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:gravity="center_vertical"
android:foreground="?android:attr/selectableItemBackground"
android:padding="16dp">
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="30dp"
android:layout_height="30dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="8dp">
<TextView
android:id="@+id/tv_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
tools:text="test@leanote.com"/>
<TextView
android:id="@+id/tv_host"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/hint_text_light"
tools:text="https://leanote.com"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_add_note_book"/>
<TextView
android:id="@+id/tv_add_account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/add_account"
android:textSize="14sp" />
</LinearLayout>

View File

@@ -5,9 +5,11 @@
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:foreground="?android:attr/selectableItemBackground"
android:background="@drawable/note_selector"
android:orientation="vertical">
<LinearLayout

View File

@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:foreground="?android:attr/selectableItemBackground"
android:background="@drawable/note_selector"
android:orientation="vertical">
<LinearLayout

View File

@@ -9,6 +9,8 @@
android:orientation="vertical">
<RelativeLayout
android:id="@+id/rl_info"
android:background="?android:attr/selectableItemBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@@ -18,22 +20,29 @@
android:id="@+id/iv_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/iv_other_account"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentEnd="true"
android:layout_alignTop="@id/iv_avatar"
android:scaleType="centerCrop" />
<LinearLayout
android:id="@+id/ll_account_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/iv_avatar"
android:layout_centerVertical="true"
android:layout_marginStart="8dp"
android:layout_toEndOf="@id/iv_avatar"
android:orientation="vertical">
<TextView
android:id="@+id/tv_user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:textColor="@color/secondary_text_light"
android:textSize="14sp"
tools:text="exercitation" />
@@ -48,16 +57,30 @@
</LinearLayout>
<org.houxg.leamonax.widget.TriangleView
android:id="@+id/tr_account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/ll_account_info"
android:layout_alignParentEnd="true"
android:padding="8dp" />
</RelativeLayout>
<include layout="@layout/divider" />
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_account"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<RelativeLayout
android:id="@+id/rl_recent_notes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground">
android:background="?android:attr/selectableItemBackground"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
@@ -105,29 +128,21 @@
android:textColor="@color/hint_text_light"
android:textSize="14sp" />
<ImageView
android:id="@+id/iv_notebook_triangle"
<org.houxg.leamonax.widget.TriangleView
android:id="@+id/tr_notebook"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:padding="8dp"
android:src="@drawable/ic_triangle" />
android:padding="8dp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_notebook_list"
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_notebook"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_notebook"
android:layout_width="240dp"
android:layout_height="match_parent" />
</RelativeLayout>
android:visibility="gone" />
<RelativeLayout
android:id="@+id/rl_tag"
@@ -155,20 +170,20 @@
android:textColor="@color/hint_text_light"
android:textSize="14sp" />
<ImageView
android:id="@+id/iv_tag_triangle"
<org.houxg.leamonax.widget.TriangleView
android:id="@+id/tr_tag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:padding="8dp"
android:src="@drawable/ic_triangle" />
android:padding="8dp" />
</RelativeLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_tag"
android:layout_width="240dp"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:visibility="gone" />
<View
android:layout_width="wrap_content"
@@ -181,8 +196,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground">
android:background="?android:attr/selectableItemBackground"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
@@ -207,8 +222,8 @@
android:id="@+id/rl_about"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground">
android:background="?android:attr/selectableItemBackground"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_delete"
android:icon="@drawable/ic_delete"
android:title="@string/delete"
app:showAsAction="always"/>
</menu>

View File

@@ -4,7 +4,7 @@
<string name="add_notebook">添加笔记本</string>
<string name="apply">应用</string>
<string name="are_you_sure_to_delete_all_data_in_this_account">你确定要删除该账户所有的数据吗?</string>
<string name="are_you_sure_to_delete_note">你确定要删除%s?</string>
<string name="are_you_sure_to_delete_note">你确定要删除这些笔记吗?</string>
<string name="are_your_sure_to_log_out">你确定要登出吗?</string>
<string name="avatar">头像</string>
<string name="cancel">取消</string>
@@ -98,4 +98,10 @@
<string name="continue_text">继续</string>
<string name="next_time">下次再说</string>
<string name="cant_open_url">无法打开该链接</string>
<string name="delete">删除</string>
<string name="no_note_was_selected">请选择笔记</string>
<string name="view_type">视图类型</string>
<string name="add_account">添加账户</string>
<string name="generate_random_note">生成随机笔记</string>
<string name="leanote">蚂蚁笔记</string>
</resources>

View File

@@ -14,4 +14,7 @@
<color name="hint_text_light">#61000000</color>
<color name="divider_light">#1f000000</color>
<color name="navigation">#F5F5F5</color>
<color name="transparent">#00000000</color>
<color name="white">#FFFFFF</color>
<color name="black">#000</color>
</resources>

View File

@@ -11,7 +11,7 @@
<string name="link">Link</string>
<string name="confirm">Confirm</string>
<string name="cancel">Cancel</string>
<string name="are_you_sure_to_delete_note">Are you sure to delete %s?</string>
<string name="are_you_sure_to_delete_note">Are you sure to delete these notes?</string>
<string name="delete_note">Delete note</string>
<string name="yes">Yes</string>
<string name="no">No</string>
@@ -83,8 +83,8 @@
<string name="continue_download">Continue download</string>
<string name="continue_text">Continue</string>
<string name="download_error">Download failed</string>
<string name="download_successful">Dowload finished</string>
<string name="downloading">Dowloading</string>
<string name="download_successful">Download finished</string>
<string name="downloading">Downloading</string>
<string name="what_s_new">What\'s new</string>
<string name="file_size">File size</string>
<string name="have_new_version">Have new version</string>
@@ -98,5 +98,13 @@
<string name="your_are_the_latest_version">You are using latest version</string>
<string name="cant_open_url">Can\'t open this url</string>
<string name="view_type">View type</string>
<string name="delete">Delete</string>
<string name="no_note_was_selected">No note was selected</string>
<string name="add_account">Add account</string>
<string name="leanote">Leanote</string>
<string name="https_material_io_icons" translatable="false">https://material.io/icons/</string>
<string name="gnu_general_public_license_v3" translatable="false">GNU GENERAL PUBLIC LICENSE V3</string>
<string name="github" translatable="false">Github</string>
<string name="generate_random_note">Generate random note</string>
</resources>

View File

@@ -7,8 +7,15 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:actionMenuTextColor">@color/menu_text</item>
<item name="windowActionModeOverlay">true</item>
<item name="windowActionBarOverlay">true</item>
<item name="android:homeAsUpIndicator">@drawable/ic_arrow_back_white</item>
<item name="actionModeStyle">@style/CustomActionMode</item>
</style>
<style name="CustomActionMode" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/white</item>
<item name="height">?attr/actionBarSize</item>
</style>
<style name="SettingsStatus">