@ -0,0 +1,18 @@
|
||||
package net.micode.notes;
|
||||
|
||||
import android.app.Application;
|
||||
import net.micode.notes.data.ThemeRepository;
|
||||
import com.google.android.material.color.DynamicColors;
|
||||
|
||||
public class NotesApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
// Apply Dynamic Colors (Material You) if available
|
||||
DynamicColors.applyToActivitiesIfAvailable(this);
|
||||
|
||||
// Apply saved theme preference
|
||||
ThemeRepository repository = new ThemeRepository(this);
|
||||
ThemeRepository.applyTheme(repository.getThemeMode());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package net.micode.notes.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Typeface;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
public class FontManager {
|
||||
public static final String PREF_FONT_FAMILY = "pref_font_family";
|
||||
|
||||
private final SharedPreferences mPrefs;
|
||||
private static FontManager sInstance;
|
||||
|
||||
private FontManager(Context context) {
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
public static synchronized FontManager getInstance(Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new FontManager(context.getApplicationContext());
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void applyFont(TextView textView) {
|
||||
String fontValue = mPrefs.getString(PREF_FONT_FAMILY, "default");
|
||||
Typeface typeface = getTypeface(fontValue);
|
||||
if (typeface != null) {
|
||||
textView.setTypeface(typeface);
|
||||
} else {
|
||||
textView.setTypeface(Typeface.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
private Typeface getTypeface(String fontValue) {
|
||||
switch (fontValue) {
|
||||
case "serif":
|
||||
return Typeface.SERIF;
|
||||
case "sans-serif":
|
||||
return Typeface.SANS_SERIF;
|
||||
case "monospace":
|
||||
return Typeface.MONOSPACE;
|
||||
case "cursive":
|
||||
// Android doesn't have a built-in cursive typeface constant,
|
||||
// but we can try to load sans-serif-light or similar as a placeholder,
|
||||
// or load from assets if we had custom fonts.
|
||||
// For now, let's map it to serif-italic style if possible or just serif.
|
||||
return Typeface.create(Typeface.SERIF, Typeface.ITALIC);
|
||||
case "default":
|
||||
default:
|
||||
return Typeface.DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package net.micode.notes.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
public class ThemeRepository {
|
||||
private static final String PREF_THEME_MODE = "pref_theme_mode";
|
||||
public static final String THEME_MODE_SYSTEM = "system";
|
||||
public static final String THEME_MODE_LIGHT = "light";
|
||||
public static final String THEME_MODE_DARK = "dark";
|
||||
|
||||
private final SharedPreferences mPrefs;
|
||||
|
||||
public ThemeRepository(Context context) {
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
public String getThemeMode() {
|
||||
return mPrefs.getString(PREF_THEME_MODE, THEME_MODE_SYSTEM);
|
||||
}
|
||||
|
||||
public void setThemeMode(String mode) {
|
||||
mPrefs.edit().putString(PREF_THEME_MODE, mode).apply();
|
||||
applyTheme(mode);
|
||||
}
|
||||
|
||||
public static void applyTheme(String mode) {
|
||||
switch (mode) {
|
||||
case THEME_MODE_LIGHT:
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
break;
|
||||
case THEME_MODE_DARK:
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
break;
|
||||
case THEME_MODE_SYSTEM:
|
||||
default:
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package net.micode.notes.model;
|
||||
|
||||
public interface Command {
|
||||
void execute();
|
||||
void undo();
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package net.micode.notes.model;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.widget.EditText;
|
||||
|
||||
public class NoteCommand implements Command {
|
||||
private final EditText mEditor;
|
||||
private final int mStart;
|
||||
private final CharSequence mBefore;
|
||||
private final CharSequence mAfter;
|
||||
|
||||
public NoteCommand(EditText editor, int start, CharSequence before, CharSequence after) {
|
||||
mEditor = editor;
|
||||
mStart = start;
|
||||
mBefore = before.toString();
|
||||
mAfter = after.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
// Redo: replace 'before' with 'after'
|
||||
Editable text = mEditor.getText();
|
||||
int end = mStart + mBefore.length();
|
||||
if (end <= text.length()) {
|
||||
text.replace(mStart, end, mAfter);
|
||||
mEditor.setSelection(mStart + mAfter.length());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo() {
|
||||
// Undo: replace 'after' with 'before'
|
||||
Editable text = mEditor.getText();
|
||||
int end = mStart + mAfter.length();
|
||||
if (end <= text.length()) {
|
||||
text.replace(mStart, end, mBefore);
|
||||
mEditor.setSelection(mStart + mBefore.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package net.micode.notes.model;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
public class UndoRedoManager {
|
||||
private static final int MAX_STACK_SIZE = 20;
|
||||
private final Stack<Command> mUndoStack = new Stack<>();
|
||||
private final Stack<Command> mRedoStack = new Stack<>();
|
||||
|
||||
public void addCommand(Command command) {
|
||||
mUndoStack.push(command);
|
||||
if (mUndoStack.size() > MAX_STACK_SIZE) {
|
||||
mUndoStack.remove(0);
|
||||
}
|
||||
mRedoStack.clear();
|
||||
}
|
||||
|
||||
public void undo() {
|
||||
if (!mUndoStack.isEmpty()) {
|
||||
Command command = mUndoStack.pop();
|
||||
command.undo();
|
||||
mRedoStack.push(command);
|
||||
}
|
||||
}
|
||||
|
||||
public void redo() {
|
||||
if (!mRedoStack.isEmpty()) {
|
||||
Command command = mRedoStack.pop();
|
||||
command.execute();
|
||||
mUndoStack.push(command);
|
||||
if (mUndoStack.size() > MAX_STACK_SIZE) {
|
||||
mUndoStack.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canUndo() {
|
||||
return !mUndoStack.isEmpty();
|
||||
}
|
||||
|
||||
public boolean canRedo() {
|
||||
return !mRedoStack.isEmpty();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mUndoStack.clear();
|
||||
mRedoStack.clear();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,257 @@
|
||||
package net.micode.notes.tool;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.BulletSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.QuoteSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
public class RichTextHelper {
|
||||
|
||||
public static void applyBold(EditText editText) {
|
||||
applyStyleSpan(editText, Typeface.BOLD);
|
||||
}
|
||||
|
||||
public static void applyItalic(EditText editText) {
|
||||
applyStyleSpan(editText, Typeface.ITALIC);
|
||||
}
|
||||
|
||||
public static void applyUnderline(EditText editText) {
|
||||
int start = editText.getSelectionStart();
|
||||
int end = editText.getSelectionEnd();
|
||||
if (start > end) { int temp = start; start = end; end = temp; }
|
||||
|
||||
Editable editable = editText.getText();
|
||||
UnderlineSpan[] spans = editable.getSpans(start, end, UnderlineSpan.class);
|
||||
if (spans != null && spans.length > 0) {
|
||||
for (UnderlineSpan span : spans) {
|
||||
editable.removeSpan(span);
|
||||
}
|
||||
} else {
|
||||
editable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyStrikethrough(EditText editText) {
|
||||
int start = editText.getSelectionStart();
|
||||
int end = editText.getSelectionEnd();
|
||||
if (start > end) { int temp = start; start = end; end = temp; }
|
||||
|
||||
Editable editable = editText.getText();
|
||||
StrikethroughSpan[] spans = editable.getSpans(start, end, StrikethroughSpan.class);
|
||||
if (spans != null && spans.length > 0) {
|
||||
for (StrikethroughSpan span : spans) {
|
||||
editable.removeSpan(span);
|
||||
}
|
||||
} else {
|
||||
editable.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyStyleSpan(EditText editText, int style) {
|
||||
int start = editText.getSelectionStart();
|
||||
int end = editText.getSelectionEnd();
|
||||
if (start > end) { int temp = start; start = end; end = temp; }
|
||||
|
||||
Editable editable = editText.getText();
|
||||
StyleSpan[] spans = editable.getSpans(start, end, StyleSpan.class);
|
||||
boolean exists = false;
|
||||
for (StyleSpan span : spans) {
|
||||
if (span.getStyle() == style) {
|
||||
editable.removeSpan(span);
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
editable.setSpan(new StyleSpan(style), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyHeading(EditText editText, int level) {
|
||||
int start = editText.getSelectionStart();
|
||||
int end = editText.getSelectionEnd();
|
||||
// Expand to full line
|
||||
Editable text = editText.getText();
|
||||
String string = text.toString();
|
||||
|
||||
// Find line start and end
|
||||
int lineStart = string.lastIndexOf('\n', start - 1) + 1;
|
||||
if (lineStart < 0) lineStart = 0;
|
||||
int lineEnd = string.indexOf('\n', end);
|
||||
if (lineEnd < 0) lineEnd = string.length();
|
||||
|
||||
// Remove existing heading spans
|
||||
RelativeSizeSpan[] sizeSpans = text.getSpans(lineStart, lineEnd, RelativeSizeSpan.class);
|
||||
for (RelativeSizeSpan span : sizeSpans) {
|
||||
text.removeSpan(span);
|
||||
}
|
||||
StyleSpan[] styleSpans = text.getSpans(lineStart, lineEnd, StyleSpan.class);
|
||||
for (StyleSpan span : styleSpans) {
|
||||
if (span.getStyle() == Typeface.BOLD) {
|
||||
text.removeSpan(span);
|
||||
}
|
||||
}
|
||||
|
||||
if (level > 0) {
|
||||
float scale = 1.0f;
|
||||
switch (level) {
|
||||
case 1: scale = 2.0f; break;
|
||||
case 2: scale = 1.5f; break;
|
||||
case 3: scale = 1.25f; break;
|
||||
case 4: scale = 1.1f; break;
|
||||
case 5: scale = 1.0f; break;
|
||||
case 6: scale = 0.8f; break;
|
||||
}
|
||||
text.setSpan(new RelativeSizeSpan(scale), lineStart, lineEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
text.setSpan(new StyleSpan(Typeface.BOLD), lineStart, lineEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyBullet(EditText editText) {
|
||||
// Simple bullet implementation for now
|
||||
int start = editText.getSelectionStart();
|
||||
Editable text = editText.getText();
|
||||
String string = text.toString();
|
||||
int lineStart = string.lastIndexOf('\n', start - 1) + 1;
|
||||
if (lineStart < 0) lineStart = 0;
|
||||
|
||||
// Check if already bulleted
|
||||
// Note: BulletSpan covers a paragraph.
|
||||
int lineEnd = string.indexOf('\n', start);
|
||||
if (lineEnd < 0) lineEnd = string.length();
|
||||
|
||||
BulletSpan[] spans = text.getSpans(lineStart, lineEnd, BulletSpan.class);
|
||||
if (spans != null && spans.length > 0) {
|
||||
for (BulletSpan span : spans) {
|
||||
text.removeSpan(span);
|
||||
}
|
||||
} else {
|
||||
text.setSpan(new BulletSpan(20, Color.BLACK), lineStart, lineEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyQuote(EditText editText) {
|
||||
int start = editText.getSelectionStart();
|
||||
Editable text = editText.getText();
|
||||
String string = text.toString();
|
||||
int lineStart = string.lastIndexOf('\n', start - 1) + 1;
|
||||
if (lineStart < 0) lineStart = 0;
|
||||
int lineEnd = string.indexOf('\n', start);
|
||||
if (lineEnd < 0) lineEnd = string.length();
|
||||
|
||||
QuoteSpan[] spans = text.getSpans(lineStart, lineEnd, QuoteSpan.class);
|
||||
if (spans != null && spans.length > 0) {
|
||||
for (QuoteSpan span : spans) {
|
||||
text.removeSpan(span);
|
||||
}
|
||||
} else {
|
||||
text.setSpan(new QuoteSpan(Color.GRAY), lineStart, lineEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyCode(EditText editText) {
|
||||
int start = editText.getSelectionStart();
|
||||
int end = editText.getSelectionEnd();
|
||||
if (start > end) { int temp = start; start = end; end = temp; }
|
||||
|
||||
Editable editable = editText.getText();
|
||||
TypefaceSpan[] spans = editable.getSpans(start, end, TypefaceSpan.class);
|
||||
boolean exists = false;
|
||||
for (TypefaceSpan span : spans) {
|
||||
if ("monospace".equals(span.getFamily())) {
|
||||
editable.removeSpan(span);
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also toggle background color for code block look
|
||||
BackgroundColorSpan[] bgSpans = editable.getSpans(start, end, BackgroundColorSpan.class);
|
||||
for (BackgroundColorSpan span : bgSpans) {
|
||||
if (span.getBackgroundColor() == 0xFFEEEEEE) {
|
||||
editable.removeSpan(span);
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
editable.setSpan(new TypefaceSpan("monospace"), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
editable.setSpan(new BackgroundColorSpan(0xFFEEEEEE), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void insertLink(Context context, final EditText editText) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle("Insert Link");
|
||||
|
||||
final EditText input = new EditText(context);
|
||||
input.setHint("http://example.com");
|
||||
builder.setView(input);
|
||||
|
||||
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String url = input.getText().toString();
|
||||
if (!TextUtils.isEmpty(url)) {
|
||||
int start = editText.getSelectionStart();
|
||||
int end = editText.getSelectionEnd();
|
||||
if (start == end) {
|
||||
// Insert url as text
|
||||
editText.getText().insert(start, url);
|
||||
end = start + url.length();
|
||||
}
|
||||
editText.getText().setSpan(new URLSpan(url), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton("Cancel", null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
public static void applyColor(EditText editText, int color, boolean isBackground) {
|
||||
int start = editText.getSelectionStart();
|
||||
int end = editText.getSelectionEnd();
|
||||
if (start > end) { int temp = start; start = end; end = temp; }
|
||||
|
||||
Editable editable = editText.getText();
|
||||
if (isBackground) {
|
||||
BackgroundColorSpan[] spans = editable.getSpans(start, end, BackgroundColorSpan.class);
|
||||
for (BackgroundColorSpan span : spans) editable.removeSpan(span);
|
||||
editable.setSpan(new BackgroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
} else {
|
||||
ForegroundColorSpan[] spans = editable.getSpans(start, end, ForegroundColorSpan.class);
|
||||
for (ForegroundColorSpan span : spans) editable.removeSpan(span);
|
||||
editable.setSpan(new ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void insertDivider(EditText editText) {
|
||||
int start = editText.getSelectionStart();
|
||||
editText.getText().insert(start, "\n-------------------\n");
|
||||
}
|
||||
|
||||
public static String toHtml(Spanned text) {
|
||||
return Html.toHtml(text);
|
||||
}
|
||||
|
||||
public static Spanned fromHtml(String html) {
|
||||
return Html.fromHtml(html);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package net.micode.notes.tool;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SearchHistoryManager {
|
||||
private static final String PREF_NAME = "search_history";
|
||||
private static final String KEY_HISTORY = "history_list";
|
||||
private static final int MAX_HISTORY_SIZE = 10;
|
||||
|
||||
private final SharedPreferences mPrefs;
|
||||
|
||||
public SearchHistoryManager(Context context) {
|
||||
mPrefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public List<String> getHistory() {
|
||||
String json = mPrefs.getString(KEY_HISTORY, "");
|
||||
List<String> list = new ArrayList<>();
|
||||
if (TextUtils.isEmpty(json)) {
|
||||
return list;
|
||||
}
|
||||
try {
|
||||
JSONArray array = new JSONArray(json);
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
list.add(array.getString(i));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void addHistory(String keyword) {
|
||||
if (TextUtils.isEmpty(keyword)) return;
|
||||
List<String> history = getHistory();
|
||||
// Remove existing to move to top
|
||||
history.remove(keyword);
|
||||
history.add(0, keyword);
|
||||
// Limit size
|
||||
if (history.size() > MAX_HISTORY_SIZE) {
|
||||
history = history.subList(0, MAX_HISTORY_SIZE);
|
||||
}
|
||||
saveHistory(history);
|
||||
}
|
||||
|
||||
public void removeHistory(String keyword) {
|
||||
List<String> history = getHistory();
|
||||
if (history.remove(keyword)) {
|
||||
saveHistory(history);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearHistory() {
|
||||
mPrefs.edit().remove(KEY_HISTORY).apply();
|
||||
}
|
||||
|
||||
private void saveHistory(List<String> history) {
|
||||
JSONArray array = new JSONArray(history);
|
||||
mPrefs.edit().putString(KEY_HISTORY, array.toString()).apply();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import net.micode.notes.R;
|
||||
import net.micode.notes.tool.ResourceParser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class NoteColorAdapter extends RecyclerView.Adapter<NoteColorAdapter.ViewHolder> {
|
||||
|
||||
public interface OnColorClickListener {
|
||||
void onColorClick(int colorId);
|
||||
}
|
||||
|
||||
private List<Integer> mColorIds;
|
||||
private int mSelectedColorId;
|
||||
private OnColorClickListener mListener;
|
||||
|
||||
public NoteColorAdapter(List<Integer> colorIds, int selectedColorId, OnColorClickListener listener) {
|
||||
mColorIds = colorIds;
|
||||
mSelectedColorId = selectedColorId;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void setSelectedColor(int colorId) {
|
||||
mSelectedColorId = colorId;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.note_color_item, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
int colorId = mColorIds.get(position);
|
||||
|
||||
if (colorId == ResourceParser.CUSTOM_COLOR_BUTTON_ID) {
|
||||
holder.colorView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||
int padding = (int) (12 * holder.itemView.getContext().getResources().getDisplayMetrics().density);
|
||||
holder.colorView.setPadding(padding, padding, padding, padding);
|
||||
holder.colorView.setImageResource(R.drawable.ic_palette);
|
||||
// Apply a dark tint to ensure visibility
|
||||
holder.colorView.setColorFilter(android.graphics.Color.DKGRAY, android.graphics.PorterDuff.Mode.SRC_IN);
|
||||
// Optional: Set a background for the icon
|
||||
holder.colorView.setBackgroundResource(R.drawable.bg_color_btn_mask);
|
||||
} else if (colorId == ResourceParser.WALLPAPER_BUTTON_ID) {
|
||||
holder.colorView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||
int padding = (int) (12 * holder.itemView.getContext().getResources().getDisplayMetrics().density);
|
||||
holder.colorView.setPadding(padding, padding, padding, padding);
|
||||
holder.colorView.setImageResource(R.drawable.ic_image);
|
||||
// Apply a dark tint to ensure visibility
|
||||
holder.colorView.setColorFilter(android.graphics.Color.DKGRAY, android.graphics.PorterDuff.Mode.SRC_IN);
|
||||
// Optional: Set a background for the icon
|
||||
holder.colorView.setBackgroundResource(R.drawable.bg_color_btn_mask);
|
||||
} else {
|
||||
holder.colorView.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
holder.colorView.setPadding(0, 0, 0, 0);
|
||||
// 使用ResourceParser获取背景资源
|
||||
int bgRes = ResourceParser.NoteBgResources.getNoteBgResource(colorId);
|
||||
holder.colorView.setImageResource(bgRes);
|
||||
holder.colorView.setBackground(null); // Clear background if reused
|
||||
|
||||
if (colorId >= ResourceParser.MIDNIGHT_BLACK || colorId < 0) {
|
||||
int color = ResourceParser.getNoteBgColor(holder.itemView.getContext(), colorId);
|
||||
holder.colorView.setColorFilter(color, android.graphics.PorterDuff.Mode.MULTIPLY);
|
||||
} else {
|
||||
holder.colorView.clearColorFilter();
|
||||
}
|
||||
}
|
||||
|
||||
if (colorId == mSelectedColorId && colorId != ResourceParser.CUSTOM_COLOR_BUTTON_ID) {
|
||||
holder.checkView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.checkView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mListener != null) {
|
||||
mListener.onColorClick(colorId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mColorIds.size();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView colorView;
|
||||
ImageView checkView;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
colorView = itemView.findViewById(R.id.color_view);
|
||||
checkView = itemView.findViewById(R.id.check_view);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,188 @@
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import net.micode.notes.R;
|
||||
import net.micode.notes.data.Notes;
|
||||
import net.micode.notes.data.NotesRepository;
|
||||
import net.micode.notes.tool.SearchHistoryManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class NoteSearchActivity extends AppCompatActivity implements SearchView.OnQueryTextListener, NoteSearchAdapter.OnItemClickListener {
|
||||
|
||||
private SearchView mSearchView;
|
||||
private RecyclerView mRecyclerView;
|
||||
private TextView mTvNoResult;
|
||||
private NoteSearchAdapter mAdapter;
|
||||
private NotesRepository mRepository;
|
||||
private SearchHistoryManager mHistoryManager;
|
||||
|
||||
private TextView mBtnShowHistory;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_note_search);
|
||||
|
||||
mRepository = new NotesRepository(getContentResolver());
|
||||
mHistoryManager = new SearchHistoryManager(this);
|
||||
|
||||
initViews();
|
||||
// Initial state: search is empty, show history button if there is history, or just show list
|
||||
// Requirement: "history option below search bar"
|
||||
showHistoryOption();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
toolbar.setNavigationOnClickListener(v -> finish());
|
||||
|
||||
mSearchView = findViewById(R.id.search_view);
|
||||
mSearchView.setOnQueryTextListener(this);
|
||||
mSearchView.setFocusable(true);
|
||||
mSearchView.setIconified(false);
|
||||
mSearchView.requestFocusFromTouch();
|
||||
|
||||
mBtnShowHistory = findViewById(R.id.btn_show_history);
|
||||
mBtnShowHistory.setOnClickListener(v -> showHistoryList());
|
||||
|
||||
mRecyclerView = findViewById(R.id.recycler_view);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
mAdapter = new NoteSearchAdapter(this, this);
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
|
||||
mTvNoResult = findViewById(R.id.tv_no_result);
|
||||
}
|
||||
|
||||
private void showHistoryOption() {
|
||||
// Show the "History" button, hide the list
|
||||
mBtnShowHistory.setVisibility(View.VISIBLE);
|
||||
mRecyclerView.setVisibility(View.GONE);
|
||||
mTvNoResult.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void showHistoryList() {
|
||||
List<String> history = mHistoryManager.getHistory();
|
||||
if (history.isEmpty()) {
|
||||
// If no history, maybe show a toast or empty state?
|
||||
// But for now, let's just show the empty list which is fine
|
||||
}
|
||||
List<Object> data = new ArrayList<>(history);
|
||||
mAdapter.setData(data, null);
|
||||
|
||||
mBtnShowHistory.setVisibility(View.GONE); // Hide button when showing list
|
||||
mTvNoResult.setVisibility(View.GONE);
|
||||
mRecyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void performSearch(String query) {
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
showHistoryOption();
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide history button when searching
|
||||
mBtnShowHistory.setVisibility(View.GONE);
|
||||
|
||||
mRepository.searchNotes(query, new NotesRepository.Callback<List<NotesRepository.NoteInfo>>() {
|
||||
@Override
|
||||
public void onSuccess(List<NotesRepository.NoteInfo> result) {
|
||||
runOnUiThread(() -> {
|
||||
List<Object> data = new ArrayList<>(result);
|
||||
mAdapter.setData(data, query);
|
||||
if (data.isEmpty()) {
|
||||
mTvNoResult.setVisibility(View.VISIBLE);
|
||||
mRecyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
mTvNoResult.setVisibility(View.GONE);
|
||||
mRecyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception error) {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(NoteSearchActivity.this, "Search failed: " + error.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
mHistoryManager.addHistory(query);
|
||||
performSearch(query);
|
||||
mSearchView.clearFocus(); // Hide keyboard
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
if (TextUtils.isEmpty(newText)) {
|
||||
showHistoryOption();
|
||||
} else {
|
||||
performSearch(newText);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNoteClick(NotesRepository.NoteInfo note) {
|
||||
// Save history when user clicks a result
|
||||
String query = mSearchView.getQuery().toString();
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
mHistoryManager.addHistory(query);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, NoteEditActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.putExtra(Intent.EXTRA_UID, note.getId());
|
||||
// Pass search keyword for highlighting in editor
|
||||
// NoteEditActivity uses SearchManager.EXTRA_DATA_KEY for ID and USER_QUERY for keyword
|
||||
intent.putExtra(android.app.SearchManager.EXTRA_DATA_KEY, String.valueOf(note.getId()));
|
||||
intent.putExtra(android.app.SearchManager.USER_QUERY, mSearchView.getQuery().toString());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHistoryClick(String keyword) {
|
||||
mSearchView.setQuery(keyword, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHistoryDelete(String keyword) {
|
||||
mHistoryManager.removeHistory(keyword);
|
||||
// Refresh history view if we are currently showing history (search box is empty)
|
||||
if (TextUtils.isEmpty(mSearchView.getQuery())) {
|
||||
showHistoryList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mRepository != null) {
|
||||
mRepository.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import net.micode.notes.R;
|
||||
import net.micode.notes.tool.SecurityManager;
|
||||
|
||||
import net.micode.notes.data.ThemeRepository;
|
||||
import androidx.preference.ListPreference;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
public static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
|
||||
public static final String PREFERENCE_SECURITY_KEY = "pref_key_security";
|
||||
public static final String PREFERENCE_THEME_MODE = "pref_theme_mode";
|
||||
public static final int REQUEST_CODE_CHECK_PASSWORD = 104;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.preferences, rootKey);
|
||||
loadThemePreference();
|
||||
loadSecurityPreference();
|
||||
loadAccountPreference();
|
||||
}
|
||||
|
||||
private void loadThemePreference() {
|
||||
ListPreference themePref = findPreference(PREFERENCE_THEME_MODE);
|
||||
if (themePref != null) {
|
||||
themePref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
ThemeRepository.applyTheme((String) newValue);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSecurityPreference() {
|
||||
Preference securityPref = findPreference(PREFERENCE_SECURITY_KEY);
|
||||
if (securityPref != null) {
|
||||
securityPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (!SecurityManager.getInstance(getActivity()).isPasswordSet()) {
|
||||
showSetPasswordDialog();
|
||||
} else {
|
||||
Intent intent = new Intent(getActivity(), PasswordActivity.class);
|
||||
intent.setAction(PasswordActivity.ACTION_CHECK_PASSWORD);
|
||||
startActivityForResult(intent, REQUEST_CODE_CHECK_PASSWORD);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAccountPreference() {
|
||||
androidx.preference.PreferenceCategory accountCategory = findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
|
||||
if (accountCategory != null) {
|
||||
accountCategory.removeAll();
|
||||
Preference accountPref = new Preference(getContext());
|
||||
accountPref.setTitle(getString(R.string.preferences_account_title));
|
||||
accountPref.setSummary(getString(R.string.preferences_account_summary));
|
||||
accountPref.setOnPreferenceClickListener(preference -> {
|
||||
Toast.makeText(getActivity(), "Google Tasks同步功能已禁用", Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
});
|
||||
accountCategory.addPreference(accountPref);
|
||||
}
|
||||
}
|
||||
|
||||
private void showSetPasswordDialog() {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle("设置密码")
|
||||
.setItems(new String[]{"数字锁", "手势锁"}, (dialog, which) -> {
|
||||
int type = (which == 0) ? SecurityManager.TYPE_PIN : SecurityManager.TYPE_PATTERN;
|
||||
Intent intent = new Intent(getActivity(), PasswordActivity.class);
|
||||
intent.setAction(PasswordActivity.ACTION_SETUP_PASSWORD);
|
||||
intent.putExtra(PasswordActivity.EXTRA_PASSWORD_TYPE, type);
|
||||
startActivity(intent);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showManagePasswordDialog() {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle("管理密码")
|
||||
.setItems(new String[]{"更改密码", "取消密码"}, (dialog, which) -> {
|
||||
if (which == 0) { // Change
|
||||
showSetPasswordDialog();
|
||||
} else { // Remove
|
||||
SecurityManager.getInstance(getActivity()).removePassword();
|
||||
Toast.makeText(getActivity(), "密码已取消", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_CODE_CHECK_PASSWORD && resultCode == RESULT_OK) {
|
||||
showManagePasswordDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:color="#88555555" />
|
||||
<item android:state_selected="true" android:color="#ff999999" />
|
||||
<item android:color="#ff000000" />
|
||||
</selector>
|
||||
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="#50000000" />
|
||||
</selector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M15.6,11.79c0.97,-0.67 1.65,-1.77 1.65,-2.79 0,-2.26 -1.75,-4 -4,-4H7v14h7c2.09,0 3.85,-1.75 3.85,-3.75 0,-1.58 -0.95,-2.9 -2.25,-3.46zM10,7.5h3c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5h-3v-3zM13.5,16.5h-3.5v-3h3.5c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M16.56,8.94L7.62,0 6.21,1.41l2.38,2.38 -5.15,5.15c-0.59,0.59 -0.59,1.54 0,2.12l5.5,5.5c0.29,0.29 0.68,0.44 1.06,0.44s0.77,-0.15 1.06,-0.44l5.5,-5.5c0.59,-0.58 0.59,-1.53 0,-2.12zM5.21,10L10,5.21 14.79,10H5.21zM19,11.5s-2,2.17 -2,3.5c0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-1.33 -2,-3.5 -2,-3.5z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M0,20h24v4H0z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M0,20h24v4H0z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M11,3L5.5,17h2.25l1.12,-3h6.25l1.12,3h2.25L13,3h-2zM10,11l2,-5.5 2,5.5h-4z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M5,4v3h5.5v12h3V7H19V4z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M10,4v3h2.21l-3.42,8H6v3h8v-3h-2.21l3.42,-8H18V4z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M4,10.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM4,4.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM4,16.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM8,19h12v-2H8v2zM8,13h12v-2H8v2zM8,5v2h12V5H8z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M6,17h3l2,-4V7H5v6h3l-2,4zM14,17h3l2,-4V7h-6v6h3l-2,4z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M10,19h4v-3h-4v3zM5,4v3h5v3h4V7h5V4H5zM3,14h18v-2H3v2z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,17c3.31,0 6,-2.69 6,-6V3h-2.5v8c0,1.93 -1.57,3.5 -3.5,3.5S8.5,12.93 8.5,11V3H6v8c0,3.31 2.69,6 6,6zM5,19v2h14v-2H5z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#000000">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19,13H5v-2h14v2z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4V7H7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9H7c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2H8v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4V17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M2.5,4v3h5v12h3V7h5V4H2.5zM21.5,9h-9v3h3v7h3v-7h3V9z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12.5,8c-2.65,0 -5.05,0.99 -6.9,2.6L2,7v9h9l-3.62,-3.62c1.39,-1.16 3.16,-1.88 5.12,-1.88 3.54,0 6.55,2.31 7.6,5.5l2.37,-0.78C21.08,11.03 17.15,8 12.5,8z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#000000">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5H16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,6c-0.83,0 -1.5,-0.67 -1.5,-1.5S8.67,3 9.5,3s1.5,0.67 1.5,1.5S10.33,6 9.5,6zM14.5,6c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,3 14.5,3s1.5,0.67 1.5,1.5S15.33,6 14.5,6zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5S18.33,12 17.5,12z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.Material3.ActionBar">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/ThemeOverlay.Material3.Light">
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<androidx.appcompat.widget.SearchView
|
||||
android:id="@+id/search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionSearch|flagNoExtractUi"
|
||||
app:iconifiedByDefault="false"
|
||||
app:queryHint="@string/search_hint" />
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<!-- 历史记录按钮 -->
|
||||
<TextView
|
||||
android:id="@+id/btn_show_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:padding="16dp"
|
||||
android:text="@string/search_history_title"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:drawablePadding="8dp"
|
||||
android:visibility="gone"
|
||||
app:drawableEndCompat="@android:drawable/arrow_down_float" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- 搜索结果列表 -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
<!-- 无结果提示 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_no_result"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/search_no_results"
|
||||
android:visibility="gone"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@ -0,0 +1,15 @@
|
||||
<?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="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/settings_header" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/settings_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,69 @@
|
||||
<?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:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="?attr/colorOutline"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<View
|
||||
android:id="@+id/view_color_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Red"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/sb_red"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:progressTint="@android:color/holo_red_light"
|
||||
android:thumbTint="@android:color/holo_red_dark"
|
||||
android:max="255" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Green"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/sb_green"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:progressTint="@android:color/holo_green_light"
|
||||
android:thumbTint="@android:color/holo_green_dark"
|
||||
android:max="255" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Blue"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/sb_blue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:progressTint="@android:color/holo_blue_light"
|
||||
android:thumbTint="@android:color/holo_blue_dark"
|
||||
android:max="255" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_margin="4dp"
|
||||
android:padding="2dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/color_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.Material3.Corner.Full"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/secondary_text_dark" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/check_view"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/selected"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
@ -0,0 +1,35 @@
|
||||
<?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:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_menu_recent_history"
|
||||
android:tint="?android:attr/textColorSecondary"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_history_keyword"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_delete_history"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:padding="4dp"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:tint="?android:attr/textColorSecondary"
|
||||
android:contentDescription="@string/menu_delete" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="background_color">#121212</color>
|
||||
<color name="text_color_primary">#DEFFFFFF</color>
|
||||
<color name="text_color_secondary">#99FFFFFF</color>
|
||||
|
||||
<!-- In Night mode, keeping note backgrounds desaturated/dark is better,
|
||||
but for now we keep them linked to original resources unless we want to override them.
|
||||
We override bg_white to be dark grey for general UI usage (if any). -->
|
||||
<color name="bg_white">#2C2C2C</color>
|
||||
|
||||
<!-- Overriding primary_text_dark to ensure legacy styles also switch if they are not updated -->
|
||||
<color name="primary_text_dark">#DEFFFFFF</color>
|
||||
<color name="secondary_text_dark">#99FFFFFF</color>
|
||||
</resources>
|
||||
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="font_family_entries">
|
||||
<item>System Default</item>
|
||||
<item>Serif</item>
|
||||
<item>Sans Serif</item>
|
||||
<item>Monospace</item>
|
||||
<item>Cursive</item>
|
||||
</string-array>
|
||||
<string-array name="font_family_values">
|
||||
<item>default</item>
|
||||
<item>serif</item>
|
||||
<item>sans-serif</item>
|
||||
<item>monospace</item>
|
||||
<item>cursive</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
Loading…
Reference in new issue