diff --git a/.gitignore b/.gitignore index c08bf69b..4fd2b224 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,8 @@ proguard/ *.el .DS_Store -.externalNativeBuild \ No newline at end of file +.externalNativeBuild + +# Node +node_modules +package-lock.json diff --git a/.gitmodules b/.gitmodules index a7400dae..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "colorpicker"] - path = colorpicker - url = https://github.com/M66B/colorpicker.git diff --git a/app/build.gradle b/app/build.gradle index dbd73666..4259acfa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,14 @@ apply plugin: "com.android.application" -apply plugin: "kotlin-android-extensions" apply plugin: "kotlin-android" +apply plugin: "kotlin-android-extensions" apply from: "${rootDir}/jdee.gradle" android { - compileSdkVersion 28 + compileSdkVersion 30 defaultConfig { applicationId "org.dystopia.email" - minSdkVersion 23 - targetSdkVersion 28 + minSdkVersion 21 + targetSdkVersion 30 versionCode 115 versionName "1.3.0" archivesBaseName = "SimpleEmail-v$versionName" @@ -58,6 +58,9 @@ repositories { jcenter() maven { url "https://repo1.maven.org/maven2/" + } + maven { + url "https://jitpack.io" } } @@ -69,6 +72,8 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) def androidx_version = "1.0.0" + def appcompat_version = "1.2.0" + def fragment_version = "1.3.0-beta01" def constraintlayout_version = "1.1.3" def lifecycle_version = "2.0.0" def room_version = "2.0.0" @@ -78,9 +83,12 @@ dependencies { def jcharset_version = "2.0" def dnsjava_version = "2.1.8" def openpgp_version = "12.0" + def colorpicker_version = "0.0.15" // https://mvnrepository.com/artifact/androidx.appcompat/appcompat - implementation "androidx.appcompat:appcompat:$androidx_version" + implementation "androidx.appcompat:appcompat:$appcompat_version" + // https://mvnrepository.com/artifact/androidx.fragment/fragment + implementation "androidx.fragment:fragment:$fragment_version" // https://mvnrepository.com/artifact/androidx.annotation/annotation implementation "androidx.annotation:annotation:$androidx_version" // https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview @@ -122,7 +130,7 @@ dependencies { implementation "org.sufficientlysecure:openpgp-api:$openpgp_version" // https://android.googlesource.com/platform/frameworks/opt/colorpicker - implementation project(path: ':colorpicker', configuration: 'default') + implementation "com.github.QuadFlask:colorpicker:$colorpicker_version" // Kotlin support implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/app/src/main/java/org/dystopia/email/ColorDialogFragment.java b/app/src/main/java/org/dystopia/email/ColorDialogFragment.java new file mode 100644 index 00000000..2ea62420 --- /dev/null +++ b/app/src/main/java/org/dystopia/email/ColorDialogFragment.java @@ -0,0 +1,157 @@ +package org.dystopia.email; + +/* + This file is part of SimpleEmail. + + SimpleEmail is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SimpleEmail is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SimpleEmail. If not, see . + + Copyright 2018, Distopico (dystopia project) and contributors +*/ + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentResultListener; + +import com.flask.colorpicker.ColorPickerView; +import com.flask.colorpicker.OnColorChangedListener; +import com.flask.colorpicker.builder.ColorPickerClickListener; +import com.flask.colorpicker.builder.ColorPickerDialogBuilder; + +import static android.app.Activity.RESULT_OK; + +public class ColorDialogFragment extends DialogFragment { + private static int requestSequence = 0; + private boolean sent = false; + private String requestKey = null; + private String targetRequestKey; + private int targetRequestCode; + private int color; + + public String getRequestKey() { + if (requestKey == null) + requestKey = getClass().getName() + "_" + (++requestSequence); + return requestKey; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + requestKey = savedInstanceState.getString("dialog:request"); + targetRequestKey = savedInstanceState.getString("dialog:key"); + targetRequestCode = savedInstanceState.getInt("dialog:code"); + } + + getParentFragmentManager().setFragmentResultListener(getRequestKey(), this, new FragmentResultListener() { + @Override + public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle result) { + try { + result.setClassLoader(ApplicationEx.class.getClassLoader()); + int requestCode = result.getInt("requestCode"); + int resultCode = result.getInt("resultCode"); + + Intent data = new Intent(); + data.putExtra("args", result); + onActivityResult(requestCode, resultCode, data); + } catch (Throwable ex) { + // LOg + } + } + }); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putInt("dialog:color", color); + outState.putString("dialog:request", requestKey); + outState.putString("dialog:key", targetRequestKey); + outState.putInt("dialog:code", targetRequestCode); + super.onSaveInstanceState(outState); + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + Bundle args = getArguments(); + color = savedInstanceState == null ? args.getInt("color") : savedInstanceState.getInt("dialog:color"); + String title = args.getString("title"); + boolean reset = args.getBoolean("reset", false); + + Context context = getContext(); + int editTextColor = Helper.resolveColor(context, android.R.attr.editTextColor); + + ColorPickerDialogBuilder builder = ColorPickerDialogBuilder + .with(context) + .setTitle(title) + .showColorEdit(true) + .setColorEditTextColor(editTextColor) + .wheelType(ColorPickerView.WHEEL_TYPE.FLOWER) + .density(6) + .lightnessSliderOnly() + .setOnColorChangedListener(new OnColorChangedListener() { + @Override + public void onColorChanged(int selectedColor) { + color = selectedColor; + } + }) + .setPositiveButton(android.R.string.ok, new ColorPickerClickListener() { + @Override + public void onClick(DialogInterface dialog, int selectedColor, Integer[] allColors) { + getArguments().putInt("color", selectedColor); + sendResult(RESULT_OK); + } + }); + + if (color != Color.TRANSPARENT) { + builder.initialColor(color); + } + + if (reset) { + builder.setNegativeButton(R.string.title_reset, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + getArguments().putInt("color", Color.TRANSPARENT); + sendResult(RESULT_OK); + } + }); + } + + return builder.build(); + } + + protected void sendResult(int resultCode) { + if (sent) { + return; + } + sent = true; + if (targetRequestKey != null) { + Bundle args = getArguments(); + if (args == null) { + args = new Bundle(); + } + args.putInt("requestCode", targetRequestCode); + args.putInt("resultCode", resultCode); + getParentFragmentManager().setFragmentResult(targetRequestKey, args); + } + } +} diff --git a/app/src/main/java/org/dystopia/email/FragmentAccount.java b/app/src/main/java/org/dystopia/email/FragmentAccount.java index 71977f5b..f1bbee6c 100644 --- a/app/src/main/java/org/dystopia/email/FragmentAccount.java +++ b/app/src/main/java/org/dystopia/email/FragmentAccount.java @@ -64,8 +64,6 @@ import androidx.annotation.Nullable; import androidx.constraintlayout.widget.Group; import androidx.core.content.ContextCompat; import androidx.lifecycle.Observer; -import com.android.colorpicker.ColorPickerDialog; -import com.android.colorpicker.ColorPickerSwatch; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.textfield.TextInputLayout; import com.sun.mail.imap.IMAPFolder; @@ -107,6 +105,8 @@ public class FragmentAccount extends FragmentEx { private TextView tvName; private EditText etName; + private ViewButtonColor btnColor; + private View vwColor; private ImageView ibColorDefault; private EditText etSignature; @@ -141,6 +141,8 @@ public class FragmentAccount extends FragmentEx { private int color = Color.TRANSPARENT; private String authorized = null; + private static final int REQUEST_COLOR = 1; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -181,6 +183,7 @@ public class FragmentAccount extends FragmentEx { btnAdvanced = view.findViewById(R.id.btnAdvanced); etName = view.findViewById(R.id.etName); + btnColor = view.findViewById(R.id.btnColor); tvName = view.findViewById(R.id.tvName); vwColor = view.findViewById(R.id.vwColor); ibColorDefault = view.findViewById(R.id.ibColorDefault); @@ -374,18 +377,15 @@ public class FragmentAccount extends FragmentEx { new View.OnClickListener() { @Override public void onClick(View v) { - int[] colors = getContext().getResources().getIntArray(R.array.colorPicker); - ColorPickerDialog colorPickerDialog = new ColorPickerDialog(); - colorPickerDialog.initialize( - R.string.title_account_color, colors, color, 4, colors.length); - colorPickerDialog.setOnColorSelectedListener( - new ColorPickerSwatch.OnColorSelectedListener() { - @Override - public void onColorSelected(int color) { - setColor(color); - } - }); - colorPickerDialog.show(getFragmentManager(), "colorpicker"); + Bundle args = new Bundle(); + args.putInt("color", btnColor.getColor()); + args.putString("title", getString(R.string.title_color)); + args.putBoolean("reset", true); + + ColorDialogFragment fragment = new ColorDialogFragment(); + fragment.setArguments(args); + fragment.setTargetFragment(FragmentAccount.this, REQUEST_COLOR); + fragment.show(getParentFragmentManager(), "account:color"); } }); diff --git a/app/src/main/java/org/dystopia/email/ViewButtonColor.java b/app/src/main/java/org/dystopia/email/ViewButtonColor.java new file mode 100644 index 00000000..43fa6118 --- /dev/null +++ b/app/src/main/java/org/dystopia/email/ViewButtonColor.java @@ -0,0 +1,118 @@ +package org.dystopia.email; + +/* + This file is part of SimpleEmail. + + SimpleEmail is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SimpleEmail is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SimpleEmail. If not, see . + + Copyright 2018, Distopico (dystopia project) and contributors +*/ + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.View; + +import androidx.appcompat.widget.AppCompatButton; +import androidx.core.graphics.ColorUtils; + +public class ViewButtonColor extends AppCompatButton { + private int color = Color.TRANSPARENT; + + public ViewButtonColor(Context context) { + super(context); + } + + public ViewButtonColor(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ViewButtonColor(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + return new SavedState(superState, this.color); + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState savedState = (SavedState) state; + super.onRestoreInstanceState(savedState.getSuperState()); + setColor(savedState.getColor()); + } + + void setColor(Integer color) { + if (color == null) + color = Color.TRANSPARENT; + this.color = color; + + GradientDrawable background = new GradientDrawable(); + background.setColor(color); + background.setStroke( + ViewHelper.dp2px(getContext(), 1), + Helper.resolveColor(getContext(), R.attr.colorSeparator)); + setBackground(background); + + if (color == Color.TRANSPARENT) + setTextColor(Helper.resolveColor(getContext(), android.R.attr.textColorPrimary)); + else { + double lum = ColorUtils.calculateLuminance(color); + setTextColor(lum < 0.5 ? Color.WHITE : Color.BLACK); + } + } + + int getColor() { + return this.color; + } + + static class SavedState extends View.BaseSavedState { + private int color; + + private SavedState(Parcelable superState, int color) { + super(superState); + this.color = color; + } + + private SavedState(Parcel in) { + super(in); + color = in.readInt(); + } + + public int getColor() { + return this.color; + } + + @Override + public void writeToParcel(Parcel destination, int flags) { + super.writeToParcel(destination, flags); + destination.writeInt(color); + } + + public static final Parcelable.Creator CREATOR = new Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/app/src/main/java/org/dystopia/email/ViewHelper.java b/app/src/main/java/org/dystopia/email/ViewHelper.java new file mode 100644 index 00000000..422de403 --- /dev/null +++ b/app/src/main/java/org/dystopia/email/ViewHelper.java @@ -0,0 +1,45 @@ +package org.dystopia.email; + +/* + This file is part of SimpleEmail. + + SimpleEmail is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SimpleEmail is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SimpleEmail. If not, see . + + Copyright 2018, Distopico (dystopia project) and contributors +*/ + +import android.content.Context; + +public class ViewHelper { + + /** + * Convert density-independent pixels units to pixel units. + * @param context - android content context to get density + * @param dp - density-independent pixel value + */ + static int dp2px(Context context, int dp) { + float scale = context.getResources().getDisplayMetrics().density; + return Math.round(dp * scale); + } + + /** + * Convert pixel units to density-independent pixels units. + * @param context - android content context to get density + * @param px - pixels value + */ + static int px2dp(Context context, float px) { + float scale = context.getResources().getDisplayMetrics().density; + return Math.round(px / scale); + } +} diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml index 57dea3ba..a7455d1c 100644 --- a/app/src/main/res/layout/fragment_account.xml +++ b/app/src/main/res/layout/fragment_account.xml @@ -240,7 +240,29 @@ app:layout_constraintBottom_toBottomOf="@id/vwColor" app:layout_constraintStart_toEndOf="@id/vwColor" app:layout_constraintTop_toBottomOf="@id/tilPassword" /> - + + + + +