From c3fb053f2eefb29e31c6e5741fc1cf4c0aa05585 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Wed, 1 Nov 2023 13:32:05 +0100 Subject: [PATCH 001/112] Adds basic dark mode. --- .../ui/adapter/NoteAdapter.java | 8 +- ...tingsFragment.java => SettingsFragment.kt} | 23 +- .../ui/main/MainActivity.java | 9 +- .../ui/notes/AudioNoteActivity.kt | 4 +- ... => ic_format_list_bulleted_icon_24dp.xml} | 2 +- ...ic_black_24dp.xml => ic_mic_icon_24dp.xml} | 2 +- ..._black_24dp.xml => ic_pause_icon_24dp.xml} | 2 +- ..._black_24dp.xml => ic_photo_icon_24dp.xml} | 2 +- ...k_24dp.xml => ic_play_arrow_icon_24dp.xml} | 2 +- ...k_24dp.xml => ic_short_text_icon_24dp.xml} | 2 +- .../res/drawable/secuso_logo_blau_blau.png | Bin 57185 -> 0 bytes .../res/drawable/secuso_logo_blau_blau.xml | 214 ++++++++++++++++++ .../res/layout-land/activity_audio_note.xml | 4 +- app/src/main/res/layout/activity_about.xml | 10 +- .../main/res/layout/activity_audio_note.xml | 5 +- app/src/main/res/layout/activity_main.xml | 1 + app/src/main/res/layout/nav_header_main.xml | 20 +- app/src/main/res/layout/note_header.xml | 4 +- app/src/main/res/layout/note_item.xml | 11 +- .../main/res/layout/simple_spinner_item.xml | 1 - .../main/res/menu/activity_main_drawer.xml | 2 +- app/src/main/res/values-night/styles.xml | 21 ++ app/src/main/res/values/arrays.xml | 6 + app/src/main/res/values/attrs.xml | 11 + app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/values/styles.xml | 13 +- app/src/main/res/xml/pref_settings.xml | 8 + backup-api | 2 +- 29 files changed, 353 insertions(+), 48 deletions(-) rename app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/{SettingsFragment.java => SettingsFragment.kt} (56%) rename app/src/main/res/drawable/{ic_format_list_bulleted_black_24dp.xml => ic_format_list_bulleted_icon_24dp.xml} (92%) rename app/src/main/res/drawable/{ic_mic_black_24dp.xml => ic_mic_icon_24dp.xml} (90%) rename app/src/main/res/drawable/{ic_pause_black_24dp.xml => ic_pause_icon_24dp.xml} (85%) rename app/src/main/res/drawable/{ic_photo_black_24dp.xml => ic_photo_icon_24dp.xml} (88%) rename app/src/main/res/drawable/{ic_play_arrow_black_24dp.xml => ic_play_arrow_icon_24dp.xml} (84%) rename app/src/main/res/drawable/{ic_short_text_black_24dp.xml => ic_short_text_icon_24dp.xml} (85%) delete mode 100644 app/src/main/res/drawable/secuso_logo_blau_blau.png create mode 100644 app/src/main/res/drawable/secuso_logo_blau_blau.xml create mode 100644 app/src/main/res/values-night/styles.xml create mode 100644 app/src/main/res/values/attrs.xml diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.java index 7488fcc0..8614c1ea 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.java +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.java @@ -71,18 +71,18 @@ public void onBindViewHolder(@NonNull NoteHolder holder, int position) { switch (currentNote.getType()) { case DbContract.NoteEntry.TYPE_TEXT: - holder.imageViewcategory.setImageResource(R.drawable.ic_short_text_black_24dp); + holder.imageViewcategory.setImageResource(R.drawable.ic_short_text_icon_24dp); holder.textViewDescription.setText(Html.fromHtml(currentNote.getContent())); holder.textViewDescription.setMaxLines(3); break; case DbContract.NoteEntry.TYPE_AUDIO: - holder.imageViewcategory.setImageResource(R.drawable.ic_mic_black_24dp); + holder.imageViewcategory.setImageResource(R.drawable.ic_mic_icon_24dp); break; case DbContract.NoteEntry.TYPE_SKETCH: - holder.imageViewcategory.setImageResource(R.drawable.ic_photo_black_24dp); + holder.imageViewcategory.setImageResource(R.drawable.ic_photo_icon_24dp); break; case DbContract.NoteEntry.TYPE_CHECKLIST: - holder.imageViewcategory.setImageResource(R.drawable.ic_format_list_bulleted_black_24dp); + holder.imageViewcategory.setImageResource(R.drawable.ic_format_list_bulleted_icon_24dp); String preview = ""; try { JSONArray content = new JSONArray(currentNote.getContent()); diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.kt similarity index 56% rename from app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.java rename to app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.kt index 3e1c1b02..c5c167b4 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.java +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/fragments/SettingsFragment.kt @@ -11,20 +11,25 @@ You should have received a copy of the GNU General Public License along with Privacy Friendly Notes. If not, see . */ -package org.secuso.privacyfriendlynotes.ui.fragments; +package org.secuso.privacyfriendlynotes.ui.fragments -import android.os.Bundle; -import android.preference.PreferenceFragment; - -import org.secuso.privacyfriendlynotes.R; +import android.os.Bundle +import android.preference.PreferenceFragment +import androidx.appcompat.app.AppCompatDelegate +import org.secuso.privacyfriendlynotes.R /** * Fragment that provides the settings. * Created by Robin on 11.09.2016. */ -public class SettingsFragment extends PreferenceFragment { - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.pref_settings); +class SettingsFragment : PreferenceFragment() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + addPreferencesFromResource(R.xml.pref_settings) + findPreference("settings_day_night_theme")?.setOnPreferenceChangeListener { _, newValue -> + AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) + true; + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java index b735ddd9..899306dc 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java @@ -15,6 +15,7 @@ import android.app.Activity; import android.content.Intent; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.preference.PreferenceManager; import com.google.android.material.navigation.NavigationView; @@ -24,8 +25,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import android.util.Log; import android.widget.SearchView; +import androidx.appcompat.app.AppCompatDelegate; import androidx.arch.core.util.Function; import androidx.core.view.GravityCompat; import androidx.appcompat.app.ActionBarDrawerToggle; @@ -107,7 +110,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); - + getSupportActionBar().setStackedBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.colorPrimary))); //set the OnClickListeners findViewById(R.id.fab_text).setOnClickListener(this); findViewById(R.id.fab_checklist).setOnClickListener(this); @@ -208,6 +211,10 @@ public boolean onQueryTextSubmit(String query) { }); PreferenceManager.setDefaultValues(this, R.xml.pref_settings, false); + + String theme = PreferenceManager.getDefaultSharedPreferences(this).getString("settings_day_night_theme", "-1"); + Log.d("Theme", theme); + AppCompatDelegate.setDefaultNightMode(Integer.parseInt(theme)); } @Override diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/AudioNoteActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/AudioNoteActivity.kt index a6023e7f..3fa83c9a 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/AudioNoteActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/AudioNoteActivity.kt @@ -254,9 +254,9 @@ class AudioNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_AUDIO) { private fun togglePlayPauseButton() { if (playing) { - btnPlayPause.setBackgroundResource(R.drawable.ic_pause_black_24dp) + btnPlayPause.setBackgroundResource(R.drawable.ic_pause_icon_24dp) } else { - btnPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_black_24dp) + btnPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_icon_24dp) } } diff --git a/app/src/main/res/drawable/ic_format_list_bulleted_black_24dp.xml b/app/src/main/res/drawable/ic_format_list_bulleted_icon_24dp.xml similarity index 92% rename from app/src/main/res/drawable/ic_format_list_bulleted_black_24dp.xml rename to app/src/main/res/drawable/ic_format_list_bulleted_icon_24dp.xml index 5937a4eb..a2a187ec 100644 --- a/app/src/main/res/drawable/ic_format_list_bulleted_black_24dp.xml +++ b/app/src/main/res/drawable/ic_format_list_bulleted_icon_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_mic_black_24dp.xml b/app/src/main/res/drawable/ic_mic_icon_24dp.xml similarity index 90% rename from app/src/main/res/drawable/ic_mic_black_24dp.xml rename to app/src/main/res/drawable/ic_mic_icon_24dp.xml index 4f0dc044..e6a2385b 100644 --- a/app/src/main/res/drawable/ic_mic_black_24dp.xml +++ b/app/src/main/res/drawable/ic_mic_icon_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_pause_black_24dp.xml b/app/src/main/res/drawable/ic_pause_icon_24dp.xml similarity index 85% rename from app/src/main/res/drawable/ic_pause_black_24dp.xml rename to app/src/main/res/drawable/ic_pause_icon_24dp.xml index bb28a6c4..2a09bfd9 100644 --- a/app/src/main/res/drawable/ic_pause_black_24dp.xml +++ b/app/src/main/res/drawable/ic_pause_icon_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_photo_black_24dp.xml b/app/src/main/res/drawable/ic_photo_icon_24dp.xml similarity index 88% rename from app/src/main/res/drawable/ic_photo_black_24dp.xml rename to app/src/main/res/drawable/ic_photo_icon_24dp.xml index b2018595..c3363c70 100644 --- a/app/src/main/res/drawable/ic_photo_black_24dp.xml +++ b/app/src/main/res/drawable/ic_photo_icon_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml b/app/src/main/res/drawable/ic_play_arrow_icon_24dp.xml similarity index 84% rename from app/src/main/res/drawable/ic_play_arrow_black_24dp.xml rename to app/src/main/res/drawable/ic_play_arrow_icon_24dp.xml index bf9b895a..a34b479e 100644 --- a/app/src/main/res/drawable/ic_play_arrow_black_24dp.xml +++ b/app/src/main/res/drawable/ic_play_arrow_icon_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_short_text_black_24dp.xml b/app/src/main/res/drawable/ic_short_text_icon_24dp.xml similarity index 85% rename from app/src/main/res/drawable/ic_short_text_black_24dp.xml rename to app/src/main/res/drawable/ic_short_text_icon_24dp.xml index 11c24c5a..c0ee7a03 100644 --- a/app/src/main/res/drawable/ic_short_text_black_24dp.xml +++ b/app/src/main/res/drawable/ic_short_text_icon_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/secuso_logo_blau_blau.png b/app/src/main/res/drawable/secuso_logo_blau_blau.png deleted file mode 100644 index 9c82d7c84a20b24a1daa16d3dd054689af71fd77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57185 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

    7a`q_)I?5iU`qS|@@v?V=(}VqIdVFnX zf=%x%T=gZi-^y5*q__ahOQqdh7+=b0&>-qh|EG1mYK*>*mmVUQOMw<5(D`+&BjPdQ zv5B0U+bknN_lw0~wwMm}a7#uHcxFB!TcCL{a?eir)OzyT9~9j{@r65Az=ONe$*xzQbd{Yv56Cq+{~aJn_dv< zc(?H_hdg6XL1yOB8EY)6yeHd>;n!i+&_Yi})_Y987tMt2_mbJ@u zaF$u&E_Ujx^QS#|YsnvdKUt4Sh=IB9W<}u43B|cVst){y@LO+kxS?=n%GeEYm@A{2 zLRMs5isl0G7{Aa`MPL?+;N5ciaC}c(@%36}NeuVZV?#J12v>5Hc35eA3{ETvGyc6( zc1^f65ccDg$wYWO1C<_XK^LEl(1Q2igBN~w>h2=MV)5l3=%n=WfRoS`UdhLP$sM3; z>U6&i(%E$ceO6p=iGT9}O)5ZQ*RHNd7k|dLygEaI z%A8VU(}g^cK(n)R7lN7h2fdf__Wf4yw`lo84 zEI{vS@!GRnx62)k{8nCa@1Nzm?$e~f&F9Z9#0P>Iq9B@^Yo+Yb<`X+COybL$0oR)E zSL}MllsbGr_Hc$%wbaQ!jRmn8|EsI435Tp+i9e0?Z@J6+Z9L{~4b|VEhk1j0l!7Rb zfkS6Mn#3iSX(98UB(-F;tU9)2DK52S_XqekHr?CUBU8bu97oqJen+guJ2Zv^X%;$z``$gt{`9rSaWO+Mc%sObduq9e6v; zIj&UIeBwRQWXooJ69Wp&vjEiVJB;nj89No8X#orn3Wq=8S>kHUo&IJ zmGL<#-QZO@<*gRg#BQRkZguR7DS8Ln`?`}6=l^IgsW2W($@S3FW;3x!-q&rIJo&r* zc};qThlb<~3%BufKLWu#1>l?BAd4g1xkQ>W=Q!AxPf2p_x_3>5u|(3jk+5%l;!)o_ z1ven(4F%onW=)I%XFGs0YWTjwOxa&W=_0l)4AJ-&t&4b2v41qlk56QA6{i2B?k+e? zz5X2Z*qiHwsS?gme*2T3UT;&RN$qvmC|=}iz(cXVR1b&ucy@n*!yRC3cYNd*Jvo(a z7i*3o1#mz2A5nQd-01HlT8BmY=SKZk90|M zz#>`;!apQN9)B}2I|-+q&iqeU)OT)u)ycP!^!+?pE%uAPmthGZ7O|w9uD8Z+G4W`C zOw*OSsnJ`2zq^4!N?kK-G!nTd4>YC-F(>dQu`=k_@CV6s+5?Ph+b9R1v2eZ9^Y*ma zVf5B&&obl^KHYF`c(o)ax4+Ks{kQ4Q8iq3HM?7ipYoD;;nU=YZ3g)wferV1Uw1LL| zdBv!n0uOLw9uyuP^1WaDvS9o}0?*k~)9Lx{1N6BK^2Qr~eR3`i&}L@M>3q4CtTxe} z$Fh2#qwaQ(U@J8CpHgoVY}CKmnIRdM5Z&;oN(WkRoPDnF%Q|6-RJSPuNCt}L{OWx& zQ0f8OAQG2U=kx>@2m~87;B#@O<|lZ2kE_z?A?;!jP{0&J=`Ih|3G=_2$U4tvBLul1 z%cgZf_eNGZn$42mO>;byT3Bh&i&=#X!2X0SDCjm^dfIcWT)#ZYHOn>CWAIr5_d}D0 zF3uMycM197?EBoka($m;Gho(9-(HUow&~%<@&g0!Tmd}*;L<$%cFcVAH0L&@C~(u3 zpA~r1x2o4(L0L6VcHesGbv~_}3gIE)1yrR_L4sqQ-FCr+@$>I^)1r_Rl!$9Rv{VCh zimTec8&+isI6OX$$>&}6u2?GJdBxJo0+pL#QTsm&RFe6c2TP^Zn?JwvdN%6CmETLV zyPkVPXVOytWeJei&CGSJn+eBfj<7{j(E;WCc)J+(V}Ou2C}@C}iDyIE_S$*IeU%Ww zSjVF;Ie|lps`15FLt!nJk|uD)*K6jLGG%bNhUenZeuKP1!XYEw>n!^YP^pS4iSfPb z;QIcFHHgt->|aFDi{P4WMTu*A!M}eA;sAz-K8WA>+XNWY--?jQWU_tRzPtRK+=UGB^LgM+!HWFKZBAJ279N{LHni!RASj(_U!Wug$KLWAb5sXP?C!!szkT1VidxtpQ#+Mzl*2Ylcc{Wr2IuE*%{5d*vSF22`~E~GL2Ji`#9 zb2j_fc(#NTlqE&7dA3*|(8TDdh6zE7I%;z8WQXNVO?ntvu8OggP9qycJ6>TJev`LG zXkzm1URT51ndn*iGKFywwh@aCD@%ZAlxlrv4MuC=F-eYO`0lr(ULOxCq_N>u%5_N8*Xmrj#j%E=g%5%H`c$bwhhW4dv*} zX=(1NnPl|m^J{@>vJ58$8iHTgLEoZFsRpYc7exud0io!jlg*SgxIND@E;hKwwIwxY zK|eY9uT>!;S`m_=u)Sw$Lta)~&7wDG3l4SKR3NvdJ&96ykmL{e6-++UP)vwa&c!pZ z!0p7iQ$5paJ?Fq5qgmB1s2_5AJ2xc$_Sb%)2Qv9hmNvw>xbA*{CD3O(%ybR{iZ(I26^nr9^%QLqaJ+fZCaxK97hE_?Qw9?Q@`H61J9IPasAnJ*v6 zC4Y@$Nq4>tta*K%cinj&!W278RT7)z_U0EG*4FamWKeR*5{L^!c$5f#hatAr$zE;r z?`ai*S(MhC@3ngg3t{3_1q@Vy3)Ym3gVv=eH<4p7#>f3N#ACu|mu}yijZ=|KqIKKe zt3+bFf)n-5(}ka1sLAkL`sdio1EaooICqJ81YCa8k8=`3k~%b&x(B?A{MuVr6IgQ< z%>A}*Jxob{+%QW_Ds*KC3kvgdF3!hs?BS5rtSo=~z>=314GJ>Nbm5|)y0mL)TypUB z&>6YW$wd|AvT4Xvzy(Yk&gW-dyuQqcFj{}V41 z1VFtpNSD;1wTuwU>Gv)U4oytmVo3IFcU}8I=wr3Xs>;+jv1EF&N)i^x>53U z5%0$ZFf+>aJuFCUcwhFj1J3G#t3q0jBP({wMCX`W;8hD8KaGM96%?fXD0o!Mz13q@!Cxb%le+ge@K_Jo+ z8Hq3IZ~U=c9>jz3j7e{PZOt3@acKItZapic8~#*9P%nu05n90O#^EHoBspUUKumI6 zBePi7Tmc2?drx5wr>Pldlw7a-q&#Lmu=mS65qvJ(fAfuS`wn1a%B*xOxguX*p_m&r zYKyq~I@>D=zBPK>oz~LQP+#DlH?S!vbj;&E9ydqfEF3Md6gKFYEBKzc8*-%6JbZU} z3Bm#EQk5SK{nm==`VIvJnL8P(s5rOvHNhI9%XGQ1&T`2^4{WbmM3w@#l7PNZPirCJ zGAZ|Tg!nV`aXaK(7ZpMIrwb=2{9T(dYH(e!P6TmHg+x}1!YpG(}D;DYA3zM8wYsZ11f-QE1+PqAX}hYs>< z@LnkY{>@VJ3*t`lY3J<=Y*^ocsx}-5rrrGICpfROBcZ9k2`fnvhE@uR)q*YTo>Ytp zd_`wZOe9JIX*G%-XA^5COlqd@N$Xb@?sEEE9EAS6x=ODHVr;DuGgj};HVzt3>@&7Y zLXEC1;t{1IR~Y?MCx^J4;exq1xappI0APn&Ew5X=D>-cab^Xf_BqoPOc5m@IWbB3px&F) z>?`quXfj;`wH5|yN;;+!ZB0W5XC3Ea7~0TKgbjY{Uo9Q$%o@<4>~f$SycuJdXj_~y zM}W^)KHe+IrqJfg#^Bl?cThu z4~B9n;_3B@r#`nq2De}U0AB+89A2q>@u_!!$fM^t{)uV?o;}UN-O>gh_l44yHh_<9 z)aH+bKv0O~^d9p}L*;R;cY^zMeo!-pc#_Jvc7<2CX?+d$sJ64pA|7UrJ^c=J&ItO4 zHva|)8A2?e3wj<3B!r&O%U}SEh)HTsz*a@$IYTW%TxYd0E-tuN|Kc2AjUzTGK=ov?ezBh_GV~;G_eA;E$^ zMK_ZQ1`r4r$ThWX0ym&zG9q`@c9w-D2lCgR=T{!x^S7O51QvhR?e&8VU8wID8I6-< zdHV&>5Q-qefBo^6F37*uM{PNSt|~Xzu~o&gyNj_@x= zgQrPOEzEnR_G^8iTDge-%ei?2%@wh$0*if>Uvj2xX5l@-U){4J;tSTScWomkqR***w&tl8g5#yZ#E(@TPWs=^O{Q9}Y<5u+S{t1$wmbWlvYVd~mzK zIld@cVN2*EbUI&CMFNjq_*CO+NTuRk#*4(e3rDgccx`h>QIB@l z)~*xSu(ecL8|`&TcDEy$A4#`fVWbl^&D=4MluHT;?xeO3B=T^ET3I*>R90wxmM3g9 zYyz?ATj3k80F$>eW5|^Ah!$8uoud1Y=G>>dF#CYx5cqPxyj2u27wN5qik%UlP z^oHVYvGE;=u$VTtH>cJ8X)b5_Pe0bU9x9U}JPnNK`TC~3mR0?%R1mg>tX*|F=>kZT zbE=P1H1Ak8HnIp>4Qg7B(!=-5A77%$uf3%k`T({*xvk~9(Weh3X`S&|B=3Gd*m)Vl zH`g=h$0qMwj(QySb<+m1i383P0c<{bq6$cb-f5=oB0zY&gX+gTRv zWWXQ`Vq{L|Y)Ws3fASSq~6)|gD3y>+upvd74<&v407a05fiQVzsy?S0};Tq{9 ze`n*mncF58Q@kaL)jd2{_H8ye1WVLdUz=OMyAQ_mMqDSa1SHwG-m z+NgMjTXgee%MUjdSrs#^KG11(K23Q$k|V81a~dKgQr!57*PJArE-s7VBNiVTVXNl4 z|CXCLspo7Cli8MU7F&M%nxW(jOkF`285;)VJPrNiseSu`?E^{8FtP|rvGS}nT3fh@;xkbj4OiAZfZG3n}K zwV`^J%TIpHim_T^40L5gpd>txMR}Lj+!pSu}0#N_*zZ(79YH0PM6raU;HpQbxesomDlTQtV$f8$+ZC+zq@pq>Jlt1eg?e zDbym06Q%H>mv!o(peO-QEN(OA<^=i)3_;RzjSlmIt(X#4h@qm#9>vo1B{KJ0#Y zMo_YC?58$8wUmtPq`ZRgstNm<5GlAraB5^ggm87O=gbQ2m|MN1m;i`0Aq6=XR1e)e z0zaT+y60SHw?36WiP_*aF9N^=?Wb{I|N0iWvC&T>Oyt0dl_1pbpMI;nBzUQ-(iyv2 z`_qk*J|eN>#zkSLEyvgk2~`^Ou7y05A6d^EW|g^7h;&q{`%;0SS59t2mlsvnyawJ9 za^AEjluZGXv+Tqk+#(0Nt|!aUG@96wR=u^w_-bSOSsZ1ngk?$t}nsv^*XS1pD;W1j@$ z>f^Jw_9UoLl`uLMqR5!z3Dh=%_b+wtw#M2Hb6*=BQppW-qf^3du++ zrc~-!IJ4Yl0mlhZ<3i7P`1cs!04_;g^HH-`=af$F`oGrGZub{qeR(@_RhKOg4Ph!kKe`v!Iy84c!6T69WeZw%GjtIAQ*48VRh z=efoe6-ag@*5vkN=FuiQi~H2us{uvJgACJ2#SH3T=J^iW{*oRY&23jF!3cb*IypSa z(X8cJ0+{qJF(~>0r}$(1Pbmy^`ejz0$2*6+yYCP1Wf!f{bhnCFV757m%%;y;P53}u zi2`zAuVw=esw~n3geZGvVDfc}?!%1*F1|OnmD8OXzSLU5XWzdnu`Et{e<)F!pK+Es zm;O@@CoEnmqx>lNn^LJnqZ)6 zud2JkBj7a@xUW9|BZd4k_r#`V*5{p#@$lJkzodb@5iC21gZp8pa zgatVLp{(fmTusOmiHKD<78M8WZ&lZdaki_T0kGHbkF#{lIT^-h6{^CVU`c9-Ryvz9XbO*Ld#UHsR`pLzZ{ymcA7j z5qyUDmB>Rb)?~N!q2zwD4B4|B<3NnQ0nDqp5s2A(d|$)bS}Q=y*x1KPHFCnf+6-V) zyzI8Ghf;Fu@N`3%3ewdmCt4$0l7}b9c(qJ8^8LDm4soa{siN?&-|+3f_wj>}!%Y6{ z(e$?hZirvMc*sNce$z$xMZc-f&?B^8V;=qWi@o^+NTWnEJPhLfHdj5?;I7=Z_jf>W z3Ax`Z77g;>^nqX6cig5QfV|+{RZh1yfp68$?N|&pl87te3|`aF%k+x~DCVRIO$i~# z$Sq(x3G3U-JC6?vI+AmQE6Ozh93iH6pJwk@br4^Xlyt)nvoYs%J9@}*6!}dx^;#5? za8}rJ97xGUd784uL1LYk`;6~c=*T~M=qczjTQ>MQu(O*X(*g|Cl|2Pz(eIqF<%HPnvPPH2!|1q!p{T}=Dj=yi{aV9B*;tx5TU%cLQGk&N7?o769{AKCuySsO! zCU2>p$WD4r-FuP7uKB=(2|(|kulCR;jJgPd zD#1z_SH)mD5dE8d8k+lS*CxzY0fOIX`?bs$Hw>W{+LU^!p6-;f-LI|=I3Eg(k2Y{0 zF46q8Te!JXi1&Jl`v&c5AJM7dEl7%bRV~cQ$}y%mkZ-W?K`KVr#2HtW;0dReQfN?+ z;o(}^+));Om%-&uvrokpRZY-NUR?py&~SdHD1D>*w!oU0<|d6`Y58o>LMks-rcJ@| zaqA2gux_g7Q24HMF{>dv^yBL!D@T13z}S7@VKkT$ys;_SYO`BWRyavc02ZACCPHKz z7<{tU)0GLAq2sL^JB$e1XOLMl;z{ggea9b`{m?T?*K}8s}pXTpkUTWS6#(LZ6$wJqUD8HM_ zYixh^_K5D(H??sq5h;~2Vs@eS`4zn!L#c{1Mj}f)%`D6f9m~@OZr-JE^;x`a|2`4N z!%svC6q%sk(nUub_Ou4>DqUK=yf%|Bqx^f;loaQOHM<7A%pF$_Dw5Qo3iO}-wMc&8 z13S2KWmVeAZ+*-SKQuFLi+#7XSC-z9i+YcqtDfQuT+X<(Gb`)pC)FGnPgBw1{$#Qf z-P%XhbGA~D{cb>-FU1Pc#9Fs=w3bfbOd*Uoo0g0>K0Qg3_*fj@KYQd%=&#ErA&K+| zYH0?O=ze5UBu=145o+zHQtfJq?UKPTpdaMlJl{ve9F7@#`dGeCYn? z@@Xbol-4tHqME3`v0$olOPWb7sYbiua)G#Ar_fFZ^4nKAO^1foe*YySx2B;!@r253 zOI%7gV6Q-!|4B2^DlFuLHa4J-=gk~2LGxmxjK)UtM9YOsB~n=tab;*6z3FvhBU2^> zJp@!@QUtt)WIgn^#!19Ocp`&me8Sx?&0a0m?vE7>s4uUk^KkXzr>M#eCK<1OIP`8V zinOR4T69%iMUPfY7Fp6^`aYwj*qU=;b5wD_rV>_q(VB;H$0BhiCHOx(^QT7u;5*>~ zAXaCD?aHuImtrnTUz{HWH=uvdR}RI^x8Ir6*MokK%x`FI4^#y{E%BZ9x1DYSN(+Z@ zo8a9ajU3!V1>BcmcEY_mvT3MhK7_dfB?%v8ks5cDL9~H7cN7@HnmYi7P(et0_t>Vm z9aW|a~sG3|Ci6RdpXIGQr8= z{G^*NR{Rdwh zYJDvBudKcwcgEt~tbP9(DfMZq#uT_U3>oaYe7Nb$pIL4at|94Bh+&E->fGB=jR)x5 zrRp1xy>F}zESM_Gr#|nh)+r8()bR6yM9_f%XNs%LYn*tYM&LVGlCP)Zp#M5q5oFIi zAn~em+$8b3WFuJ{3fb0fG%$V^0*e<(jIV6@?>&uX^Zlzb+f;Ft^Av38bhTsB$yU~< z7<#%;S*fJZZ0z)KKff~c2zMSzq6T9emNtN1htNk$X?XQh(nW#vaA1|)HHxq>KUz-O z27Q%ETNOP_pO~A-Fu8PfQ1}Pev}4Vf%7CNqfZu~|6YP(pTdr7G ztt20{WM9CS1PjBh9wvP#(lt*&_nA2b8R#D7z;igL**WOq(b_1&FL6>AO}+06&|Tx$ z`1e|&OJsQj0Oxozzq%g7+7|LfwnkSQ2yFy5n6gcGn7EcVS_0ucyD+YFmfYH6{86%N}ZG_oeEyNRdBbnw+rcg zzSfW-M)i}h@q3*<8eqUy6U-({CDr+iW6CBK;B6BQ^&ki(Hv>8<+=BNq1(~IYTLK7l z0bnT~q<(d*J0XxS6^d>lJvsyCCd8~G?2KB*6m)jhgYFX0uova#u$ zZY=0*IXuvUFF*dhK(^cB9ZbhxEbMXZiSH#)lpbenD|;w=JGsl0hMpzwA8>-a^*}KG?Z|vX5jU(v8^C00iFjou z>oS>|ond`=q$i3I8O?dI)iXf?0zCY8@})M%k-D1gHRpqcbCW;%dQ{xlg)^>v4wy_S zdEpel`o-d~+cHB0!AhuqOb_v2iO$#!T{9Ki8n-GFiI$AP*C^=?3u_NIdn{%ZKR^VB zb@>o1O+&mrEU^ia%7DJ~BXObmNeDkVNASYoCg&Ug2VdY$dI2$bx_DdRt%PszDLV%j zO9;DEloYmF*oIieH`k6_POEv8)E3ReCaCgzsYWGWYrWzEDpmAWG`K8zO7j4TdCQFz zL+AgF=y4`kIz60FA{>*|R3eaqdDBFr>I)!>!+7G27;t@S?dDO&VXdDxYHp^friI_A zzaZt4)(c*(V8o}6MK~1=jg_@c7%nRtW%=_tmj3}ofU*Lb<7^+Flz(1ZV#nq z4xP1a&fi0YdUp=C(v$E(5DIP_gkj^Rj+tE0iZo`BGiU1Cn5TNkOk>RPO_A}DL#w+*KRaZMlPYn*;A zAV@Y(vuprQ8#@Rj$6pmj0s%S}ed*A=H?4OqXt?W + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_audio_note.xml b/app/src/main/res/layout-land/activity_audio_note.xml index b9d6c341..38e4cc96 100644 --- a/app/src/main/res/layout-land/activity_audio_note.xml +++ b/app/src/main/res/layout-land/activity_audio_note.xml @@ -30,7 +30,7 @@ android:id="@+id/btn_play_pause" android:layout_width="48dp" android:layout_height="48dp" - android:background="@drawable/ic_play_arrow_black_24dp" + android:background="@drawable/ic_play_arrow_icon_24dp" android:padding="16dp" android:visibility="invisible" /> @@ -38,7 +38,7 @@ android:id="@+id/btn_record" android:layout_width="48dp" android:layout_height="48dp" - android:background="@drawable/ic_mic_black_24dp" + android:background="@drawable/ic_mic_icon_24dp" android:padding="16dp" /> diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index 259b71b0..62754a4b 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -1,25 +1,25 @@ + android:background="?attr/colorSurface"> + tools:context=".ui.AboutActivity"> diff --git a/app/src/main/res/layout/activity_audio_note.xml b/app/src/main/res/layout/activity_audio_note.xml index 1158393f..36bb2b4b 100644 --- a/app/src/main/res/layout/activity_audio_note.xml +++ b/app/src/main/res/layout/activity_audio_note.xml @@ -1,6 +1,7 @@ + android:background="@drawable/ic_mic_icon_24dp" /> + android:background="@drawable/ic_play_arrow_icon_24dp"/> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8e69e22f..bf3e097a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -19,6 +19,7 @@ android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" + android:background="?attr/colorBackground" app:headerLayout="@layout/nav_header_main" app:menu="@menu/activity_main_drawer" /> diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml index a514fb20..02a82dcd 100644 --- a/app/src/main/res/layout/nav_header_main.xml +++ b/app/src/main/res/layout/nav_header_main.xml @@ -2,37 +2,41 @@ + android:orientation="horizontal" + android:background="?attr/colorNavbarHeaderSurface" + android:paddingTop="7dp" + android:paddingBottom="7dp"> + android:layout_toRightOf="@id/imageView" + android:textColor="?attr/colorNavbarHeaderText" + android:textSize="18sp"/> diff --git a/app/src/main/res/layout/note_header.xml b/app/src/main/res/layout/note_header.xml index ae136e43..72853079 100644 --- a/app/src/main/res/layout/note_header.xml +++ b/app/src/main/res/layout/note_header.xml @@ -34,7 +34,9 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:inputType="none" - android:singleLine="true"/> + android:singleLine="true" + android:textColor="?attr/editTextColor" + /> \ No newline at end of file diff --git a/app/src/main/res/layout/note_item.xml b/app/src/main/res/layout/note_item.xml index 44176335..d0f1277d 100644 --- a/app/src/main/res/layout/note_item.xml +++ b/app/src/main/res/layout/note_item.xml @@ -4,18 +4,22 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:layout_marginHorizontal="@dimen/activity_horizontal_margin"> + android:layout_marginHorizontal="@dimen/activity_horizontal_margin" + app:cardBackgroundColor="?attr/colorSurface" + app:cardCornerRadius="8dp" + > + app:srcCompat="@drawable/ic_format_list_bulleted_icon_24dp" /> + android:textAppearance="@style/TextAppearance.AppCompat.Large" + /> \ No newline at end of file diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index aa8bd675..a0cbfec1 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -18,7 +18,7 @@ diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..2876dcd9 --- /dev/null +++ b/app/src/main/res/values-night/styles.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 6b46c5bb..b7dc397a 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -47,4 +47,10 @@ 30 + + -1 + 1 + 2 + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 00000000..6b49c61d --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index ef80205e..8e50f5b7 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,6 +3,12 @@ SecUSo colors: <--> #024265 #024265 + #FFFFFF + #373a3d + #FFFFFF + #050a0f + #FFFFFF + #222222 #0274B2 #00000000 #A8A8A8 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 82f783a3..2f51dd7b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -154,5 +154,11 @@ Formatting Note not saved + Design + + System + Light + Dark + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 0ee66388..a4ad8780 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,13 +1,22 @@ - + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index a4ad8780..edbe119a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -43,4 +43,7 @@ @color/white + - - - - + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index edbe119a..16dece79 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -33,9 +33,9 @@ From 087a8eb9ddc6669854c2a6b4af8b923fb0564ffc Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Fri, 3 Nov 2023 09:15:45 +0100 Subject: [PATCH 012/112] Allows to share text or text/plain files to open a new text note. --- app/src/main/AndroidManifest.xml | 26 ++++++++++++++++++- .../ui/notes/BaseNoteActivity.kt | 4 +++ .../ui/notes/TextNoteActivity.kt | 16 ++++++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ca1c1e59..0709dcb2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -38,7 +38,31 @@ android:name=".ui.notes.TextNoteActivity" android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/title_textnote" - android:parentActivityName=".ui.main.MainActivity" /> + android:parentActivityName=".ui.main.MainActivity" + android:exported="true"> + + + + + + + + + + + + + + + + + + + + + + + (private val status: Boolean, val ok: O?, val err: E? = null) { fun isOk(): Boolean { return this.status diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt index 53dd51e8..41ac6e05 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/TextNoteActivity.kt @@ -17,6 +17,7 @@ import android.content.Intent import android.content.res.ColorStateList import android.graphics.Color import android.graphics.Typeface +import android.net.Uri import android.os.Bundle import android.text.Html import android.text.Spannable @@ -31,10 +32,10 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton import org.secuso.privacyfriendlynotes.R import org.secuso.privacyfriendlynotes.room.DbContract import org.secuso.privacyfriendlynotes.room.model.Note +import java.io.InputStreamReader import java.io.OutputStream import java.io.PrintWriter - /** * Activity that allows to add, edit and delete text notes. */ @@ -83,7 +84,18 @@ class TextNoteActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_TEXT) { } override fun onNewNote() { - + if (intent != null) { + val uri: Uri? = listOf(intent.data, intent.getParcelableExtra(Intent.EXTRA_STREAM)).firstNotNullOfOrNull { it } + if (uri != null) { + val text = InputStreamReader(contentResolver.openInputStream(uri)).readLines(); + super.setTitle(text[0]) + etContent.setText(Html.fromHtml(text.subList(1,text.size).joinToString(System.lineSeparator()))) + } + val text = intent.getStringExtra(Intent.EXTRA_TEXT) + if (text != null) { + etContent.setText(Html.fromHtml(text)) + } + } } override fun onLoadActivity() { From bb5191e4ec6ba0953eff513ace1f71cef9725d8e Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Sat, 4 Nov 2023 09:40:02 +0100 Subject: [PATCH 013/112] Updates .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index da1f061a..9ef5f066 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ local.properties .idea/modules.xml .idea/scopes/scope_settings.xml .idea/vcs.xml +.idea/* *.iml # OS-specific files From 790fadff717777b2c60bf5db15cebf4c454fbe6e Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Sat, 4 Nov 2023 09:41:56 +0100 Subject: [PATCH 014/112] Untracks .idea files --- .idea/assetWizardSettings.xml | 52 ----------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 .idea/assetWizardSettings.xml diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml deleted file mode 100644 index 376667eb..00000000 --- a/.idea/assetWizardSettings.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - \ No newline at end of file From 200386d6b51aa66722518a5ac292ca72978fea67 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Sat, 4 Nov 2023 10:56:43 +0100 Subject: [PATCH 015/112] Resolves #150, #59, Reworks notes filtering. Adds more sorting orders. Sorting Order is now persistent. --- .../model/SortingOrder.kt | 25 ++++++ .../preference/PreferenceKeys.java | 2 + .../privacyfriendlynotes/room/dao/NoteDao.kt | 20 ++++- .../ui/helper/SortingOptionDialog.kt | 83 +++++++++++++++++++ .../ui/main/MainActivity.java | 29 ++++--- .../ui/main/MainActivityViewModel.kt | 33 ++++++-- .../ic_baseline_access_time_icon_24dp.xml | 6 ++ .../ic_baseline_edit_note_icon_24dp.xml | 5 ++ .../main/res/drawable/ic_block_icon_24dp.xml | 5 ++ ...4dp.xml => ic_sort_by_alpha_icon_24dp.xml} | 2 +- .../res/layout/dialog_sorting_options.xml | 12 +++ .../layout/dialog_sorting_options_item.xml | 20 +++++ app/src/main/res/values-de/strings.xml | 8 ++ app/src/main/res/values/arrays.xml | 9 ++ app/src/main/res/values/strings.xml | 8 ++ 15 files changed, 238 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt create mode 100644 app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/SortingOptionDialog.kt create mode 100644 app/src/main/res/drawable/ic_baseline_access_time_icon_24dp.xml create mode 100644 app/src/main/res/drawable/ic_baseline_edit_note_icon_24dp.xml create mode 100644 app/src/main/res/drawable/ic_block_icon_24dp.xml rename app/src/main/res/drawable/{ic_sort_by_alpha_white_24dp.xml => ic_sort_by_alpha_icon_24dp.xml} (91%) create mode 100644 app/src/main/res/layout/dialog_sorting_options.xml create mode 100644 app/src/main/res/layout/dialog_sorting_options_item.xml diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt new file mode 100644 index 00000000..7b6d0b43 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt @@ -0,0 +1,25 @@ +package org.secuso.privacyfriendlynotes.model + +import android.content.Context +import android.preference.PreferenceManager +import org.secuso.privacyfriendlynotes.preference.PreferenceKeys + +class SortingOrder(context: Context) { + + val prefManager = PreferenceManager.getDefaultSharedPreferences(context) + var ordering = Options.values().filter { + it.name == prefManager.getString(PreferenceKeys.SP_NOTES_ORDERING, Options.Creation.name) + }.getOrElse(0) { _ -> Options.Creation } + set(value) { + prefManager.edit() + .putString(PreferenceKeys.SP_NOTES_ORDERING, value.name) + .apply() + field = value + } + + enum class Options { + AlphabeticalAscending, + TypeAscending, + Creation + } +} \ No newline at end of file diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/preference/PreferenceKeys.java b/app/src/main/java/org/secuso/privacyfriendlynotes/preference/PreferenceKeys.java index a26dbbf7..2651e21d 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/preference/PreferenceKeys.java +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/preference/PreferenceKeys.java @@ -22,4 +22,6 @@ public class PreferenceKeys { public static final String SP_DATA_DISPLAY_TRASH_MESSAGE = "sp_data_display_trash_message"; public static final String SP_VALUES = "values"; public static final String SP_VALUES_NAMECOUNTER = "sp_values_namecounter"; + + public static final String SP_NOTES_ORDERING = "notes_ordering"; } diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt index a2ae3e88..60384492 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt @@ -39,8 +39,17 @@ interface NoteDao { @get:Query("SELECT * FROM notes WHERE in_trash = 0 ORDER BY name ASC") val allNotesAlphabetical: LiveData> + @get:Query("SELECT * FROM notes WHERE in_trash = 0 ORDER BY name ASC") + val allActiveNotesAlphabetical: Flow> + @get:Query("SELECT * FROM notes WHERE in_trash = 0 ORDER BY name DESC") - val allActiveNotes: LiveData> + val allActiveNotesAlphabeticalDesc: Flow> + + @get:Query("SELECT * FROM notes WHERE in_trash = 0 ORDER BY type ASC") + val allActiveNotesType: Flow> + + @get:Query("SELECT * FROM notes WHERE in_trash = 0 ORDER BY _id ASC") + val allActiveNotesCreation: Flow> @get:Query("SELECT * FROM notes WHERE in_trash = 1 ORDER BY name DESC") val allTrashedNotes: LiveData> @@ -48,12 +57,15 @@ interface NoteDao { @Query("SELECT * FROM notes WHERE category=:thisCategory AND in_trash='0'") fun notesFromCategory(thisCategory: Integer): LiveData?> - @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='0' ORDER BY name DESC") - fun activeNotesFiltered(thisFilterText: String): Flow?> - @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='0' ORDER BY name ASC") fun activeNotesFilteredAlphabetical(thisFilterText: String): Flow?> + @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='0' ORDER BY type ASC") + fun activeNotesFilteredType(thisFilterText: String): Flow?> + + @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='0' ORDER BY _id ASC") + fun activeNotesFilteredCreation(thisFilterText: String): Flow?> + @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='1' ORDER BY name DESC") fun trashedNotesFiltered(thisFilterText: String): Flow> diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/SortingOptionDialog.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/SortingOptionDialog.kt new file mode 100644 index 00000000..3b63ed36 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/helper/SortingOptionDialog.kt @@ -0,0 +1,83 @@ +package org.secuso.privacyfriendlynotes.ui.helper + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.core.util.Consumer +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.bottomsheet.BottomSheetDialog +import org.secuso.privacyfriendlynotes.R +import org.secuso.privacyfriendlynotes.model.SortingOrder + +class SortingOptionDialog( + context: Context, + sortingOptionTextResId: Int, + sortingOptionIconResId: Int, + onChosen: Consumer +) { + + private val dialog = BottomSheetDialog(context) + private val recyclerView by lazy { + dialog.findViewById(R.id.sorting_options)!! + } + + init { + dialog.setContentView(R.layout.dialog_sorting_options) + recyclerView.layoutManager = LinearLayoutManager(context) + + val icons = context.resources.obtainTypedArray(sortingOptionIconResId); + val options = context.resources.getStringArray(sortingOptionTextResId) + .zip((0 until icons.length()).map { icons.getResourceId(it, 0) }) + .mapIndexed { i, (text, icon) -> SortingOptionData( + text, + icon, + SortingOrder.Options.values()[i] + ) } + icons.recycle() + recyclerView.adapter = SortingOptionAdapter(options) { option -> + onChosen.accept(option) + dialog.dismiss() + } + } + + fun chooseSortingOption() { + dialog.show() + } + + data class SortingOptionData( + val text: String, + val icon: Int, + val option: SortingOrder.Options + ) + + inner class SortingOptionAdapter( + private val options: List, + private val onChosen: Consumer + ): RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SortingOptionHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.dialog_sorting_options_item, parent, false) + return SortingOptionHolder(view) + } + + override fun getItemCount(): Int { + return options.size + } + + override fun onBindViewHolder(holder: SortingOptionHolder, position: Int) { + holder.textView.text = options[position].text + holder.imgView.setImageResource(options[position].icon) + holder.itemView.setOnClickListener { _ -> onChosen.accept(options[position].option) } + } + + inner class SortingOptionHolder(view: View): RecyclerView.ViewHolder(view) { + val textView: TextView = view.findViewById(R.id.sorting_option_text) + val imgView: ImageView = view.findViewById(R.id.sorting_option_icon) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java index c9e657ff..b997f577 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivity.java @@ -15,7 +15,6 @@ import android.app.Activity; import android.content.Intent; -import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.preference.PreferenceManager; @@ -60,6 +59,7 @@ import org.secuso.privacyfriendlynotes.room.model.Note; import org.secuso.privacyfriendlynotes.ui.AboutActivity; import org.secuso.privacyfriendlynotes.ui.TutorialActivity; +import org.secuso.privacyfriendlynotes.ui.helper.SortingOptionDialog; import org.secuso.privacyfriendlynotes.ui.notes.AudioNoteActivity; import org.secuso.privacyfriendlynotes.ui.notes.BaseNoteActivity; import org.secuso.privacyfriendlynotes.ui.notes.ChecklistNoteActivity; @@ -141,14 +141,8 @@ protected void onCreate(Bundle savedInstanceState) { recyclerView.setHasFixedSize(true); adapter = new NoteAdapter(mainActivityViewModel); recyclerView.setAdapter(adapter); - adapter.notifyDataSetChanged(); - mainActivityViewModel.getActiveNotes().observe(this, new Observer>() { - @Override - public void onChanged(@Nullable List notes) { - adapter.setNotes(notes); - } - }); + mainActivityViewModel.getActiveNotes().observe(this, notes -> adapter.setNotes(notes)); new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT) { @Override @@ -259,9 +253,16 @@ public boolean onOptionsItemSelected(MenuItem item) { //noinspection SimplifiableIfStatement if (id == R.id.action_sort_alphabetical) { - //switch to an alphabetically ascending or descending order - updateListAlphabetical(searchView.getQuery().toString()); - return true; + SortingOptionDialog dialog = new SortingOptionDialog( + this, + R.array.notes_sort_ordering_text, + R.array.notes_sort_ordering_icons, + option -> { + mainActivityViewModel.setOrder(option); + updateList(searchView.getQuery().toString()); + } + ); + dialog.chooseSortingOption(); } return super.onOptionsItemSelected(item); @@ -371,10 +372,8 @@ public void onChanged(@Nullable List categories) { * Sorts filtered notes alphabetical in descending or ascending order. * @param filter */ - private void updateListAlphabetical(String filter) { - LiveData> data = alphabeticalAsc ? - mainActivityViewModel.getActiveNotesFiltered(filter) - : mainActivityViewModel.getActiveNotesFilteredAlphabetical(filter); + private void updateList(String filter) { + LiveData> data = mainActivityViewModel.getActiveNotesFiltered(filter); data.observe(this, notes -> { adapter.setNotes(notes); diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt index 1791acbd..9db68191 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.json.JSONArray import org.json.JSONException +import org.secuso.privacyfriendlynotes.model.SortingOrder import org.secuso.privacyfriendlynotes.room.DbContract import org.secuso.privacyfriendlynotes.room.NoteDatabase import org.secuso.privacyfriendlynotes.room.model.Category @@ -41,11 +42,15 @@ import java.io.File class MainActivityViewModel(application: Application) : AndroidViewModel(application) { private val repository: NoteDatabase = NoteDatabase.getInstance(application) - val activeNotes: LiveData> = repository.noteDao().allActiveNotes val trashedNotes: LiveData> = repository.noteDao().allTrashedNotes val allCategoriesLive: LiveData> = repository.categoryDao().allCategoriesLive val filesDir = application.filesDir val resources = application.resources + val sortingOrder = SortingOrder(application) + + fun setOrder(ordering: SortingOrder.Options) { + this.sortingOrder.ordering = ordering + } fun insert(note: Note) { viewModelScope.launch(Dispatchers.Default) { @@ -104,20 +109,30 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica }; } - fun getActiveNotesFiltered(filter: String): LiveData?> { - var filteredNotes = MutableLiveData>(); + fun getActiveNotes(): LiveData?> { + val notes = MutableLiveData>(); viewModelScope.launch(Dispatchers.Main) { - filterNoteFlow(filter, repository.noteDao().activeNotesFiltered(filter)).collect { - filteredNotes.value = it + val flow = when(sortingOrder.ordering) { + SortingOrder.Options.AlphabeticalAscending -> repository.noteDao().allActiveNotesAlphabetical + SortingOrder.Options.TypeAscending -> repository.noteDao().allActiveNotesType + SortingOrder.Options.Creation -> repository.noteDao().allActiveNotesCreation + } + flow.collect { + notes.value = it } } - return filteredNotes + return notes } - fun getActiveNotesFilteredAlphabetical(filter: String): LiveData?>{ - var filteredNotes = MutableLiveData>(); + fun getActiveNotesFiltered(filter: String): LiveData?> { + val filteredNotes = MutableLiveData>(); viewModelScope.launch(Dispatchers.Main) { - filterNoteFlow(filter, repository.noteDao().activeNotesFilteredAlphabetical(filter)).collect { + val flow = when(sortingOrder.ordering) { + SortingOrder.Options.AlphabeticalAscending -> repository.noteDao().activeNotesFilteredAlphabetical(filter) + SortingOrder.Options.TypeAscending -> repository.noteDao().activeNotesFilteredType(filter) + SortingOrder.Options.Creation -> repository.noteDao().activeNotesFilteredCreation(filter) + } + filterNoteFlow(filter, flow).collect { filteredNotes.value = it } } diff --git a/app/src/main/res/drawable/ic_baseline_access_time_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_access_time_icon_24dp.xml new file mode 100644 index 00000000..d72f9645 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_access_time_icon_24dp.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/drawable/ic_baseline_edit_note_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_edit_note_icon_24dp.xml new file mode 100644 index 00000000..ad87c776 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_edit_note_icon_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_block_icon_24dp.xml b/app/src/main/res/drawable/ic_block_icon_24dp.xml new file mode 100644 index 00000000..6ced7636 --- /dev/null +++ b/app/src/main/res/drawable/ic_block_icon_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_sort_by_alpha_white_24dp.xml b/app/src/main/res/drawable/ic_sort_by_alpha_icon_24dp.xml similarity index 91% rename from app/src/main/res/drawable/ic_sort_by_alpha_white_24dp.xml rename to app/src/main/res/drawable/ic_sort_by_alpha_icon_24dp.xml index c8d41361..ad2f51c3 100644 --- a/app/src/main/res/drawable/ic_sort_by_alpha_white_24dp.xml +++ b/app/src/main/res/drawable/ic_sort_by_alpha_icon_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/layout/dialog_sorting_options.xml b/app/src/main/res/layout/dialog_sorting_options.xml new file mode 100644 index 00000000..3638195e --- /dev/null +++ b/app/src/main/res/layout/dialog_sorting_options.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_sorting_options_item.xml b/app/src/main/res/layout/dialog_sorting_options_item.xml new file mode 100644 index 00000000..3b75cfbd --- /dev/null +++ b/app/src/main/res/layout/dialog_sorting_options_item.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5498e05c..b5182d35 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -135,4 +135,12 @@ Für diese Funktion ist die Berechtigung zum Senden von Benachrichtigungen und zur Planung von Alarmen und Erinnerungen erforderlich. Alle Notizen löschen Wollen Sie alle Notizen entgültig löschen? + + + + + Alphabetisch + Notizart + Erstellungszeit + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index b7dc397a..5d47f77a 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -53,4 +53,13 @@ 2 + + + + @drawable/ic_sort_by_alpha_icon_24dp + @drawable/ic_baseline_edit_note_icon_24dp + @drawable/ic_baseline_access_time_icon_24dp + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a2942589..458492a1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -165,4 +165,12 @@ Dark + + + + Alphabetical + Note Type + Creation Time + + From 3113b48aa2afcef6b20fc91002bb6b672b76e734 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Sat, 4 Nov 2023 11:29:04 +0100 Subject: [PATCH 016/112] Migrates database. Adds new sorting fields to notes. Adds new color field to category. Adds migration from 4 to 5. --- .../room/NoteDatabase.java | 42 ++++++++++++++++++- .../room/model/Category.kt | 14 +++++-- .../privacyfriendlynotes/room/model/Note.kt | 13 +++++- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/NoteDatabase.java b/app/src/main/java/org/secuso/privacyfriendlynotes/room/NoteDatabase.java index 94ec1f90..cf5edf12 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/NoteDatabase.java +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/NoteDatabase.java @@ -47,7 +47,7 @@ ) public abstract class NoteDatabase extends RoomDatabase { - public static final int VERSION = 4; + public static final int VERSION = 5; public static final String DATABASE_NAME = "allthenotes"; private static NoteDatabase instance; @@ -93,6 +93,43 @@ public void onCreate(@NonNull SupportSQLiteDatabase db) { } }; + static final Migration MIGRATION_4_5 = new Migration(4,5) { + + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + + // Adds new color field + database.execSQL("ALTER TABLE categories ADD COLUMN color TEXT"); + + // Adds new fields to sort by + database.execSQL( + "CREATE TABLE notes_new (_id INTEGER NOT NULL DEFAULT 0," + + "in_trash INTEGER NOT NULL DEFAULT 0," + + "name TEXT NOT NULL DEFAULT 'TEXT'," + + "type INTEGER NOT NULL DEFAULT 0," + + "category INTEGER NOT NULL DEFAULT 0," + + "content TEXT NOT NULL DEFAULT 'TEXT'," + + "last_modified TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP," + + "custom_order INTEGER NOT NULL DEFAULT 0," + + "PRIMARY KEY(_id));"); + database.execSQL("INSERT INTO notes_new(_id, in_trash,name,type,category,content,custom_order) SELECT _id, in_trash,name,type,category,content,_id as custom_order FROM notes ORDER BY _id ASC;"); + database.execSQL("DROP TABLE notes;"); + database.execSQL("ALTER TABLE notes_new RENAME TO notes"); + database.execSQL( + "CREATE TRIGGER [UpdateLastModified] AFTER UPDATE ON notes FOR EACH ROW WHEN NEW.last_modified = OLD.last_modified " + + "BEGIN " + + "UPDATE notes SET last_modified = DateTime('now') WHERE _id=NEW._id; " + + "END;" + ); + database.execSQL( + "CREATE TRIGGER [InsertCustomOrder] AFTER INSERT ON notes FOR EACH ROW " + + "BEGIN " + + "UPDATE notes SET custom_order = _id WHERE _id=NEW._id; " + + "END;" + ); + } + }; + /** * Provides data migration from database version 3 to 4 which checks for an error in the previous * migration when a backup was imported @@ -233,6 +270,7 @@ public void migrate(SupportSQLiteDatabase database) { MIGRATION_1_2, MIGRATION_1_3, MIGRATION_2_3, - MIGRATION_3_4 + MIGRATION_3_4, + MIGRATION_4_5 }; } diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Category.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Category.kt index 0065267d..f56563a5 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Category.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Category.kt @@ -24,11 +24,19 @@ import androidx.room.PrimaryKey data class Category( @PrimaryKey(autoGenerate = true) val _id: Int, - val name: String) { + val name: String, + val color: String? +) { - constructor(name: String) : this( + constructor(name: String): this( name = name, - _id = 0 + _id = 0, + color = null + ) + constructor(name: String, color: String?) : this( + name = name, + _id = 0, + color = color ) } \ No newline at end of file diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Note.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Note.kt index 491d1652..bf4bf864 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Note.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/model/Note.kt @@ -13,8 +13,11 @@ */ package org.secuso.privacyfriendlynotes.room.model +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import java.time.Instant +import java.util.Calendar /** * Provides note class with variables and constructor. @@ -28,7 +31,11 @@ data class Note( var content: String, var type: Int, var category: Int, - var in_trash: Int = 0) { + var in_trash: Int = 0, + @ColumnInfo(defaultValue = "CURRENT_TIMESTAMP") + var last_modified: String, + var custom_order: Int +) { constructor(name: String, content: String, type: Int, category: Int) : this( name = name, @@ -36,6 +43,8 @@ data class Note( type = type, category = category, in_trash = 0, - _id = 0 + _id = 0, + last_modified = Calendar.getInstance().time.toString(), + custom_order = 0 ) } \ No newline at end of file From e4908898d3bca8f8291e457dadabb7038a9ceff3 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Sat, 4 Nov 2023 11:49:36 +0100 Subject: [PATCH 017/112] Resolves #43. Adds last modified sort option. --- .../org/secuso/privacyfriendlynotes/model/SortingOrder.kt | 3 ++- .../org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt | 6 ++++++ .../privacyfriendlynotes/ui/main/MainActivityViewModel.kt | 2 ++ app/src/main/res/drawable/ic_baseline_mode_edit_24dp.xml | 5 +++++ app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values/arrays.xml | 1 + app/src/main/res/values/strings.xml | 1 + 7 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_baseline_mode_edit_24dp.xml diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt index 7b6d0b43..a1ddf0f6 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/model/SortingOrder.kt @@ -20,6 +20,7 @@ class SortingOrder(context: Context) { enum class Options { AlphabeticalAscending, TypeAscending, - Creation + Creation, + LastModified } } \ No newline at end of file diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt index 60384492..33f59d23 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/NoteDao.kt @@ -51,6 +51,9 @@ interface NoteDao { @get:Query("SELECT * FROM notes WHERE in_trash = 0 ORDER BY _id ASC") val allActiveNotesCreation: Flow> + @get:Query("SELECT * FROM notes WHERE in_trash = 0 ORDER BY last_modified DESC") + val allActiveNotesModified: Flow> + @get:Query("SELECT * FROM notes WHERE in_trash = 1 ORDER BY name DESC") val allTrashedNotes: LiveData> @@ -66,6 +69,9 @@ interface NoteDao { @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='0' ORDER BY _id ASC") fun activeNotesFilteredCreation(thisFilterText: String): Flow?> + @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='0' ORDER BY last_modified DESC") + fun activeNotesFilteredModified(thisFilterText: String): Flow?> + @Query("SELECT * FROM notes WHERE ((LOWER(name) LIKE '%'|| LOWER(:thisFilterText) || '%') OR (LOWER(content) LIKE '%'|| LOWER(:thisFilterText) || '%' AND type = 3) OR type = 1) AND in_trash='1' ORDER BY name DESC") fun trashedNotesFiltered(thisFilterText: String): Flow> diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt index 9db68191..f75c4afd 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt @@ -116,6 +116,7 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica SortingOrder.Options.AlphabeticalAscending -> repository.noteDao().allActiveNotesAlphabetical SortingOrder.Options.TypeAscending -> repository.noteDao().allActiveNotesType SortingOrder.Options.Creation -> repository.noteDao().allActiveNotesCreation + SortingOrder.Options.LastModified -> repository.noteDao().allActiveNotesModified } flow.collect { notes.value = it @@ -131,6 +132,7 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica SortingOrder.Options.AlphabeticalAscending -> repository.noteDao().activeNotesFilteredAlphabetical(filter) SortingOrder.Options.TypeAscending -> repository.noteDao().activeNotesFilteredType(filter) SortingOrder.Options.Creation -> repository.noteDao().activeNotesFilteredCreation(filter) + SortingOrder.Options.LastModified -> repository.noteDao().activeNotesFilteredModified(filter) } filterNoteFlow(filter, flow).collect { filteredNotes.value = it diff --git a/app/src/main/res/drawable/ic_baseline_mode_edit_24dp.xml b/app/src/main/res/drawable/ic_baseline_mode_edit_24dp.xml new file mode 100644 index 00000000..6787c41a --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_mode_edit_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b5182d35..560f6693 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -142,5 +142,6 @@ Alphabetisch Notizart Erstellungszeit + Zuletzt verändert \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 5d47f77a..fdd4f87f 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -59,6 +59,7 @@ @drawable/ic_sort_by_alpha_icon_24dp @drawable/ic_baseline_edit_note_icon_24dp @drawable/ic_baseline_access_time_icon_24dp + @drawable/ic_baseline_mode_edit_24dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 458492a1..bbeba942 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -171,6 +171,7 @@ Alphabetical Note Type Creation Time + Last Modified From 11f5b35dccb925d12e599711a74a16eae7378d79 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Sat, 4 Nov 2023 11:52:35 +0100 Subject: [PATCH 018/112] Improves dark mode stylings. --- .../privacyfriendlynotes/ui/adapter/NoteAdapter.kt | 11 +++++++---- app/src/main/res/layout/activity_sketch.xml | 2 +- app/src/main/res/layout/item_category.xml | 2 +- app/src/main/res/menu/main.xml | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt index f32ad933..b092d770 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt @@ -13,7 +13,6 @@ */ package org.secuso.privacyfriendlynotes.ui.adapter -import android.opengl.Visibility import android.preference.PreferenceManager import android.text.Html import android.util.Log @@ -35,8 +34,7 @@ import org.secuso.privacyfriendlynotes.ui.main.MainActivityViewModel * @see org.secuso.privacyfriendlynotes.ui.RecycleActivity */ class NoteAdapter(private val mainActivityViewModel: MainActivityViewModel) : RecyclerView.Adapter() { - private var notes: List = ArrayList() - private val notesFilteredList: List = ArrayList() + private var notes: MutableList = ArrayList() private var listener: ((Note) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteHolder { val itemView = LayoutInflater.from(parent.context) @@ -57,6 +55,10 @@ class NoteAdapter(private val mainActivityViewModel: MainActivityViewModel) : Re holder.textViewDescription.text = "" val pref = PreferenceManager.getDefaultSharedPreferences(holder.itemView.context) holder.textViewDescription.visibility = if (pref.getBoolean("settings_show_preview", true)) View.VISIBLE else View.GONE + holder.textViewExtraText.visibility = View.GONE + holder.textViewExtraText.text = null + holder.imageViewcategory.visibility = View.GONE + holder.imageViewcategory.setImageResource(0) when (currentNote.type) { DbContract.NoteEntry.TYPE_TEXT -> { holder.textViewDescription.text = Html.fromHtml(currentNote.content) @@ -100,7 +102,8 @@ class NoteAdapter(private val mainActivityViewModel: MainActivityViewModel) : Re } fun setNotes(notes: List) { - this.notes = notes + this.notes.clear() + this.notes.addAll(notes) notifyDataSetChanged() } diff --git a/app/src/main/res/layout/activity_sketch.xml b/app/src/main/res/layout/activity_sketch.xml index 2c84cf93..4fc05af0 100644 --- a/app/src/main/res/layout/activity_sketch.xml +++ b/app/src/main/res/layout/activity_sketch.xml @@ -38,7 +38,7 @@ android:layout_height="0dp" android:layout_weight="1" android:id="@+id/draw_view" - android:background="@drawable/border" + android:background="?attr/colorSurface" android:padding="3dp"/> diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml index 62b17a1a..dcbe67a8 100644 --- a/app/src/main/res/layout/item_category.xml +++ b/app/src/main/res/layout/item_category.xml @@ -9,7 +9,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" - android:textColor="@color/black" + android:textColor="?attr/colorOnBackground" android:textSize="20sp" android:layout_margin="10dp"/> diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index 66eda19d..14127251 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -5,6 +5,6 @@ android:id="@+id/action_sort_alphabetical" android:orderInCategory="100" android:title="@string/action_sort_alphabetical" - android:icon="@drawable/ic_sort_by_alpha_white_24dp" + android:icon="@drawable/ic_sort_by_alpha_icon_24dp" app:showAsAction="ifRoom" /> From 21d7d989f45c076488838a502e17909671615525 Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Sat, 4 Nov 2023 17:11:09 +0100 Subject: [PATCH 019/112] Adds Undo/Redo-Option to sketches. --- .../ui/notes/SketchActivity.kt | 63 +++++++++++++++++++ .../res/drawable/ic_baseline_redo_icon_24.xml | 5 ++ .../drawable/ic_baseline_undo_icon_24dp.xml | 5 ++ app/src/main/res/menu/activity_sketch.xml | 16 +++++ app/src/main/res/menu/base_note.xml | 4 +- app/src/main/res/values-de/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 7 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_redo_icon_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_undo_icon_24dp.xml create mode 100644 app/src/main/res/menu/activity_sketch.xml diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.kt index 1e1ed318..19e429ec 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.kt @@ -13,6 +13,7 @@ */ package org.secuso.privacyfriendlynotes.ui.notes +import android.annotation.SuppressLint import android.content.Intent import android.graphics.Bitmap import android.graphics.Canvas @@ -20,6 +21,10 @@ import android.graphics.Color import android.graphics.Matrix import android.graphics.drawable.BitmapDrawable import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.MotionEvent import android.view.View import android.widget.Button import androidx.core.content.FileProvider @@ -41,9 +46,14 @@ import java.io.OutputStream class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH) { private val drawView: InkView by lazy { findViewById(R.id.draw_view) } private val btnColorSelector: Button by lazy { findViewById(R.id.btn_color_selector) } + private lateinit var undoButton: MenuItem + private lateinit var redoButton: MenuItem private var mFileName = "finde_die_datei.mp4" private var mFilePath: String? = null private var sketchLoaded = false + private val undoStates = mutableListOf() + private var redoStates = mutableListOf() + private var state: Bitmap? = null private fun emptyBitmap(): Bitmap { return Bitmap.createBitmap( @@ -53,6 +63,7 @@ class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH) { ) } + @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { setContentView(R.layout.activity_sketch) @@ -61,6 +72,22 @@ class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH) { drawView.setColor(Color.BLACK) drawView.setMinStrokeWidth(1.5f) drawView.setMaxStrokeWidth(6f) + drawView.setOnTouchListener { view, motionEvent -> + if (motionEvent.actionMasked == MotionEvent.ACTION_UP) { + if (state == null) { + state = emptyBitmap() + } + undoStates.add(state!!) + redoStates.clear() + if (undoStates.size > 32) { + undoStates.removeFirst() + } + state = drawView.bitmap.copy(Bitmap.Config.ARGB_8888, false) + undoButton.isEnabled = true + redoButton.isEnabled = false + } + return@setOnTouchListener view.onTouchEvent(motionEvent) + } super.onCreate(savedInstanceState) } @@ -79,6 +106,42 @@ class SketchActivity : BaseNoteActivity(DbContract.NoteEntry.TYPE_SKETCH) { mFilePath = filesDir.path + "/sketches" + mFileName } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.activity_sketch, menu) + undoButton = menu!!.findItem(R.id.action_sketch_undo) + redoButton = menu.findItem(R.id.action_sketch_redo) + undoButton.isEnabled = false + redoButton.isEnabled = false + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when(item.itemId) { + R.id.action_sketch_undo -> { + drawView.clear() + if (undoStates.isNotEmpty()) { + redoStates.add(state!!) + undoRedoState(undoStates.removeLast()) + } + } + R.id.action_sketch_redo -> { + if (redoStates.isNotEmpty()) { + undoStates.add(state!!) + undoRedoState(redoStates.removeLast()) + } + } + else -> {} + } + return super.onOptionsItemSelected(item) + } + + private fun undoRedoState(state: Bitmap) { + this.state = state + drawView.drawBitmap(state, 0F, 0F, null) + undoButton.isEnabled = undoStates.isNotEmpty() + redoButton.isEnabled = redoStates.isNotEmpty() + } + override fun shareNote(name: String): ActionResult { val tempPath = mFilePath!!.substring(0, mFilePath!!.length - 3) + "jpg" val sketchFile = File(tempPath) diff --git a/app/src/main/res/drawable/ic_baseline_redo_icon_24.xml b/app/src/main/res/drawable/ic_baseline_redo_icon_24.xml new file mode 100644 index 00000000..61057da8 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_redo_icon_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_undo_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_undo_icon_24dp.xml new file mode 100644 index 00000000..35d9e2f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_undo_icon_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/menu/activity_sketch.xml b/app/src/main/res/menu/activity_sketch.xml new file mode 100644 index 00000000..0374de47 --- /dev/null +++ b/app/src/main/res/menu/activity_sketch.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/base_note.xml b/app/src/main/res/menu/base_note.xml index e422f02a..d877873e 100644 --- a/app/src/main/res/menu/base_note.xml +++ b/app/src/main/res/menu/base_note.xml @@ -6,13 +6,13 @@ android:orderInCategory="50" android:title="@string/action_reminder" android:icon="@drawable/ic_alarm_add_white_24dp" - app:showAsAction="ifRoom" /> + app:showAsAction="always" /> + app:showAsAction="always" /> Checklist Skizze Text + Rückgängig + Wiederherstellen Name Neuer Punkt Notiz diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bbeba942..da57f360 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,6 +48,8 @@ U Export Category + Undo + Redo Name From f283a9e60ec6f4f3c0700e0255ea112aba80ecfe Mon Sep 17 00:00:00 2001 From: FroggieFrog Date: Wed, 7 Sep 2022 14:13:00 +0200 Subject: [PATCH 020/112] - use global JDK - better performance during build --- .idea/gradle.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .idea/gradle.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 00000000..4ea4864c --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file From 0527625d49c4bcc046eb8385ae9b041f0627d424 Mon Sep 17 00:00:00 2001 From: FroggieFrog Date: Wed, 7 Sep 2022 14:13:54 +0200 Subject: [PATCH 021/112] - replace dependency with more up-to-date one - remove jcenter --- app/build.gradle | 2 +- .../ui/notes/SketchActivity.java | 706 ++++++++++++++++++ 2 files changed, 707 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.java diff --git a/app/build.gradle b/app/build.gradle index 0d52f12f..02da6ceb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,7 +76,7 @@ dependencies { implementation 'com.google.android.material:material:1.10.0' implementation 'com.getbase:floatingactionbutton:1.10.1' implementation 'com.simplify:ink:1.0.0' - implementation 'petrov.kristiyan:colorpicker-library:1.1.10' + implementation 'io.github.eltos:simpledialogfragments:3.6.3' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.java new file mode 100644 index 00000000..f25ce292 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/notes/SketchActivity.java @@ -0,0 +1,706 @@ +/* + This file is part of the application Privacy Friendly Notes. + Privacy Friendly Notes 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 any later version. + Privacy Friendly Notes 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 Privacy Friendly Notes. If not, see . + */ +package org.secuso.privacyfriendlynotes.ui.notes; + +import android.Manifest; +import android.app.DatePickerDialog; +import android.app.TimePickerDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.drawable.BitmapDrawable; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.DatePicker; +import android.widget.EditText; +import android.widget.PopupMenu; +import android.widget.Spinner; +import android.widget.TimePicker; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.simplify.ink.InkView; + +import org.secuso.privacyfriendlynotes.R; +import org.secuso.privacyfriendlynotes.preference.PreferenceKeys; +import org.secuso.privacyfriendlynotes.room.DbContract; +import org.secuso.privacyfriendlynotes.room.model.Category; +import org.secuso.privacyfriendlynotes.room.model.Note; +import org.secuso.privacyfriendlynotes.room.model.Notification; +import org.secuso.privacyfriendlynotes.ui.SettingsActivity; +import org.secuso.privacyfriendlynotes.ui.helper.NotificationHelper; +import org.secuso.privacyfriendlynotes.ui.manageCategories.ManageCategoriesActivity; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import petrov.kristiyan.colorpicker.ColorPicker; + +/** + * Activity that allows to add, edit and delete sketch notes. + */ + +public class SketchActivity extends AppCompatActivity implements View.OnClickListener, DatePickerDialog.OnDateSetListener, TimePickerDialog.OnTimeSetListener, PopupMenu.OnMenuItemClickListener { + public static final String EXTRA_ID = "org.secuso.privacyfriendlynotes.ID"; + public static final String EXTRA_TITLE = "org.secuso.privacyfriendlynotes.TITLE"; + public static final String EXTRA_CONTENT = "org.secuso.privacyfriendlynotes.CONTENT"; + public static final String EXTRA_CATEGORY = "org.secuso.privacyfriendlynotes.CATEGORY"; + public static final String EXTRA_ISTRASH = "org.secuso.privacyfriendlynotes.ISTRASH"; + + + + private static final int REQUEST_CODE_EXTERNAL_STORAGE = 1; + + EditText etName; + InkView drawView; + Button btnColorSelector; + Spinner spinner; + + private String mFileName = "finde_die_datei.mp4"; + private String mFilePath; + + private int dayOfMonth, monthOfYear, year; + + private boolean edit = false; + private boolean hasAlarm = false; + private boolean shouldSave = true; + private int id = -1; + private int currentCat; + + private Notification notification; + private String title; + List allCategories; + ArrayAdapter adapter; + private Menu menu; + private MenuItem item; + private CreateEditNoteViewModel createEditNoteViewModel; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sketch); + findViewById(R.id.btn_cancel).setOnClickListener(this); + findViewById(R.id.btn_delete).setOnClickListener(this); + findViewById(R.id.btn_save).setOnClickListener(this); + + etName = (EditText) findViewById(R.id.etName); + drawView = (InkView) findViewById(R.id.draw_view); + btnColorSelector = (Button) findViewById(R.id.btn_color_selector); + spinner = (Spinner) findViewById(R.id.spinner_category); + + btnColorSelector.setOnClickListener(this); + drawView.setColor(Color.BLACK); + drawView.setMinStrokeWidth(1.5f); + drawView.setMaxStrokeWidth(6f); + + //CategorySpinner + createEditNoteViewModel = new ViewModelProvider(this).get(CreateEditNoteViewModel.class); + adapter = new ArrayAdapter(this,R.layout.simple_spinner_item); + adapter.add(getString(R.string.default_category)); + + createEditNoteViewModel.getAllCategoriesLive().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List categories) { + allCategories = categories; + for(Category currentCat : categories){ + adapter.add(currentCat.getName()); + } + } + }); + + Intent intent = getIntent(); + currentCat = intent.getIntExtra(EXTRA_CATEGORY, -1); + + createEditNoteViewModel.getCategoryNameFromId(currentCat).observe(this, new Observer() { + @Override + public void onChanged(String s) { + Integer position = adapter.getPosition(s); + spinner.setSelection(position); + } + }); + + // observe notifications + notification = new Notification(-1,-1); + createEditNoteViewModel.getAllNotifications().observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List notifications) { + for(Notification currentNotification : notifications){ + if(currentNotification.get_noteId() == id){ + notification.set_noteId(id); + notification.setTime(currentNotification.getTime()); + } + } + + } + }); + + + + loadActivity(true); + } + + @Override + public void onBackPressed() { + Toast.makeText(getBaseContext(), R.string.toast_canceled, Toast.LENGTH_SHORT).show(); + shouldSave = false; + finish(); + } + + private void loadActivity(boolean initial){ + + //Look for a note ID in the intent. If we got one, then we will edit that note. Otherwise we create a new one. + if (id == -1) { + Intent intent = getIntent(); + id = intent.getIntExtra(EXTRA_ID, -1); + + + } + edit = (id != -1); + + // Should we set a custom font size? + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + if (sp.getBoolean(SettingsActivity.PREF_CUSTOM_FONT, false)) { + etName.setTextSize(Float.parseFloat(sp.getString(SettingsActivity.PREF_CUSTOM_FONT_SIZE, "15"))); + } + + + + + if (adapter.getCount() == 0) { + displayCategoryDialog(); + } else { + String[] from = {DbContract.CategoryEntry.COLUMN_NAME}; + int[] to = {R.id.text1}; + + spinner.setAdapter(adapter); + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String catName = (String) parent.getItemAtPosition(position); + currentCat = 0; + for(Category cat :allCategories){ + if(catName == cat.getName()){ + currentCat = cat.get_id(); + } + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + } + + //fill in values if update + if (edit) { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + + createEditNoteViewModel.getNoteByID(id).observe(this, noteFromDB -> { + title = noteFromDB.getName(); + etName.setText(title); + mFileName = noteFromDB.getContent(); + mFilePath = getFilesDir().getPath() + "/sketches" + mFileName; + //find the current category and set spinner to that + currentCat = noteFromDB.getCategory(); + + + findViewById(R.id.btn_delete).setEnabled(true); + ((Button) findViewById(R.id.btn_save)).setText(getString(R.string.action_update)); + drawView.setBackground(new BitmapDrawable(getResources(), mFilePath)); + }); + } else { + findViewById(R.id.btn_delete).setEnabled(false); + mFileName = "/sketch_" + System.currentTimeMillis() + ".PNG"; + mFilePath = getFilesDir().getPath() + "/sketches"; + new File(mFilePath).mkdirs(); //ensure that the file exists + mFilePath = getFilesDir().getPath() + "/sketches" + mFileName; + + // = false; // will be set to true, once we have a drawing + } + if(!initial) { + invalidateOptionsMenu(); + } + + + } + + + @Override + protected void onPause() { + super.onPause(); + //The Activity is not visible anymore. Save the work! + if (shouldSave) { + if (edit) { + updateNote(); + } else { + saveNote(); + } + } + } + + @Override + protected void onResume(){ + super.onResume(); + loadActivity(false); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + if (edit){ + getMenuInflater().inflate(R.menu.audio, menu); + } + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + this.menu = menu; + item = menu.findItem(R.id.action_reminder); + if(notification.get_noteId() >= 0) { + hasAlarm = true; + } else { + hasAlarm = false; + } + + if (hasAlarm) { + item.setIcon(R.drawable.ic_alarm_on_white_24dp); + } else { + if(edit){ + item.setIcon(R.drawable.ic_alarm_add_white_24dp); + } + } + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_reminder) { + //open the schedule dialog + final Calendar c = Calendar.getInstance(); + + //fill the notificationCursor + + if(notification.get_noteId() >= 0) { + hasAlarm = true; + } else { + hasAlarm = false; + } + if (hasAlarm) { + //ask whether to delete or update the current alarm + PopupMenu popupMenu = new PopupMenu(this, findViewById(R.id.action_reminder)); + popupMenu.inflate(R.menu.reminder); + popupMenu.setOnMenuItemClickListener(this); + popupMenu.show(); + } else { + //create a new one + int year = c.get(Calendar.YEAR); + int month = c.get(Calendar.MONTH); + int day = c.get(Calendar.DAY_OF_MONTH); + + DatePickerDialog dpd = new DatePickerDialog(SketchActivity.this, this, year, month, day); + dpd.getDatePicker().setMinDate(c.getTimeInMillis()); + dpd.show(); + } + return true; + } else if (id == R.id.action_save) { + if (ContextCompat.checkSelfPermission(SketchActivity.this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + // Should we show an explanation? + if (ActivityCompat.shouldShowRequestPermissionRationale(SketchActivity.this, + Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + // Show an expanation to the user *asynchronously* -- don't block + // this thread waiting for the user's response! After the user + // sees the explanation, try again to request the permission. + ActivityCompat.requestPermissions(SketchActivity.this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + REQUEST_CODE_EXTERNAL_STORAGE); + } else { + // No explanation needed, we can request the permission. + ActivityCompat.requestPermissions(SketchActivity.this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + REQUEST_CODE_EXTERNAL_STORAGE); + } + } else { + saveToExternalStorage(); + } + return true; + } else if (id == R.id.action_share){ + String tempPath = mFilePath.substring(0, mFilePath.length()-3) + "jpg"; + File sketchFile = new File(tempPath); + + Bitmap bm = overlay(new BitmapDrawable(getResources(), mFilePath).getBitmap(), drawView.getBitmap()); + Canvas canvas = new Canvas(bm); + canvas.drawColor(Color.WHITE); + canvas.drawBitmap(overlay(new BitmapDrawable(getResources(), mFilePath).getBitmap(), drawView.getBitmap()), 0, 0, null); + try { + bm.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(sketchFile)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + Uri contentUri = FileProvider.getUriForFile(getApplicationContext(), "org.secuso.privacyfriendlynotes", sketchFile); + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.setType("image/*"); + sendIntent.putExtra(Intent.EXTRA_STREAM, contentUri); + sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + startActivity(Intent.createChooser(sendIntent, null)); + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_cancel: + Toast.makeText(getBaseContext(), R.string.toast_canceled, Toast.LENGTH_SHORT).show(); + shouldSave = false; + finish(); + break; + case R.id.btn_delete: + if (edit) { //note only exists in edit mode + displayTrashDialog(); + } + break; + case R.id.btn_save: + Bitmap emptyBitmap = Bitmap.createBitmap(drawView.getBitmap().getWidth(), drawView.getBitmap().getHeight(), drawView.getBitmap().getConfig()); + Intent intent = getIntent(); + if(!drawView.getBitmap().sameAs(emptyBitmap) || -5 != intent.getIntExtra(EXTRA_CATEGORY, -5)){ //safe only if note is not empty + shouldSave = true; //safe on exit + finish(); + break; + } else { + Toast.makeText(getApplicationContext(), R.string.toast_emptyNote, Toast.LENGTH_SHORT).show(); + } + break; + case R.id.btn_color_selector: + displayColorDialog(); + break; + default: + } + } + + private void updateNote(){ + fillNameIfEmpty(); + Bitmap oldSketch = new BitmapDrawable(getResources(), mFilePath).getBitmap(); + Bitmap newSketch = drawView.getBitmap(); + try { + FileOutputStream fo = new FileOutputStream(new File(mFilePath)); + overlay(oldSketch, newSketch).compress(Bitmap.CompressFormat.PNG, 0, fo); + fo.flush(); + fo.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + Note note = new Note(etName.getText().toString(),mFileName,DbContract.NoteEntry.TYPE_SKETCH,currentCat); + note.set_id(id); + createEditNoteViewModel.update(note); + Toast.makeText(getApplicationContext(), R.string.toast_updated, Toast.LENGTH_SHORT).show(); + } + + private void saveNote(){ + fillNameIfEmpty(); + Bitmap bitmap = drawView.getBitmap(); + try { + FileOutputStream fo = new FileOutputStream(new File(mFilePath)); + bitmap.compress(Bitmap.CompressFormat.PNG, 0, fo); + fo.flush(); + fo.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + Note note = new Note(etName.getText().toString(),mFileName,DbContract.NoteEntry.TYPE_SKETCH,currentCat); + createEditNoteViewModel.insert(note); + + Toast.makeText(getApplicationContext(), R.string.toast_saved, Toast.LENGTH_SHORT).show(); + } + + private void fillNameIfEmpty(){ + if (etName.getText().toString().isEmpty()) { + SharedPreferences sp = getSharedPreferences(PreferenceKeys.SP_VALUES, Context.MODE_PRIVATE); + int counter = sp.getInt(PreferenceKeys.SP_VALUES_NAMECOUNTER, 1); + etName.setText(String.format(getString(R.string.note_standardname), counter)); + SharedPreferences.Editor editor = sp.edit(); + editor.putInt(PreferenceKeys.SP_VALUES_NAMECOUNTER, counter+1); + editor.commit(); + } + } + + private void displayCategoryDialog() { + new AlertDialog.Builder(SketchActivity.this) + .setTitle(getString(R.string.dialog_need_category_title)) + .setMessage(getString(R.string.dialog_need_category_message)) + .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(SketchActivity.this, ManageCategoriesActivity.class)); + } + }) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + } + + private void displayTrashDialog() { + SharedPreferences sp = getSharedPreferences(PreferenceKeys.SP_DATA, Context.MODE_PRIVATE); + if (sp.getBoolean(PreferenceKeys.SP_DATA_DISPLAY_TRASH_MESSAGE, true)){ + //we never displayed the message before, so show it now + new AlertDialog.Builder(SketchActivity.this) + .setTitle(getString(R.string.dialog_trash_title)) + .setMessage(getString(R.string.dialog_trash_message)) + .setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + shouldSave = false; + SharedPreferences.Editor editor = sp.edit(); + editor.putBoolean(PreferenceKeys.SP_DATA_DISPLAY_TRASH_MESSAGE, false); + editor.commit(); + Intent intent = getIntent(); + Note note = new Note(intent.getStringExtra(EXTRA_TITLE),intent.getStringExtra(EXTRA_CONTENT),DbContract.NoteEntry.TYPE_SKETCH,intent.getIntExtra(EXTRA_CATEGORY,-1)); + note.set_id(id); + note.setIn_trash(1); + createEditNoteViewModel.update(note); + + finish(); + } + }) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + + } else { + shouldSave = false; + Intent intent = getIntent(); + Note note = new Note(intent.getStringExtra(EXTRA_TITLE),intent.getStringExtra(EXTRA_CONTENT),DbContract.NoteEntry.TYPE_SKETCH,intent.getIntExtra(EXTRA_CATEGORY,-1)); + note.set_id(id); + note.setIn_trash(intent.getIntExtra(EXTRA_ISTRASH,0)); + if(note.getIn_trash() == 1){ + createEditNoteViewModel.delete(note); + } else { + note.set_id(id); + note.setIn_trash(1); + createEditNoteViewModel.update(note); + } + finish(); + } + } + + private void displayColorDialog() { + new ColorPicker(this) + .setOnFastChooseColorListener(new ColorPicker.OnFastChooseColorListener() { + @Override + public void setOnFastChooseColorListener(int position, int color) { + drawView.setColor(color); + btnColorSelector.setBackgroundColor(color); + } + }) + .setColors(R.array.mdcolor_500) + .setTitle("") + .show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + case REQUEST_CODE_EXTERNAL_STORAGE: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + //Save the file + saveToExternalStorage(); + } else { + Toast.makeText(getApplicationContext(), R.string.toast_need_permission_write_external, Toast.LENGTH_LONG).show(); + } + break; + } + } + + @Override + public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { + this.dayOfMonth = dayOfMonth; + this.monthOfYear = monthOfYear; + this.year = year; + final Calendar c = Calendar.getInstance(); + if (hasAlarm) { + c.setTimeInMillis(notification.getTime()); + } + TimePickerDialog tpd = new TimePickerDialog(SketchActivity.this, this, c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), true); + tpd.show(); + } + + @Override + public void onTimeSet(TimePicker view, int hourOfDay, int minute) { + Calendar alarmtime = Calendar.getInstance(); + alarmtime.set(year, monthOfYear, dayOfMonth, hourOfDay, minute); + Intent intent = getIntent(); + id = intent.getIntExtra(EXTRA_ID, -1); + Notification notificationTimeSet = new Notification(id, (int) alarmtime.getTimeInMillis()); + + if (hasAlarm) { + //Update the current alarm + createEditNoteViewModel.update(notificationTimeSet); + + } else { + //create new alarm + createEditNoteViewModel.insert(notificationTimeSet); + hasAlarm = true; + notification = new Notification(id, (int) alarmtime.getTimeInMillis()); + item.setIcon(R.drawable.ic_alarm_on_white_24dp); + } + + //Store a reference for the notification in the database. This is later used by the service. + + NotificationHelper.addNotificationToAlarmManager(this,id, DbContract.NoteEntry.TYPE_SKETCH, title, alarmtime.getTimeInMillis()); + NotificationHelper.showAlertScheduledToast(this, dayOfMonth,monthOfYear,year,hourOfDay,minute); + + loadActivity(false); + } + + private void cancelNotification(){ + NotificationHelper.removeNotificationFromAlarmManager(this,id, DbContract.NoteEntry.TYPE_SKETCH, title); + + Intent intent = getIntent(); + id = intent.getIntExtra(EXTRA_ID, -1); + Notification notification = new Notification(id, 0); + createEditNoteViewModel.delete(notification); + hasAlarm = false; + loadActivity(false); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + int id = item.getItemId(); + if (id == R.id.action_reminder_edit) { + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(notification.getTime()); + int year = c.get(Calendar.YEAR); + int month = c.get(Calendar.MONTH); + int day = c.get(Calendar.DAY_OF_MONTH); + DatePickerDialog dpd = new DatePickerDialog(SketchActivity.this, this, year, month, day); + dpd.getDatePicker().setMinDate(new Date().getTime()); + dpd.show(); + return true; + } else if (id == R.id.action_reminder_delete) { + cancelNotification(); + notification = new Notification(-1,-1); + item.setIcon(R.drawable.ic_alarm_add_white_24dp); + return true; + } + return false; + } + + private void saveToExternalStorage(){ + String state = Environment.getExternalStorageState(); + if (Environment.MEDIA_MOUNTED.equals(state)) { + File path; + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ + path = new File(Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOCUMENTS), "/PrivacyFriendlyNotes"); + } else{ + path = new File(Environment.getExternalStorageDirectory(), "/PrivacyFriendlyNotes"); + } + File file = new File(path, "/" + etName.getText().toString() + ".jpeg"); + try { + // Make sure the directory exists. + boolean path_exists = path.exists() || path.mkdirs(); + if (path_exists) { + Bitmap bm = overlay(new BitmapDrawable(getResources(), mFilePath).getBitmap(), drawView.getBitmap()); + Canvas canvas = new Canvas(bm); + canvas.drawColor(Color.WHITE); + canvas.drawBitmap(overlay(new BitmapDrawable(getResources(), mFilePath).getBitmap(), drawView.getBitmap()), 0, 0, null); + bm.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file)); + + // Tell the media scanner about the new file so that it is + // immediately available to the user. + MediaScannerConnection.scanFile(this, + new String[] { file.toString() }, null, + new MediaScannerConnection.OnScanCompletedListener() { + public void onScanCompleted(String path, Uri uri) { + Log.i("ExternalStorage", "Scanned " + path + ":"); + Log.i("ExternalStorage", "-> uri=" + uri); + } + }); + + Toast.makeText(getApplicationContext(), String.format(getString(R.string.toast_file_exported_to), file.getAbsolutePath()), Toast.LENGTH_LONG).show(); + } + } catch (IOException e) { + // Unable to create file, likely because external storage is + // not currently mounted. + Log.w("ExternalStorage", "Error writing " + file, e); + } + } else { + Toast.makeText(getApplicationContext(), R.string.toast_external_storage_not_mounted, Toast.LENGTH_LONG).show(); + } + } + + //taken from http://stackoverflow.com/a/10616868 + public static Bitmap overlay(Bitmap bmp1, Bitmap bmp2) { + Bitmap bmOverlay = Bitmap.createBitmap(bmp1.getWidth(), bmp1.getHeight(), bmp1.getConfig()); + Canvas canvas = new Canvas(bmOverlay); + canvas.drawBitmap(bmp1, new Matrix(), null); + canvas.drawBitmap(bmp2, 0, 0, null); + return bmOverlay; + } +} From 2d7bb1bf12a6abf46fe6b3a3bdc63d85d09755ed Mon Sep 17 00:00:00 2001 From: Patrick Schneider Date: Sat, 4 Nov 2023 23:55:53 +0100 Subject: [PATCH 022/112] Adds feature to color notes according to their color. --- .../room/dao/CategoryDao.kt | 3 + .../ui/adapter/NoteAdapter.kt | 21 ++- .../ui/main/MainActivityViewModel.kt | 7 + .../ManageCategoriesActivity.java | 145 ------------------ .../ManageCategoriesActivity.kt | 134 ++++++++++++++++ .../ic_baseline_expand_less_icon_24dp.xml | 5 + .../ic_baseline_expand_more_icon_24dp.xml | 5 + .../res/layout/activity_manage_categories.xml | 104 ++++++++++--- app/src/main/res/layout/note_item.xml | 1 + app/src/main/res/values/styles.xml | 8 + 10 files changed, 265 insertions(+), 168 deletions(-) delete mode 100644 app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.java create mode 100644 app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.kt create mode 100644 app/src/main/res/drawable/ic_baseline_expand_less_icon_24dp.xml create mode 100644 app/src/main/res/drawable/ic_baseline_expand_more_icon_24dp.xml diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/CategoryDao.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/CategoryDao.kt index 4da253a0..88f837cd 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/CategoryDao.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/room/dao/CategoryDao.kt @@ -41,4 +41,7 @@ interface CategoryDao { @Query("SELECT name FROM categories WHERE _id=:thisCategoryId ") fun categoryNameFromId(thisCategoryId: Integer): LiveData + + @Query("SELECT color FROM categories WHERE _id=:category ") + fun getCategoryColor(category: Int): String? } \ No newline at end of file diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt index b092d770..c4ab38a5 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/adapter/NoteAdapter.kt @@ -13,6 +13,8 @@ */ package org.secuso.privacyfriendlynotes.ui.adapter +import android.content.res.Configuration +import android.graphics.Color import android.preference.PreferenceManager import android.text.Html import android.util.Log @@ -33,7 +35,7 @@ import org.secuso.privacyfriendlynotes.ui.main.MainActivityViewModel * * @see org.secuso.privacyfriendlynotes.ui.RecycleActivity */ -class NoteAdapter(private val mainActivityViewModel: MainActivityViewModel) : RecyclerView.Adapter() { +class NoteAdapter(private val mainActivityViewModel: MainActivityViewModel, ) : RecyclerView.Adapter() { private var notes: MutableList = ArrayList() private var listener: ((Note) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteHolder { @@ -59,6 +61,21 @@ class NoteAdapter(private val mainActivityViewModel: MainActivityViewModel) : Re holder.textViewExtraText.text = null holder.imageViewcategory.visibility = View.GONE holder.imageViewcategory.setImageResource(0) + + mainActivityViewModel.categoryColor(currentNote.category) { + if (it != null) { + when(holder.textViewTitle.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES -> { + holder.textViewTitle.setTextColor(Color.parseColor(it)) + holder.textViewExtraText.setTextColor(Color.parseColor(it)) + } + else -> { + holder.viewNoteItem.setBackgroundColor(Color.parseColor(it)) + } + } + } + } + when (currentNote.type) { DbContract.NoteEntry.TYPE_TEXT -> { holder.textViewDescription.text = Html.fromHtml(currentNote.content) @@ -112,12 +129,14 @@ class NoteAdapter(private val mainActivityViewModel: MainActivityViewModel) : Re val textViewDescription: TextView val imageViewcategory: ImageView val textViewExtraText: TextView + val viewNoteItem: View init { textViewTitle = itemView.findViewById(R.id.text_view_title) textViewDescription = itemView.findViewById(R.id.text_view_description) imageViewcategory = itemView.findViewById(R.id.imageView_category) textViewExtraText = itemView.findViewById(R.id.note_text_extra) + viewNoteItem = itemView.findViewById(R.id.note_item) itemView.setOnClickListener { val position = adapterPosition if (listener != null && position != RecyclerView.NO_POSITION) { diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt index f75c4afd..6a184804 100644 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/main/MainActivityViewModel.kt @@ -18,6 +18,7 @@ import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.text.Html import androidx.core.graphics.drawable.toBitmap +import androidx.core.util.Consumer import androidx.lifecycle.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -76,6 +77,12 @@ class MainActivityViewModel(application: Application) : AndroidViewModel(applica } } + fun categoryColor(category: Int, consumer: Consumer) { + viewModelScope.launch(Dispatchers.Default) { + consumer.accept(repository.categoryDao().getCategoryColor(category)) + } + } + private fun filterNoteFlow (filter: String, notes: Flow?>): Flow> { return notes.map { it.orEmpty().filter { note -> diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.java b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.java deleted file mode 100644 index 9aeab0cf..00000000 --- a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - This file is part of the application Privacy Friendly Notes. - Privacy Friendly Notes 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 any later version. - Privacy Friendly Notes 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 Privacy Friendly Notes. If not, see . - */ -package org.secuso.privacyfriendlynotes.ui.manageCategories; - -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.os.Bundle; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import android.preference.PreferenceManager; -import android.view.View; -import android.widget.EditText; - -import org.secuso.privacyfriendlynotes.R; -import org.secuso.privacyfriendlynotes.room.model.Category; -import org.secuso.privacyfriendlynotes.ui.adapter.CategoryAdapter; -import org.secuso.privacyfriendlynotes.room.model.Note; -import org.secuso.privacyfriendlynotes.ui.SettingsActivity; - -import java.util.List; - -/** - * Activity provides possibility to add, delete categories. - * Data is provided by the ManageCategoriesViewModel - * @see ManageCategoriesViewModel - */ - -public class ManageCategoriesActivity extends AppCompatActivity implements View.OnClickListener { - - RecyclerView recycler_list; - ManageCategoriesViewModel manageCategoriesViewModel; - List allCategories; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_manage_categories); - - findViewById(R.id.btn_add).setOnClickListener(this); - - recycler_list = (RecyclerView) findViewById(R.id.recyclerview_category); - - recycler_list.setLayoutManager(new LinearLayoutManager(this)); - recycler_list.setHasFixedSize(true); - final CategoryAdapter adapter = new CategoryAdapter(); - recycler_list.setAdapter(adapter); - - manageCategoriesViewModel = new ViewModelProvider(this).get(ManageCategoriesViewModel.class); - manageCategoriesViewModel.getAllCategoriesLive().observe(this, new Observer>() { - @Override - public void onChanged(List categories) { - adapter.setCategories(categories); - allCategories = categories; - - } - }); - - adapter.setOnItemClickListener(new CategoryAdapter.OnItemClickListener() { - @Override - public void onItemClick(Category currentCategory) { - new AlertDialog.Builder(ManageCategoriesActivity.this) - .setTitle(String.format(getString(R.string.dialog_delete_title), currentCategory.getName())) - .setMessage(String.format(getString(R.string.dialog_delete_message), currentCategory.getName())) - .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - //do nothing - } - }) - .setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - deleteCategory(currentCategory); - } - }) - .setIcon(android.R.drawable.ic_dialog_alert) - .show(); - } - }); - - - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.btn_add: - EditText name = (EditText) findViewById(R.id.etName); - if (!name.getText().toString().isEmpty()){ - Category category = new Category(name.getText().toString()); - boolean duplicate = false; - for(Category currentCat: allCategories){ - if(currentCat.getName().equals(category.getName())){ - duplicate = true; - } - } - if(!duplicate){ - manageCategoriesViewModel.insert(category); - } - } - name.setText(""); - break; - } - } - - - private void deleteCategory(Category cat){ - - // Delete all notes from category if the option is set - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - if (sp.getBoolean(SettingsActivity.PREF_DEL_NOTES, false)) { - manageCategoriesViewModel.getAllNotesLiveData().observe(this, new Observer>() { - @Override - public void onChanged(@Nullable List notes) { - for(Note currentNote: notes){ - if(currentNote.getCategory() == cat.get_id()){ - manageCategoriesViewModel.delete(currentNote); - } - } - } - }); - } - - manageCategoriesViewModel.delete(cat); - } -} diff --git a/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.kt b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.kt new file mode 100644 index 00000000..f9cdacf3 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlynotes/ui/manageCategories/ManageCategoriesActivity.kt @@ -0,0 +1,134 @@ +/* + This file is part of the application Privacy Friendly Notes. + Privacy Friendly Notes 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 any later version. + Privacy Friendly Notes 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 Privacy Friendly Notes. If not, see . + */ +package org.secuso.privacyfriendlynotes.ui.manageCategories + +import android.content.DialogInterface +import android.os.Bundle +import android.preference.PreferenceManager +import android.view.View +import android.widget.EditText +import android.widget.ImageButton +import android.widget.LinearLayout +import androidx.annotation.ColorInt +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.button.MaterialButton +import eltos.simpledialogfragment.SimpleDialog.OnDialogResultListener +import eltos.simpledialogfragment.color.SimpleColorDialog +import org.secuso.privacyfriendlynotes.R +import org.secuso.privacyfriendlynotes.room.model.Category +import org.secuso.privacyfriendlynotes.ui.SettingsActivity +import org.secuso.privacyfriendlynotes.ui.adapter.CategoryAdapter + +/** + * Activity provides possibility to add, delete categories. + * Data is provided by the ManageCategoriesViewModel + * @see ManageCategoriesViewModel + */ +class ManageCategoriesActivity : AppCompatActivity(), View.OnClickListener, OnDialogResultListener { + var manageCategoriesViewModel: ManageCategoriesViewModel? = null + var allCategories: List? = null + private val etName: EditText by lazy { findViewById(R.id.etName) } + private val recyclerList: RecyclerView by lazy { findViewById(R.id.recyclerview_category) } + private val btnResetColor: ImageButton by lazy { findViewById(R.id.category_menu_color_reset) } + private val btnColorSelector: MaterialButton by lazy { findViewById(R.id.btn_color_selector) } + private val btnExpandMenu: ImageButton by lazy { findViewById(R.id.category_expand_menu_button) } + private val expandMenu: LinearLayout by lazy { findViewById(R.id.category_expand_menu) } + private var catColor: String? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_manage_categories) + findViewById(R.id.btn_add).setOnClickListener(this) + + this.recyclerList.layoutManager = LinearLayoutManager(this) + this.recyclerList.setHasFixedSize(true) + val adapter = CategoryAdapter() + this.recyclerList.adapter = adapter + manageCategoriesViewModel = ViewModelProvider(this).get(ManageCategoriesViewModel::class.java) + manageCategoriesViewModel!!.allCategoriesLive.observe(this) { categories -> + adapter.setCategories(categories) + allCategories = categories + } + adapter.setOnItemClickListener { currentCategory -> + AlertDialog.Builder(this@ManageCategoriesActivity) + .setTitle(String.format(getString(R.string.dialog_delete_title), currentCategory.name)) + .setMessage(String.format(getString(R.string.dialog_delete_message), currentCategory.name)) + .setNegativeButton(android.R.string.no) { dialog, which -> + //do nothing + } + .setPositiveButton(R.string.dialog_ok) { dialog, which -> deleteCategory(currentCategory) } + .setIcon(android.R.drawable.ic_dialog_alert) + .show() + } + + btnColorSelector.setBackgroundColor(resources.getColor(R.color.transparent)) + btnExpandMenu.setOnClickListener { expandMenu.visibility = if (expandMenu.visibility == View.GONE) { View.VISIBLE } else { View.GONE } } + btnResetColor.setOnClickListener { + btnColorSelector.setBackgroundColor(resources.getColor(R.color.transparent)) + catColor = null + } + btnColorSelector.setOnClickListener { displayColorDialog() } + } + + override fun onClick(v: View) { + if (v.id == R.id.btn_add) { + if (etName.text.isNotEmpty()) { + val category = Category(etName.text.toString(), catColor) + if (allCategories!!.firstOrNull { it.name == category.name } == null) { + manageCategoriesViewModel!!.insert(category) + } + } + etName.setText("") + } + } + + private fun deleteCategory(cat: Category) { + + // Delete all notes from category if the option is set + val sp = PreferenceManager.getDefaultSharedPreferences(this) + if (sp.getBoolean(SettingsActivity.PREF_DEL_NOTES, false)) { + manageCategoriesViewModel!!.allNotesLiveData.observe(this) { + notes -> notes.filter { it.category == cat._id }.forEach { manageCategoriesViewModel!!.delete(it) } + } + } + manageCategoriesViewModel!!.delete(cat) + } + + private fun displayColorDialog() { + SimpleColorDialog.build() + .title("") + .allowCustom(true) + .cancelable(true) //allows close by tapping outside of dialog + .colors(this, R.array.mdcolor_500) + .choiceMode(SimpleColorDialog.SINGLE_CHOICE_DIRECT) //auto-close on selection + .show(this, TAG_COLORDIALOG) + } + + override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean { + if (dialogTag == TAG_COLORDIALOG && which == DialogInterface.BUTTON_POSITIVE) { + @ColorInt val color = extras.getInt(SimpleColorDialog.COLOR) + btnColorSelector.setBackgroundColor(color) + catColor = "#${Integer.toHexString(color)}" + return true + } + return false + } + + companion object { + private const val TAG_COLORDIALOG = "org.secuso.privacyfriendlynotes.COLORDIALOG" + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_expand_less_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_expand_less_icon_24dp.xml new file mode 100644 index 00000000..a31d8302 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_expand_less_icon_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_expand_more_icon_24dp.xml b/app/src/main/res/drawable/ic_baseline_expand_more_icon_24dp.xml new file mode 100644 index 00000000..48368f35 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_expand_more_icon_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_manage_categories.xml b/app/src/main/res/layout/activity_manage_categories.xml index 45fff6fc..3ad3bd41 100644 --- a/app/src/main/res/layout/activity_manage_categories.xml +++ b/app/src/main/res/layout/activity_manage_categories.xml @@ -1,36 +1,96 @@ - + android:layout_height="wrap_content"> - - -