diff --git a/app/src/main/java/com/baruckis/kriptofolio/repository/AppExecutors.kt b/app/src/main/java/com/baruckis/kriptofolio/repository/AppExecutors.kt new file mode 100644 index 0000000..147a2b4 --- /dev/null +++ b/app/src/main/java/com/baruckis/kriptofolio/repository/AppExecutors.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2018-2019 Andrius Baruckis www.baruckis.com | kriptofolio.app + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baruckis.kriptofolio.repository + +import android.os.Handler +import android.os.Looper +import java.util.concurrent.Executor +import java.util.concurrent.Executors +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Global executor pools for the whole application. + * + * Grouping tasks like this avoids the effects of task starvation (e.g. disk reads don't wait behind + * webservice requests). + */ +@Singleton +open class AppExecutors( + private val diskIO: Executor, + private val networkIO: Executor, + private val mainThread: Executor +) { + + @Inject + constructor() : this( + Executors.newSingleThreadExecutor(), + Executors.newFixedThreadPool(3), + MainThreadExecutor() + ) + + fun diskIO(): Executor { + return diskIO + } + + fun networkIO(): Executor { + return networkIO + } + + fun mainThread(): Executor { + return mainThread + } + + private class MainThreadExecutor : Executor { + private val mainThreadHandler = Handler(Looper.getMainLooper()) + override fun execute(command: Runnable) { + mainThreadHandler.post(command) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/baruckis/kriptofolio/ui/mainlist/MainListItemKeyProvider.kt b/app/src/main/java/com/baruckis/kriptofolio/ui/mainlist/MainListItemKeyProvider.kt new file mode 100644 index 0000000..3f3bcc8 --- /dev/null +++ b/app/src/main/java/com/baruckis/kriptofolio/ui/mainlist/MainListItemKeyProvider.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2018-2019 Andrius Baruckis www.baruckis.com | kriptofolio.app + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baruckis.kriptofolio.ui.mainlist + +import androidx.recyclerview.selection.ItemKeyProvider +import com.baruckis.kriptofolio.db.MyCryptocurrency + + +/** + * This class decide on the key type used to identify selected items. For each item we need unique + * key that can be three types: Parcelable, String, and Long ItemKey provider conjunction of stable + * IDs. It will allow for a quick mapping between the IDs and the items that will handle the + * selection by the selection library. + */ +class MainListItemKeyProvider(private var myCryptocurrencyList: List, + scope: Int = ItemKeyProvider.SCOPE_CACHED) : ItemKeyProvider(scope) { + + private lateinit var keyToPosition: MutableMap + + init { + updataData(myCryptocurrencyList) + } + + fun updataData(newCryptocurrencyList: List) { + myCryptocurrencyList = newCryptocurrencyList + keyToPosition = HashMap(myCryptocurrencyList.size) + + for ((i, cryptocurrency) in myCryptocurrencyList.withIndex()) { + keyToPosition[cryptocurrency.myId.toString()] = i + } + } + + + override fun getKey(position: Int): String? { + // As unique identifier lets make id which is also unique for each cryptocurrency. + return myCryptocurrencyList[position].myId.toString() + } + + override fun getPosition(key: String): Int { + return keyToPosition.get(key) ?: -1 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/baruckis/kriptofolio/ui/settings/SettingsActivity.kt b/app/src/main/java/com/baruckis/kriptofolio/ui/settings/SettingsActivity.kt new file mode 100644 index 0000000..7db0e6b --- /dev/null +++ b/app/src/main/java/com/baruckis/kriptofolio/ui/settings/SettingsActivity.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2018-2019 Andrius Baruckis www.baruckis.com | kriptofolio.app + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baruckis.kriptofolio.ui.settings + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.navigation.Navigation +import com.baruckis.kriptofolio.R +import com.baruckis.kriptofolio.ui.common.BaseActivity +import dagger.android.AndroidInjector +import dagger.android.DispatchingAndroidInjector +import dagger.android.support.HasSupportFragmentInjector +import javax.inject.Inject + +/** + * A [AppCompatActivity] that presents a set of application settings. + */ +class SettingsActivity : BaseActivity(), HasSupportFragmentInjector { + + @Inject + lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector + + override fun supportFragmentInjector(): AndroidInjector = dispatchingAndroidInjector + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Get a support ActionBar corresponding to this toolbar and enable the Up button. + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + setContentView(R.layout.activity_settings) + } + + // We want to finish the activity when we are at the start destination of Navigation component. + // Navigation library would hide the back arrow whenever it is at the start destination, so + // we do not use recommended "setupActionBarWithNavController" and control everything manually. + override fun onSupportNavigateUp() = + Navigation.findNavController(this, R.id.nav_host_fragment).navigateUp() || + super.onSupportNavigateUp() +} \ No newline at end of file diff --git a/app/src/main/java/com/baruckis/kriptofolio/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/baruckis/kriptofolio/ui/settings/SettingsViewModel.kt new file mode 100644 index 0000000..6777716 --- /dev/null +++ b/app/src/main/java/com/baruckis/kriptofolio/ui/settings/SettingsViewModel.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2018-2019 Andrius Baruckis www.baruckis.com | kriptofolio.app + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baruckis.kriptofolio.ui.settings + +import androidx.lifecycle.ViewModel +import com.baruckis.kriptofolio.repository.CryptocurrencyRepository +import com.baruckis.kriptofolio.repository.LicensesRepository +import com.baruckis.kriptofolio.utilities.localization.StringsLocalization +import javax.inject.Inject + + +class SettingsViewModel @Inject constructor( + cryptocurrencyRepository: CryptocurrencyRepository, + licensesRepository: LicensesRepository, + val stringsLocalization: StringsLocalization) : ViewModel() { + + var videoAdIsRequested: Boolean = false + + val currentLanguage = cryptocurrencyRepository.getCurrentLanguage() + + val currentFiatCurrencyCode = cryptocurrencyRepository.getCurrentFiatCurrencyCode() + + val currentDateFormat = cryptocurrencyRepository.getCurrentDateFormat() + + val appLicenseData: String = licensesRepository.getAppLicense() + + val noBrowserFoundMessage: String = licensesRepository.getNoBrowserFoundMessage() +} \ No newline at end of file diff --git a/app/src/main/java/com/baruckis/kriptofolio/utilities/DebugUtils.kt b/app/src/main/java/com/baruckis/kriptofolio/utilities/DebugUtils.kt new file mode 100644 index 0000000..9cb1ca2 --- /dev/null +++ b/app/src/main/java/com/baruckis/kriptofolio/utilities/DebugUtils.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2018-2019 Andrius Baruckis www.baruckis.com | kriptofolio.app + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baruckis.kriptofolio.utilities + +import android.util.Log +import com.baruckis.kriptofolio.BuildConfig + + +/** + * Debug console logger for verbose message. + * + * @param message + */ +fun logConsoleVerbose(message: String) { + if (BuildConfig.DEBUG) { + Log.v(LOG_TAG, message) + } +} + +/** + * Debug console logger for warning message. + * + * @param message + */ +fun logConsoleWarn(message: String) { + if (BuildConfig.DEBUG) { + Log.w(LOG_TAG, message) + } +} + +/** + * Debug console logger for error message. + * + * @param message + */ +fun logConsoleError(message: String) { + if (BuildConfig.DEBUG) { + Log.e(LOG_TAG, message) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/baruckis/kriptofolio/utilities/ExtensionsValidation.kt b/app/src/main/java/com/baruckis/kriptofolio/utilities/ExtensionsValidation.kt new file mode 100644 index 0000000..5cca8a8 --- /dev/null +++ b/app/src/main/java/com/baruckis/kriptofolio/utilities/ExtensionsValidation.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2018-2019 Andrius Baruckis www.baruckis.com | kriptofolio.app + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baruckis.kriptofolio.utilities + +import android.text.Editable +import android.text.TextWatcher +import android.widget.EditText + +/** + * Extension functions allow you to add behaviour to a class without the need of getting to its + * source code, since it can be declared outside the scope of its class. + */ + +// If the text changes, do some actions. +fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) { + this.addTextChangedListener(object: TextWatcher { + override fun afterTextChanged(s: Editable?) { + afterTextChanged.invoke(s.toString()) + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { } + }) +} + +// Check if edit text is empty or not and invoke actions accordingly. +fun EditText.nonEmpty(onEmpty: (() -> Unit), onNotEmpty: (() -> Unit)) { + if (this.text.toString().isEmpty()) onEmpty.invoke() + this.afterTextChanged { + if (it.isEmpty()) onEmpty.invoke() + if (it.isNotEmpty()) onNotEmpty.invoke() + } +} + +// Validate user input with custom validator and show error if validation did not pass. +fun EditText.validate(validator: (String) -> Boolean, message: String):Boolean { + val isValid = validator(this.text.toString()) + this.error = if (isValid) null else message + return isValid +} \ No newline at end of file diff --git a/app/src/main/java/com/baruckis/kriptofolio/utilities/LiveDataCallAdapter.kt b/app/src/main/java/com/baruckis/kriptofolio/utilities/LiveDataCallAdapter.kt new file mode 100644 index 0000000..4733f02 --- /dev/null +++ b/app/src/main/java/com/baruckis/kriptofolio/utilities/LiveDataCallAdapter.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2018-2019 Andrius Baruckis www.baruckis.com | kriptofolio.app + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baruckis.kriptofolio.utilities + +import androidx.lifecycle.LiveData +import com.baruckis.kriptofolio.api.ApiResponse +import retrofit2.Call +import retrofit2.CallAdapter +import retrofit2.Callback +import retrofit2.Response +import java.lang.reflect.Type +import java.util.concurrent.atomic.AtomicBoolean + +/** + * A Retrofit adapter that converts the Call into a LiveData of ApiResponse. + * @param + */ +class LiveDataCallAdapter(private val responseType: Type) : + CallAdapter>> { + + override fun responseType() = responseType + + override fun adapt(call: Call): LiveData> { + return object : LiveData>() { + private var started = AtomicBoolean(false) + override fun onActive() { + super.onActive() + if (started.compareAndSet(false, true)) { + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + postValue(ApiResponse.create(response)) + } + + override fun onFailure(call: Call, throwable: Throwable) { + postValue(ApiResponse.create(throwable)) + } + }) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/baruckis/kriptofolio/utilities/PrimaryActionModeController.kt b/app/src/main/java/com/baruckis/kriptofolio/utilities/PrimaryActionModeController.kt new file mode 100644 index 0000000..8a42dbb --- /dev/null +++ b/app/src/main/java/com/baruckis/kriptofolio/utilities/PrimaryActionModeController.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2018-2019 Andrius Baruckis www.baruckis.com | kriptofolio.app + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baruckis.kriptofolio.utilities + + +import android.os.Build +import android.view.Menu +import android.view.MenuItem +import androidx.annotation.MenuRes +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode +import androidx.core.content.ContextCompat +import com.baruckis.kriptofolio.R + + +/** + * Helper callback class to create primary type action mode. Primary mode means a contextual action + * bar is shown over an existing app bar or in place of one if your theme/layout does not include one. + */ +class PrimaryActionModeController : ActionMode.Callback { + + private lateinit var activity: AppCompatActivity + private var statusBarColor: Int = 0 + + // A simple interface that listens for some action mode events. + interface PrimaryActionModeListener { + fun onEnterActionMode() + fun onLeaveActionMode() + fun onActionItemClick(item: MenuItem) + } + + private var primaryActionModeListener: PrimaryActionModeListener? = null + + private var mode: ActionMode? = null + @MenuRes + private var menuResId: Int = 0 + private var title: String? = null + private var subtitle: String? = null + + + // Called after startActionMode. + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + primaryActionModeListener?.onEnterActionMode() + + mode?.let { + // Inflate a menu resource providing context menu items. + mode.menuInflater.inflate(menuResId, menu) + mode.title = title + mode.subtitle = subtitle + this.mode = it + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + statusBarColor = activity.window.statusBarColor + activity.window.statusBarColor = ContextCompat.getColor(activity, R.color.colorForActionModeStatusBar) + } + } + return true + } + + // Called each time the action mode is shown. + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { + return false + } + + // Called when the action mode is finished. + override fun onDestroyActionMode(mode: ActionMode?) { + primaryActionModeListener?.onLeaveActionMode() + + this.mode = null + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.window.statusBarColor = statusBarColor + } + } + + // Called when the user selects a contextual menu item. + override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { + item?.let { + primaryActionModeListener?.onActionItemClick(item) + } + return true + } + + + fun startActionMode(activity: AppCompatActivity, + primaryActionModeListener: PrimaryActionModeListener, + @MenuRes menuResId: Int, + title: String? = null, + subtitle: String? = null + ) { + this.menuResId = menuResId + this.title = title + this.subtitle = subtitle + this.activity = activity + this.primaryActionModeListener = primaryActionModeListener + + activity.startSupportActionMode(this) + } + + fun finishActionMode() { + mode?.finish() + } + + fun isInMode(): Boolean { + return mode != null + } + + fun setTitle(text: String) { + mode?.title = text + } + +} \ No newline at end of file