From 0d7b3a52fc3746c1fcc3c862493cebab24cd81ca Mon Sep 17 00:00:00 2001 From: Christopher Beckmann Date: Sun, 3 Sep 2017 18:34:32 +0200 Subject: [PATCH] Added a screen for the break and also introduced ExerciseSets. Database restructured and added methods to the SQLDatabaseHelper. --- app/src/main/AndroidManifest.xml | 8 +- app/src/main/assets/exercises.sqlite | Bin 240640 -> 240640 bytes .../activities/BreakActivity.java | 10 +- .../activities/ExerciseActivity.java | 32 ++ .../ExerciseSetOverviewActivity.java | 77 ++++ .../activities/TimerActivity.java | 245 ++++++++++-- .../activities/adapter/ExerciseAdapter.java | 76 ++++ .../activities/helper/CursorFilter.java | 75 ++++ .../helper/CursorRecyclerViewAdapter.java | 376 ++++++++++++++++++ .../database/DBHandler.java | 192 --------- .../database/SQLiteHelper.java | 291 ++++++++++++++ .../database/columns/ExerciseColumns.java | 12 +- ...Columns.java => ExerciseLocalColumns.java} | 27 +- .../database/columns/ExerciseSetColumns.java | 49 +++ .../database/data/Exercise.java | 41 +- .../database/data/ExerciseSet.java | 53 +++ .../service/TimerService.java | 66 +-- .../ic_play_arrow_black_48dp.png | Bin 0 -> 265 bytes .../ic_play_arrow_black_48dp.png | Bin 0 -> 208 bytes .../ic_play_arrow_black_48dp.png | Bin 0 -> 320 bytes .../ic_play_arrow_black_48dp.png | Bin 0 -> 394 bytes .../ic_play_arrow_black_48dp.png | Bin 0 -> 515 bytes app/src/main/res/drawable/circular_small.xml | 22 + app/src/main/res/layout/activity_exercise.xml | 152 +++++++ .../main/res/layout/activity_exercise_set.xml | 36 ++ app/src/main/res/layout/activity_timer.xml | 116 +++++- app/src/main/res/layout/layout_exercise.xml | 81 ++++ .../main/res/layout/layout_exercise_set.xml | 68 ++++ app/src/main/res/values/styles.xml | 7 + 29 files changed, 1829 insertions(+), 283 deletions(-) create mode 100644 app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/ExerciseActivity.java create mode 100644 app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/ExerciseSetOverviewActivity.java create mode 100644 app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/adapter/ExerciseAdapter.java create mode 100644 app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/helper/CursorFilter.java create mode 100644 app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/helper/CursorRecyclerViewAdapter.java delete mode 100644 app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/DBHandler.java create mode 100644 app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/SQLiteHelper.java rename app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/{ExercisesLocalColumns.java => ExerciseLocalColumns.java} (64%) create mode 100644 app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseSetColumns.java create mode 100644 app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/ExerciseSet.java create mode 100644 app/src/main/res/drawable-hdpi/ic_play_arrow_black_48dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_play_arrow_black_48dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_play_arrow_black_48dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_48dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_48dp.png create mode 100644 app/src/main/res/drawable/circular_small.xml create mode 100644 app/src/main/res/layout/activity_exercise.xml create mode 100644 app/src/main/res/layout/activity_exercise_set.xml create mode 100644 app/src/main/res/layout/layout_exercise.xml create mode 100644 app/src/main/res/layout/layout_exercise_set.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1997841..b8b1620 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,6 @@ android:screenOrientation="portrait" android:supportsRtl="true" android:theme="@style/AppTheme"> - + + @@ -65,12 +66,11 @@ - + - + + \ No newline at end of file diff --git a/app/src/main/assets/exercises.sqlite b/app/src/main/assets/exercises.sqlite index bce08a5d184789e995c48d4abc8c5db364d040e6..6aa2396f1c2f92df2a902288d5facc89b4d1ecb7 100644 GIT binary patch delta 3723 zcmZ`+3slr)7XROS|MP+Y2UG^~HL_X|n6R~q1}ZXBYG??kXcVAK!8Y<5OxqNgKX`J_ zCc=C*|612_)kb=5I_kg2CbC)SDO=sO-7IVC^e!vgt&_)fZSOaPh}F#b-S0Wyci;D$ zwm0J2-iYrSmGHQtD4#R7F|Jb-*w~$UHBVjz%S=2&^c7RZwW~p+__10G_s3{!n2uf+ zv@aD))#}Le_5wkVl0Kxj=rHZ4y+(Hq_(^2BGb=0>QbrbfZ}Z#Cq@)yi zeRtIQ9VSw)C~2sztMa)D>V2zL)>&0joTc?m{(1}X{yE;NY725_fp^927T`{P(@MX* z%SOim1E(&AupGL}ia_}=n}(X$82C^$M6QiOf98xq9A z(LQ)d>}Q%NGR7Q&Br$(_NOI#8onu#nLy!3mD&;tt7smE?I>$B#T|Gw694se8Ey8mY zOiR&I$}s@%iOp&6Sp0!hRl6SasyNyr#$5T4Ww}z!iZvP9mpCt-x{x*wADhx(w!l*p@z`rVuGw}xX4mZ3g|pfYmk)@h~)G=ER6 zakxVT9bjR(hMOs;io-E*)D7iaq(I9#?7p z>_U&L^3vtX=;glp6;-|;Zi-1~3xu3va?51dn{RQB8wY`PLxj1Y9)F&;#_U{}A$DP^ z__RHVVyE6Dy5Vy1RcnHn-nub4j@mfP-%;>wd;~JNBfnirml(fy4b^Hb-GR}fYwcvq z4O)xYSuu^zaV?Wd8MH)2_Qk3hoVRFqYU`qgTpu@txuHKF@?){O#g#d9{m0s`sI9KF z&0SFHx!zMOrUyo478TE(pI^Mxb%SSV+7LU*otc9(JW3av0x8!^{^h_`?77ZU>{(FY zDRB*M<5hN-A$&zQ-?+f#_7r;9Rt5Pb1^I3dpEYH|g?&H0Cnh~xTPr5DWKFOI#%Xs& zjin9M$Dpy0eQu0(;6(PaThz7zwVWSV|P zrs+S)K;9?Q^e&mEcgr;WPno78GEF}s)AYkKO}{MDbXcb8Kgu+{L#FA+WSZV1)AYwO zqughfSF7-~f)VuNFX8JY;9R2YmUIbpR7CCTZ}inRJG{%A>Q`&6n%@##x>>CIVrs0G zC&w~IvxR+Kj`i2=Xm+k!g8DJUJTXPfFby9ZZr3v4V=;bhkr>yWKr`A=I2T1@3)iRD(uM5FY>oHUQ-qY9UvweFs4nl+tq)eihP!Z*afzJQ;Wf z&RgUuhWQkprO*LXcd9B{V`PPyOzCD?0wSS(Vrm+{QzpWIFUgp)#F=T_ zg&BWZ*nzH44|-z*BOO*qfNKLYXBrJXm`d=x{=F4^&>QXm)s8G5&<3W;PTih^yWm4( ze-6&3po`gvM*HW*G=%gzVR-60eYfV|4`q*7K`CYBViqW ziEu1ji0T9>KYwIUz0LTP@fqVIMjzuZj92st9XPpF`qRCP43qmvO}JjFP|c#{$JY;NUKfsC~EcoGdYnbN?>dC}i(YqJ8fXML42O?S zQ@EMbJ(LJ0ughCkZJay^U$AptB&f`r9jxxqA=qMUX`|aHhTyzH^Y9rsZ^X~S{+kfP r)ue_v@fzxBdBYB7GVwUh)=Paf(YSL5-YswC)hyPX)VZN0<+lEj8k8fd5mS{i5~X(?JN@M|X)s%_fxQBVprqp+@0QU*F& zXef$5rP5x=Lq+kUOHmPpj6U47mC-3oaJVr=#ILDCH>aTDm?G@ll**S&e*g3CJ@>wM z&pG#h&$+9QCagZ1up!y87~L86v|;?N=p-NzCQ9&+UGd^!(!5@H2DhJ)g#F5N#U>7D=0mO?^S1lY9-6Ru4=IH%k5yiKJ zq#KE%b=O^?z6S#%ca6UnVm=L)@wFMC))zsl zdgxCuNv?V#Qh&o7gc9xbd3f3o)6-nc*9-yGGaL7d_M3hQ6LwA88UrEmSXn^5wE~uj zbtkLTCFST=EAL@jBU;7H7|MfINYEb0gL<}W3g?2)9Ibf_Y#VGk3AabaRaO85#QNDy zxOBLqT6Jc`y+d!R;mu{B zw$`!*M!8FI!6LraE=q5_Mm#gO9EODV12-XVQFO|EG_14=Q&kYm!d=p9psuCa#e?dS zSXizO9gBud-{YuQNtw203lEC=8E!FQ{tg(RIyb{0b?Z~uq_fg!g1BeFomR;|o#IjO zBvyk^dK8cFm{X*Ke`Z|=5x53h@I2d9BU|OjxW@C?S@uLNH%Vq&S(v2qPQ;Y<2wU|R zN!jC)vMWA z#wL9{{#ViAD22VsTG{n@JB|WSdazrmr~jg0sovf=1C&K|ZTBXM=NhtN?^qu2FP*z6 zSk~6;kcznA!eDufeZ~xE87&(BJuWUdT=p|Fp#?;D)3xG}l{WEh(*QPiQMD+!BT>A+ zB;=_|ucB^MhRYaR;=8$cl5duCYT>LtrK_rTUfsfF^A`H6GK?9XV42=tm?Cy!syMVX zjyKzbK5=~MP*JnY9ur7)rW<3c#P{`CqI`LlZj!w;6UC&!Ffq7kkO&0U_y(S4tQ=S--f)C_X1b> zOngv$sdo$nYdTkkTc-IJ23*b(f92susN^NyIt8HFs9IwQR}?p zL9Ei*eGosS-wJQG%bNo>$Ca;o$GW^;uq9um_qe>*QCO`>_R0PQzwp*c&;*~-z&cKfIHe*~igbm5$cV~uu61S=y`VhNH8w_e=(!A(SkTQ2saR4A5I zfVwV@++YNDN@1tiE9``pV8Sr7TOhXD9~;f^hHAfo8X1hupp9NR+?*cISPJ-8_?IqN z6z~T!!e(@ayr9?gz4#pqxiL}4hA<1NmRx41TsKi$;>O3(7RQpI!5?UF78Lpn%oc5z zs*6u!Ej+27H$ZLt1|-vgt&<*fsHR-p2?w<9T%628XK?H8tvHW$UcN%T65itM=(4EdApbR{4!9w^YTz0BmS<#b)x6|MD1WN<}!`HNL+8veF#2? zR-)~FmFTUXszguqRibSdD$&PRR-zrzO7tpKqNT#(c=KWr(zIpxHyvo|`d{Nd_)WC3 z98kMgq7OPXOCx@PmUm?m=gOlB9>#o_raVetXw@FggZV5?hJb!WgjWck5&lW|2VpPa z4}{;TX)Tzw>Jw_dM0kzxdqOWklHdg4Q^G*PK7!22LkJ}V6G1NCOlTquCLALK2#Ew6 zp@%SmkU$tf2oYp*ZzEU;8wgJ-%5eA#7p7>FCsI?uG|J zx}_tB*<5@Ui`9f}xR?f~YWvD@0=xRKS{(g{bywGR?7@iBQ3x6@pk+gwnH3>j9k~zu z+MTuR9?5OL>a1ZV@!8BQ?J+lgh46Bey6|jhKeW)@Y&BE)8GKl4pTqn_v;rIo+KFCx z4?}}MZ|G+&O(Z?>^%xp!6jp(G#N-;)JPl@QHGA+8$|Pk9OTrD%1M(P6CbJMCWzElG z*u;3qz^oU`efS`y@^ZXD`EV_bZ>ap~ zG)^W@wXs6pFxBYCiTK*z_F0U6K z%Smg7Bo|ftQ!kw)FN6nF>wWC&SY?HRZdj|t1fx>qbJGn!q zf<)FDvF`o~i4`kDPSAJi)0jci-;d|$js3`tOvzI4ZaQvR!g4fKb9?bH3#Bp2Z@G-} z8$~*S!8wH$;R|pMK7oDe;Usuh{qiUtxxwj+AzPD^lj(w-QQnvAF66xEquOsTl{0KM z8{M3vGo)TZJLhKx>?A|Ds&}*DZ6ygBrM1_HMN`<>xKO&R!yB=I?9S14Y(yUmCCY1E z3sg(iZh=ldNA@?;Msy`IJva2*NPBq_`aZAemq;HzrZspT`$)6KLo^a2LmdI?Laa8k> allAvailableExercises; private List sections; private Random random; @@ -86,7 +86,7 @@ public class BreakActivity extends AppCompatActivity implements View.OnClickList ct_text.setText(bufferZeroMinute + mins + ":00"); ct_text.setOnClickListener(this); - dbHandler = new DBHandler(this); + SQLiteHelper = new SQLiteHelper(this); random = new Random(); sections = new ArrayList<>(); setRandomExercises(); @@ -253,7 +253,7 @@ public class BreakActivity extends AppCompatActivity implements View.OnClickList while (notFoundYet) { currentExerciseSection = random.nextInt(exercises.length); if (!usedSectionsString.contains(exercises[currentExerciseSection])) { - List list = dbHandler.getExercisesFromSection("de",exercises[currentExerciseSection]); + List list = SQLiteHelper.getExercisesFromSection("de",exercises[currentExerciseSection]); allAvailableExercises.add(list); usedSectionsString += exercises[currentExerciseSection] + "."; editor.putString("currently_done_exercises", usedSectionsString); @@ -452,7 +452,7 @@ public class BreakActivity extends AppCompatActivity implements View.OnClickList //Close database connection if (!noExercises) - dbHandler.close(); + SQLiteHelper.close(); finish(); } }.start(); diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/ExerciseActivity.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/ExerciseActivity.java new file mode 100644 index 0000000..4b938a9 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/ExerciseActivity.java @@ -0,0 +1,32 @@ +package org.secuso.privacyfriendlybreakreminder.activities; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.widget.ImageView; + +import org.secuso.privacyfriendlybreakreminder.R; + +public class ExerciseActivity extends AppCompatActivity { + + private ImageView playButton; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_exercise); + + initResources(); + } + + private void initResources() { + playButton = (ImageView) findViewById(R.id.button_playPause); + } + + private void updatePlayButton(boolean isRunning) { + if(isRunning) { + playButton.setImageResource(R.drawable.ic_pause_black_48dp); + } else { + playButton.setImageResource(R.drawable.ic_play_arrow_black_48dp); + } + } +} diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/ExerciseSetOverviewActivity.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/ExerciseSetOverviewActivity.java new file mode 100644 index 0000000..c2047e2 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/ExerciseSetOverviewActivity.java @@ -0,0 +1,77 @@ +package org.secuso.privacyfriendlybreakreminder.activities; + +import android.database.Cursor; +import android.support.v4.content.AsyncTaskLoader; +import android.support.v4.content.Loader; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.widget.TextView; + +import org.secuso.privacyfriendlybreakreminder.R; +import org.secuso.privacyfriendlybreakreminder.activities.adapter.ExerciseAdapter; +import org.secuso.privacyfriendlybreakreminder.database.SQLiteHelper; + +public class ExerciseSetOverviewActivity extends AppCompatActivity implements android.support.v4.app.LoaderManager.LoaderCallbacks{ + + private TextView exerciseSetName; + private RecyclerView exerciseList; + + private ExerciseAdapter exerciseAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_exercise_set); + + initResources(); + } + + private void initResources() { + exerciseSetName = (TextView) findViewById(R.id.exercise_set_name); + exerciseList = (RecyclerView) findViewById(R.id.exercise_list); + exerciseAdapter = new ExerciseAdapter(this, null); + exerciseList.setAdapter(exerciseAdapter); + exerciseList.setLayoutManager(new GridLayoutManager(this, 2)); + + getSupportLoaderManager().initLoader(0, null, this); + } + + @Override + public Loader onCreateLoader(int id, final Bundle args) { + return new AsyncTaskLoader(this) { + @Override + public Cursor loadInBackground() { + SQLiteHelper helper = new SQLiteHelper(getContext()); + return helper.getExerciseCursorForSet(2, "de"); // TODO; get correct subset list + } + + @Override + protected void onStartLoading() { + forceLoad(); + } + + @Override + protected void onReset() {} + }; + } + + @Override + public void onLoadFinished(Loader loader, Cursor cursor) { + cursor.moveToFirst(); + exerciseAdapter.changeCursor(cursor); + } + + @Override + public void onLoaderReset(Loader loader) { + ((ExerciseAdapter) exerciseList.getAdapter()).changeCursor(null); + } + + @Override + protected void onResume() { + super.onResume(); + + getSupportLoaderManager().restartLoader(0, null, this); + } +} diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/TimerActivity.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/TimerActivity.java index 8af888e..24b4abd 100644 --- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/TimerActivity.java +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/TimerActivity.java @@ -1,5 +1,7 @@ package org.secuso.privacyfriendlybreakreminder.activities; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -7,23 +9,61 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.os.IBinder; +import android.preference.PreferenceManager; +import android.support.annotation.ColorRes; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; +import android.util.Log; import android.view.View; import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.NumberPicker; import android.widget.ProgressBar; import android.widget.TextView; import org.secuso.privacyfriendlybreakreminder.R; import org.secuso.privacyfriendlybreakreminder.service.TimerService; +import java.util.Locale; + public class TimerActivity extends AppCompatActivity { + private static final String TAG = TimerActivity.class.getSimpleName(); + private static final String PREF_PICKER_SECONDS = TAG + ".PREF_PICKER_SECONDS"; + private static final String PREF_PICKER_MINUTES = TAG + ".PREF_PICKER_MINUTES"; + private static final String PREF_PICKER_HOURS = TAG + ".PREF_PICKER_HOURS"; // UI private ProgressBar progressBar; private TextView timerText; + private ImageButton playButton; + private ImageButton resetButton; + private NumberPicker secondsPicker; + private NumberPicker minutesPicker; + private NumberPicker hoursPicker; + private LinearLayout pickerLayout; + + // animation + private int mShortAnimationDuration; + + private static final String[] SECONDS_MINUTES = new String[60]; + private static final String[] HOURS = new String[24]; + + static { + for(int i = 0; i < SECONDS_MINUTES.length; ++i) { + SECONDS_MINUTES[i] = String.format(Locale.US, "%02d", i); + } + for(int i = 0; i < HOURS.length; ++i) { + HOURS[i] = String.format(Locale.US, "%02d", i); + } + } // Service private TimerService mTimerService = null; @@ -44,17 +84,22 @@ public class TimerActivity extends AppCompatActivity { }; private void onServiceConnected() { - if(mTimerService.isRunning()) { - progressBar.setMax((int) mTimerService.getInitialDuration()); - } + updateUI(); } private final BroadcastReceiver timerReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { long millisUntilDone = intent.getLongExtra("onTickMillis", -1L); + long initialDuration = intent.getLongExtra("initialMillis", 0L); + boolean isRunning = intent.getBooleanExtra("isRunning", false); + boolean isPaused = intent.getBooleanExtra("isPaused", false); - updateProgress(millisUntilDone); + if(mTimerService != null) { + updateUI(); + } else { + updateUI(isRunning, isPaused, initialDuration, millisUntilDone); + } } }; @@ -90,6 +135,7 @@ public class TimerActivity extends AppCompatActivity { if(mTimerService != null && !mTimerService.isRunning()) { updateProgress(mTimerService.getInitialDuration()); } + updateUI(); } @Override @@ -111,19 +157,39 @@ public class TimerActivity extends AppCompatActivity { } private void initResources() { + SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); + + mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); + progressBar = (ProgressBar) findViewById(R.id.progressBar); timerText = (TextView) findViewById(R.id.timerText); + playButton = (ImageButton) findViewById(R.id.button_playPause); + resetButton = (ImageButton) findViewById(R.id.button_reset); - timerText.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - int duration = 1000 * 60 * 1; // 1 minutes - if(mTimerService != null) { - mTimerService.startTimer(duration); - progressBar.setMax(duration); - } - } - }); + secondsPicker = (NumberPicker) findViewById(R.id.seconds_picker); + minutesPicker = (NumberPicker) findViewById(R.id.minutes_picker); + hoursPicker = (NumberPicker) findViewById(R.id.hours_picker); + + secondsPicker.setDisplayedValues(SECONDS_MINUTES); + secondsPicker.setMinValue(0); + secondsPicker.setMaxValue(SECONDS_MINUTES.length - 1); + secondsPicker.setValue(pref.getInt(PREF_PICKER_SECONDS, 0)); + + minutesPicker.setDisplayedValues(SECONDS_MINUTES); + minutesPicker.setMinValue(0); + minutesPicker.setMaxValue(SECONDS_MINUTES.length - 1); + minutesPicker.setValue(pref.getInt(PREF_PICKER_MINUTES, 30)); + + hoursPicker.setDisplayedValues(HOURS); + hoursPicker.setMinValue(0); + hoursPicker.setMaxValue(HOURS.length - 1); + hoursPicker.setValue(pref.getInt(PREF_PICKER_HOURS, 1)); + + setDividerColor(secondsPicker, R.color.transparent); + setDividerColor(minutesPicker, R.color.transparent); + setDividerColor(hoursPicker, R.color.transparent); + + pickerLayout = (LinearLayout) findViewById(R.id.picker_layout); } private void updateProgress(long millisUntilFinished) { @@ -135,15 +201,8 @@ public class TimerActivity extends AppCompatActivity { int seconds = secondsUntilFinished % 60; int minutes = minutesUntilFinished % 60; - StringBuilder sb = new StringBuilder(); - - if(hours > 0) sb.append(hours).append(":"); - if(minutes < 10) sb.append(0); - sb.append(minutes).append(":"); - if(seconds < 10) sb.append(0); - sb.append(seconds); - - timerText.setText(sb.toString()); + String time = String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, seconds); + timerText.setText(time); //progressBar.setMax(1000); //ObjectAnimator animation = ObjectAnimator.ofInt(progressBar, "progress", 0, 1000 * percentFinished); // see this max value coming back here, we animale towards that value @@ -154,8 +213,148 @@ public class TimerActivity extends AppCompatActivity { } + public void onClick(View view) { + switch(view.getId()) { + case R.id.button_playPause: + case R.id.progressBar: + handlePlayPressed(); + break; + case R.id.button_reset: + mTimerService.stopAndResetTimer(); + break; + case R.id.button_chooseExercise: + startActivity(new Intent(this, ExerciseSetOverviewActivity.class)); + break; + } + updateUI(); + } + private void handlePlayPressed() { + if(mTimerService != null) { + if(mTimerService.isPaused()) { + mTimerService.resumeTimer(); + } + else if(mTimerService.isRunning()){ + mTimerService.pauseTimer(); + } else { + long duration = getCurrentSetDuration(); + saveCurrentSetDuration(); + mTimerService.startTimer(duration); + progressBar.setMax((int)duration); + } + } + } + private void saveCurrentSetDuration() { + SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); + pref.edit().putInt(PREF_PICKER_SECONDS, secondsPicker.getValue()) + .putInt(PREF_PICKER_MINUTES, minutesPicker.getValue()) + .putInt(PREF_PICKER_HOURS, hoursPicker.getValue()).apply(); + } + + private long getCurrentSetDuration() { + long duration = secondsPicker.getValue() * 1000; + duration += minutesPicker.getValue() * 1000 * 60; + duration += hoursPicker.getValue() * 1000 * 60 * 60; + return duration; + } + + private void updateUI() { + if(mTimerService != null) { + updateUI(mTimerService.isRunning(), mTimerService.isPaused(), mTimerService.getInitialDuration(), mTimerService.getRemainingDuration()); + } else { + showPicker(true); + } + } + + private void updateUI(boolean running, boolean isPaused, long initialDuration, long remainingDuration) { + updatePlayButton(running); + progressBar.setMax((int) initialDuration); + updateProgress(remainingDuration); + showPicker(!running && !isPaused); + } + + private void showPicker(boolean showPicker) { + if(showPicker && pickerLayout.getVisibility() != View.VISIBLE) { + pickerLayout.setAlpha(0f); + pickerLayout.setVisibility(View.VISIBLE); + pickerLayout.animate() + .alpha(1f) + .setStartDelay(mShortAnimationDuration) + .setDuration(mShortAnimationDuration) + .setListener(null); + + timerText.animate() + .alpha(0f) + .setDuration(mShortAnimationDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + timerText.setVisibility(View.INVISIBLE); + } + }); + + progressBar.animate() + .alpha(0f) + .setDuration(mShortAnimationDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + progressBar.setVisibility(View.INVISIBLE); + } + }); + + } else if(!showPicker && pickerLayout.getVisibility() == View.VISIBLE) { + pickerLayout.animate() + .alpha(0f) + .setDuration(mShortAnimationDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + pickerLayout.setVisibility(View.INVISIBLE); + } + }); + + timerText.setAlpha(0f); + timerText.setVisibility(View.VISIBLE); + timerText.animate() + .alpha(1f) + .setDuration(mShortAnimationDuration) + .setListener(null); + + progressBar.setAlpha(0f); + progressBar.setVisibility(View.VISIBLE); + progressBar.animate() + .alpha(1f) + .setDuration(mShortAnimationDuration) + .setStartDelay(mShortAnimationDuration) + .setListener(null); + } + } + + private void updatePlayButton(boolean isRunning) { + if(isRunning) { + playButton.setImageResource(R.drawable.ic_pause_black_48dp); + } else { + playButton.setImageResource(R.drawable.ic_play_arrow_black_48dp); + } + } + + private void setDividerColor(NumberPicker picker, @ColorRes int color) { + java.lang.reflect.Field[] pickerFields = NumberPicker.class.getDeclaredFields(); + for (java.lang.reflect.Field pf : pickerFields) { + if (pf.getName().equals("mSelectionDivider")) { + pf.setAccessible(true); + try { + ColorDrawable colorDrawable = new ColorDrawable(ContextCompat.getColor(this, color)); + pf.set(picker, colorDrawable); + } catch (IllegalArgumentException | Resources.NotFoundException | IllegalAccessException e) { + Log.e(TAG, e.getMessage(), e); + } + break; + } + } + } } diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/adapter/ExerciseAdapter.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/adapter/ExerciseAdapter.java new file mode 100644 index 0000000..79a7ec2 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/adapter/ExerciseAdapter.java @@ -0,0 +1,76 @@ +package org.secuso.privacyfriendlybreakreminder.activities.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.support.v7.widget.RecyclerView.ViewHolder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.secuso.privacyfriendlybreakreminder.R; +import org.secuso.privacyfriendlybreakreminder.activities.helper.CursorRecyclerViewAdapter; +import org.secuso.privacyfriendlybreakreminder.database.columns.ExerciseColumns; +import org.secuso.privacyfriendlybreakreminder.database.data.Exercise; + +/** + * Created by Christopher Beckmann on 30.08.2017. + */ +public class ExerciseAdapter extends CursorRecyclerViewAdapter { + + + public ExerciseAdapter(Context context, Cursor cursor) { + super(context, cursor, ExerciseColumns._ID); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_exercise, parent, false); + return new ExerciseViewHolder(itemView); + } + + @Override + public void onBindViewHolder(ViewHolder viewHolder, Cursor cursor) { + final Exercise exercise = ExerciseColumns.fromCursor(cursor); + + ExerciseViewHolder vh = (ExerciseViewHolder) viewHolder; + + String imageID = exercise.getImageID(); + String[] imageIDSplit = imageID.split(","); + + if(imageIDSplit.length > 1) { + imageID = imageIDSplit[0]; // only take the first image as a display image + } + + int imageResID = mContext.getResources().getIdentifier( + "exercise_" + imageID, + "drawable", + mContext.getPackageName()); + vh.image.setImageResource(imageResID); + vh.name.setText(exercise.getName()); + vh.executionText.setText(exercise.getExecution()); + vh.descriptionText.setText(exercise.getDescription()); + vh.section.setText(exercise.getSection()); + } + + public class ExerciseViewHolder extends ViewHolder { + + ImageView image; + TextView name; + TextView executionText; + TextView descriptionText; + TextView section; + + public ExerciseViewHolder(View itemView) { + super(itemView); + + name = (TextView) itemView.findViewById(R.id.exercise_name); + image = (ImageView) itemView.findViewById(R.id.exercise_image); + executionText = (TextView) itemView.findViewById(R.id.exercise_execution); + descriptionText = (TextView) itemView.findViewById(R.id.exercise_description); + section = (TextView) itemView.findViewById(R.id.exercise_section); + + } + } +} diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/helper/CursorFilter.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/helper/CursorFilter.java new file mode 100644 index 0000000..ac5e7d2 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/helper/CursorFilter.java @@ -0,0 +1,75 @@ +package org.secuso.privacyfriendlybreakreminder.activities.helper; +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Matthieu Harlé + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import android.database.Cursor; +import android.widget.Filter; + +/** + * Taken from: https://gist.github.com/Shywim/127f207e7248fe48400b + */ +public class CursorFilter extends Filter { + + CursorFilterClient mClient; + + interface CursorFilterClient { + CharSequence convertToString(Cursor cursor); + Cursor runQueryOnBackgroundThread(CharSequence constraint); + Cursor getCursor(); + void changeCursor(Cursor cursor); + } + + CursorFilter(CursorFilterClient client) { + mClient = client; + } + + @Override + public CharSequence convertResultToString(Object resultValue) { + return mClient.convertToString((Cursor) resultValue); + } + + @Override + protected FilterResults performFiltering(CharSequence constraint) { + Cursor cursor = mClient.runQueryOnBackgroundThread(constraint); + + FilterResults results = new FilterResults(); + if (cursor != null) { + results.count = cursor.getCount(); + results.values = cursor; + } else { + results.count = 0; + results.values = null; + } + return results; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + Cursor oldCursor = mClient.getCursor(); + + if (results.values != null && results.values != oldCursor) { + mClient.changeCursor((Cursor) results.values); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/helper/CursorRecyclerViewAdapter.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/helper/CursorRecyclerViewAdapter.java new file mode 100644 index 0000000..a927e28 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/activities/helper/CursorRecyclerViewAdapter.java @@ -0,0 +1,376 @@ +package org.secuso.privacyfriendlybreakreminder.activities.helper; +/* + * Copyright (C) 2014 skyfish.jy@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Matthieu Harlé + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.database.CursorJoiner; +import android.database.DataSetObserver; +import android.os.Handler; +import android.provider.BaseColumns; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.widget.Filter; +import android.widget.FilterQueryProvider; +import android.widget.Filterable; + +/** + * Provide a {@link android.support.v7.widget.RecyclerView.Adapter} implementation with cursor + * support. + * + * Child classes only need to implement {@link #onCreateViewHolder(android.view.ViewGroup, int)} and + * {@link #onBindViewHolder(RecyclerView.ViewHolder, Cursor)}. + * + * This class does not implement deprecated fields and methods from CursorAdapter! Incidentally, + * only {@link android.widget.CursorAdapter#FLAG_REGISTER_CONTENT_OBSERVER} is available, so the + * flag is implied, and only the Adapter behavior using this flag has been ported. + * + * @param {@inheritDoc} + * + * @see android.support.v7.widget.RecyclerView.Adapter + * @see android.widget.CursorAdapter + * @see Filterable + * @see CursorFilter.CursorFilterClient + */ +public abstract class CursorRecyclerViewAdapter extends RecyclerView.Adapter implements CursorFilter.CursorFilterClient, Filterable { + + protected Context mContext; + private Cursor mCursor; + private boolean mDataValid; + private int mRowIdColumn; + private DataSetObserver mDataSetObserver; + private ChangeObserver mChangeObserver; + private CursorFilter mCursorFilter; + private FilterQueryProvider mFilterQueryProvider; + private boolean mUseIndividualNotifies; + + private String mIdIdentifier = "_id"; + + public CursorRecyclerViewAdapter(Context context, Cursor cursor, String idIdentifier) { + this(context, null); + this.setIdIdentifier(idIdentifier); + this.changeCursor(cursor); + } + + public CursorRecyclerViewAdapter(Context context, Cursor cursor) { + mContext = context; + mCursor = cursor; + mDataValid = cursor != null; + mRowIdColumn = mDataValid ? mCursor.getColumnIndex(mIdIdentifier) : -1; + + mDataSetObserver = new NotifyingDataSetObserver(); + mChangeObserver = new ChangeObserver(); + + if (mDataValid) { + if (mChangeObserver != null) cursor.registerContentObserver(mChangeObserver); + if (mDataSetObserver != null) cursor.registerDataSetObserver(mDataSetObserver); + } + } + + public void setIdIdentifier(String identifier) { + mIdIdentifier = TextUtils.isEmpty(identifier) ? + "_id" : + identifier; + } + + public Cursor getCursor() { + return mCursor; + } + + @Override + public int getItemCount() { + if (mDataValid && mCursor != null) { + return mCursor.getCount(); + } + return 0; + } + + @Override + public long getItemId(int position) { + if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) { + return mCursor.getLong(mRowIdColumn); + } + return 0; + } + + @Override + public void setHasStableIds(boolean hasStableIds) { + super.setHasStableIds(hasStableIds); + } + + public abstract void onBindViewHolder(VH viewHolder, Cursor cursor); + + @Override + public void onBindViewHolder(VH viewHolder, int position) { + if (!mDataValid) { + throw new IllegalStateException("this should only be called when the cursor is valid"); + } + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("couldn't move cursor to position " + position); + } + onBindViewHolder(viewHolder, mCursor); + } + + /** + * Change the underlying cursor to a new cursor. If there is an existing cursor it will be + * closed. + */ + public void changeCursor(Cursor cursor) { + Cursor old = swapCursor(cursor); + if (old != null) { + old.close(); + } + } + + /** + * Swap in a new Cursor, returning the old Cursor. Unlike + * {@link #changeCursor(Cursor)}, the returned old Cursor is not + * closed. + */ + public Cursor swapCursor(Cursor newCursor) { + if (newCursor == mCursor) { + return null; + } + // unregister observers on old cursor + final Cursor oldCursor = mCursor; + if (oldCursor != null) { + if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver); + if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver); + } + // register observers on the new cursor + mCursor = newCursor; + if (mCursor != null) { + if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver); + if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver); + + mRowIdColumn = newCursor.getColumnIndexOrThrow(mIdIdentifier); + mDataValid = true; + } else { + mRowIdColumn = -1; + mDataValid = false; + } + + // notify that Dataset has Changed + boolean notifyDataChanged = true; + + // check if we can get better notifies + if(mUseIndividualNotifies && oldCursor != null && newCursor != null && !oldCursor.isClosed() && !newCursor.isClosed()) { + notifyDataChanged = notifyItems(oldCursor, newCursor); + } + + if(notifyDataChanged) { + notifyDataSetChanged(); + } + + return oldCursor; + } + + public void setUseIndividualNotifies(boolean use) { + mUseIndividualNotifies = use; + } + + /** + * Compares the two cursors and calls notifyChanged on every item that changed. + * @param oldCursor + * @param newCursor + * @return + */ + private boolean notifyItems(Cursor oldCursor, Cursor newCursor) { + String[] columns = new String[] { BaseColumns._ID }; + CursorJoiner joiner = new CursorJoiner(oldCursor, columns, newCursor, columns); + for (CursorJoiner.Result res : joiner) { + switch (res) { + case LEFT: + notifyItemRemoved(newCursor.getPosition()); + break; + case RIGHT: + notifyItemInserted(newCursor.getPosition()); + break; + case BOTH: +// for(int i = 0; i < newCursor.getColumnCount(); i++) { +// if(!oldCursor.getString(i).equals(newCursor.getString(i))) { +// notifyItemChanged(newCursor.getPosition()); +// } +// } + if (getRowHash(oldCursor) != getRowHash(newCursor)) { + notifyItemChanged(newCursor.getPosition()); + } + break; + } + } + + return false; + } + + private int getRowHash(Cursor cursor) { + StringBuilder result = new StringBuilder("row"); + for (int i = 0; i < cursor.getColumnCount(); i++) { + result.append(cursor.getString(i)); + } + return result.toString().hashCode(); + } + + /** + *

Converts the cursor into a CharSequence. Subclasses should override this + * method to convert their results. The default implementation returns an + * empty String for null values or the default String representation of + * the value.

+ * + * @param cursor the cursor to convert to a CharSequence + * @return a CharSequence representing the value + */ + public CharSequence convertToString(Cursor cursor) { + return cursor == null ? "" : cursor.toString(); + } + + /** + * Runs a query with the specified constraint. This query is requested + * by the filter attached to this adapter. + * + * The query is provided by a + * {@link FilterQueryProvider}. + * If no provider is specified, the current cursor is not filtered and returned. + * + * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)} + * and the previous cursor is closed. + * + * This method is always executed on a background thread, not on the + * application's main thread (or UI thread.) + * + * Contract: when constraint is null or empty, the original results, + * prior to any filtering, must be returned. + * + * @param constraint the constraint with which the query must be filtered + * + * @return a Cursor representing the results of the new query + * + * @see #getFilter() + * @see #getFilterQueryProvider() + * @see #setFilterQueryProvider(FilterQueryProvider) + */ + public Cursor runQueryOnBackgroundThread(CharSequence constraint) { + if (mFilterQueryProvider != null) { + return mFilterQueryProvider.runQuery(constraint); + } + + return mCursor; + } + + public Filter getFilter() { + if (mCursorFilter == null) { + mCursorFilter = new CursorFilter(this); + } + return mCursorFilter; + } + + /** + * Returns the query filter provider used for filtering. When the + * provider is null, no filtering occurs. + * + * @return the current filter query provider or null if it does not exist + * + * @see #setFilterQueryProvider(FilterQueryProvider) + * @see #runQueryOnBackgroundThread(CharSequence) + */ + public FilterQueryProvider getFilterQueryProvider() { + return mFilterQueryProvider; + } + + /** + * Sets the query filter provider used to filter the current Cursor. + * The provider's + * {@link FilterQueryProvider#runQuery(CharSequence)} + * method is invoked when filtering is requested by a client of + * this adapter. + * + * @param filterQueryProvider the filter query provider or null to remove it + * + * @see #getFilterQueryProvider() + * @see #runQueryOnBackgroundThread(CharSequence) + */ + public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) { + mFilterQueryProvider = filterQueryProvider; + } + + /** + * Called when the {@link ContentObserver} on the cursor receives a change notification. + * Can be implemented by sub-class. + * + * @see ContentObserver#onChange(boolean) + */ + protected void onContentChanged() { + + } + + private class ChangeObserver extends ContentObserver { + public ChangeObserver() { + super(new Handler()); + } + + @Override + public boolean deliverSelfNotifications() { + return true; + } + + @Override + public void onChange(boolean selfChange) { + onContentChanged(); + } + } + + private class NotifyingDataSetObserver extends DataSetObserver { + private final String TAG = NotifyingDataSetObserver.class.getSimpleName(); + @Override + public void onChanged() { + super.onChanged(); + mDataValid = true; + notifyDataSetChanged(); + } + + @Override + public void onInvalidated() { + super.onInvalidated(); + mDataValid = false; + notifyDataSetChanged(); + //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/DBHandler.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/DBHandler.java deleted file mode 100644 index 015ac1d..0000000 --- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/DBHandler.java +++ /dev/null @@ -1,192 +0,0 @@ -package org.secuso.privacyfriendlybreakreminder.database; - - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -import org.secuso.privacyfriendlybreakreminder.database.data.Exercise; -import org.secuso.privacyfriendlybreakreminder.database.columns.ExerciseColumns; -import org.secuso.privacyfriendlybreakreminder.database.columns.ExercisesLocalColumns; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -public class DBHandler extends SQLiteOpenHelper { - - private SQLiteDatabase dataBase; - private static final String DATABASE_NAME = "exercises.sqlite"; - private static final String DATABASE_PATH = "/data/data/org.secuso.privacyfriendlybreakreminder/databases/"; - private static final int DATABASE_VERSION = 3; - - private static final String[] deleteQueryList = { - ExerciseColumns.SQL_DELETE_ENTRIES, - ExercisesLocalColumns.SQL_DELETE_ENTRIES}; - - public DBHandler(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - - //Check if database exists - File databaseFile = context.getDatabasePath(DATABASE_NAME); - if (!databaseFile.exists()) { - this.getReadableDatabase(); - try { - copyDataBase(context); - this.close(); - } catch (Exception e) { - Log.v("db log", "Copying data didn´t work!!"); - } - } - } - - @Override - public void onCreate(SQLiteDatabase db) { - - } - - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - execSQLList(db, deleteQueryList); - } - - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - onUpgrade(db, oldVersion, newVersion); - } - - private void execSQLList(SQLiteDatabase db, String[] queryList) { - for (String query : queryList) { - db.execSQL(query); - } - } - - - public Cursor getExerciseCursor(String language) { - SQLiteDatabase database = getReadableDatabase(); - - return database.rawQuery(buildQuery(false), new String[]{language}); - } - - public List getExerciseList(String language) { - SQLiteDatabase database = getReadableDatabase(); - - Cursor c = database.rawQuery(buildQuery(false), new String[]{language}); - - List result = new ArrayList<>(); - - if(c != null) { - - while(!c.isAfterLast()) { - result.add(ExerciseColumns.getExercise(c)); - c.moveToNext(); - } - - c.close(); - } - - return result; - } - - /** - * SELECT - * E._id, - * E.section, - * E.image_id, - * L.local_id, - * L.language, - * L.exercise_id, - * L.name, - * L.description, - * L.execution - * FROM exercises E LEFT OUTER JOIN exercises_local L - * ON E._id = L.exercise_id - * WHERE L.language = "de" [AND E.section LIKE %?%] - * ORDER BY E._id ASC - * - * @return the sql query without the ; at the end. - */ - private String buildQuery(boolean addSectionCheck) { - StringBuilder sqlQuery = new StringBuilder(); - - sqlQuery.append("SELECT "); - - for(String field : ExerciseColumns.PROJECTION) { - sqlQuery.append("E.").append(field).append(", "); - } - for(String field : ExercisesLocalColumns.PROJECTION) { - sqlQuery.append("L.").append(field).append(", "); - } - // delete the last comma - sqlQuery.setLength(sqlQuery.length()-2); - - sqlQuery.append(" FROM "); - sqlQuery.append(ExerciseColumns.TABLE_NAME); - sqlQuery.append(" E LEFT OUTER JOIN "); - sqlQuery.append(ExercisesLocalColumns.TABLE_NAME); - sqlQuery.append(" L"); - - sqlQuery.append("ON E."); - sqlQuery.append(ExerciseColumns._ID); - sqlQuery.append(" = L."); - sqlQuery.append(ExercisesLocalColumns.EXERCISE_ID); - - sqlQuery.append("WHERE "); - sqlQuery.append("L."); - sqlQuery.append(ExercisesLocalColumns.LANGUAGE); - sqlQuery.append("= ? "); - - if(addSectionCheck) { - sqlQuery.append("AND E."); - sqlQuery.append(ExerciseColumns.SECTION); - sqlQuery.append("LIKE ? "); - } - - sqlQuery.append("ORDER BY E."); - sqlQuery.append(ExerciseColumns._ID); - sqlQuery.append(" ASC"); - - return sqlQuery.toString(); - } - - - public List getExercisesFromSection(String language, String section) { - SQLiteDatabase database = getReadableDatabase(); - - Cursor c = database.rawQuery(buildQuery(true), new String[]{language, "%"+section+"%"}); - - List result = new ArrayList<>(); - - if(c != null) { - - while(!c.isAfterLast()) { - result.add(ExerciseColumns.getExercise(c)); - c.moveToNext(); - } - - c.close(); - } - - return result; - } - - private void copyDataBase(Context context) throws IOException { - InputStream myInput = context.getAssets().open(DATABASE_NAME); - String outFileName = DATABASE_PATH + DATABASE_NAME; - OutputStream myOutput = new FileOutputStream(outFileName); - - byte[] buffer = new byte[1024]; - int length; - while ((length = myInput.read(buffer)) > 0) { - myOutput.write(buffer, 0, length); - } - - myOutput.flush(); - myOutput.close(); - myInput.close(); - } -} diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/SQLiteHelper.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/SQLiteHelper.java new file mode 100644 index 0000000..284a5dd --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/SQLiteHelper.java @@ -0,0 +1,291 @@ +package org.secuso.privacyfriendlybreakreminder.database; + + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import org.secuso.privacyfriendlybreakreminder.database.columns.ExerciseSetColumns; +import org.secuso.privacyfriendlybreakreminder.database.data.Exercise; +import org.secuso.privacyfriendlybreakreminder.database.columns.ExerciseColumns; +import org.secuso.privacyfriendlybreakreminder.database.columns.ExerciseLocalColumns; +import org.secuso.privacyfriendlybreakreminder.database.data.ExerciseSet; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +public class SQLiteHelper extends SQLiteOpenHelper { + + private static final String TAG = SQLiteHelper.class.getSimpleName(); + + private Context mContext; + private static final String DATABASE_NAME = "exercises.sqlite"; + private static final String DATABASE_PATH = "/data/data/org.secuso.privacyfriendlybreakreminder/databases/"; + private static final int DATABASE_VERSION = 4; + + private static final String[] deleteQueryList = { + ExerciseColumns.SQL_DELETE_ENTRIES, + ExerciseLocalColumns.SQL_DELETE_ENTRIES, + ExerciseSetColumns.SQL_DELETE_ENTRIES}; + + private boolean onCreate; + private boolean onUpgrade; + + public SQLiteHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + mContext = context; + } + + @Override + public void onOpen(SQLiteDatabase db) { + if (onCreate || onUpgrade) { + onCreate = onUpgrade = false; + copyDatabaseFromAssets(db); + } + } + + @Override + public void onCreate(SQLiteDatabase db) { + onCreate = true; + } + + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + onUpgrade = true; + } + + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + onUpgrade(db, oldVersion, newVersion); + } + + + public synchronized Cursor getExerciseCursor(String language) { + SQLiteDatabase database = getReadableDatabase(); + return database.rawQuery(buildQuery(false), new String[]{language}); + } + + public synchronized List getExerciseList(String language) { + Cursor c = getExerciseCursor(language); + return buildExerciseList(c); + } + + public synchronized Cursor getExerciseCursorForSet(int setId, String language) { + SQLiteDatabase database = getReadableDatabase(); + + String sql = "SELECT *\n" + + "FROM exercise_set ES LEFT OUTER JOIN exercise_set_exercises ESE\n" + + "\tON ES.exercise_set_id = ESE.exercise_set_id\n" + + "LEFT OUTER JOIN exercises E\n" + + "\tON ESE.exercise_id = E.exercise_id\n" + + "LEFT OUTER JOIN exercises_local L\n" + + "\tON E.exercise_id = L.exercise_id\n" + + "WHERE ES.exercise_set_id = ? AND L.language = ?\n" + + "ORDER BY ESE.exercise_id ASC"; + + String sql2 = "SELECT *\n" + + "\tFROM (SELECT * \n" + + "\t\t\tFROM (SELECT *\n" + + "\t\t\t\tFROM "+ExerciseSetColumns.TABLE_NAME+" ES LEFT OUTER JOIN exercise_set_exercises ESE\n" + + "\t\t\t\tON ES."+ExerciseSetColumns._ID+" = ESE."+ExerciseSetColumns._ID+"\n" + + "\t\t\t\tWHERE ES."+ExerciseSetColumns._ID+" = ?\n" + + "\t\t\t\tORDER BY ESE."+ExerciseColumns._ID+" ASC) ES_ESE \n" + + "\t\t\tLEFT OUTER JOIN "+ExerciseColumns.TABLE_NAME+" E\n" + + "\t\t\tON ES_ESE."+ExerciseColumns._ID+" = E."+ExerciseColumns._ID+") ES_ESE_E \n" + + "\t\tLEFT OUTER JOIN "+ExerciseLocalColumns.TABLE_NAME+" L\n" + + "\t\tON ES_ESE_E."+ExerciseColumns._ID+" = L."+ExerciseLocalColumns.EXERCISE_ID+"\n" + + "\t\tWHERE L."+ExerciseLocalColumns.LANGUAGE+" = ?"; + + return database.rawQuery(sql, new String[]{String.valueOf(setId), language}); + } + + public synchronized ExerciseSet getExerciseListForSet(int setId, String language) { + Cursor c = getExerciseCursorForSet(setId, language); + + ExerciseSet result = null; + + if(c != null) { + + c.moveToFirst(); + + result = ExerciseSetColumns.fromCursor(c); + + while(!c.isAfterLast()) { + result.add(ExerciseColumns.fromCursor(c)); + c.moveToNext(); + } + + c.close(); + } + + return result; + } + + private List buildExerciseList(Cursor c) { + List result = new ArrayList<>(); + + if(c != null) { + + c.moveToFirst(); + + while(!c.isAfterLast()) { + result.add(ExerciseColumns.fromCursor(c)); + c.moveToNext(); + } + + c.close(); + } + + return result; + } + + + private String buildQuery(boolean addSectionCheck) { + return buildQuery(addSectionCheck, ""); + } + + public synchronized List getExercisesFromSection(String language, String section) { // TODO: Rename after old activities are deleted + SQLiteDatabase database = getReadableDatabase(); + + Cursor c = database.rawQuery(buildQuery(true), new String[]{language, "%"+section+"%"}); + + return buildExerciseList(c); + } + + /** + * SELECT + * E._id, + * E.section, + * E.image_id, + * L.local_id, + * L.language, + * L.exercise_id, + * L.name, + * L.description, + * L.execution + * FROM exercises E LEFT OUTER JOIN exercises_local L + * ON E._id = L.exercise_id + * WHERE L.language = "de" [AND E.section LIKE %?%] + * ORDER BY E._id ASC + * + * @return the sql query without the ; at the end. + */ + private String buildQuery(boolean addSectionCheck, String customWhereClause) { + StringBuilder sqlQuery = new StringBuilder(); + + sqlQuery.append("SELECT "); + + for(String field : ExerciseColumns.PROJECTION) { + sqlQuery.append("E.").append(field).append(", "); + } + for(String field : ExerciseLocalColumns.PROJECTION) { + sqlQuery.append("L.").append(field).append(", "); + } + // delete the last comma + sqlQuery.setLength(sqlQuery.length()-2); + + sqlQuery.append(" FROM "); + sqlQuery.append(ExerciseColumns.TABLE_NAME); + sqlQuery.append(" E LEFT OUTER JOIN "); + sqlQuery.append(ExerciseLocalColumns.TABLE_NAME); + sqlQuery.append(" L "); + + sqlQuery.append("ON E."); + sqlQuery.append(ExerciseColumns._ID); + sqlQuery.append(" = L."); + sqlQuery.append(ExerciseLocalColumns.EXERCISE_ID); + sqlQuery.append(" "); + + sqlQuery.append("WHERE "); + sqlQuery.append("L."); + sqlQuery.append(ExerciseLocalColumns.LANGUAGE); + sqlQuery.append(" = ? "); + + if(addSectionCheck) { + sqlQuery.append("AND E."); + sqlQuery.append(ExerciseColumns.SECTION); + sqlQuery.append(" LIKE ? "); + } + + sqlQuery.append("ORDER BY E."); + sqlQuery.append(ExerciseColumns._ID); + sqlQuery.append(" ASC"); + + return sqlQuery.toString(); + } + + private void copyDataBase(Context context) throws IOException { + InputStream myInput = context.getAssets().open(DATABASE_NAME); + String outFileName = DATABASE_PATH + DATABASE_NAME; + OutputStream myOutput = new FileOutputStream(outFileName); + + byte[] buffer = new byte[1024]; + int length; + while ((length = myInput.read(buffer)) > 0) { + myOutput.write(buffer, 0, length); + } + + myOutput.flush(); + myOutput.close(); + myInput.close(); + + SQLiteDatabase copiedDb = context.openOrCreateDatabase(DATABASE_NAME, 0, null); + copiedDb.execSQL("PRAGMA user_version = " + DATABASE_VERSION); + copiedDb.close(); + } + + /** + * Copy packaged database from assets folder to the database created in the + * application package context. + * + * @param db + * The target database in the application package context. + */ + private void copyDatabaseFromAssets(SQLiteDatabase db) { + Log.i(TAG, "copyDatabase"); + InputStream myInput = null; + OutputStream myOutput = null; + try { + // Open db packaged as asset as the input stream + myInput = mContext.getAssets().open(DATABASE_NAME); + + // Open the db in the application package context: + myOutput = new FileOutputStream(db.getPath()); + + // Transfer db file contents: + byte[] buffer = new byte[1024]; + int length; + while ((length = myInput.read(buffer)) > 0) { + myOutput.write(buffer, 0, length); + } + myOutput.flush(); + + // Set the version of the copied database to the current + // version: + SQLiteDatabase copiedDb = mContext.openOrCreateDatabase(DATABASE_NAME, 0, null); + copiedDb.execSQL("PRAGMA user_version = " + DATABASE_VERSION); + copiedDb.close(); + + } catch (IOException e) { + e.printStackTrace(); + throw new Error(TAG + " Error copying database"); + } finally { + // Close the streams + try { + if (myOutput != null) { + myOutput.close(); + } + if (myInput != null) { + myInput.close(); + } + } catch (IOException e) { + e.printStackTrace(); + throw new Error(TAG + " Error closing streams"); + } + } + } +} diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseColumns.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseColumns.java index 759d872..ce5a19d 100644 --- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseColumns.java +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseColumns.java @@ -2,7 +2,6 @@ package org.secuso.privacyfriendlybreakreminder.database.columns; import android.content.ContentValues; import android.database.Cursor; -import android.provider.BaseColumns; import org.secuso.privacyfriendlybreakreminder.database.data.Exercise; @@ -10,10 +9,11 @@ import org.secuso.privacyfriendlybreakreminder.database.data.Exercise; * Created by Christopher Beckmann on 23.08.2017. */ -public class ExerciseColumns implements BaseColumns { +public final class ExerciseColumns { public static final String TABLE_NAME = "exercises"; + public static final String _ID = "exercise_id"; public static final String SECTION = "section"; public static final String IMAGE_ID = "image_id"; @@ -24,8 +24,8 @@ public class ExerciseColumns implements BaseColumns { }; public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + TABLE_NAME; - public static Exercise getExercise(Cursor c) { - Exercise e = ExercisesLocalColumns.getExercise(c); + public static Exercise fromCursor(Cursor c) { + Exercise e = ExerciseLocalColumns.fromCursor(c); e.setId(c.getInt(c.getColumnIndexOrThrow(ExerciseColumns._ID))); e.setSection(c.getString(c.getColumnIndexOrThrow(ExerciseColumns.SECTION))); @@ -42,8 +42,10 @@ public class ExerciseColumns implements BaseColumns { values.put(ExerciseColumns.SECTION, record.getSection()); values.put(ExerciseColumns.IMAGE_ID, record.getImageID()); - values.putAll(ExercisesLocalColumns.getValues(record)); + values.putAll(ExerciseLocalColumns.getValues(record)); return values; } + + private ExerciseColumns() {} } diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExercisesLocalColumns.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseLocalColumns.java similarity index 64% rename from app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExercisesLocalColumns.java rename to app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseLocalColumns.java index 5721b89..c58f322 100644 --- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExercisesLocalColumns.java +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseLocalColumns.java @@ -2,7 +2,6 @@ package org.secuso.privacyfriendlybreakreminder.database.columns; import android.content.ContentValues; import android.database.Cursor; -import android.provider.BaseColumns; import org.secuso.privacyfriendlybreakreminder.database.data.Exercise; @@ -10,7 +9,7 @@ import org.secuso.privacyfriendlybreakreminder.database.data.Exercise; * Created by Christopher Beckmann on 25.08.2017. */ -public class ExercisesLocalColumns implements BaseColumns { +public final class ExerciseLocalColumns { public static final String TABLE_NAME = "exercises_local"; @@ -31,14 +30,14 @@ public class ExercisesLocalColumns implements BaseColumns { }; public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + TABLE_NAME; - public static Exercise getExercise(Cursor c) { + public static Exercise fromCursor(Cursor c) { Exercise e = new Exercise(); - e.setLocalId(c.getInt(c.getColumnIndexOrThrow(ExercisesLocalColumns._ID))); - e.setLanguage(c.getString(c.getColumnIndexOrThrow(ExercisesLocalColumns.LANGUAGE))); - e.setDescription(c.getString(c.getColumnIndexOrThrow(ExercisesLocalColumns.DESCRIPTION))); - e.setExecution(c.getString(c.getColumnIndexOrThrow(ExercisesLocalColumns.EXECUTION))); - e.setName(c.getString(c.getColumnIndexOrThrow(ExercisesLocalColumns.NAME))); + e.setLocalId(c.getInt(c.getColumnIndexOrThrow(ExerciseLocalColumns._ID))); + e.setLanguage(c.getString(c.getColumnIndexOrThrow(ExerciseLocalColumns.LANGUAGE))); + e.setDescription(c.getString(c.getColumnIndexOrThrow(ExerciseLocalColumns.DESCRIPTION))); + e.setExecution(c.getString(c.getColumnIndexOrThrow(ExerciseLocalColumns.EXECUTION))); + e.setName(c.getString(c.getColumnIndexOrThrow(ExerciseLocalColumns.NAME))); return e; } @@ -47,13 +46,15 @@ public class ExercisesLocalColumns implements BaseColumns { ContentValues values = new ContentValues(); if(record.getLocalId() != -1) { - values.put(ExercisesLocalColumns._ID, record.getLocalId()); + values.put(ExerciseLocalColumns._ID, record.getLocalId()); } - values.put(ExercisesLocalColumns.LANGUAGE, record.getLanguage()); - values.put(ExercisesLocalColumns.DESCRIPTION, record.getDescription()); - values.put(ExercisesLocalColumns.EXECUTION, record.getExecution()); - values.put(ExercisesLocalColumns.NAME, record.getName()); + values.put(ExerciseLocalColumns.LANGUAGE, record.getLanguage()); + values.put(ExerciseLocalColumns.DESCRIPTION, record.getDescription()); + values.put(ExerciseLocalColumns.EXECUTION, record.getExecution()); + values.put(ExerciseLocalColumns.NAME, record.getName()); return values; } + + private ExerciseLocalColumns() {} } diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseSetColumns.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseSetColumns.java new file mode 100644 index 0000000..e14306b --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/columns/ExerciseSetColumns.java @@ -0,0 +1,49 @@ +package org.secuso.privacyfriendlybreakreminder.database.columns; + +import android.content.ContentValues; +import android.database.Cursor; +import android.provider.BaseColumns; + +import org.secuso.privacyfriendlybreakreminder.database.data.Exercise; +import org.secuso.privacyfriendlybreakreminder.database.data.ExerciseSet; + +/** + * Created by Christopher Beckmann on 03.09.2017. + */ + +public final class ExerciseSetColumns { + + public static final String TABLE_NAME = "exercise_set"; + + public static final String _ID = "exercise_set_id"; + public static final String NAME = "name"; + + public static final String[] PROJECTION = { + _ID, + NAME + }; + + public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + TABLE_NAME; + + public static ExerciseSet fromCursor(Cursor c) { + ExerciseSet e = new ExerciseSet(); + + e.setId(c.getInt(c.getColumnIndexOrThrow(ExerciseSetColumns._ID))); + e.setName(c.getString(c.getColumnIndexOrThrow(ExerciseSetColumns.NAME))); + + return e; + } + + public static ContentValues getValues(ExerciseSet record) { + ContentValues values = new ContentValues(); + + if(record.getId() != -1) { + values.put(ExerciseSetColumns._ID, record.getId()); + } + values.put(ExerciseSetColumns.NAME, record.getName()); + + return values; + } + + private ExerciseSetColumns() {} +} diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/Exercise.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/Exercise.java index bd023d9..d43b898 100644 --- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/Exercise.java +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/Exercise.java @@ -4,12 +4,12 @@ package org.secuso.privacyfriendlybreakreminder.database.data; public class Exercise { private int id; private int localId; - private String section; - private String execution; - private String description; - private String name; + private String section = ""; + private String execution = ""; + private String description = ""; + private String name = ""; private String imageID; - private String language; + private String language = ""; public Exercise() { this.localId = -1; @@ -60,4 +60,35 @@ public class Exercise { public void setDescription(String description) { this.description = description; } + + @Override + public int hashCode() { + StringBuilder sb = new StringBuilder(); + + sb.append(id) + .append(localId) + .append(section) + .append(execution) + .append(description) + .append(name) + .append(imageID) + .append(language); + + return sb.toString().hashCode(); + } + + @Override + public boolean equals(Object object) { + if(!(object instanceof Exercise)) return false; + Exercise other = (Exercise) object; + + return this.id != other.id + || this.localId != other.localId + || !this.section.equals(other.section) + || !this.execution.equals(other.execution) + || !this.description.equals(other.description) + || !this.name.equals(other.name) + || !this.imageID.equals(other.imageID) + || !this.language.equals(other.language); + } } diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/ExerciseSet.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/ExerciseSet.java new file mode 100644 index 0000000..acf1568 --- /dev/null +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/database/data/ExerciseSet.java @@ -0,0 +1,53 @@ +package org.secuso.privacyfriendlybreakreminder.database.data; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Christopher Beckmann on 03.09.2017. + */ + +public class ExerciseSet { + private int id = -1; + private String name = null; + private List exercises = new ArrayList<>(); + + public ExerciseSet() {} + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void add(Exercise exercise) { + if(!exercises.contains(exercise)) { + exercises.add(exercise); + } + } + + public void remove(Exercise exercise) { + if(exercises.contains(exercise)) { + exercises.remove(exercise); + } + } + + public void get(int index) { + exercises.get(index); + } + + public int size() { + return exercises.size(); + } + +} diff --git a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/service/TimerService.java b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/service/TimerService.java index 052041e..a7920fb 100644 --- a/app/src/main/java/org/secuso/privacyfriendlybreakreminder/service/TimerService.java +++ b/app/src/main/java/org/secuso/privacyfriendlybreakreminder/service/TimerService.java @@ -22,6 +22,7 @@ import org.secuso.privacyfriendlybreakreminder.R; import org.secuso.privacyfriendlybreakreminder.activities.TimerActivity; import java.io.FileDescriptor; +import java.util.Locale; import java.util.Timer; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; @@ -74,6 +75,7 @@ public class TimerService extends Service { mTimer = createTimer(duration); mTimer.start(); isRunning = true; + sendBroadcast(buildBroadcast()); } } @@ -81,6 +83,7 @@ public class TimerService extends Service { if(isRunning) { mTimer.cancel(); isRunning = false; + sendBroadcast(buildBroadcast()); } } @@ -89,17 +92,29 @@ public class TimerService extends Service { mTimer = createTimer(remainingDuration); mTimer.start(); isRunning = true; + sendBroadcast(buildBroadcast()); } } public synchronized void resetTimer() { if(isRunning) { mTimer.cancel(); - isRunning = false; - remainingDuration = 0; + mTimer = createTimer(initialDuration); + mTimer.start(); } + remainingDuration = initialDuration; + sendBroadcast(buildBroadcast()); } + public synchronized void stopAndResetTimer() { + if(isRunning) mTimer.cancel(); + isRunning = false; + remainingDuration = initialDuration; + sendBroadcast(buildBroadcast()); + } + + public synchronized boolean isPaused() { return !isRunning && remainingDuration > 0 && remainingDuration != initialDuration; } + public synchronized boolean isRunning() { return isRunning; } @@ -111,29 +126,36 @@ public class TimerService extends Service { @Override public void onTick(long millisUntilFinished) { - int secondsUntilFinished = (int) Math.ceil(millisUntilFinished / 1000.0); + synchronized (TimerService.this) { + remainingDuration = millisUntilFinished; + } - remainingDuration = millisUntilFinished; - - Intent broadcast = new Intent(TIMER_BROADCAST); - broadcast.putExtra("onTickMillis", millisUntilFinished); - broadcast.putExtra("countdown_seconds", secondsUntilFinished); - sendBroadcast(broadcast); + sendBroadcast(buildBroadcast()); } @Override public void onFinish() { - // TODO: finish broadcast - Intent broadcast = new Intent(TIMER_BROADCAST); + Intent broadcast = buildBroadcast(); broadcast.putExtra("done", true); - broadcast.putExtra("onTickMillis", 0); - broadcast.putExtra("countdown_seconds", 0); - sendBroadcast(broadcast); - resetTimer(); + sendBroadcast(buildBroadcast()); + + stopAndResetTimer(); } }; } + private synchronized Intent buildBroadcast() { + int secondsUntilFinished = (int) Math.ceil(remainingDuration / 1000.0); + + Intent broadcast = new Intent(TIMER_BROADCAST); + broadcast.putExtra("onTickMillis", remainingDuration); + broadcast.putExtra("initialMillis", initialDuration); + broadcast.putExtra("countdown_seconds", secondsUntilFinished); + broadcast.putExtra("isRunning", isRunning()); + broadcast.putExtra("isPaused", isPaused()); + return (broadcast); + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); @@ -157,15 +179,9 @@ public class TimerService extends Service { int seconds = secondsUntilFinished % 60; int minutes = minutesUntilFinished % 60; - StringBuilder sb = new StringBuilder(); + String time = String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, seconds); - if(hours > 0) sb.append(hours).append(":"); - if(minutes < 10) sb.append(0); - sb.append(minutes).append(":"); - if(seconds < 10) sb.append(0); - sb.append(seconds); - - builder.setContentText(sb.toString()); + builder.setContentText(time); builder.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, TimerActivity.class), FLAG_UPDATE_CURRENT)); builder.setColor(ContextCompat.getColor(this, R.color.colorAccent)); builder.setPriority(NotificationCompat.PRIORITY_HIGH); @@ -189,6 +205,10 @@ public class TimerService extends Service { context.startService(new Intent(context.getApplicationContext(), TimerService.class)); } + public synchronized long getRemainingDuration() { + return remainingDuration; + } + public class TimerServiceBinder extends Binder { public TimerService getService() { return TimerService.this; diff --git a/app/src/main/res/drawable-hdpi/ic_play_arrow_black_48dp.png b/app/src/main/res/drawable-hdpi/ic_play_arrow_black_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..5345ee3c4a7a3576b7493cfe3b4243d183c5d996 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawE_k{)hEy=Vona_=h(Y9NocJ*= z2gVEsR;>f9D;UIQtYDYfuzAy#I~!Bp9Eok4p0B{HuiHB}$V+qS6!&d`8=uTAZp(j*(RKzESv+D^YJz!E~ySM++u72Lv s%Lwqa0{zC|>FVdQ&MBb@0JEE2djJ3c literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_play_arrow_black_48dp.png b/app/src/main/res/drawable-mdpi/ic_play_arrow_black_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..f208795fccb514a4b7b698bbf9b3550c5cbed580 GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0D>7Fi*Ar*{ory2?#3J_p5=S^i< zz+kd~N%R3zNCUUe5oW#@nU_^~t_E*7>YF#eLHFOe+F8CAO!U94SW~mpXhze2wZ3%; zsn5=x6HLkX>r;rXFtTcTbNHrEN@uu`N}TV63RlNRS6Mp4MZlcZTw-!PuJV=7($cI7 z4g8O`9<(o8dakrd=SQlVMwc3d zf(=X;8n{9lxC0pV3s}T;nne-? z)~>paPb2Pm^0|K<=BZzL=UhB(GofEdzxb2E^TH}`gTLOMTxqQ$XU@Gn_$BM&>AQ0e zeqmy>mM7*}PBNfg!`- M>FVdQ&MBb@05rae3;+NC literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_48dp.png b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..1c57756b001e8603899db40189e0bd55407fa857 GIT binary patch literal 394 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ)^zFa~4-4ofl{Z7~H@0_RqYg zGmm4=^jkLWd6)Ok@1euYtTP0@B%S;zO=7AU{CD^|j?_jXg?g7W|RFJPhZfk|cV$(bQ@w)cZXJYD@< J);T3K0RUM-zj6Qo literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..904bbdbb0b49af289d7b60101d3ff799b0ad4e48 GIT binary patch literal 515 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE1xWt5x}=AJf$@~5i(^OyV{bI|OvU|_#&b6JW zyPvtPy`W)kf90Idiqa2uT;C~qh_TG>d8J-kPQ&AS&&oL1E&S#jWA3rKXX=n;(S$qh*R>Hg7a?cvsY7WT<=tNeUSKmoF(0-;MhPA g>d4{G!0_RG4P%>>?y~Mn&TBw2p00i_>zopr0E+m~NdN!< literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/circular_small.xml b/app/src/main/res/drawable/circular_small.xml new file mode 100644 index 0000000..7e6da40 --- /dev/null +++ b/app/src/main/res/drawable/circular_small.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_exercise.xml b/app/src/main/res/layout/activity_exercise.xml new file mode 100644 index 0000000..00ec4a7 --- /dev/null +++ b/app/src/main/res/layout/activity_exercise.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_exercise_set.xml b/app/src/main/res/layout/activity_exercise_set.xml new file mode 100644 index 0000000..31a213e --- /dev/null +++ b/app/src/main/res/layout/activity_exercise_set.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_timer.xml b/app/src/main/res/layout/activity_timer.xml index db5923d..5d1f4be 100644 --- a/app/src/main/res/layout/activity_timer.xml +++ b/app/src/main/res/layout/activity_timer.xml @@ -4,33 +4,112 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_margin="@dimen/activity_horizontal_margin" + android:padding="0dp" + android:layout_margin="0dp" tools:context="org.secuso.privacyfriendlybreakreminder.activities.TimerActivity"> + + + + + android:layout_marginEnd="8dp" + app:layout_constraintHorizontal_bias="0.0" + android:layout_marginTop="0dp" + app:layout_constraintTop_toBottomOf="@id/exercise"> + + + + + + + + + + + + + + + android:visibility="invisible" /> + android:textSize="36sp" + android:textStyle="bold" + android:visibility="invisible" /> + app:layout_constraintHorizontal_bias="0.33" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" /> + app:layout_constraintHorizontal_bias="0.66" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" /> diff --git a/app/src/main/res/layout/layout_exercise.xml b/app/src/main/res/layout/layout_exercise.xml new file mode 100644 index 0000000..0448b91 --- /dev/null +++ b/app/src/main/res/layout/layout_exercise.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_exercise_set.xml b/app/src/main/res/layout/layout_exercise_set.xml new file mode 100644 index 0000000..3ae59d2 --- /dev/null +++ b/app/src/main/res/layout/layout_exercise_set.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + \ 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 545b9c6..4934cf3 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -13,6 +13,13 @@ true + +