├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable-xhdpi
│ │ │ │ ├── logo.png
│ │ │ │ ├── ic_edit.png
│ │ │ │ ├── ic_delete.png
│ │ │ │ └── ic_settings.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ ├── logo.png
│ │ │ │ ├── ic_edit.png
│ │ │ │ ├── ic_delete.png
│ │ │ │ └── ic_settings.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ │ ├── logo.png
│ │ │ │ ├── ic_delete.png
│ │ │ │ ├── ic_edit.png
│ │ │ │ └── ic_settings.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── layout
│ │ │ │ ├── recycler_view.xml
│ │ │ │ ├── pref_dialog_time.xml
│ │ │ │ ├── spinner_layout.xml
│ │ │ │ ├── content_home.xml
│ │ │ │ ├── exercise_loading.xml
│ │ │ │ ├── exercise_break_splash.xml
│ │ │ │ ├── content_fragment.xml
│ │ │ │ ├── home.xml
│ │ │ │ ├── exercise_series.xml
│ │ │ │ ├── exercises_item.xml
│ │ │ │ ├── exercise_break.xml
│ │ │ │ └── exercise_new.xml
│ │ │ ├── drawable
│ │ │ │ ├── progress_bar_background.xml
│ │ │ │ ├── splash_background.xml
│ │ │ │ ├── oval_accent.xml
│ │ │ │ ├── oval_primary.xml
│ │ │ │ └── progress_bar_accent.xml
│ │ │ ├── drawable-v21
│ │ │ │ ├── oval_accent.xml
│ │ │ │ └── oval_primary.xml
│ │ │ ├── values
│ │ │ │ ├── arrays.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── themes.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-pl
│ │ │ │ ├── arrays.xml
│ │ │ │ └── strings.xml
│ │ │ ├── menu
│ │ │ │ ├── home.xml
│ │ │ │ └── exercise.xml
│ │ │ └── xml
│ │ │ │ └── settings.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── morcinek
│ │ │ │ └── workout
│ │ │ │ ├── exercise
│ │ │ │ ├── ExerciseState.kt
│ │ │ │ ├── di
│ │ │ │ │ ├── ExerciseComponent.kt
│ │ │ │ │ └── ExerciseModule.kt
│ │ │ │ ├── fragments
│ │ │ │ │ ├── SeriesFragment.kt
│ │ │ │ │ ├── BreakSplashFragment.kt
│ │ │ │ │ ├── LoadingFragment.kt
│ │ │ │ │ ├── BreakFragment.kt
│ │ │ │ │ └── NewFragment.kt
│ │ │ │ ├── ExerciseDataManager.kt
│ │ │ │ ├── TimerService.kt
│ │ │ │ └── ExerciseActivity.kt
│ │ │ │ ├── common
│ │ │ │ ├── firebase
│ │ │ │ │ ├── data
│ │ │ │ │ │ ├── DataModel.kt
│ │ │ │ │ │ ├── InteractorDelegate.kt
│ │ │ │ │ │ ├── DataManagerImpl.kt
│ │ │ │ │ │ ├── DataManager.kt
│ │ │ │ │ │ └── DataProvider.kt
│ │ │ │ │ ├── database
│ │ │ │ │ │ └── ReferenceManager.kt
│ │ │ │ │ ├── auth
│ │ │ │ │ │ └── UserManager.kt
│ │ │ │ │ └── di
│ │ │ │ │ │ └── modules
│ │ │ │ │ │ └── FirebaseModule.kt
│ │ │ │ ├── di
│ │ │ │ │ ├── ActivityScope.kt
│ │ │ │ │ ├── DaggerUtils.kt
│ │ │ │ │ ├── modules
│ │ │ │ │ │ ├── ApplicationModule.kt
│ │ │ │ │ │ └── AndroidModule.kt
│ │ │ │ │ └── ApplicationComponent.kt
│ │ │ │ ├── utils
│ │ │ │ │ ├── FragmentUtils.kt
│ │ │ │ │ ├── CalendarUtils.kt
│ │ │ │ │ ├── AndroidUtils.kt
│ │ │ │ │ ├── StartActivityUtils.kt
│ │ │ │ │ ├── IntentUtils.kt
│ │ │ │ │ └── SharedPreferencesUtils.kt
│ │ │ │ ├── FunctionalCountDownTimer.kt
│ │ │ │ ├── preference
│ │ │ │ │ ├── TimePreference.kt
│ │ │ │ │ └── TimePreferenceDialogFragmentCompat.kt
│ │ │ │ ├── fragment
│ │ │ │ │ ├── BaseFragment.kt
│ │ │ │ │ ├── ContentFragmentManager.kt
│ │ │ │ │ └── SingleFragmentActivity.kt
│ │ │ │ └── NotificationCenter.kt
│ │ │ │ ├── core
│ │ │ │ └── data
│ │ │ │ │ ├── exercises
│ │ │ │ │ ├── ExercisesProvider.kt
│ │ │ │ │ ├── ExerciseManager.kt
│ │ │ │ │ └── ExerciseDataModel.kt
│ │ │ │ │ ├── DataUtils.kt
│ │ │ │ │ └── DataModule.kt
│ │ │ │ ├── Application.kt
│ │ │ │ ├── settings
│ │ │ │ ├── SettingsPreferencesUtils.kt
│ │ │ │ └── SettingsFragment.kt
│ │ │ │ ├── splash
│ │ │ │ └── SplashActivity.kt
│ │ │ │ └── home
│ │ │ │ ├── exercises
│ │ │ │ ├── adapter
│ │ │ │ │ └── ExerciseViewAdapter.kt
│ │ │ │ ├── ExercisesFragment.kt
│ │ │ │ └── ExercisesInteractor.kt
│ │ │ │ └── HomeActivity.kt
│ │ └── AndroidManifest.xml
│ ├── debug
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ └── test
│ │ └── java
│ │ └── com
│ │ └── morcinek
│ │ └── workout
│ │ └── ExampleUnitTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── config
└── debug.keystore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/config/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/config/debug.keystore
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xhdpi/logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xxhdpi/logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xxxhdpi/logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xhdpi/ic_edit.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xxhdpi/ic_edit.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Workout-Debug
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xhdpi/ic_settings.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xxhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xxxhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xxxhdpi/ic_edit.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | google-services.json
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xxhdpi/ic_settings.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmorcinek/kotlin-android-tutorial/HEAD/app/src/main/res/drawable-xxxhdpi/ic_settings.png
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/ExerciseState.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise
2 |
3 | enum class ExerciseState {
4 | Series, Break, Splash, New, Loading
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/firebase/data/DataModel.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.firebase.data
2 |
3 | interface DataModel {
4 |
5 | fun toMap(): Map
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/di/ActivityScope.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.di
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @Retention
7 | annotation class ActivityScope
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/firebase/data/InteractorDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.firebase.data
2 |
3 | interface InteractorDelegate {
4 | fun success(values: T)
5 | fun failed(errorMessage: String)
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/firebase/data/DataManagerImpl.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.firebase.data
2 |
3 | import com.google.firebase.database.DatabaseReference
4 |
5 | open class DataManagerImpl(override val reference: DatabaseReference) : DataManager()
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Dec 02 10:30:21 CET 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/recycler_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/utils/FragmentUtils.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.utils
2 |
3 | import android.support.v7.app.AppCompatActivity
4 |
5 | fun android.support.v4.app.Fragment.setTitle(resourceId: Int) {
6 | (activity as AppCompatActivity).supportActionBar!!.setTitle(resourceId)
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/progress_bar_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/oval_accent.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | -
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/oval_primary.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | -
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/splash_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/pref_dialog_time.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/spinner_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - Chest
6 | - Back
7 | - Shoulders
8 | - Biceps
9 | - Triceps
10 | - Legs
11 | - Calves
12 | - Forearms
13 | - Abs
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pl/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - Klatka
6 | - Plecy
7 | - Barki
8 | - Biceps
9 | - Triceps
10 | - Nogi
11 | - Łydki
12 | - Przedramiona
13 | - Brzuch
14 |
15 |
--------------------------------------------------------------------------------
/app/src/test/java/com/morcinek/workout/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/core/data/exercises/ExercisesProvider.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.core.data.exercises
2 |
3 | import com.morcinek.workout.common.firebase.data.DataProvider
4 | import com.morcinek.workout.common.firebase.database.ReferenceManager
5 |
6 | class ExercisesProvider(private val referenceManager: ReferenceManager) : DataProvider() {
7 |
8 | override val type = ExerciseDataModel::class.java
9 |
10 | override val reference by lazy { referenceManager.exercises() }
11 | }
--------------------------------------------------------------------------------
/app/src/main/res/menu/home.xml:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/oval_accent.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/oval_primary.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/utils/CalendarUtils.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.utils
2 |
3 | import com.morcinek.workout.BuildConfig
4 | import java.text.SimpleDateFormat
5 | import java.util.*
6 |
7 | /**
8 | * Copyright 2016 Tomasz Morcinek. All rights reserved.
9 | */
10 |
11 | fun Calendar.formatWith(dateFormat: SimpleDateFormat) = dateFormat.format(time)
12 |
13 | fun dateFormat() = SimpleDateFormat(BuildConfig.DATE_FORMAT)
14 | fun timeFormat() = SimpleDateFormat(BuildConfig.TIME_FORMAT)
15 | fun dayOfWeekFormat() = SimpleDateFormat(BuildConfig.DAY_OF_WEEK_FORMAT)
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/di/DaggerUtils.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.di
2 |
3 | import android.app.Service
4 | import android.support.v4.app.Fragment
5 | import android.support.v7.app.AppCompatActivity
6 | import com.morcinek.workout.Application
7 |
8 | val AppCompatActivity.component: ApplicationComponent
9 | get() = (application as Application).component
10 |
11 | val Fragment.component: ApplicationComponent
12 | get() = (activity.application as Application).component
13 |
14 | val Service.component: ApplicationComponent
15 | get() = (application as Application).component
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/core/data/DataUtils.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.core.data
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.os.Parcelable
6 | import com.morcinek.workout.common.utils.putParcelableExtra
7 |
8 | fun Intent.putKeyExtra(value: String) = putExtra("key", value)
9 | fun Intent.getKeyExtra() : String? = getStringExtra("key")
10 |
11 | fun Bundle.putKey(value: String) = putString("key", value)
12 |
13 | inline fun Intent.putPair(pair: Pair) {
14 | putKeyExtra(pair.first)
15 | putParcelableExtra(pair.second)
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/firebase/data/DataManager.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.firebase.data
2 |
3 | import com.google.firebase.database.DatabaseReference
4 |
5 | abstract class DataManager {
6 |
7 | protected abstract val reference: DatabaseReference
8 |
9 | var key: String? = null
10 |
11 | private val objectReference by lazy { if (key != null) reference.child(key) else reference.push() }
12 |
13 | fun update(data: DataModel) = objectReference.updateChildren(data.toMap())
14 |
15 | fun remove() = objectReference.removeValue()
16 |
17 | fun get() = objectReference
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/core/data/exercises/ExerciseManager.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.core.data.exercises
2 |
3 | import com.morcinek.workout.common.firebase.data.DataManagerImpl
4 | import com.morcinek.workout.common.firebase.database.ReferenceManager
5 |
6 | class ExerciseManager(referenceManager: ReferenceManager) : DataManagerImpl(referenceManager.exercises()){
7 |
8 | fun setName(name: String) = get().child("name").setValue(name)
9 | fun setCategory(category: String) = get().child("category").setValue(category)
10 | fun setNumberOfSeries(numberOfSeries: Int) = get().child("numberOfSeries").setValue(numberOfSeries)
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/firebase/database/ReferenceManager.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.firebase.database
2 |
3 | import com.google.firebase.auth.FirebaseAuth
4 | import com.google.firebase.database.FirebaseDatabase
5 |
6 | class ReferenceManager(val firebaseAuth: FirebaseAuth, val firebaseDatabase: FirebaseDatabase) {
7 |
8 | init {
9 | firebaseDatabase.setPersistenceEnabled(true)
10 | }
11 |
12 | private val userId: String
13 | get() = firebaseAuth.currentUser!!.uid
14 |
15 | fun exercises() = firebaseDatabase.getReference("data")
16 | .child(userId)
17 | .child("exercises")
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/di/modules/ApplicationModule.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.di.modules
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import android.os.PowerManager
6 | import android.os.Vibrator
7 | import com.morcinek.workout.common.NotificationCenter
8 | import dagger.Module
9 | import dagger.Provides
10 |
11 | @Module
12 | class ApplicationModule() {
13 |
14 | @Provides
15 | fun provideNotificationCenter(context: Context, vibrator: Vibrator, powerManager: PowerManager, sharedPreferences: SharedPreferences)
16 | = NotificationCenter(context, vibrator, powerManager, sharedPreferences)
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/di/ExerciseComponent.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise.di
2 |
3 | import com.morcinek.workout.common.di.ActivityScope
4 | import com.morcinek.workout.exercise.ExerciseActivity
5 | import com.morcinek.workout.exercise.fragments.*
6 | import dagger.Subcomponent
7 |
8 | @ActivityScope
9 | @Subcomponent(modules = arrayOf(ExerciseModule::class))
10 | interface ExerciseComponent {
11 |
12 | fun inject(activity: ExerciseActivity)
13 |
14 | fun inject(fragment: SeriesFragment)
15 | fun inject(fragment: BreakFragment)
16 | fun inject(fragment: BreakSplashFragment)
17 | fun inject(fragment: LoadingFragment)
18 | fun inject(fragment: NewFragment)
19 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/progress_bar_accent.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
10 |
11 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/exercise_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/utils/AndroidUtils.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.utils
2 |
3 | import android.app.Activity
4 | import android.app.Service
5 | import android.content.BroadcastReceiver
6 | import android.content.Context
7 | import android.content.Intent
8 |
9 | inline fun Activity.stopService() {
10 | stopService(Intent(this, T::class.java))
11 | }
12 |
13 | inline fun broadcastReceiver(crossinline onReceiveFunction: (Intent) -> Unit) = object : BroadcastReceiver() {
14 | override fun onReceive(context: Context, intent: Intent) = onReceiveFunction(intent)
15 | }
16 |
17 | fun handleOptionItemAction(function: () -> Any): Boolean {
18 | function()
19 | return true
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/utils/StartActivityUtils.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.utils
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import org.jetbrains.anko.internals.AnkoInternals
7 |
8 | inline fun Context.startActivity(vararg params: Any) {
9 | val arrayOfPairs = params.map { Pair(it.javaClass.name, it) }.toTypedArray()
10 | AnkoInternals.internalStartActivity(this, T::class.java, arrayOfPairs)
11 | }
12 |
13 | inline fun Activity.startActivityFun(function: Intent.() -> Any) {
14 | val intent = AnkoInternals.createIntent(this, T::class.java, emptyArray())
15 | function(intent)
16 | startActivity(intent)
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/core/data/DataModule.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.core.data
2 |
3 | import android.content.Context
4 | import com.morcinek.workout.common.firebase.database.ReferenceManager
5 | import com.morcinek.workout.common.firebase.di.modules.FirebaseModule
6 | import com.morcinek.workout.core.data.exercises.ExercisesProvider
7 | import com.morcinek.workout.home.exercises.ExercisesInteractor
8 | import dagger.Module
9 | import dagger.Provides
10 |
11 | @Module(includes = arrayOf(FirebaseModule::class))
12 | class DataModule {
13 |
14 | @Provides
15 | fun provideExercisesProvider(referenceManager: ReferenceManager) = ExercisesProvider(referenceManager)
16 |
17 | @Provides
18 | fun provideExercisesInteractor(context: Context, exercisesProvider: ExercisesProvider) = ExercisesInteractor(context, exercisesProvider)
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/FunctionalCountDownTimer.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common
2 |
3 | import android.os.CountDownTimer
4 |
5 | class FunctionalCountDownTimer(millisInFuture: Long, countDownInterval: Long) : CountDownTimer(millisInFuture, countDownInterval) {
6 |
7 | private var onFinishFunction: (() -> Unit)? = null
8 | private var onTickFunction: ((Long) -> Unit)? = null
9 |
10 | fun onFinish(onFinishFunction: () -> Unit) {
11 | this.onFinishFunction = onFinishFunction
12 | }
13 |
14 | fun onTick(onTickFunction: (Long) -> Unit) {
15 | this.onTickFunction = onTickFunction
16 | }
17 |
18 | override fun onFinish() {
19 | onFinishFunction?.invoke()
20 | }
21 |
22 | override fun onTick(millisUntilFinished: Long) {
23 | onTickFunction?.invoke(millisUntilFinished)
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/firebase/auth/UserManager.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.firebase.auth
2 |
3 | import com.google.firebase.auth.AuthCredential
4 | import com.google.firebase.auth.FirebaseAuth
5 | import com.google.firebase.auth.FirebaseUser
6 |
7 | class UserManager(private val firebaseAuth: FirebaseAuth) {
8 |
9 | val currentUser: FirebaseUser?
10 | get() = firebaseAuth.currentUser
11 |
12 | val isUserAuthenticated: Boolean
13 | get() = currentUser != null
14 |
15 | fun signInAnonymously() = firebaseAuth.signInAnonymously()
16 |
17 | fun connectWithCredentials(authCredential: AuthCredential) =
18 | if (isUserAuthenticated) {
19 | currentUser!!.linkWithCredential(authCredential)
20 | } else {
21 | firebaseAuth.signInWithCredential(authCredential)
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 | 100dp
4 | 42dp
5 | 32dp
6 |
7 | 8dp
8 | 12dp
9 | 16dp
10 |
11 | 24dp
12 | 72dp
13 |
14 | 260dp
15 | 68sp
16 |
17 | 22sp
18 |
19 | 15sp
20 | 18sp
21 | 12sp
22 | 13sp
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #2374AB
4 | #1A557D
5 | #FF8484
6 | #D16D6D
7 |
8 | #FFFFFF
9 | #FEFEFE
10 |
11 |
12 | #DD000000
13 | #89000000
14 | #42000000
15 |
16 |
17 | #FFFFFF
18 | #B2FFFFFF
19 | #4CFFFFFF
20 |
21 | #4DCCBD
22 | #104259
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/preference/TimePreference.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.preference
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.support.v7.preference.DialogPreference
6 | import android.util.AttributeSet
7 | import com.morcinek.workout.R
8 |
9 | class TimePreference(context: Context?, attrs: AttributeSet?) : DialogPreference(context, attrs) {
10 |
11 | var time: Int = 0
12 | set(value) {
13 | field = value
14 | persistInt(time)
15 | }
16 |
17 | override fun getDialogLayoutResource() = R.layout.pref_dialog_time
18 |
19 | override fun onGetDefaultValue(a: TypedArray?, index: Int): Any? = a?.getInt(index, 0)
20 |
21 | override fun onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any?) {
22 | time = if (restorePersistedValue) getPersistedInt(time) else defaultValue as Int
23 | }
24 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | DEBUG_STORE_FILE = ../config/debug.keystore
20 | DEBUG_STORE_PASSWORD = android
21 | DEBUG_KEY_ALIAS = androiddebugkey
22 | DEBUG_KEY_PASSWORD = android
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/di/modules/AndroidModule.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.di.modules
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.os.PowerManager
6 | import android.os.Vibrator
7 | import dagger.Module
8 | import dagger.Provides
9 | import org.jetbrains.anko.defaultSharedPreferences
10 | import javax.inject.Singleton
11 |
12 | @Module
13 | class AndroidModule(private val application: Application) {
14 |
15 | @Provides
16 | fun provideApplicationContext(): Context = application
17 |
18 | @Provides
19 | @Singleton
20 | fun providePowerManager() = application.getSystemService(Context.POWER_SERVICE) as PowerManager
21 |
22 | @Provides
23 | @Singleton
24 | fun provideVibrator() = application.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
25 |
26 | @Provides
27 | @Singleton
28 | fun provideSharedPreferences() = application.defaultSharedPreferences
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/Application.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout
2 |
3 | import android.app.Application
4 | import com.morcinek.workout.common.di.ApplicationComponent
5 | import com.morcinek.workout.common.di.DaggerApplicationComponent
6 | import com.morcinek.workout.common.di.modules.AndroidModule
7 | import com.morcinek.workout.common.di.modules.ApplicationModule
8 | import com.morcinek.workout.core.data.DataModule
9 | import com.morcinek.workout.common.firebase.di.modules.FirebaseModule
10 |
11 | class Application : Application() {
12 |
13 | lateinit var component: ApplicationComponent
14 |
15 | override fun onCreate() {
16 | super.onCreate()
17 | component = DaggerApplicationComponent.builder()
18 | .androidModule(AndroidModule(this))
19 | .applicationModule(ApplicationModule())
20 | .firebaseModule(FirebaseModule())
21 | .dataModule(DataModule())
22 | .build()
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/utils/IntentUtils.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.utils
2 |
3 | import android.content.Intent
4 | import android.os.Parcelable
5 | import java.io.Serializable
6 |
7 | inline fun Intent.getSerializableExtra() = getSerializableExtra(T::class.java.name) as T?
8 |
9 | fun Intent.putParcelableExtra(value: T) = putExtra(value.javaClass.getName(), value)
10 | fun Intent.putSerializableExtra(value: T) = putExtra(value.javaClass.getName(), value)
11 |
12 | fun Intent.getString() = getStringExtra(String::class.java.name)
13 | fun Intent.put(value: String) = putExtra(String::class.java.name, value)
14 |
15 | fun Intent.getFloat() = getFloatExtra(Float::class.java.name, 0F)
16 | fun Intent.put(value: Float) = putExtra(Float::class.java.name, value)
17 |
18 | fun Intent.getLong() = getLongExtra(Long::class.java.name, 0L)
19 | fun Intent.put(value: Long) = putExtra(Long::class.java.name, value)
20 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/exercise.xml:
--------------------------------------------------------------------------------
1 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/core/data/exercises/ExerciseDataModel.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.core.data.exercises
2 |
3 | import com.google.firebase.database.Exclude
4 | import com.google.firebase.database.IgnoreExtraProperties
5 | import com.morcinek.workout.common.firebase.data.DataModel
6 | import java.util.*
7 |
8 | @IgnoreExtraProperties
9 | class ExerciseDataModel(var name: String = "", var category: String = "", var timeInMillis: Long = Calendar.getInstance().timeInMillis, var numberOfSeries: Int = 1) : DataModel {
10 |
11 | @Exclude
12 | override fun toMap() = mapOf(
13 | "name" to name,
14 | "category" to category,
15 | "timeInMillis" to timeInMillis,
16 | "numberOfSeries" to numberOfSeries
17 | )
18 |
19 | var date: Calendar
20 | @Exclude get() = Calendar.getInstance().apply { timeInMillis = this@ExerciseDataModel.timeInMillis }
21 | @Exclude set(value) {
22 | timeInMillis = value.timeInMillis
23 | }
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/tomaszmorcinek/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/fragment/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.fragment
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.Fragment
5 | import android.view.*
6 |
7 | abstract class BaseFragment : Fragment() {
8 |
9 | protected abstract val layoutResourceId: Int
10 |
11 | open protected val menuResourceId: Int? = null
12 |
13 | private val hasMenu by lazy { menuResourceId != null }
14 |
15 | final override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?)
16 | = inflater?.inflate(layoutResourceId, container, false)
17 |
18 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
19 | super.onViewCreated(view, savedInstanceState)
20 | setHasOptionsMenu(hasMenu)
21 | }
22 |
23 | override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
24 | if (hasMenu) {
25 | inflater?.inflate(menuResourceId!!, menu)
26 | }
27 | super.onCreateOptionsMenu(menu, inflater)
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/di/ExerciseModule.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise.di
2 |
3 | import android.content.SharedPreferences
4 | import android.support.v4.app.FragmentActivity
5 | import com.morcinek.workout.common.di.ActivityScope
6 | import com.morcinek.workout.common.firebase.database.ReferenceManager
7 | import com.morcinek.workout.common.fragment.ContentFragmentManager
8 | import com.morcinek.workout.core.data.exercises.ExerciseManager
9 | import com.morcinek.workout.exercise.ExerciseDataManager
10 | import dagger.Module
11 | import dagger.Provides
12 |
13 | @Module
14 | class ExerciseModule(private val activity: FragmentActivity) {
15 |
16 | @Provides
17 | @ActivityScope
18 | fun provideExerciseDataManager(sharedPreferences: SharedPreferences) = ExerciseDataManager(sharedPreferences)
19 |
20 | @Provides
21 | @ActivityScope
22 | fun provideExerciseManager(referenceManager: ReferenceManager) = ExerciseManager(referenceManager)
23 |
24 | @Provides
25 | fun provideContentFragmentManager() = ContentFragmentManager(activity)
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/fragments/SeriesFragment.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import com.morcinek.workout.R
6 | import com.morcinek.workout.common.fragment.BaseFragment
7 | import com.morcinek.workout.exercise.ExerciseDataManager
8 | import com.morcinek.workout.exercise.exerciseComponent
9 | import kotlinx.android.synthetic.main.exercise_series.*
10 | import javax.inject.Inject
11 |
12 | class SeriesFragment : BaseFragment() {
13 |
14 | override val layoutResourceId = R.layout.exercise_series
15 |
16 | @Inject lateinit var exerciseDataManager: ExerciseDataManager
17 |
18 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
19 | super.onViewCreated(view, savedInstanceState)
20 | exerciseComponent.inject(this)
21 |
22 | seriesNumberText.text = "${exerciseDataManager.numberOfSeries}"
23 | breakButton.setOnClickListener { exerciseDataManager.showBreak() }
24 | finishButton.setOnClickListener { activity.onBackPressed() }
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/utils/SharedPreferencesUtils.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.utils
2 |
3 | import android.content.SharedPreferences
4 |
5 | fun SharedPreferences.getInt(preferenceKey: String) = getInt(preferenceKey, 0)
6 | fun SharedPreferences.getIntOrNull(preferenceKey: String) = if (contains(preferenceKey)) getInt(preferenceKey) else null
7 |
8 | fun SharedPreferences.getLong(preferenceKey: String) = getLong(preferenceKey, 0)
9 | fun SharedPreferences.getLongOrNull(preferenceKey: String) = if (contains(preferenceKey)) getLong(preferenceKey, 0) else null
10 |
11 | fun SharedPreferences.getBoolean(preferenceKey: String) = getBoolean(preferenceKey, false)
12 | fun SharedPreferences.getBooleanOrNull(preferenceKey: String) = if (contains(preferenceKey)) getBoolean(preferenceKey, false) else null
13 |
14 | fun SharedPreferences.commitFunction(function: SharedPreferences.Editor.() -> SharedPreferences.Editor) = edit().function().commit()
15 |
16 | fun SharedPreferences.applyFunction(function: SharedPreferences.Editor.() -> SharedPreferences.Editor) = edit().function().apply()
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/firebase/data/DataProvider.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.firebase.data
2 |
3 | import com.google.firebase.database.*
4 |
5 | abstract class DataProvider : ValueEventListener {
6 |
7 | lateinit var delegate: Delegate
8 |
9 | protected abstract val reference: Query
10 | protected abstract val type: Class
11 |
12 | fun register() {
13 | reference.addValueEventListener(this)
14 | }
15 |
16 | fun registerForSingleValue() {
17 | reference.addListenerForSingleValueEvent(this)
18 | }
19 |
20 | fun unregister() {
21 | reference.removeEventListener(this)
22 | }
23 |
24 | override fun onDataChange(dataSnapshot: DataSnapshot?) {
25 | delegate.success(dataSnapshot!!.children.map { it.key to it.getValue(type)!! })
26 | }
27 |
28 | override fun onCancelled(error: DatabaseError?) {
29 | delegate.failed(error!!.message)
30 | }
31 |
32 | interface Delegate {
33 | fun success(values: List>)
34 | fun failed(errorMessage: String)
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/fragments/BreakSplashFragment.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import com.morcinek.workout.R
6 | import com.morcinek.workout.common.fragment.BaseFragment
7 | import com.morcinek.workout.exercise.ExerciseDataManager
8 | import com.morcinek.workout.exercise.exerciseComponent
9 | import kotlinx.android.synthetic.main.exercise_break_splash.*
10 | import javax.inject.Inject
11 |
12 | class BreakSplashFragment : BaseFragment() {
13 |
14 | override val layoutResourceId = R.layout.exercise_break_splash
15 |
16 | @Inject lateinit var exerciseDataManager: ExerciseDataManager
17 |
18 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
19 | super.onViewCreated(view, savedInstanceState)
20 | exerciseComponent.inject(this)
21 |
22 | breakSplashText.text = getString(R.string.break_splash_series_text, exerciseDataManager.numberOfSeries)
23 | view?.setOnClickListener {
24 | exerciseDataManager.showSeries()
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/fragment/ContentFragmentManager.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.fragment
2 |
3 | import android.support.v4.app.Fragment
4 | import android.support.v4.app.FragmentActivity
5 | import com.morcinek.workout.R
6 |
7 | class ContentFragmentManager(private val activity: FragmentActivity, private val contentLayoutId: Int = R.id.contentFrame) {
8 |
9 | private val fragmentManager by lazy { activity.supportFragmentManager }
10 |
11 | val currentFragment: Fragment?
12 | get() = fragmentManager.fragments.firstOrNull { it?.isVisible ?: false }
13 |
14 | fun replaceFragment(fragment: Fragment, tag: String = fragment.javaClass.name, addToBackStack: Boolean = false) {
15 | if (currentFragment?.javaClass != fragment.javaClass) {
16 | val fragmentTransaction = fragmentManager.beginTransaction()
17 | fragmentTransaction.replace(contentLayoutId, fragment, tag)
18 | if (addToBackStack) {
19 | fragmentTransaction.addToBackStack(tag)
20 | }
21 | fragmentTransaction.commit()
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/firebase/di/modules/FirebaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.firebase.di.modules
2 |
3 | import com.google.firebase.auth.FirebaseAuth
4 | import com.google.firebase.database.FirebaseDatabase
5 | import com.morcinek.workout.common.di.modules.AndroidModule
6 | import com.morcinek.workout.common.firebase.auth.UserManager
7 | import com.morcinek.workout.common.firebase.database.ReferenceManager
8 | import dagger.Module
9 | import dagger.Provides
10 | import javax.inject.Singleton
11 |
12 | @Module(includes = arrayOf(AndroidModule::class))
13 | class FirebaseModule {
14 |
15 | @Provides
16 | @Singleton
17 | fun provideFirebaseDatabase() = FirebaseDatabase.getInstance()
18 |
19 | @Provides
20 | @Singleton
21 | fun provideFirebaseAuth() = FirebaseAuth.getInstance()
22 |
23 | @Provides
24 | fun provideUserManager(firebaseAuth: FirebaseAuth) = UserManager(firebaseAuth)
25 |
26 | @Provides
27 | @Singleton
28 | fun provideFirebaseDataManager(firebaseAuth: FirebaseAuth, firebaseDatabase: FirebaseDatabase)
29 | = ReferenceManager(firebaseAuth, firebaseDatabase)
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/settings/SettingsPreferencesUtils.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.settings
2 |
3 | import android.content.SharedPreferences
4 | import com.morcinek.workout.common.utils.getBoolean
5 | import com.morcinek.workout.common.utils.getInt
6 |
7 |
8 | const val BREAK_TIME = "generalBreakTime"
9 | const val VIBRATION_ENABLED = "notificationVibrationEnabled"
10 | const val VIBRATION_VALUE = "notificationVibrationValue"
11 | const val SOUND_ENABLED = "notificationSoundEnabled"
12 |
13 | inline val SharedPreferences.breakTime: Int
14 | get() = getInt(BREAK_TIME)
15 | // set(value) = applyFunction { putInt(BREAK_TIME, value) }
16 |
17 | inline val SharedPreferences.vibrationEnabled: Boolean
18 | get() = getBoolean(VIBRATION_ENABLED)
19 | // set(value) = applyFunction { putBoolean(VIBRATION_ENABLED, value) }
20 |
21 | inline val SharedPreferences.vibrationValue: Int
22 | get() = getInt(VIBRATION_VALUE)
23 | // set(value) = applyFunction { putInt(VIBRATION_VALUE, value) }
24 |
25 | inline val SharedPreferences.soundEnabled: Boolean
26 | get() = getBoolean(SOUND_ENABLED)
27 | // set(value) = applyFunction { putBoolean(SOUND_ENABLED, value) }
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/exercise_break_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
19 |
20 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/di/ApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.di
2 |
3 | import com.morcinek.workout.common.di.modules.AndroidModule
4 | import com.morcinek.workout.common.di.modules.ApplicationModule
5 | import com.morcinek.workout.core.data.DataModule
6 | import com.morcinek.workout.common.firebase.di.modules.FirebaseModule
7 | import com.morcinek.workout.exercise.TimerService
8 | import com.morcinek.workout.exercise.di.ExerciseComponent
9 | import com.morcinek.workout.exercise.di.ExerciseModule
10 | import com.morcinek.workout.home.HomeActivity
11 | import com.morcinek.workout.home.exercises.ExercisesFragment
12 | import com.morcinek.workout.splash.SplashActivity
13 | import dagger.Component
14 | import javax.inject.Singleton
15 |
16 | @Singleton
17 | @Component(modules = arrayOf(AndroidModule::class, ApplicationModule::class, FirebaseModule::class, DataModule::class))
18 | interface ApplicationComponent {
19 |
20 | fun inject(activity: HomeActivity)
21 | fun inject(activity: SplashActivity)
22 |
23 | fun inject(fragment: ExercisesFragment)
24 |
25 | fun inject(service: TimerService)
26 |
27 | fun add(exerciseModule: ExerciseModule): ExerciseComponent
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/splash/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.splash
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import com.morcinek.workout.common.di.component
6 | import com.morcinek.workout.common.firebase.auth.UserManager
7 | import com.morcinek.workout.home.HomeActivity
8 | import org.jetbrains.anko.startActivity
9 | import javax.inject.Inject
10 |
11 | class SplashActivity : AppCompatActivity() {
12 |
13 | @Inject lateinit var userManager: UserManager
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | component.inject(this)
18 |
19 | if (!userManager.isUserAuthenticated) {
20 | authenticateUser()
21 | } else {
22 | startNextActivity()
23 | }
24 | }
25 |
26 | private fun startNextActivity() {
27 | startActivity()
28 | finish()
29 | }
30 |
31 | private fun authenticateUser() {
32 | userManager.signInAnonymously().addOnCompleteListener {
33 | if (it.isSuccessful) {
34 | startNextActivity()
35 | } else {
36 | finish()
37 | }
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/NotificationCenter.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import android.media.RingtoneManager
6 | import android.os.PowerManager
7 | import android.os.Vibrator
8 | import com.morcinek.workout.settings.soundEnabled
9 | import com.morcinek.workout.settings.vibrationEnabled
10 | import com.morcinek.workout.settings.vibrationValue
11 |
12 | class NotificationCenter(private val context: Context, private val vibrator: Vibrator,
13 | private val powerManager: PowerManager, private val sharedPreferences: SharedPreferences) {
14 |
15 | private val flags: Int
16 | get() = PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP
17 |
18 | private val ringtone by lazy {
19 | RingtoneManager.getRingtone(context, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
20 | }
21 |
22 | fun sendNotifications() {
23 | if (sharedPreferences.soundEnabled) {
24 | ringtone.play()
25 | }
26 |
27 | if (sharedPreferences.vibrationEnabled) {
28 | vibrator.vibrate(vibrationTime())
29 | }
30 |
31 | powerManager.newWakeLock(flags, javaClass.name).apply {
32 | acquire(1000)
33 | }
34 | }
35 |
36 | private fun vibrationTime() = sharedPreferences.vibrationValue * 200L
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/home.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
35 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/fragment/SingleFragmentActivity.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.fragment
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.Fragment
5 | import android.support.v7.app.AppCompatActivity
6 | import android.view.MenuItem
7 | import com.morcinek.workout.R
8 | import com.morcinek.workout.common.utils.getSerializableExtra
9 | import kotlinx.android.synthetic.main.content_fragment.*
10 |
11 | class SingleFragmentActivity : AppCompatActivity() {
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | // overridePendingTransition(R.anim.slide_in_left, R.anim.slide_outside_right)
16 | setContentView(R.layout.content_fragment)
17 | setupToolbar()
18 | if (savedInstanceState == null) {
19 | val classType = intent.getSerializableExtra>()!!
20 | supportFragmentManager.beginTransaction().replace(R.id.contentFrame, classType.newInstance()).commit();
21 | }
22 | }
23 |
24 | private fun setupToolbar() {
25 | setSupportActionBar(toolbar)
26 | supportActionBar!!.setDisplayHomeAsUpEnabled(true)
27 | }
28 |
29 | override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
30 | android.R.id.home -> {
31 | onBackPressed()
32 | true
33 | }
34 | else -> super.onOptionsItemSelected(item)
35 | }
36 |
37 | override fun finish() {
38 | super.finish()
39 | // overridePendingTransition(R.anim.slide_inside_left, R.anim.slide_out_right)
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/exercise_series.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
24 |
25 |
30 |
31 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/home/exercises/adapter/ExerciseViewAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.home.exercises.adapter
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.TextView
8 | import com.morcinek.kotlin.adapter.SectionRecyclerViewAdapter
9 | import com.morcinek.workout.R
10 | import kotlinx.android.synthetic.main.exercises_item.view.*
11 |
12 | /**
13 | * Copyright 2017 Tomasz Morcinek. All rights reserved.
14 | */
15 | class ExerciseViewAdapter : SectionRecyclerViewAdapter.SectionViewAdapter {
16 |
17 | override fun onBindViewHolder(holder: ViewHolder, item: ExerciseViewModel, position: Int) {
18 | holder.name.text = item.name
19 | holder.category.text = item.category
20 | holder.date.text = item.date
21 | holder.time.text = item.time
22 | }
23 |
24 | override fun onCreateViewHolder(layoutInflater: LayoutInflater, parent: ViewGroup, viewType: Int)
25 | = ViewHolder(layoutInflater.inflate(R.layout.exercises_item, parent, false))
26 |
27 | override fun clickableViews(holder: ViewHolder) = listOf(holder.itemView)
28 |
29 | inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
30 | val name: TextView
31 | get() = itemView.name
32 | val category: TextView
33 | get() = itemView.category
34 | val date: TextView
35 | get() = itemView.date
36 | val time: TextView
37 | get() = itemView.time
38 | }
39 | }
40 |
41 | class ExerciseViewModel(val key: String, val name: String, val category: String, val date: String, val time: String)
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/ExerciseDataManager.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise
2 |
3 | import android.content.SharedPreferences
4 | import com.morcinek.workout.core.data.exercises.ExerciseDataModel
5 | import com.morcinek.workout.settings.breakTime
6 |
7 | class ExerciseDataManager(private val sharedPreferences: SharedPreferences) {
8 |
9 | var exerciseState: ExerciseState = ExerciseState.Loading
10 |
11 | lateinit var exerciseDataModel: ExerciseDataModel
12 |
13 | var numberOfSeries: Int
14 | get() = exerciseDataModel.numberOfSeries
15 | set(value) {
16 | exerciseDataModel.numberOfSeries = value
17 | }
18 |
19 | val breakIntervalSeconds: Long
20 | get() = sharedPreferences.breakTime.toLong()
21 |
22 | var delegate: Delegate? = null
23 | set(value) {
24 | field = value
25 | notifyStateChanged()
26 | }
27 |
28 | fun incrementSeriesNumber() {
29 | numberOfSeries += 1
30 | }
31 |
32 | fun showNew() {
33 | exerciseState = ExerciseState.New
34 | notifyStateChanged()
35 | }
36 |
37 | fun showBreak() {
38 | exerciseState = ExerciseState.Break
39 | notifyStateChanged()
40 | }
41 |
42 | fun showBreakSplash() {
43 | exerciseState = ExerciseState.Splash
44 | notifyStateChanged()
45 | }
46 |
47 | fun showSeries() {
48 | exerciseState = ExerciseState.Series
49 | notifyStateChanged()
50 | }
51 |
52 | private fun notifyStateChanged() {
53 | delegate?.onStateChanged(exerciseState)
54 | }
55 |
56 | interface Delegate {
57 | fun onStateChanged(exerciseState: ExerciseState)
58 | }
59 |
60 | val isEditable: Boolean
61 | get() = exerciseState != ExerciseState.New
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/TimerService.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise
2 |
3 | import android.app.Service
4 | import android.content.Intent
5 | import android.content.SharedPreferences
6 | import android.text.format.DateUtils
7 | import com.morcinek.workout.common.FunctionalCountDownTimer
8 | import com.morcinek.workout.common.di.component
9 | import com.morcinek.workout.common.utils.put
10 | import com.morcinek.workout.settings.breakTime
11 | import javax.inject.Inject
12 |
13 |
14 | val TIMER_SERVICE_TICK = "com.morcinek.workout.TimerService.Tick"
15 | val TIMER_SERVICE_FINISH = "com.morcinek.workout.TimerService.Finish"
16 |
17 | class TimerService : Service() {
18 |
19 | @Inject lateinit var sharedPreferences: SharedPreferences
20 |
21 | private val breakIntervalInMillis: Long
22 | get() = sharedPreferences.breakTime * DateUtils.SECOND_IN_MILLIS
23 |
24 | private var timer : FunctionalCountDownTimer? = null
25 |
26 | override fun onCreate() {
27 | super.onCreate()
28 | component.inject(this)
29 | }
30 |
31 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
32 | if (timer == null) {
33 | timer = FunctionalCountDownTimer(breakIntervalInMillis, 100).apply {
34 | onTick { sendBroadcast(Intent(TIMER_SERVICE_TICK).apply { put(it) }) }
35 | onFinish {
36 | sendBroadcast(Intent(TIMER_SERVICE_FINISH))
37 | timer = null
38 | }
39 | start()
40 | }
41 | }
42 | return super.onStartCommand(intent, flags, startId)
43 | }
44 |
45 | override fun onDestroy() {
46 | timer?.cancel()
47 | super.onDestroy()
48 | }
49 |
50 | override fun onBind(intent: Intent?) = null
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/home/HomeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.home
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import android.view.Menu
6 | import android.view.MenuItem
7 | import com.morcinek.workout.R
8 | import com.morcinek.workout.common.di.component
9 | import com.morcinek.workout.common.fragment.ContentFragmentManager
10 | import com.morcinek.workout.common.fragment.SingleFragmentActivity
11 | import com.morcinek.workout.common.utils.putSerializableExtra
12 | import com.morcinek.workout.common.utils.startActivityFun
13 | import com.morcinek.workout.exercise.ExerciseActivity
14 | import com.morcinek.workout.home.exercises.ExercisesFragment
15 | import com.morcinek.workout.settings.SettingsFragment
16 | import kotlinx.android.synthetic.main.home.*
17 | import org.jetbrains.anko.startActivity
18 |
19 | class HomeActivity : AppCompatActivity() {
20 |
21 | private val contentFragmentManager by lazy { ContentFragmentManager(this)}
22 |
23 | override fun onCreate(savedInstanceState: Bundle?) {
24 | super.onCreate(savedInstanceState)
25 | setContentView(R.layout.home)
26 | component.inject(this)
27 |
28 | setSupportActionBar(toolbar)
29 |
30 | fab.setOnClickListener { startActivity() }
31 |
32 | contentFragmentManager.replaceFragment(ExercisesFragment())
33 | }
34 |
35 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
36 | menuInflater.inflate(R.menu.home, menu)
37 | return true
38 | }
39 |
40 | override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
41 | R.id.action_settings -> {
42 | startActivityFun { putSerializableExtra(SettingsFragment::class.java) }
43 | true
44 | }
45 | else -> super.onOptionsItemSelected(item)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
22 |
23 |
29 |
30 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/common/preference/TimePreferenceDialogFragmentCompat.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.common.preference
2 |
3 | import android.support.v7.preference.PreferenceDialogFragmentCompat
4 | import android.view.View
5 | import android.widget.NumberPicker
6 | import com.morcinek.workout.R
7 |
8 | const val ARG_KEY = "key"
9 | const val ARG_MIN = "min"
10 | const val ARG_MAX = "max"
11 | const val ARG_INCREMENT = "increment"
12 |
13 | class TimePreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() {
14 |
15 | private lateinit var mTimePicker: NumberPicker
16 |
17 | private val max by lazy { arguments[ARG_MAX] as Int }
18 | private val min by lazy { arguments[ARG_MIN] as Int }
19 | private val increment by lazy { arguments[ARG_INCREMENT] as Int }
20 |
21 | private val timePreference: TimePreference
22 | get() = preference as TimePreference
23 |
24 | override fun onBindDialogView(view: View?) {
25 | super.onBindDialogView(view)
26 |
27 | mTimePicker = view!!.findViewById(R.id.edit)
28 |
29 | val minValue = 0
30 | val maxValue = (max - min) / increment
31 |
32 | mTimePicker.minValue = minValue
33 | mTimePicker.maxValue = maxValue
34 | mTimePicker.displayedValues = (minValue..maxValue).map { min + it * increment }
35 | .map { "$it" }
36 | .toTypedArray()
37 |
38 | mTimePicker.value = getPickerValue(timePreference.time)
39 | }
40 |
41 | private fun getReturnValue(localValue: Int) = localValue * increment + min
42 |
43 | private fun getPickerValue(returnValue: Int) = (returnValue - min) / increment
44 |
45 | override fun onDialogClosed(positiveResult: Boolean) {
46 | if (positiveResult) {
47 | val value = mTimePicker.value
48 | if (preference.callChangeListener(value)) {
49 | timePreference.time = getReturnValue(value)
50 | }
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-pl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Siłka
3 | Ustawienia
4 | Edycja
5 | Usuń
6 |
7 | Nie
8 | Tak
9 |
10 | Ćwiczenie
11 | Nazwa ćwiczenia
12 | Czy chcesz zachować to ćwiczenie?
13 |
14 | Przerwa
15 | Ćwiczenie
16 | Skończ ćwiczenie
17 | Numer serii:
18 | Przerwa się skończyła
19 | Zacznij serię: %d
20 |
21 | (%d serii) %s
22 |
23 | Stop
24 | Restart
25 |
26 | Ustawienia
27 |
28 | %d sekund
29 |
30 | Ogólne
31 | Notyfikacje
32 | Wibracja
33 | Włącz wibrację na koniec przerwy
34 | Czas wibracji
35 | Notyfikacja dźwiękowa
36 | Włącz dźwięk na koniec przerwy
37 | Czas przerwy
38 | Czas przerwy (sekundy)
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Workout
3 | Settings
4 | Edit
5 | Delete
6 |
7 | No
8 | Yes
9 |
10 | Exercise
11 | Exercise Name
12 | Do you want to save this exercise?
13 | Do you want to delete this exercise?
14 |
15 | Start Break
16 | Exercise
17 | Finish Exercise
18 | Series number:
19 | Break has finished
20 | Start series: %d
21 |
22 | (%d series) %s
23 |
24 | Stop
25 | Restart
26 |
27 | Settings
28 |
29 | %d seconds
30 |
31 | General
32 | Notifications
33 | Vibration
34 | Enable vibration when break finishes
35 | Vibration Time
36 | Sound Notification
37 | Enable sound when break finishes
38 | Break Time
39 | Break Time (seconds)
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/exercises_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
23 |
24 |
33 |
34 |
41 |
42 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/fragments/LoadingFragment.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import com.google.firebase.database.DataSnapshot
6 | import com.google.firebase.database.DatabaseError
7 | import com.google.firebase.database.ValueEventListener
8 | import com.morcinek.workout.R
9 | import com.morcinek.workout.common.fragment.BaseFragment
10 | import com.morcinek.workout.core.data.exercises.ExerciseDataModel
11 | import com.morcinek.workout.core.data.exercises.ExerciseManager
12 | import com.morcinek.workout.core.data.getKeyExtra
13 | import com.morcinek.workout.exercise.ExerciseDataManager
14 | import com.morcinek.workout.exercise.exerciseComponent
15 | import org.jetbrains.anko.design.snackbar
16 | import javax.inject.Inject
17 |
18 | class LoadingFragment : BaseFragment(), ValueEventListener {
19 |
20 | override val layoutResourceId = R.layout.exercise_loading
21 |
22 | @Inject lateinit var exerciseDataManager: ExerciseDataManager
23 | @Inject lateinit var exerciseManager: ExerciseManager
24 |
25 | val key by lazy { activity.intent.getKeyExtra() }
26 |
27 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
28 | super.onViewCreated(view, savedInstanceState)
29 | exerciseComponent.inject(this)
30 |
31 | if (key == null) {
32 | val exerciseDataModel = ExerciseDataModel("Exercise 1")
33 | exerciseManager.update(exerciseDataModel).addOnCompleteListener {
34 | if (it.isSuccessful) {
35 | exerciseDataManager.showNew()
36 | exerciseDataManager.exerciseDataModel = exerciseDataModel
37 | } else {
38 | snackbar(view!!, it.exception?.localizedMessage ?: "")
39 | }
40 | }
41 | } else {
42 | exerciseManager.key = key
43 | exerciseManager.get().addListenerForSingleValueEvent(this)
44 | }
45 | }
46 |
47 | override fun onCancelled(p0: DatabaseError?) {
48 | snackbar(view!!, p0?.message ?: "")
49 | }
50 |
51 | override fun onDataChange(p0: DataSnapshot?) {
52 | exerciseDataManager.exerciseDataModel = p0!!.getValue(ExerciseDataModel::class.java)!!
53 | exerciseDataManager.showSeries()
54 | }
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/home/exercises/ExercisesFragment.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.home.exercises
2 |
3 | import android.os.Bundle
4 | import android.support.v7.widget.LinearLayoutManager
5 | import android.view.View
6 | import com.morcinek.kotlin.adapter.SectionRecyclerViewAdapter
7 | import com.morcinek.workout.R
8 | import com.morcinek.workout.common.di.component
9 | import com.morcinek.workout.common.firebase.data.InteractorDelegate
10 | import com.morcinek.workout.common.fragment.BaseFragment
11 | import com.morcinek.workout.common.utils.startActivityFun
12 | import com.morcinek.workout.core.data.putKeyExtra
13 | import com.morcinek.workout.exercise.ExerciseActivity
14 | import com.morcinek.workout.home.exercises.adapter.ExerciseViewAdapter
15 | import com.morcinek.workout.home.exercises.adapter.ExerciseViewModel
16 | import kotlinx.android.synthetic.main.recycler_view.*
17 | import org.jetbrains.anko.design.snackbar
18 | import javax.inject.Inject
19 |
20 | class ExercisesFragment : BaseFragment(), InteractorDelegate> {
21 |
22 | override val layoutResourceId = R.layout.recycler_view
23 |
24 | @Inject lateinit var exercisesInteractor: ExercisesInteractor
25 |
26 | private val adapter: SectionRecyclerViewAdapter
27 | get() = recyclerView.adapter as SectionRecyclerViewAdapter
28 |
29 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
30 | super.onViewCreated(view, savedInstanceState)
31 | component.inject(this)
32 |
33 | setupRecyclerView()
34 | exercisesInteractor.register(this)
35 | }
36 |
37 | override fun onDestroyView() {
38 | super.onDestroyView()
39 | exercisesInteractor.unregister()
40 | }
41 |
42 | override fun success(values: List) = adapter.setList(values)
43 |
44 | override fun failed(errorMessage: String) {
45 | snackbar(view!!, errorMessage)
46 | }
47 |
48 | private fun setupRecyclerView() {
49 | recyclerView.layoutManager = LinearLayoutManager(activity)
50 | recyclerView.adapter = SectionRecyclerViewAdapter().apply {
51 | addSectionViewAdapter(ExerciseViewAdapter())
52 | setItemClickListener {
53 | activity.startActivityFun {
54 | it as ExerciseViewModel
55 | putKeyExtra(it.key)
56 | }
57 | }
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/home/exercises/ExercisesInteractor.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.home.exercises
2 |
3 | import android.content.Context
4 | import android.text.format.DateUtils
5 | import com.morcinek.workout.R
6 | import com.morcinek.workout.common.firebase.data.DataProvider
7 | import com.morcinek.workout.common.firebase.data.InteractorDelegate
8 | import com.morcinek.workout.common.utils.dayOfWeekFormat
9 | import com.morcinek.workout.common.utils.formatWith
10 | import com.morcinek.workout.common.utils.timeFormat
11 | import com.morcinek.workout.core.data.exercises.ExerciseDataModel
12 | import com.morcinek.workout.core.data.exercises.ExercisesProvider
13 | import com.morcinek.workout.home.exercises.adapter.ExerciseViewModel
14 | import java.util.*
15 |
16 | class ExercisesInteractor(private val context: Context, private val exercisesProvider: ExercisesProvider) : DataProvider.Delegate {
17 |
18 | private val timeFormat by lazy { timeFormat() }
19 | private val dayOfWeekFormat by lazy { dayOfWeekFormat() }
20 |
21 | lateinit var delegate: InteractorDelegate>
22 |
23 | fun register(delegate: InteractorDelegate>) {
24 | this.delegate = delegate
25 | exercisesProvider.delegate = this
26 | exercisesProvider.register()
27 | }
28 |
29 | fun unregister() = exercisesProvider.unregister()
30 |
31 | override fun failed(errorMessage: String) = delegate.failed(errorMessage)
32 |
33 | override fun success(values: List>) = delegate.success(values.sortedByDescending { it.second.timeInMillis }.map {
34 | val exerciseDataModel = it.second
35 | ExerciseViewModel(it.first, exerciseDataModel.name, exerciseDataModel.category ?: "", dateFormat(exerciseDataModel.date), timeText(exerciseDataModel))
36 | })
37 |
38 | private fun timeText(exerciseDataModel: ExerciseDataModel) = context.getString(R.string.exercise_item_name, exerciseDataModel.numberOfSeries, timeFormat(exerciseDataModel.date))
39 |
40 | private fun timeFormat(date: Calendar) = date.formatWith(timeFormat)
41 |
42 | private fun dateFormat(date: Calendar) = "${relativeDateFormat(date)}, ${dayOfWeekFormat(date)}"
43 |
44 | private fun dayOfWeekFormat(date: Calendar) = date.formatWith(dayOfWeekFormat)
45 |
46 | private fun relativeDateFormat(date: Calendar)
47 | = DateUtils.getRelativeTimeSpanString(date.timeInMillis, Date().time, DateUtils.DAY_IN_MILLIS, DateUtils.FORMAT_SHOW_YEAR).toString()
48 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/exercise_break.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
22 |
23 |
32 |
33 |
42 |
43 |
50 |
51 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/fragments/BreakFragment.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise.fragments
2 |
3 | import android.content.IntentFilter
4 | import android.os.Bundle
5 | import android.text.format.DateUtils
6 | import android.view.View
7 | import com.morcinek.workout.R
8 | import com.morcinek.workout.common.fragment.BaseFragment
9 | import com.morcinek.workout.common.utils.broadcastReceiver
10 | import com.morcinek.workout.common.utils.getLong
11 | import com.morcinek.workout.common.utils.stopService
12 | import com.morcinek.workout.exercise.ExerciseDataManager
13 | import com.morcinek.workout.exercise.TIMER_SERVICE_TICK
14 | import com.morcinek.workout.exercise.TimerService
15 | import com.morcinek.workout.exercise.exerciseComponent
16 | import kotlinx.android.synthetic.main.exercise_break.*
17 | import org.jetbrains.anko.support.v4.startService
18 | import javax.inject.Inject
19 |
20 |
21 | class BreakFragment : BaseFragment() {
22 |
23 | override val layoutResourceId = R.layout.exercise_break
24 |
25 | @Inject lateinit var exerciseDataManager: ExerciseDataManager
26 |
27 | private val breakIntervalInMillis: Long
28 | get() = exerciseDataManager.breakIntervalSeconds * DateUtils.SECOND_IN_MILLIS
29 |
30 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
31 | super.onViewCreated(view, savedInstanceState)
32 | exerciseComponent.inject(this)
33 |
34 | seriesNumberText.text = "${exerciseDataManager.numberOfSeries}"
35 | startNewSeriesButton.setOnClickListener {
36 | exerciseDataManager.incrementSeriesNumber()
37 | exerciseDataManager.showSeries()
38 | }
39 | cancelButton.setOnClickListener {
40 | activity.stopService()
41 | exerciseDataManager.showSeries()
42 | }
43 | progressBar.max = breakIntervalInMillis.toInt()
44 | startService()
45 | }
46 |
47 | override fun onResume() {
48 | super.onResume()
49 | activity.registerReceiver(broadcastReceiver, IntentFilter(TIMER_SERVICE_TICK))
50 | }
51 |
52 | override fun onPause() {
53 | super.onPause()
54 | activity.unregisterReceiver(broadcastReceiver)
55 | }
56 |
57 | private fun updateProgress(progress: Long) {
58 | timerText.text = "${progress / 1000}.${progress % 1000 / 100}"
59 | progressBar.progress = (breakIntervalInMillis - progress).toInt()
60 | }
61 |
62 | private val broadcastReceiver = broadcastReceiver {
63 | updateProgress(it.getLong())
64 | }
65 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/exercise_new.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
21 |
22 |
23 |
24 |
29 |
30 |
37 |
38 |
43 |
44 |
54 |
55 |
63 |
64 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/settings/SettingsFragment.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.settings
2 |
3 | import android.content.SharedPreferences
4 | import android.os.Bundle
5 | import android.support.v4.app.DialogFragment
6 | import android.support.v7.preference.Preference
7 | import android.support.v7.preference.PreferenceFragmentCompat
8 | import com.morcinek.workout.R
9 | import com.morcinek.workout.common.preference.*
10 | import com.morcinek.workout.common.utils.setTitle
11 | import org.jetbrains.anko.bundleOf
12 |
13 |
14 | class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
15 |
16 | override fun onActivityCreated(savedInstanceState: Bundle?) {
17 | super.onActivityCreated(savedInstanceState)
18 | setTitle(R.string.settings_label)
19 | }
20 |
21 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
22 | addPreferencesFromResource(R.xml.settings)
23 |
24 | updateGeneralBreakTime(preferenceManager.sharedPreferences)
25 | updateNotificationVibrationTime(preferenceManager.sharedPreferences)
26 | }
27 |
28 | override fun onDisplayPreferenceDialog(preference: Preference?) =
29 | if (preference is TimePreference) {
30 | showDialogFragment(TimePreferenceDialogFragmentCompat().apply {
31 | arguments = bundleOf(
32 | ARG_KEY to preference.getKey(),
33 | ARG_MIN to 10,
34 | ARG_MAX to 200,
35 | ARG_INCREMENT to 5
36 | )
37 | })
38 | } else {
39 | super.onDisplayPreferenceDialog(preference)
40 | }
41 |
42 | private fun showDialogFragment(dialogFragment: DialogFragment) {
43 | dialogFragment.setTargetFragment(this, 0)
44 | dialogFragment.show(this.fragmentManager, "android.support.v7.preference.PreferenceFragment.DIALOG")
45 | }
46 |
47 | override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
48 | when (key) {
49 | VIBRATION_ENABLED -> updateNotificationVibrationTime(sharedPreferences)
50 | BREAK_TIME -> updateGeneralBreakTime(sharedPreferences)
51 | }
52 | }
53 |
54 | private fun updateGeneralBreakTime(sharedPreferences: SharedPreferences?) {
55 | preferenceScreen.findPreference(BREAK_TIME).summary = getString(R.string.breakTimeSummary, sharedPreferences?.breakTime)
56 | }
57 |
58 | private fun updateNotificationVibrationTime(sharedPreferences: SharedPreferences?) {
59 | preferenceScreen.findPreference(VIBRATION_VALUE).isEnabled = sharedPreferences!!.vibrationEnabled
60 | }
61 |
62 | override fun onResume() {
63 | super.onResume()
64 | preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
65 | }
66 |
67 | override fun onPause() {
68 | super.onPause()
69 | preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
70 | }
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/fragments/NewFragment.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise.fragments
2 |
3 | import android.os.Bundle
4 | import android.text.format.DateUtils
5 | import android.view.View
6 | import android.widget.ArrayAdapter
7 | import com.morcinek.workout.R
8 | import com.morcinek.workout.common.fragment.BaseFragment
9 | import com.morcinek.workout.common.utils.formatWith
10 | import com.morcinek.workout.common.utils.timeFormat
11 | import com.morcinek.workout.core.data.exercises.ExerciseDataModel
12 | import com.morcinek.workout.exercise.ExerciseDataManager
13 | import com.morcinek.workout.exercise.exerciseComponent
14 | import kotlinx.android.synthetic.main.exercise_new.*
15 | import org.jetbrains.anko.sdk25.coroutines.onItemSelectedListener
16 | import org.jetbrains.anko.sdk25.coroutines.textChangedListener
17 | import java.util.*
18 | import javax.inject.Inject
19 |
20 |
21 | class NewFragment : BaseFragment() {
22 |
23 | override val layoutResourceId = R.layout.exercise_new
24 |
25 | @Inject lateinit var exerciseDataManager: ExerciseDataManager
26 |
27 | private val timeFormat by lazy { timeFormat() }
28 |
29 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
30 | super.onViewCreated(view, savedInstanceState)
31 | exerciseComponent.inject(this)
32 |
33 | val exerciseDataModel = exerciseDataManager.exerciseDataModel
34 | setupName(exerciseDataModel)
35 | setupDate(exerciseDataModel)
36 | setupCategory(exerciseDataModel)
37 |
38 | breakButton.setOnClickListener { exerciseDataManager.showBreak() }
39 | exerciseButton.setOnClickListener { exerciseDataManager.showSeries() }
40 | }
41 |
42 | private fun setupName(exerciseDataModel: ExerciseDataModel) {
43 | nameEditText.setText(exerciseDataModel.name)
44 | nameEditText.textChangedListener {
45 | onTextChanged { charSequence, _, _, _ ->
46 | exerciseDataModel.name = charSequence.toString()
47 | }
48 | }
49 | }
50 |
51 | private fun setupDate(exerciseDataModel: ExerciseDataModel) {
52 | dateTextView.text = formatDate(exerciseDataModel.date)
53 | }
54 |
55 | private fun formatDate(date: Calendar) = "${relativeDateFormat(date)}, ${date.formatWith(timeFormat)}"
56 |
57 | private fun relativeDateFormat(date: Calendar)
58 | = DateUtils.getRelativeTimeSpanString(date.timeInMillis, Date().time, DateUtils.DAY_IN_MILLIS, DateUtils.FORMAT_SHOW_YEAR).toString()
59 |
60 | private fun setupCategory(exerciseDataModel: ExerciseDataModel) {
61 | val adapter = ArrayAdapter.createFromResource(context, R.array.categories, R.layout.spinner_layout)
62 | categorySpinner.adapter = adapter
63 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
64 | categorySpinner.setSelection(adapter.getPosition(exerciseDataModel.category))
65 | categorySpinner.onItemSelectedListener {
66 | onItemSelected { _, _, position, _ -> exerciseDataModel.category = adapter.getItem(position).toString() }
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'kotlin-kapt'
5 |
6 | android {
7 | compileSdkVersion 26
8 | buildToolsVersion "26.0.2"
9 | defaultConfig {
10 | applicationId "com.morcinek.workout"
11 | minSdkVersion 16
12 | targetSdkVersion 26
13 | versionCode 1
14 | versionName "1.0.0"
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 | }
17 | signingConfigs {
18 | release {
19 | storeFile file(RELEASE_STORE_FILE)
20 | storePassword RELEASE_STORE_PASSWORD
21 | keyAlias RELEASE_KEY_ALIAS
22 | keyPassword RELEASE_KEY_PASSWORD
23 | }
24 | debug {
25 | storeFile file(DEBUG_STORE_FILE)
26 | storePassword DEBUG_STORE_PASSWORD
27 | keyAlias DEBUG_KEY_ALIAS
28 | keyPassword DEBUG_KEY_PASSWORD
29 | }
30 | }
31 | buildTypes {
32 | release {
33 | minifyEnabled false
34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
35 | signingConfig signingConfigs.release
36 | }
37 | debug {
38 | applicationIdSuffix ".debug"
39 | }
40 | }
41 | lintOptions {
42 | checkReleaseBuilds false
43 | abortOnError false
44 | }
45 | packagingOptions {
46 | exclude 'META-INF/LICENSE'
47 | }
48 | applicationVariants.all { variant ->
49 | variant.buildConfigField 'String', 'DATE_FORMAT', '"dd/MM/yyyy"'
50 | variant.buildConfigField 'String', 'TIME_FORMAT', '"HH:mm"'
51 | variant.buildConfigField 'String', 'DAY_OF_WEEK_FORMAT', '"EEEE"'
52 | }
53 | }
54 |
55 | ext.versions = [
56 | anko : '0.10.1',
57 | dagger : '2.12',
58 | support : '26.+',
59 | firebase: '11.6.2',
60 | ]
61 |
62 | dependencies {
63 | compile fileTree(dir: 'libs', include: ['*.jar'])
64 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
65 | compile "org.jetbrains.anko:anko:$versions.anko"
66 | compile "org.jetbrains.anko:anko-design:$versions.anko"
67 |
68 | compile "com.google.dagger:dagger:$versions.dagger"
69 | kapt "com.google.dagger:dagger-compiler:$versions.dagger"
70 |
71 | compile "com.android.support:appcompat-v7:$versions.support"
72 | compile "com.android.support:design:$versions.support"
73 | compile "com.android.support:preference-v14:$versions.support"
74 | compile "com.android.support:cardview-v7:$versions.support"
75 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
76 |
77 | compile "com.google.firebase:firebase-core:$versions.firebase"
78 | compile "com.google.firebase:firebase-crash:$versions.firebase"
79 | compile "com.google.firebase:firebase-auth:$versions.firebase"
80 | compile "com.google.firebase:firebase-database:$versions.firebase"
81 |
82 | compile 'com.github.tmorcinek:kotlin-section-adapter:1.0.3'
83 |
84 | testCompile 'junit:junit:4.12'
85 | }
86 |
87 | apply plugin: 'com.google.gms.google-services'
88 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
17 |
18 |
24 |
25 |
32 |
33 |
38 |
39 |
44 |
45 |
50 |
51 |
56 |
57 |
62 |
63 |
72 |
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/morcinek/workout/exercise/ExerciseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.morcinek.workout.exercise
2 |
3 | import android.content.IntentFilter
4 | import android.os.Bundle
5 | import android.support.v7.app.AppCompatActivity
6 | import android.view.Menu
7 | import android.view.MenuItem
8 | import com.morcinek.workout.R
9 | import com.morcinek.workout.common.NotificationCenter
10 | import com.morcinek.workout.common.di.component
11 | import com.morcinek.workout.common.fragment.BaseFragment
12 | import com.morcinek.workout.common.fragment.ContentFragmentManager
13 | import com.morcinek.workout.common.fragment.SingleFragmentActivity
14 | import com.morcinek.workout.common.utils.*
15 | import com.morcinek.workout.core.data.exercises.ExerciseManager
16 | import com.morcinek.workout.core.data.getKeyExtra
17 | import com.morcinek.workout.exercise.di.ExerciseComponent
18 | import com.morcinek.workout.exercise.di.ExerciseModule
19 | import com.morcinek.workout.exercise.fragments.*
20 | import com.morcinek.workout.settings.SettingsFragment
21 | import kotlinx.android.synthetic.main.content_fragment.*
22 | import org.jetbrains.anko.alert
23 | import javax.inject.Inject
24 |
25 | class ExerciseActivity : AppCompatActivity(), ExerciseDataManager.Delegate {
26 |
27 | @Inject lateinit var contentFragmentManager: ContentFragmentManager
28 |
29 | @Inject lateinit var exerciseDataManager: ExerciseDataManager
30 | @Inject lateinit var exerciseManager: ExerciseManager
31 | @Inject lateinit var notificationCenter: NotificationCenter
32 |
33 | val key by lazy { intent.getKeyExtra() }
34 |
35 | val exerciseComponent by lazy { component.add(ExerciseModule(this)) }
36 |
37 | override fun onCreate(savedInstanceState: Bundle?) {
38 | super.onCreate(savedInstanceState)
39 | setContentView(R.layout.content_fragment)
40 | exerciseComponent.inject(this)
41 | setupToolbar()
42 | registerReceiver(timerReceiver, IntentFilter(TIMER_SERVICE_FINISH))
43 | }
44 |
45 | override fun onDestroy() {
46 | super.onDestroy()
47 | unregisterReceiver(timerReceiver)
48 | stopService()
49 | }
50 |
51 | private fun setupToolbar() {
52 | setSupportActionBar(toolbar)
53 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
54 | }
55 |
56 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
57 | menuInflater.inflate(R.menu.exercise, menu)
58 | return true
59 | }
60 |
61 | override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
62 | menu?.findItem(R.id.action_edit)?.isVisible = exerciseDataManager.isEditable
63 | return super.onPrepareOptionsMenu(menu)
64 | }
65 |
66 | override fun onStateChanged(exerciseState: ExerciseState) {
67 | contentFragmentManager.replaceFragment(when (exerciseState) {
68 | ExerciseState.Series -> SeriesFragment()
69 | ExerciseState.Break -> BreakFragment()
70 | ExerciseState.Splash -> BreakSplashFragment()
71 | ExerciseState.New -> NewFragment()
72 | ExerciseState.Loading -> LoadingFragment()
73 | })
74 | invalidateOptionsMenu()
75 | }
76 |
77 | override fun onResume() {
78 | super.onResume()
79 | exerciseDataManager.delegate = this
80 | }
81 |
82 | override fun onPause() {
83 | super.onPause()
84 | exerciseDataManager.delegate = null
85 | }
86 |
87 | private val timerReceiver = broadcastReceiver {
88 | notificationCenter.sendNotifications()
89 | exerciseDataManager.incrementSeriesNumber()
90 | exerciseDataManager.showBreakSplash()
91 | }
92 |
93 | override fun onOptionsItemSelected(item: MenuItem?) = when (item?.itemId) {
94 | android.R.id.home -> handleOptionItemAction { onBackPressed() }
95 | R.id.action_edit -> handleOptionItemAction { exerciseDataManager.showNew() }
96 | R.id.action_delete -> handleOptionItemAction {
97 | alert(R.string.exercise_exit_message) {
98 | positiveButton(R.string.yes) { finishWithDelete() }
99 | negativeButton(R.string.no) { }
100 | }.show()
101 | }
102 | R.id.action_settings -> handleOptionItemAction {
103 | startActivityFun { putSerializableExtra(SettingsFragment::class.java) }
104 | }
105 |
106 | else -> super.onOptionsItemSelected(item)
107 | }
108 |
109 |
110 | override fun onBackPressed() {
111 | if (key == null) {
112 | alert(R.string.exercise_exit_message) {
113 | positiveButton(R.string.yes) { saveAndFinish() }
114 | negativeButton(R.string.no) { finishWithDelete() }
115 | }.show()
116 | } else {
117 | saveAndFinish()
118 | }
119 | }
120 |
121 | private fun finishWithDelete() {
122 | exerciseManager.remove()
123 | finish()
124 | }
125 |
126 | private fun saveAndFinish() {
127 | exerciseManager.update(exerciseDataManager.exerciseDataModel)
128 | finish()
129 | }
130 | }
131 |
132 | inline val BaseFragment.exerciseComponent: ExerciseComponent
133 | get() = (activity as ExerciseActivity).exerciseComponent
134 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Kotlin Android Tutorial
2 | ================
3 |
4 |
5 | ## Content
6 |
7 | This is a tutorial project to show how to setup and use Kotlin in Android Project.
8 | I have recently started going to the gym, and I found out that it would be easier if I had
9 | application to assist me. I am calling this application 'Workout'.
10 |
11 | Work on this application goes in steps, each step either integrates some new functionality
12 | or demonstrates usage of specific feature of Kotlin Language.
13 |
14 | Each step has its own tag (name in the round brackets), and a corresponding video on Youtube.
15 |
16 | ### 1 Project Setup (project-setup)
17 |
18 | https://youtu.be/zfmT8fVlMhY
19 |
20 | - Creating new Project in Android Studio
21 | - Initialising GIT
22 |
23 |
24 | ### 2 Kotlin Integration (kotlin-integration)
25 |
26 | https://youtu.be/8HuBoPYb0gw
27 |
28 | - Adding Kotlin plugins to build.gradle
29 | - Converting Java Code into Kotlin Code
30 |
31 |
32 | ### 3 Kotlin View Injections (view-injections)
33 |
34 | https://youtu.be/cs4YZg3P4Ik
35 |
36 | - Adding Kotlin Android Extensions Plugin
37 | `apply plugin: 'kotlin-android-extensions'`
38 | - Using auto-generated properties for views in View Class (Activity)
39 | `import kotlinx.android.synthetic.main.home.*`
40 |
41 |
42 | ### 4 Anko Integration (anko-integration)
43 |
44 | https://youtu.be/SG77qTm38nw
45 |
46 | - Code Cleanup
47 | - Adding Anko library to the project
48 | `compile "org.jetbrains.anko:anko:$versions.anko"`
49 | - Examples of one line functions (Kotlin introduces specific syntax)
50 | `fun isThisAJoke() = "NO Its not a Joke"`
51 |
52 |
53 | ### 5 Splash Screen (splash-screen)
54 |
55 | https://youtu.be/kUI1LRmPCBo
56 |
57 | - Adding Anko Design library to the project
58 | `compile "org.jetbrains.anko:anko-design:$versions.anko"`
59 | - Examples of Anko usage:
60 | `snackbar`
61 | `doAsync`
62 | `uiThread`
63 | `startActivity()`
64 |
65 |
66 | ### 6 Splash Screen according to Google Guidelines (google-splash)
67 |
68 | https://youtu.be/r9KPvkAk0BM
69 |
70 | - Implementation of Splash Screen to enhance User Experience
71 | - Based on: https://www.bignerdranch.com/blog/splash-screens-the-right-way/
72 |
73 |
74 | ### 7 Dagger Integration (dagger-integration)
75 |
76 | https://youtu.be/F0OwtSrhh0o
77 |
78 | - Integration of dagger into Android Kotlin Project.
79 | - Creation and usage of 'Extension Function'
80 | `fun AppCompatActivity.component() : ApplicationComponent = (application as Application).component`
81 | - Creation and usage of 'Extension Property'
82 | `val AppCompatActivity.component: ApplicationComponent
83 | get() = (application as Application).component`
84 |
85 |
86 | ### 8 Counter Initial (counter-initial)
87 |
88 | https://youtu.be/v7pe5JN_eLs
89 |
90 | - Using CountDownTimer for executing task after interval.
91 | - Introduction to 'val' property
92 | `val name: String = "tomasz" `
93 | - Introduction to 'var' property
94 | `@Inject lateinit var context: Context`
95 | - Introduction to anonymous class in kotlin
96 | `val timer = object : CountDownTimer(...`
97 |
98 |
99 | ### 9 Lateinit, Nullability (lateinit-nullability)
100 |
101 | https://youtu.be/0N72ZisrwSE
102 |
103 | - Lateinit var property
104 | `lateinit var onFinishFunction: () -> Unit)`
105 | - Nullability in practice
106 | `private var onFinishFunction: (() -> Unit)? = null`
107 | - Function/Lambda as a property
108 | `lateinit var onTickFunction: (Long) -> Unit`
109 | - Function/Lambda as a method argument
110 | `fun onFinish(onFinishFunction: () -> Unit) {`
111 | - Invocation of Function/Lambda
112 | `onFinishFunction.invoke()`
113 | `onFinishFunction()`
114 |
115 |
116 | ### 10 Dagger Injections, Val Property (dagger-val-property)
117 |
118 | https://youtu.be/DXamJSAHyGQ
119 |
120 | - Adding new Dagger Module
121 | - Initializing component with two Modules
122 | - Creating Singleton object in Module
123 | - Val Property Lazy Initialization
124 | `private val ringtone by lazy {`
125 | ` RingtoneManager.getRingtone(context, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))`
126 | `}`
127 | - Val Property with Getter
128 | `private val flags: Int`
129 | `get() = PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP`
130 |
131 |
132 | ### 11 Method with Default Arguments (default-arguments-method)
133 |
134 | https://youtu.be/mbXjX3xeOL0
135 |
136 | - Method with Default Arguments
137 | ` fun replaceFragment(fragment: Fragment, tag: String = fragment.javaClass.name, addToBackStack: Boolean = false) {`
138 | - Constructor with Default Arguments
139 | `class ContentFragmentManager(private val activity: FragmentActivity, private val contentLayoutId: Int = R.id.contentFrame) {`
140 | - Showing Fragment inside Activity
141 | - Creating `ContentFragmentManager` to replace fragments in `FrameLayout`
142 |
143 |
144 | ### 12 Abstract Property, Open Modifier (abstract-open-property)
145 |
146 | https://youtu.be/W08I-X7fWVw
147 |
148 | - Creating BaseFragment
149 | - Creating Abstract Property
150 | `protected abstract val layoutResourceId: Int`
151 | - Creating Open Property
152 | `open protected val menuResourceId: Int? = null`
153 | - Making 'Java Virtual method' final
154 | `final override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?)`
155 |
156 |
157 | ### 13 Custom Property Setter (custom-property-setter)
158 |
159 | https://youtu.be/xyzYjlaSkAs
160 |
161 | - Creating Custom Property Setter
162 | ` var delegate: Delegate? = null`
163 | ` set(value) {`
164 | ` field = value`
165 | ` notifyStateChanged()`
166 | ` }`
167 | - Creating Data Model
168 | - Creating Data Manager
169 |
170 |
171 | ### 14 String Templates (string-templates)
172 |
173 | https://youtu.be/jM3YmULqUcQ
174 |
175 | - String Template with value
176 | `timerText.text = "$value"`
177 | - String Template with expression
178 | `seriesNumberText.text = "${exerciseDataManager.exerciseData.seriesNumber}"`
179 |
180 |
181 | ### 15 Elvis Operator, Equality (elvis-operator-equality)
182 |
183 | https://youtu.be/Wn9-JarDvok
184 |
185 | - Using Elvis Operator
186 | `fragmentManager.fragments.firstOrNull { it?.isVisible ?: false }`
187 | - Using equality '==' sign correctly
188 | `if (currentFragment?.javaClass != fragment.javaClass) {`
189 |
190 |
191 | ### 16 Dagger Subcomponent, Custom Scope (dagger-subcomponent)
192 |
193 | https://youtu.be/xRRRujQly-0
194 |
195 | - Creating Dagger Module for specific Activity
196 | - Creating Dagger Subcomponent
197 | `@Subcomponent(modules = arrayOf(ExerciseModule::class))`
198 | - Creating Custom Scope to be used by Subcomponent
199 | `annotation class ActivityScope`
200 |
201 |
202 | ### 17 Anko Alert Dialog (anko-alert-dialog)
203 |
204 | https://youtu.be/qOxt4xOas2M
205 |
206 | - Creating Alert Dialog using Anko Library
207 |
208 |
209 | ## Useful links
210 |
211 | [Kotlin.org] (https://kotlinlang.org/)
212 |
213 | [Kotlink.Anko] (https://github.com/Kotlin/anko/)
214 |
215 | [Dagger] (https://github.com/google/dagger)
216 |
217 |
218 | ## Developed By
219 |
220 | Tomasz Morcinek
221 | tomasz.morcinek@gmail.com
222 | tomasz.morcinek.dev@gmail.com
223 |
224 |
225 |
227 |
228 |
229 |
231 |
232 |
233 |
234 | ## License
235 |
236 | You are free to do whatever you want with it.
237 |
--------------------------------------------------------------------------------