新增富文本编辑、主题管理、撤销重做等功能

蒋天翔 4 weeks ago
parent fcb9a6b8e5
commit ecd79606ba

@ -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,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,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();
}
}
}

@ -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,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,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…
Cancel
Save