diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..69bda6a --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + id("com.android.library") + id("kotlin-android") + id("kotlin-parcelize") + id("dagger.hilt.android.plugin") + id("kotlin-kapt") +} + +android { + setCompileSdkVersion(Configs.compileSdkVersion) + defaultConfig { + setMinSdkVersion(Configs.minSdkVersion) + setTargetSdkVersion(Configs.targetSdkVersion.toString()) + vectorDrawables.useSupportLibrary = true + multiDexEnabled = true + } + buildFeatures { + viewBinding = true + dataBinding = true + } +} + +dependencies { + requiredLibraries() + dateTimeLibraries() + roomLibraries() + supportLibraries() + imageLoaderLibraries() +} \ No newline at end of file diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a7bed39 --- /dev/null +++ b/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/src/main/java/com/nativedevps/support/base_class/ActionBarActivity.kt b/src/main/java/com/nativedevps/support/base_class/ActionBarActivity.kt new file mode 100644 index 0000000..4564d85 --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/ActionBarActivity.kt @@ -0,0 +1,44 @@ +package com.nativedevps.support.base_class + +import android.os.Bundle +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import nativedevps.support.R +import nativedevps.support.databinding.ActivityActionbarBinding + +abstract class ActionBarActivity( + private val layout: Int, + viewModelClass: Class, +) : BaseActivity(R.layout.activity_actionbar, viewModelClass) { + + protected lateinit var childBinding: VB + + /*super call required*/ + override fun onInit(savedInstanceState: Bundle?) { + viewBind() + initActionBar() + } + + private fun viewBind() { + childBinding = + DataBindingUtil.inflate(layoutInflater, layout, binding.rootFrameLayout, false) + binding.rootFrameLayout.addView(childBinding.root) + } + + private fun initActionBar() { + setSupportActionBar(binding.toolbar) + } + + fun setTitle(title: String) { + supportActionBar?.title = title + } + + fun setNavigateUpEnabled(boolean: Boolean) { + supportActionBar?.setDisplayHomeAsUpEnabled(boolean) + } + + override fun onBackPressed() { + super.onBackPressed() + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/base_class/BaseActivity.kt b/src/main/java/com/nativedevps/support/base_class/BaseActivity.kt new file mode 100644 index 0000000..d98f312 --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/BaseActivity.kt @@ -0,0 +1,87 @@ +package com.nativedevps.support.base_class + +import android.content.Context +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.ViewDataBinding +import androidx.lifecycle.ViewModelProvider +import androidx.viewbinding.ViewBinding +import com.nativedevps.support.custom_views.ProgressDialog +import com.nativedevps.support.inline.orElse +import com.nativedevps.support.utility.language.ContextWrapper +import com.nativedevps.support.utility.language.Utility.getDeviceLocale +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.util.* + + +abstract class BaseActivity( + internal val bindingFactory: Int, + private val viewModelClass: Class, +) : AppCompatActivity() { + protected val viewModel: VM by lazy { ViewModelProvider(this).get(viewModelClass) } + private var progressDialog: ProgressDialog? = null + + protected val binding: B by contentView(bindingFactory) + + private lateinit var _binding: ViewBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initBinding() + preInit() + setContentView(binding.root) + + initObserver() + onInit(savedInstanceState) + viewModel.onCreate() + } + + open fun preInit() { + } + + private fun initObserver() { + viewModel.liveDataProgressBar.observe(this, { triple -> + if (triple.first) { + progressDialog?.setProgress(triple.second).orElse { + progressDialog = ProgressDialog(this) + } + progressDialog?.setMessage(triple.third)?.build() + } else { + progressDialog?.dismiss() + progressDialog = null + } + }) + } + + private fun initBinding() { + binding.lifecycleOwner = this + } + + abstract fun onInit(savedInstanceState: Bundle?) + + override fun onDestroy() { + super.onDestroy() + progressDialog?.dismiss() + } + + fun runOnNewThread(callback: suspend CoroutineScope.() -> Unit) { + CoroutineScope(Dispatchers.IO).launch { + callback() + } + } + + override fun attachBaseContext(context: Context) { + val lang = getLocale(context) ?: getDeviceLocale()?.language + val contextWrapper = Locale(lang).let { + Locale.setDefault(it) + ContextWrapper.wrap(context, it) + } + super.attachBaseContext(contextWrapper) + } + + open fun getLocale(context: Context): String? { + return null + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/base_class/BaseAdapter2.kt b/src/main/java/com/nativedevps/support/base_class/BaseAdapter2.kt new file mode 100644 index 0000000..5bd13a7 --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/BaseAdapter2.kt @@ -0,0 +1,154 @@ +package com.nativedevps.support.base_class + +import android.util.Log +import androidx.recyclerview.widget.DiffUtil +import com.nativedevps.support.custom_views.ListAdapter +import com.nativedevps.support.inline.orElse + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.util.* + +abstract class BaseAdapter2() : + ListAdapter>() { + + private val TAG: String = "BaseAdapter" + private val selectionList: MutableList = ArrayList() + + init { + val diffItemCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ITEM_TYPE, + newItem: ITEM_TYPE, + ): Boolean { + return isSameItem(oldItem, newItem) + } + + override fun areContentsTheSame( + oldItem: ITEM_TYPE, + newItem: ITEM_TYPE, + ): Boolean { + return isSameContent(oldItem, newItem) + } + + override fun getChangePayload(oldItem: ITEM_TYPE, newItem: ITEM_TYPE): Any? { + return getChangePayload()?.let { it } + .orElse { super.getChangePayload(oldItem, newItem) } + } + } + init(diffItemCallback) + } + + companion object { + const val PAYLOAD_SELECTION_MODE: String = "is_selection_mode" + } + + fun setSelected(key: SELECTION_TYPE) { + if (isSelectable(key)) { + if (selectionList.contains(key)) { + Log.e(TAG, "error: item already selected") + return + } + selectionList.add(key) + notifyItemChanged( + getIndex(key), + PAYLOAD_SELECTION_MODE + ) + } + } + + fun clearSelection(topic: SELECTION_TYPE) { + if (!selectionList.contains(topic)) { + Log.e(TAG, "error: item already cleared") + return + } + selectionList.remove(topic) + notifyItemChanged( + getIndex(topic), + PAYLOAD_SELECTION_MODE + ) + } + + fun clearSelection(notifyAll: Boolean = false) { + if (!notifyAll) { + for (topicId in selectionList) { + val itemId = getIndex(topicId) + if (itemId != -1) { + notifyItemChanged(itemId, PAYLOAD_SELECTION_MODE) + } else { + Log.e(TAG, "error: item not found $topicId") + } + } + } else { + notifyDataSetChanged() + } + selectionList.clear() + } + + fun selectAll(notifyAll: Boolean = true) { + val selectableKeys = mutableListOf() + for (key in getAllKeys()) { + if (isSelectable(key)) { + selectableKeys.add(key) + } + } + selectionList.clear() + selectionList.addAll(selectableKeys) + if (notifyAll) { + notifyDataSetChanged() + } else { + for (selectableKey in selectableKeys) { + val itemId = getIndex(selectableKey) + if (itemId != -1) { + notifyItemChanged(itemId, PAYLOAD_SELECTION_MODE) + } + } + } + + } + + fun isSelected(topic: SELECTION_TYPE): Boolean { + return selectionList.contains(topic) + } + + fun isSelectionMode(): Boolean { + return selectionList.isNotEmpty() + } + + fun getSelections(): MutableList { + return selectionList + } + + abstract fun getList(): List + + open fun getIndex(itemKey: SELECTION_TYPE): Int { + return -1 + } + + open fun getAllKeys(): List { + return listOf() + } + + open fun isSelectable(key: SELECTION_TYPE): Boolean { + return true + } + + open fun isSameItem(oldItem: ITEM_TYPE, newItem: ITEM_TYPE): Boolean { + return false + } + + open fun isSameContent(oldItem: ITEM_TYPE, newItem: ITEM_TYPE): Boolean { + return true + } + + open fun getChangePayload(): Any? { + return null + } + + suspend fun runOnUiThread(callback: suspend CoroutineScope.() -> Unit) { + withContext(Dispatchers.Main) { + callback() + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/base_class/BaseRepository.kt b/src/main/java/com/nativedevps/support/base_class/BaseRepository.kt new file mode 100644 index 0000000..e73b2e6 --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/BaseRepository.kt @@ -0,0 +1,3 @@ +package com.nativedevps.support.base_class + +open class BaseRepository diff --git a/src/main/java/com/nativedevps/support/base_class/BaseViewHolder.kt b/src/main/java/com/nativedevps/support/base_class/BaseViewHolder.kt new file mode 100644 index 0000000..236aa4f --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/BaseViewHolder.kt @@ -0,0 +1,50 @@ +package com.nativedevps.support.base_class + +import android.content.Context +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import org.jetbrains.anko.toast + +abstract class BaseViewHolder( + private val selectedList: List, + itemView: View +) : RecyclerView.ViewHolder(itemView) { + + val context: Context get() = itemView.context + + fun toast(resId: Int) { + itemView.context.toast(resId) + } + + fun toast(text: String) { + itemView.context.toast(text) + } + + abstract fun bind(position: Int, item: M) + + open fun bind( + position: Int, + item: M, + payload: List + ) { + } + + open fun setOptions( + position: Int, + item: M, + option: Map = mutableMapOf() + ) { + } + + fun getSelectionList(): List { + return selectedList + } + + fun isSelected(text: SELECTION_TYPE): Boolean { + return getSelectionList().contains(text) + } + + fun isSelectionMode(): Boolean { + return getSelectionList().isNotEmpty() + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/base_class/BaseViewModel.kt b/src/main/java/com/nativedevps/support/base_class/BaseViewModel.kt new file mode 100644 index 0000000..f4dffba --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/BaseViewModel.kt @@ -0,0 +1,51 @@ +package com.nativedevps.support.base_class + +import android.app.Application +import android.content.Context +import android.util.Log +import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.jetbrains.anko.runOnUiThread +import org.jetbrains.anko.toast + +abstract class BaseViewModel constructor(application: Application) : + AndroidViewModel(application) { + protected val context: Context get() = getApplication() + val liveDataProgressBar = MutableLiveData>() + + abstract fun onCreate() + + open fun showProgressDialog(message: String = "loading..", progress: Int = -1) { + Log.e("JK", "showProgressDialog") + context.runOnUiThread { + liveDataProgressBar.value = Triple(true, progress, message) + } + } + + open fun hideProgressDialog() { + Log.e("JK", "hideProgressDialog") + context.runOnUiThread { + liveDataProgressBar.value = Triple(false, -1, "") + } + } + + open fun toast(string: String) { + context.toast(string) + } + + fun runOnNewThread(callback: suspend CoroutineScope.() -> Unit) { + CoroutineScope(Dispatchers.IO).launch { + callback() + } + } + + fun runOnUiThread(callback: () -> Unit) { + context.runOnUiThread { + callback() + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/base_class/BindingUtility.kt b/src/main/java/com/nativedevps/support/base_class/BindingUtility.kt new file mode 100644 index 0000000..32cdbc0 --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/BindingUtility.kt @@ -0,0 +1,35 @@ +package com.nativedevps.support.base_class + + +import android.app.Activity +import androidx.annotation.LayoutRes +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import kotlin.reflect.KProperty + +/** + * A delegate who lazily inflates a data binding layout, calls [Activity.setContentView], sets + * the lifecycle owner and returns the binding. + * + * created by Jeyakumar@01092020 + */ +class ContentViewBindingDelegate( + @LayoutRes private val layoutRes: Int, +) { + + private var binding: T? = null + + operator fun getValue(activity: R, property: KProperty<*>): T { + if (binding == null) { + binding = DataBindingUtil.setContentView(activity, layoutRes).apply { + lifecycleOwner = activity + } + } + return binding!! + } +} + +fun contentView( + @LayoutRes layoutRes: Int, +): ContentViewBindingDelegate = ContentViewBindingDelegate(layoutRes) \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/base_class/dialog/BaseMaterialDialog.kt b/src/main/java/com/nativedevps/support/base_class/dialog/BaseMaterialDialog.kt new file mode 100644 index 0000000..25f9f16 --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/dialog/BaseMaterialDialog.kt @@ -0,0 +1,52 @@ +package com.nativedevps.support.base_class.dialog + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.viewbinding.ViewBinding +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.nativedevps.support.inline.orElse +import org.jetbrains.anko.layoutInflater + +abstract class BaseMaterialDialog( + context: Context, + private val bindingFactory: (LayoutInflater) -> B, +) : MaterialAlertDialogBuilder(context) { + + protected var alertDialog: AlertDialog? = null + private var _binding: ViewBinding = bindingFactory.invoke(context.layoutInflater) + protected val binding: B by lazy { _binding as B } + + override fun create(): AlertDialog { + setView(binding.root) + return super.create() + } + + override fun setView(view: View?): MaterialAlertDialogBuilder { + super.setView(binding.root).let { + return it.also { + onCreate(binding) + } + } + } + + override fun show(): AlertDialog { + return super.show().also { + this.alertDialog = it + onShow(alertDialog) + } + } + + fun dismiss() { + this.alertDialog?.dismiss() + } + + open fun isShowing(): Boolean { + return alertDialog?.isShowing.orElse { false } + } + + abstract fun onCreate(binding: B) + + protected open fun onShow(alertDialog: AlertDialog?) {} +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/base_class/room/AppDao.kt b/src/main/java/com/nativedevps/support/base_class/room/AppDao.kt new file mode 100644 index 0000000..f052efd --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/room/AppDao.kt @@ -0,0 +1,66 @@ +package com.support.room + + +import androidx.room.* +import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.sqlite.db.SupportSQLiteQuery + +import java.lang.reflect.ParameterizedType + +@Dao +abstract class AppDao { + + // tableName = StringUtil.toSnakeCase(clazz.getSimpleName()); + val tableName: String + get() { + val clazz = (javaClass.superclass!!.genericSuperclass as ParameterizedType) + .actualTypeArguments[0] as Class<*> + return clazz.simpleName + } + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun save(obj: T): Long + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun save(vararg objs: T): LongArray + + @Insert(onConflict = OnConflictStrategy.FAIL) + abstract fun insert(obj: T): Long + + @Insert(onConflict = OnConflictStrategy.FAIL) + abstract fun insert(vararg objs: T): LongArray + + @Delete + abstract fun delete(obj: T) + + fun deleteAll(): Int { + val query = SimpleSQLiteQuery( + "delete from $tableName" + ) + return doDeleteAll(query) + } + + fun findAllValid(): List { + val query = SimpleSQLiteQuery( + "select * from $tableName where deleteFlag = 0 order by sortKey" + ) + return doFindAllValid(query) + } + + fun find(id: Long): T { + val query = SimpleSQLiteQuery( + "select * from $tableName where deleteFlag = 0 and id = ?", + arrayOf(id) + ) + return doFind(query) + } + + @RawQuery + protected abstract fun doFindAllValid(query: SupportSQLiteQuery): List + + @RawQuery + protected abstract fun doFind(query: SupportSQLiteQuery): T + + @RawQuery + protected abstract fun doDeleteAll(query: SupportSQLiteQuery): Int +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/base_class/room/BaseDao.kt b/src/main/java/com/nativedevps/support/base_class/room/BaseDao.kt new file mode 100644 index 0000000..98d06cf --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/room/BaseDao.kt @@ -0,0 +1,75 @@ +package com.nativedevps.support.base_class.room + + +import androidx.room.* +import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.sqlite.db.SupportSQLiteQuery + +import java.lang.reflect.ParameterizedType + +@Dao +abstract class BaseDao { + + protected val tableName: String + get() { + val clazz = (javaClass.superclass!!.genericSuperclass as ParameterizedType) + .actualTypeArguments[0] as Class<*> + return clazz.simpleName + } + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insert(obj: T): Long + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertAll(obj: List): LongArray + + @Update + abstract fun update(obj: T) + + @Delete + abstract fun delete(obj: T) + + fun find(key: String, value: String): T { + val query = SimpleSQLiteQuery( + "select * from $tableName where $key=?", + arrayOf(value) + ) + return doFind(query) + } + + fun deleteAll() { + val query = SimpleSQLiteQuery( + "TRUNCATE $tableName" + ) + doDeleteAll(query) + } + + @RawQuery + protected abstract fun doDeleteAll(query: SimpleSQLiteQuery): Int + + @RawQuery + protected abstract fun doFind(query: SimpleSQLiteQuery): T + + + fun findAllValid(): List { + val query = SimpleSQLiteQuery( + "select * from $tableName where is_delete = 0 order by sortKey" + ) + return doFindAllValid(query) + } + + fun find(id: Long): T { + val query = SimpleSQLiteQuery( + "select * from $tableName where is_delete = 0 and id = ?", + arrayOf(id) + ) + return doFind(query) + } + + @RawQuery + protected abstract fun doFindAllValid(query: SupportSQLiteQuery): List + + @RawQuery + protected abstract fun doFind(query: SupportSQLiteQuery): T + +} diff --git a/src/main/java/com/nativedevps/support/base_class/room/BaseEntity.kt b/src/main/java/com/nativedevps/support/base_class/room/BaseEntity.kt new file mode 100644 index 0000000..d29040c --- /dev/null +++ b/src/main/java/com/nativedevps/support/base_class/room/BaseEntity.kt @@ -0,0 +1,32 @@ +package com.nativedevps.support.base_class.room + +import androidx.room.ColumnInfo +import androidx.room.TypeConverters +import com.nativedevps.support.utility.room.DateConverter +import org.joda.time.DateTime + + +open class BaseEntity { + + object Fields { + const val IS_DELETED = "is_deleted" + const val IS_ACTIVE = "is_active" + const val CREATED_AT = "created_at" + const val UPDATED_AT = "updated_at" + } + + @ColumnInfo(name = Fields.IS_ACTIVE) + var isActive: Boolean = false + + @ColumnInfo(name = Fields.IS_DELETED) + var isDeleted: Boolean = false + + @TypeConverters(DateConverter::class) + @ColumnInfo(name = Fields.CREATED_AT) + var createdAt: DateTime? = null + + @TypeConverters(DateConverter::class) + @ColumnInfo(name = Fields.UPDATED_AT) + var updatedAt: DateTime? = null + +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/coroutines/TimerJob.kt b/src/main/java/com/nativedevps/support/coroutines/TimerJob.kt new file mode 100644 index 0000000..398eb2c --- /dev/null +++ b/src/main/java/com/nativedevps/support/coroutines/TimerJob.kt @@ -0,0 +1,62 @@ +package com.nativedevps.support.coroutines + +import com.nativedevps.support.utility.date_time_utility.MillisecondUtility.now +import kotlinx.coroutines.* + +object TimerJob { + + /** + * Infinite job occurring every delay within same thread, + * [milliseconds] are the delay to [block] get called, + * [block] get called on same thread + */ + fun emitOnDelay(milliseconds: Long, block: () -> Unit): Job { + return CoroutineScope(Dispatchers.Default).launch { + while (isActive) { + block() + delay(milliseconds) + } + } + } + + /** + * Emit until duration [milliseconds] when interval meets [interval] got called + * until duration meets [block] not called, if duration meets equal or more than + * duration [block] called + */ + fun emitPostDuration( + milliseconds: Long, + intervalMillis: Long = 0, + interval: (suspend CoroutineScope.(interval: Long) -> Unit?)? = null, + block: suspend CoroutineScope.() -> Unit, + ): Job { + return CoroutineScope(Dispatchers.Default).launch { + val initialTime = now + var nextInterval = intervalMillis + while (isActive) { + val currentDuration = now - initialTime + if (currentDuration >= milliseconds) { + block() + break + } else if (intervalMillis != 0L && currentDuration >= nextInterval) { + interval?.let { it(currentDuration) } + nextInterval += intervalMillis + } + delay(if (intervalMillis != 0L) intervalMillis else milliseconds) + } + } + } + + fun runAfter(durationMilliseconds: Long, block: () -> Unit): Job { + return CoroutineScope(Dispatchers.Default).launch { + delay(durationMilliseconds) + block() + } + } + + suspend fun runOnUiThread(callback: () -> Unit) { + withContext(Dispatchers.Main) { + callback() + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/custom_views/ArrayDrawableListViewAdapter.kt b/src/main/java/com/nativedevps/support/custom_views/ArrayDrawableListViewAdapter.kt new file mode 100644 index 0000000..0d39699 --- /dev/null +++ b/src/main/java/com/nativedevps/support/custom_views/ArrayDrawableListViewAdapter.kt @@ -0,0 +1,22 @@ +package com.nativedevps.support.custom_views + +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import com.nativedevps.support.utility.view.ViewUtils.setLeftDrawable +import nativedevps.support.R +import nativedevps.support.databinding.ItemSimpleListViewBinding + +class ArrayDrawableListViewAdapter( + var activity: Activity, + var items: List>, +) : ArrayAdapter>(activity, R.layout.item_simple_list_view, items) { + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val binding = ItemSimpleListViewBinding.inflate(activity.layoutInflater, parent, false) + binding.text1.setLeftDrawable(items[position].first) + binding.text1.text = items[position].second + return binding.root + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/custom_views/ListAdapter.java b/src/main/java/com/nativedevps/support/custom_views/ListAdapter.java new file mode 100644 index 0000000..39b7078 --- /dev/null +++ b/src/main/java/com/nativedevps/support/custom_views/ListAdapter.java @@ -0,0 +1,202 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * 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.nativedevps.support.custom_views; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.AdapterListUpdateCallback; +import androidx.recyclerview.widget.AsyncDifferConfig; +import androidx.recyclerview.widget.AsyncListDiffer; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +/** + * {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting List data in a + * {@link RecyclerView}, including computing diffs between Lists on a background thread. + *

+ * This class is a convenience wrapper around {@link AsyncListDiffer} that implements Adapter common + * default behavior for item access and counting. + *

+ * While using a LiveData<List> is an easy way to provide data to the adapter, it isn't required + * - you can use {@link #submitList(List)} when new lists are available. + *

+ * A complete usage pattern with Room would look like this: + *

+ * {@literal @}Dao
+ * interface UserDao {
+ *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
+ *     public abstract LiveData<List<User>> usersByLastName();
+ * }
+ *
+ * class MyViewModel extends ViewModel {
+ *     public final LiveData<List<User>> usersList;
+ *     public MyViewModel(UserDao userDao) {
+ *         usersList = userDao.usersByLastName();
+ *     }
+ * }
+ *
+ * class MyActivity extends AppCompatActivity {
+ *     {@literal @}Override
+ *     public void onCreate(Bundle savedState) {
+ *         super.onCreate(savedState);
+ *         MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
+ *         RecyclerView recyclerView = findViewById(R.id.user_list);
+ *         UserAdapter<User> adapter = new UserAdapter();
+ *         viewModel.usersList.observe(this, list -> adapter.submitList(list));
+ *         recyclerView.setAdapter(adapter);
+ *     }
+ * }
+ *
+ * class UserAdapter extends ListAdapter<User, UserViewHolder> {
+ *     public UserAdapter() {
+ *         super(User.DIFF_CALLBACK);
+ *     }
+ *     {@literal @}Override
+ *     public void onBindViewHolder(UserViewHolder holder, int position) {
+ *         holder.bindTo(getItem(position));
+ *     }
+ *     public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK =
+ *             new DiffUtil.ItemCallback<User>() {
+ *         {@literal @}Override
+ *         public boolean areItemsTheSame(
+ *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ *             // User properties may have changed if reloaded from the DB, but ID is fixed
+ *             return oldUser.getId() == newUser.getId();
+ *         }
+ *         {@literal @}Override
+ *         public boolean areContentsTheSame(
+ *                 {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ *             // NOTE: if you use equals, your object must properly override Object#equals()
+ *             // Incorrectly returning false here will result in too many animations.
+ *             return oldUser.equals(newUser);
+ *         }
+ *     }
+ * }
+ *

+ * Advanced users that wish for more control over adapter behavior, or to provide a specific base + * class should refer to {@link AsyncListDiffer}, which provides custom mapping from diff events + * to adapter positions. + * + * @param Type of the Lists this Adapter will receive. + * @param A class that extends ViewHolder that will be used by the adapter. + */ +public abstract class ListAdapter + extends RecyclerView.Adapter { + AsyncListDiffer mDiffer = null; + private final AsyncListDiffer.ListListener mListener = + new AsyncListDiffer.ListListener() { + @Override + public void onCurrentListChanged( + @NonNull List previousList, @NonNull List currentList) { + ListAdapter.this.onCurrentListChanged(previousList, currentList); + } + }; + + protected ListAdapter() { + } + + @SuppressWarnings("unused") + protected ListAdapter(@NonNull DiffUtil.ItemCallback diffCallback) { + mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), + new AsyncDifferConfig.Builder<>(diffCallback).build()); + mDiffer.addListListener(mListener); + } + + @SuppressWarnings("unused") + protected ListAdapter(@NonNull AsyncDifferConfig config) { + mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), config); + mDiffer.addListListener(mListener); + } + + protected void init(@NonNull DiffUtil.ItemCallback diffCallback) { + mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), + new AsyncDifferConfig.Builder<>(diffCallback).build()); + mDiffer.addListListener(mListener); + } + + /** + * Submits a new list to be diffed, and displayed. + *

+ * If a list is already being displayed, a diff will be computed on a background thread, which + * will dispatch Adapter.notifyItem events on the main thread. + * + * @param list The new list to be displayed. + */ + public void submitList(@Nullable List list) { + mDiffer.submitList(list); + } + + /** + * Set the new list to be displayed. + *

+ * If a List is already being displayed, a diff will be computed on a background thread, which + * will dispatch Adapter.notifyItem events on the main thread. + *

+ * The commit callback can be used to know when the List is committed, but note that it + * may not be executed. If List B is submitted immediately after List A, and is + * committed directly, the callback associated with List A will not be run. + * + * @param list The new list to be displayed. + * @param commitCallback Optional runnable that is executed when the List is committed, if + * it is committed. + */ + public void submitList(@Nullable List list, @Nullable final Runnable commitCallback) { + mDiffer.submitList(list, commitCallback); + } + + protected T getItem(int position) { + return mDiffer.getCurrentList().get(position); + } + + @Override + public int getItemCount() { + return mDiffer.getCurrentList().size(); + } + + /** + * Get the current List - any diffing to present this list has already been computed and + * dispatched via the ListUpdateCallback. + *

+ * If a null List, or no List has been submitted, an empty list will be returned. + *

+ * The returned list may not be mutated - mutations to content must be done through + * {@link #submitList(List)}. + * + * @return The list currently being displayed. + * @see #onCurrentListChanged(List, List) + */ + @NonNull + public List getCurrentList() { + return mDiffer.getCurrentList(); + } + + /** + * Called when the current List is updated. + *

+ * If a null List is passed to {@link #submitList(List)}, or no List has been + * submitted, the current List is represented as an empty List. + * + * @param previousList List that was displayed previously. + * @param currentList new List being displayed, will be empty if {@code null} was passed to + * {@link #submitList(List)}. + * @see #getCurrentList() + */ + public void onCurrentListChanged(@NonNull List previousList, @NonNull List currentList) { + } +} diff --git a/src/main/java/com/nativedevps/support/custom_views/OneTimePasswordEditText.java b/src/main/java/com/nativedevps/support/custom_views/OneTimePasswordEditText.java new file mode 100644 index 0000000..cdabc77 --- /dev/null +++ b/src/main/java/com/nativedevps/support/custom_views/OneTimePasswordEditText.java @@ -0,0 +1,103 @@ +package com.nativedevps.support.custom_views; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.text.Editable; +import android.util.AttributeSet; +import android.view.ActionMode; +import android.view.View; +import androidx.appcompat.widget.AppCompatEditText; + +import nativedevps.support.R; + + +public class OneTimePasswordEditText extends AppCompatEditText { + private float mSpace = 24; //24 dp by default, space between the lines + private float mNumChars = 6; + private float mLineSpacing = 8; //8dp by default, height of the text from our lines + private int mMaxLength = 4; + private float mLineStroke = 2; + private Paint mLinesPaint; + private OnClickListener mClickListener; + + public OneTimePasswordEditText(Context context) { + super(context); + } + + public OneTimePasswordEditText(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public OneTimePasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + float multi = context.getResources().getDisplayMetrics().density; + mLineStroke = multi * mLineStroke; + mLinesPaint = new Paint(getPaint()); + mLinesPaint.setStrokeWidth(mLineStroke); + mLinesPaint.setColor(getResources().getColor(R.color.trans_gray)); + setBackgroundResource(0); + mSpace = multi * mSpace; //convert to pixels for our density + mLineSpacing = multi * mLineSpacing; //convert to pixels for our density + mNumChars = mMaxLength; + + super.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + // When tapped, move cursor to end of text. + setSelection(getText().length()); + if (mClickListener != null) { + mClickListener.onClick(v); + } + } + }); + } + + @Override + public void setOnClickListener(OnClickListener l) { + mClickListener = l; + } + + @Override + public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { + throw new RuntimeException("setCustomSelectionActionModeCallback() not supported."); + } + + @Override + protected void onDraw(Canvas canvas) { + int availableWidth = getWidth() - getPaddingRight() - getPaddingLeft(); + float mCharSize; + if (mSpace < 0) { + mCharSize = (availableWidth / (mNumChars * 2 - 1)); + } else { + mCharSize = (availableWidth - (mSpace * (mNumChars - 1))) / mNumChars; + } + + int startX = getPaddingLeft(); + int bottom = getHeight() - getPaddingBottom(); + + //Text Width + Editable text = getText(); + int textLength = text.length(); + float[] textWidths = new float[textLength]; + getPaint().getTextWidths(getText(), 0, textLength, textWidths); + + for (int i = 0; i < mNumChars; i++) { + canvas.drawLine(startX, bottom, startX + mCharSize, bottom, mLinesPaint); + if (getText().length() > i) { + float middle = startX + mCharSize / 2; + canvas.drawText(text, i, i + 1, middle - textWidths[0] / 2, bottom - mLineSpacing, getPaint()); + } + if (mSpace < 0) { + startX += mCharSize * 2; + } else { + startX += mCharSize + mSpace; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/custom_views/ProgressDialog.kt b/src/main/java/com/nativedevps/support/custom_views/ProgressDialog.kt new file mode 100644 index 0000000..af4a17e --- /dev/null +++ b/src/main/java/com/nativedevps/support/custom_views/ProgressDialog.kt @@ -0,0 +1,53 @@ +package com.nativedevps.support.custom_views + +import android.content.Context +import android.os.Build +import com.google.android.material.progressindicator.CircularProgressIndicator +import com.nativedevps.support.base_class.dialog.BaseMaterialDialog +import nativedevps.support.databinding.DProgressBarBinding + + +class ProgressDialog(context: Context) : + BaseMaterialDialog(context, DProgressBarBinding::inflate) { + + override fun onCreate(binding: DProgressBarBinding) { + setMessage("Loading..") + progressBar.setOnClickListener { + setProgress(progressBar.progress + 10) + } + } + + fun setIndeterminate(boolean: Boolean): ProgressDialog { + progressBar.isIndeterminate = boolean + return this + } + + fun setMessage(string: String): ProgressDialog { + binding.messageAppCompatTextView.text = string + return this + } + + fun setProgress(int: Int): ProgressDialog { + if (int == -1) { + return this + } + if (progressBar.isIndeterminate) { + progressBar.isIndeterminate = false + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + progressBar.setProgress(int, true) + } else { + progressBar.progress = int + } + return this + } + + fun build(): ProgressDialog { + if (!isShowing()) { + show() + } + return this + } + + private val progressBar: CircularProgressIndicator get() = binding.progressCircularProgressIndicator +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/inline/orElse.kt b/src/main/java/com/nativedevps/support/inline/orElse.kt new file mode 100644 index 0000000..44ece4e --- /dev/null +++ b/src/main/java/com/nativedevps/support/inline/orElse.kt @@ -0,0 +1,6 @@ +package com.nativedevps.support.inline + +//?.let{}.orElse{} +inline fun R?.orElse(block: () -> R): R { + return this ?: block() +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/inline/viewModels.kt b/src/main/java/com/nativedevps/support/inline/viewModels.kt new file mode 100644 index 0000000..457f356 --- /dev/null +++ b/src/main/java/com/nativedevps/support/inline/viewModels.kt @@ -0,0 +1,18 @@ +package com.nativedevps.support.inline + +import androidx.activity.ComponentActivity +import androidx.annotation.MainThread +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelLazy +import androidx.lifecycle.ViewModelProvider.Factory + +@MainThread +inline fun ComponentActivity.viewModels( + noinline factoryProducer: (() -> Factory)? = null, +): Lazy { + val factoryPromise = factoryProducer ?: { + defaultViewModelProviderFactory + } + + return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise) +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/date_time_utility/DateTimeConversion.kt b/src/main/java/com/nativedevps/support/utility/date_time_utility/DateTimeConversion.kt new file mode 100644 index 0000000..81cbf9d --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/date_time_utility/DateTimeConversion.kt @@ -0,0 +1,4 @@ +package com.nativedevps.support.utility.date_time_utility + +object DateTimeConversion { +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/date_time_utility/DateUtility.kt b/src/main/java/com/nativedevps/support/utility/date_time_utility/DateUtility.kt new file mode 100644 index 0000000..1a3db3e --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/date_time_utility/DateUtility.kt @@ -0,0 +1,4 @@ +package com.nativedevps.support.utility.date_time_utility + +object DateUtility { +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/date_time_utility/MillisecondUtility.kt b/src/main/java/com/nativedevps/support/utility/date_time_utility/MillisecondUtility.kt new file mode 100644 index 0000000..3319a83 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/date_time_utility/MillisecondUtility.kt @@ -0,0 +1,5 @@ +package com.nativedevps.support.utility.date_time_utility + +object MillisecondUtility { + val now: Long get() = System.currentTimeMillis() +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/date_time_utility/TimeUtility.kt b/src/main/java/com/nativedevps/support/utility/date_time_utility/TimeUtility.kt new file mode 100644 index 0000000..3030677 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/date_time_utility/TimeUtility.kt @@ -0,0 +1,4 @@ +package com.nativedevps.support.utility.date_time_utility + +object TimeUtility { +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/device/AppSignatureHelper.kt b/src/main/java/com/nativedevps/support/utility/device/AppSignatureHelper.kt new file mode 100644 index 0000000..ebf574b --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/device/AppSignatureHelper.kt @@ -0,0 +1,79 @@ +package com.nativedevps.support.utility.device + +import android.content.Context +import android.content.ContextWrapper +import android.content.pm.PackageManager +import android.util.Base64 +import android.util.Log +import java.nio.charset.StandardCharsets +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.util.ArrayList +import java.util.Arrays + +/** + * Created on : May 21, 2019 + * Author : AndroidWave + */ +class AppSignatureHelper(context: Context) : ContextWrapper(context) { + + /** + * Get all the app signatures for the current package + */ + // Get all package signatures for the current package + // For each signature create a compatible hash + val appSignatures: ArrayList + get() { + val appCodes = ArrayList() + + try { + val packageName = packageName + val packageManager = packageManager + val signatures = packageManager.getPackageInfo( + packageName, + PackageManager.GET_SIGNATURES + ).signatures + for (signature in signatures) { + val hash = hash(packageName, signature.toCharsString()) + if (hash != null) { + appCodes.add(String.format("%s", hash)) + } + } + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Unable to find package to obtain hash.", e) + } + + return appCodes + } + + companion object { + + private val TAG = "AppSignatureHelper" + private val HASH_TYPE = "SHA-256" + val NUM_HASHED_BYTES = 9 + val NUM_BASE64_CHAR = 11 + + private fun hash(packageName: String, signature: String): String? { + val appInfo = "$packageName $signature" + try { + val messageDigest = MessageDigest.getInstance(HASH_TYPE) + messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8)) + var hashSignature = messageDigest.digest() + + // truncated into NUM_HASHED_BYTES + hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES) + // encode into Base64 + var base64Hash = + Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP) + base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR) + + Log.d(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash)) + return base64Hash + } catch (e: NoSuchAlgorithmException) { + Log.e(TAG, "hash:NoSuchAlgorithm", e) + } + + return null + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/device/audio/DeviceAudio.kt b/src/main/java/com/nativedevps/support/utility/device/audio/DeviceAudio.kt new file mode 100644 index 0000000..9644aaa --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/device/audio/DeviceAudio.kt @@ -0,0 +1,13 @@ +package com.nativedevps.support.utility.device.audio + +import android.content.Context +import android.media.AudioManager + +object DeviceAudio { + fun Context.getCurrentVolume(): Int { + val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager + val volumeLevel: Int = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + val maxVolumeLevel: Int = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + return (volumeLevel.toFloat() / maxVolumeLevel * 100).toInt() + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/device/mime/MimeWildCard.kt b/src/main/java/com/nativedevps/support/utility/device/mime/MimeWildCard.kt new file mode 100644 index 0000000..79065a4 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/device/mime/MimeWildCard.kt @@ -0,0 +1,7 @@ +package com.nativedevps.support.utility.device.mime + +object MimeWildCard { + val MIME_IMAGE_WILD_CARD = "image/*" + val MIME_VIDEO_WILD_CARD = "video/*" + val MIME_APPLICATION_WILD_CARD = "application/*" +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/device/permission/EasyPermissions.kt b/src/main/java/com/nativedevps/support/utility/device/permission/EasyPermissions.kt new file mode 100644 index 0000000..bc02fe5 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/device/permission/EasyPermissions.kt @@ -0,0 +1,412 @@ +package com.nativedevps.support.utility.device.permission + +import android.Manifest +import android.R +import android.annotation.TargetApi +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.provider.Settings +import android.text.TextUtils +import android.util.Log +import androidx.annotation.StringRes +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import java.util.* + +object EasyPermissions { + + private val TAG = "EasyPermissions" + internal var timeWhenRequestingStart: Long = 0 + internal lateinit var `object`: Any + //private var finalPermissionDialog: MConfirmationDialog? = null + private var callbacks: PermissionCallbacks? = null + private var permissionGroups: HashMap>? = null + + /** + * Check if the calling context has a set of permissions. + * + * @param context the calling context. + * @param perms one ore more permissions, such as `android.Manifest.permission.CAMERA`. + * @return true if all permissions are already granted, false if at least one permission + * is not yet granted. + */ + fun hasPermissions(context: Context?, vararg perms: String): Boolean { + // Always return true for SDK < M, let the system deal with the permissions + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + Log.w(TAG, "hasPermissions: API version < M, returning true by default") + return true + } + + for (perm in perms) { + val hasPerm = ContextCompat.checkSelfPermission(context!!, perm) == PackageManager.PERMISSION_GRANTED + if (!hasPerm) { + return false + } + } + + return true + } + + /** + * Request a set of permissions, showing rationale if the system requests it. + * + * @param object Activity or Fragment requesting permissions. Should implement + * [ActivityCompat.OnRequestPermissionsResultCallback] + * or + * @param rationale a message explaining why the application needs this set of permissions, will + * be displayed if the user rejects the request the first time. + * @param requestCode request code to track this request, must be < 256. + * @param perms a set of permissions to be requested. + */ + fun requestPermissions( + `object`: Any, rationale: String, + requestCode: Int, callback: PermissionCallbacks, vararg perms: String + ) { + requestPermissions( + `object`, rationale, + R.string.ok, + R.string.cancel, + requestCode, callback, *perms + ) + } + + /** + * Request a set of permissions, showing rationale if the system requests it. + * + * @param object Activity or Fragment requesting permissions. Should implement + * [ActivityCompat.OnRequestPermissionsResultCallback] + * or + * be displayed if the user rejects the request the first time. + * @param requestCode request code to track this request, must be < 256. + * @param perms a set of permissions to be requested. + */ + fun requestPermissions( + `object`: Any, + requestCode: Int, callback: PermissionCallbacks, vararg perms: String + ) { + requestPermissions( + `object`, "", + R.string.ok, + R.string.cancel, + requestCode, callback, *perms + ) + } + + /** + * Request a set of permissions, showing rationale if the system requests it. + * + * @param obj Activity or Fragment requesting permissions. Should implement + * [ActivityCompat.OnRequestPermissionsResultCallback] + * or + * @param rationale a message explaining why the application needs this set of permissions, will + * be displayed if the user rejects the request the first time. + * @param positiveButton custom text for positive button + * @param negativeButton custom text for negative button + * @param requestCode request code to track this request, must be < 256. + * @param permission a set of permissions or permission.group to be requested. + */ + fun requestPermissions( + obj: Any, rationale: String, + @StringRes positiveButton: Int, + @StringRes negativeButton: Int, + requestCode: Int, callback: PermissionCallbacks, vararg permission: String + ) { + + callbacks = callback + `object` = obj + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + // only for lower of M + // PermissionCallbacks callbacks = (PermissionCallbacks) object; + callbacks!!.onPermissionsGranted(requestCode, ArrayList(Arrays.asList(*permission))) + return + } + + + checkCallingObjectSuitability(`object`) + // final PermissionCallbacks callbacks = (PermissionCallbacks) object; + val perms = getActualPermissions( + `object`, + permission + ) + + if (perms.size <= 0) { + callbacks!!.onPermissionsGranted(requestCode, ArrayList(Arrays.asList(*permission))) + return + } + + var shouldShowRationale = false + for (perm in perms) { + shouldShowRationale = shouldShowRationale || shouldShowRequestPermissionRationale( + `object`, perm) + } + + if (shouldShowRationale) { + if (!TextUtils.isEmpty(rationale)) { + Log.i(TAG, "shouldShowRationale: ") + +// val builder = MConfirmationDialog.Builder(getActivity(`object`)!!) +// .setMessage(rationale) +// .setTitle("Permission necessary") +// .setPositiveButton(positiveButton) +// .setNegativeButton(negativeButton) +// .setOnNegativeClickListener(DialogInterface.OnClickListener { dialog, which -> +// callbacks!!.onPermissionsDenied(requestCode, Arrays.asList(*perms)) +// finalPermissionDialog!!.dismiss() +// }).setOnPositiveClickListener(DialogInterface.OnClickListener { dialog, which -> +// executePermissionsRequest(`object`, perms, requestCode) +// finalPermissionDialog!!.dismiss() +// }) +// finalPermissionDialog = MConfirmationDialog(getActivity(`object`), builder) +// finalPermissionDialog!!.show() + } else { + executePermissionsRequest(`object`, perms, requestCode) + } + } else { + for (perm in perms) { + shouldShowRationale = shouldShowRationale || shouldShowRequestPermissionRationale( + `object`, perm) + } + if (shouldShowRationale) { + Log.d(TAG, "requestPermissions: show dialog") + + } else { + timeWhenRequestingStart = System.currentTimeMillis() + executePermissionsRequest(`object`, perms, requestCode) + } + + } + } + + + private fun getActualPermissions(`object`: Any, permission: Array): Array { + initPermissionGroups() + val permissionList = ArrayList() + for (indiPerm in permission) { + if (permissionGroups!!.containsKey(indiPerm)) { + val arr = permissionGroups!![indiPerm] + for (s in arr!!) { + if (!hasPermissions(getActivity(`object`), s)) { + permissionList.add(s) + } + } + } else { + if (!hasPermissions(getActivity(`object`), indiPerm)) { + permissionList.add(indiPerm) + } + } + } + val set = LinkedHashSet(permissionList) + + return set.toTypedArray() + } + + private fun initPermissionGroups() { + if (permissionGroups == null) { + permissionGroups = HashMap() + permissionGroups!![Manifest.permission_group.CALENDAR] = + arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR) + permissionGroups!![Manifest.permission_group.CAMERA] = arrayOf(Manifest.permission.CAMERA) + permissionGroups!![Manifest.permission_group.CONTACTS] = + arrayOf( + Manifest.permission.READ_CONTACTS, + Manifest.permission.WRITE_CONTACTS, + Manifest.permission.GET_ACCOUNTS + ) + permissionGroups!![Manifest.permission_group.LOCATION] = + arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION) + permissionGroups!![Manifest.permission_group.MICROPHONE] = arrayOf(Manifest.permission.RECORD_AUDIO) + permissionGroups!![Manifest.permission_group.PHONE] = arrayOf( + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.CALL_PHONE, + Manifest.permission.READ_CALL_LOG, + Manifest.permission.WRITE_CALL_LOG, + Manifest.permission.ADD_VOICEMAIL, + Manifest.permission.USE_SIP, + Manifest.permission.PROCESS_OUTGOING_CALLS + ) + permissionGroups!![Manifest.permission_group.SENSORS] = arrayOf(Manifest.permission.BODY_SENSORS) + permissionGroups!![Manifest.permission_group.SMS] = arrayOf( + Manifest.permission.SEND_SMS, + Manifest.permission.RECEIVE_SMS, + Manifest.permission.READ_SMS, + Manifest.permission.RECEIVE_WAP_PUSH, + Manifest.permission.RECEIVE_MMS + ) + permissionGroups!![Manifest.permission_group.STORAGE] = + arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + } + + /** + * Handle the result of a permission request, should be called from the calling Activity's + * [ActivityCompat.OnRequestPermissionsResultCallback.onRequestPermissionsResult] + * method. + * + * + * If any permissions were granted or denied, the Activity will receive the appropriate + * callbacks through [PermissionCallbacks] and methods annotated with + * + * @param requestCode requestCode argument to permission result callback. + * @param permissions permissions argument to permission result callback. + * @param grantResults grantResults argument to permission result callback. + * @throws IllegalArgumentException if the calling Activity does not implement + * [PermissionCallbacks]. + */ + fun onRequestPermissionsResult( + requestCode: Int, permissions: Array, + grantResults: IntArray + ) { + var isPermenantlyDisabled = false + // checkCallingObjectSuitability(object); + // PermissionCallbacks callbacks = (PermissionCallbacks) object; + + // Make a collection of granted and denied permissions from the request. + val granted = ArrayList() + val denied = ArrayList() + for (i in permissions.indices) { + val perm = permissions[i] + if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { + granted.add(perm) + } else { + /**if deny then it will true and on never as me again it will return false + */ + if(EasyPermissions::`object`.isInitialized) { + val showRationale = shouldShowRequestPermissionRationale(`object`, perm) + if (showRationale) { + isPermenantlyDisabled = false //deny + // timeWhenRequestingStart = System.currentTimeMillis() - 2; + } else { + isPermenantlyDisabled = true //never ask me again + } + denied.add(perm) + } + } + } + + // Report granted permissions, if any. + if (!granted.isEmpty() && denied.isEmpty()) { + // Notify callbacks + callbacks!!.onPermissionsGranted(requestCode, granted) + } else if (granted.isEmpty() && !denied.isEmpty() && isPermenantlyDisabled) { + val diff = System.currentTimeMillis() - timeWhenRequestingStart + // if (diff < 350) { + //means it is permenantly disabled + callbacks!!.onPermissionsPermanentlyDeclined(requestCode, denied) + // } + Log.i("TAG", diff.toString() + "") + }// Report denied permissions, if any. + /*if (!denied.isEmpty()) { + callbacks.onPermissionsDenied(requestCode, denied); + }*///if 100% fail then check for whether timing + + // Report denied permissions, if any. + if (!denied.isEmpty() && !isPermenantlyDisabled) { + callbacks!!.onPermissionsDenied(requestCode, denied) + } + + /*// If 100% successful, call annotated methods + if (!granted.isEmpty() && denied.isEmpty()) { + runAnnotatedMethods(object, requestCode); + }*/ + } + + @TargetApi(23) + private fun shouldShowRequestPermissionRationale(`object`: Any, perm: String): Boolean { + return if (`object` is Activity) { + ActivityCompat.shouldShowRequestPermissionRationale(`object`, perm) + } else (`object` as? Fragment)?.shouldShowRequestPermissionRationale(perm) + ?: ((`object` as? android.app.Fragment)?.shouldShowRequestPermissionRationale(perm) ?: false) + } + + @TargetApi(23) + private fun executePermissionsRequest(`object`: Any, perms: Array, requestCode: Int) { + checkCallingObjectSuitability(`object`) + + if (`object` is Activity) { + ActivityCompat.requestPermissions(`object`, perms, requestCode) + } else if (`object` is Fragment) { + `object`.requestPermissions(perms, requestCode) + } else if (`object` is android.app.Fragment) { + `object`.requestPermissions(perms, requestCode) + } + } + + @TargetApi(11) + private fun getActivity(`object`: Any): Activity? { + return `object` as? Activity ?: if (`object` is Fragment) { + `object`.activity + } else if (`object` is android.app.Fragment) { + `object`.activity + } else { + null + } + } + + private fun checkCallingObjectSuitability(`object`: Any) { + // Make sure Object is an Activity or Fragment + val isActivity = `object` is Activity + val isSupportFragment = `object` is Fragment + val isAppFragment = `object` is android.app.Fragment + val isMinSdkM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + + if (!(isSupportFragment || isActivity || isAppFragment && isMinSdkM)) { + if (isAppFragment) { + throw IllegalArgumentException( + "Target SDK needs to be greater than 23 if caller is android.app.Fragment" + ) + } else { + throw IllegalArgumentException("Caller must be an Activity or a Fragment.") + } + } + + /*// Make sure Object implements callbacks + if (!(object instanceof PermissionCallbacks)) { + throw new IllegalArgumentException("Caller must implement PermissionCallbacks."); + }*/ + } + + fun startSetting() { + val intent = Intent() + intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + val uri = Uri.fromParts("package", getActivity(`object`)!!.packageName, null) + intent.data = uri + getActivity(`object`)!!.startActivity(intent) + } + + interface PermissionCallbacks { + + fun onPermissionsGranted(requestCode: Int, perms: List) + + fun onPermissionsDenied(requestCode: Int, perms: List) + + fun onPermissionsPermanentlyDeclined(requestCode: Int, perms: List) + } + + /*private fun showSamplePermission() { + EasyPermissions.requestPermissions(this, "Rational message", 102, object : EasyPermissions.PermissionCallbacks { + override fun onPermissionsGranted(requestCode: Int, perms: List) { + Log.w("Permission:", "GRANTED"); + } + + override fun onPermissionsDenied(requestCode: Int, perms: List) { + Log.w("Permission:", "Denied"); + } + + override fun onPermissionsPermanentlyDeclined(requestCode: Int, perms: List) { + Log.w("Permission:", "Declined"); + } + + }, Manifest.permission.READ_EXTERNAL_STORAGE) + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults) + }*/ + +} diff --git a/src/main/java/com/nativedevps/support/utility/device/permission/Utility.kt b/src/main/java/com/nativedevps/support/utility/device/permission/Utility.kt new file mode 100644 index 0000000..d91bfb2 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/device/permission/Utility.kt @@ -0,0 +1,33 @@ +package com.nativedevps.support.utility.device.permission + +import android.Manifest.permission.READ_EXTERNAL_STORAGE +import android.Manifest.permission.WRITE_EXTERNAL_STORAGE +import android.app.Activity +import android.content.pm.PackageManager +import android.os.Build +import android.os.Build.VERSION.SDK_INT +import android.os.Environment +import androidx.core.content.ContextCompat + + +object Utility { + fun Activity.checkReadPermission(): Boolean { + return if (SDK_INT >= Build.VERSION_CODES.R) { + Environment.isExternalStorageManager() + } else { + val result = + ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE) + result == PackageManager.PERMISSION_GRANTED + } + } + + fun Activity.checkWritePermission(): Boolean { + return if (SDK_INT >= Build.VERSION_CODES.R) { + Environment.isExternalStorageManager() + } else { + val result1 = + ContextCompat.checkSelfPermission(this, WRITE_EXTERNAL_STORAGE) + result1 == PackageManager.PERMISSION_GRANTED + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/device/storage_access_framework/UriHelper.kt b/src/main/java/com/nativedevps/support/utility/device/storage_access_framework/UriHelper.kt new file mode 100644 index 0000000..2411c95 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/device/storage_access_framework/UriHelper.kt @@ -0,0 +1,41 @@ +package com.nativedevps.support.utility.device.storage_access_framework + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.database.Cursor +import android.net.Uri +import android.os.Build +import android.provider.OpenableColumns +import androidx.annotation.RequiresApi + + +object UriHelper { + @RequiresApi(Build.VERSION_CODES.KITKAT) + fun Context.getPersistablePermission(uri: Uri): Uri { + contentResolver.takePersistableUriPermission(uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION) + return uri + } + + @SuppressLint("Range") + fun Context.getFileName(uri: Uri): String? { + var result: String? = null + if (uri.scheme == "content") { + val cursor: Cursor = contentResolver.query(uri, null, null, null, null)!! + cursor.use { cursor -> + if (cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) + } + } + } + if (result == null) { + result = uri.path + val cut = result!!.lastIndexOf('/') + if (cut != -1) { + result = result?.substring(cut + 1) + } + } + return result + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/device/storage_access_framework/Utility.kt b/src/main/java/com/nativedevps/support/utility/device/storage_access_framework/Utility.kt new file mode 100644 index 0000000..0c1a2db --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/device/storage_access_framework/Utility.kt @@ -0,0 +1,22 @@ +package com.nativedevps.support.utility.device.storage_access_framework + +import android.app.Activity +import android.content.Intent +import android.os.Build +import com.nativedevps.support.utility.device.mime.MimeWildCard.MIME_IMAGE_WILD_CARD + +object Utility { + fun Activity.showGalleryPicker(mime: String = MIME_IMAGE_WILD_CARD, reqCode: Int) { + if (Build.VERSION.SDK_INT >= 19) { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) + intent.type = mime + intent.addCategory(Intent.CATEGORY_OPENABLE) + startActivityForResult(intent, reqCode) + } else { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = mime + intent.addCategory(Intent.CATEGORY_OPENABLE) + startActivityForResult(intent, reqCode) + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/image/bitmap/Parceling.kt b/src/main/java/com/nativedevps/support/utility/image/bitmap/Parceling.kt new file mode 100644 index 0000000..35f0b52 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/image/bitmap/Parceling.kt @@ -0,0 +1,23 @@ +package com.nativedevps.support.utility.image.bitmap + +import android.graphics.Bitmap +import android.util.Base64 +import java.io.ByteArrayOutputStream + + +object Parceling { + fun Bitmap.toBase64(): String? { + var byteArrayOutputStream: ByteArrayOutputStream? = null + try { + byteArrayOutputStream = ByteArrayOutputStream() + compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) + val bytes: ByteArray = byteArrayOutputStream.toByteArray() + return Base64.encodeToString(bytes, Base64.DEFAULT) + } catch (e: Exception) { + e.printStackTrace() + } finally { + byteArrayOutputStream?.close() + } + return null + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/image/bitmap/Utility.kt b/src/main/java/com/nativedevps/support/utility/image/bitmap/Utility.kt new file mode 100644 index 0000000..c58dd64 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/image/bitmap/Utility.kt @@ -0,0 +1,21 @@ +package com.nativedevps.support.utility.image.bitmap + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import java.io.InputStream + + +object Utility { + @Throws(OutOfMemoryError::class) + fun Context.getBitmap(uri: Uri): Bitmap? { + var inputStream: InputStream? = null + try { + inputStream = contentResolver.openInputStream(uri); + return BitmapFactory.decodeStream(inputStream) + } finally { + inputStream?.close() + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/language/ContextWrapper.java b/src/main/java/com/nativedevps/support/utility/language/ContextWrapper.java new file mode 100644 index 0000000..13bf14b --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/language/ContextWrapper.java @@ -0,0 +1,37 @@ +package com.nativedevps.support.utility.language; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Build; +import android.os.LocaleList; + +import java.util.Locale; + +public class ContextWrapper extends android.content.ContextWrapper { + + public ContextWrapper(Context base) { + super(base); + } + + public static ContextWrapper wrap(Context context, Locale newLocale) { + Resources res = context.getResources(); + Configuration configuration = res.getConfiguration(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + configuration.setLocale(newLocale); + + LocaleList localeList = new LocaleList(newLocale); + LocaleList.setDefault(localeList); + configuration.setLocales(localeList); + + context = context.createConfigurationContext(configuration); + + } else { + configuration.locale = newLocale; + res.updateConfiguration(configuration, res.getDisplayMetrics()); + } + + return new ContextWrapper(context); + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/language/Utility.kt b/src/main/java/com/nativedevps/support/utility/language/Utility.kt new file mode 100644 index 0000000..1d86c20 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/language/Utility.kt @@ -0,0 +1,16 @@ +package com.nativedevps.support.utility.language + +import android.content.res.Resources +import android.os.Build +import java.util.* + +object Utility { + + fun getDeviceLocale(): Locale? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + Resources.getSystem().configuration.locales.get(0) + } else { + Resources.getSystem().configuration.locale + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/room/DateConverter.kt b/src/main/java/com/nativedevps/support/utility/room/DateConverter.kt new file mode 100644 index 0000000..123f32f --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/room/DateConverter.kt @@ -0,0 +1,17 @@ +package com.nativedevps.support.utility.room + +import androidx.room.TypeConverter +import org.joda.time.DateTime + +class DateConverter { + + @TypeConverter + fun toDate(timestamp: Long?): DateTime? { + return if (timestamp == null) null else DateTime(timestamp) + } + + @TypeConverter + fun toTimestamp(date: DateTime?): Long? { + return date?.millis + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/shared_preference/SharedPreferences.java b/src/main/java/com/nativedevps/support/utility/shared_preference/SharedPreferences.java new file mode 100644 index 0000000..2084404 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/shared_preference/SharedPreferences.java @@ -0,0 +1,513 @@ +package com.nativedevps.support.utility.shared_preference; + + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.SharedPreferences.Editor; +import android.os.Build; +import android.text.TextUtils; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +@SuppressWarnings("unused") +public final class SharedPreferences { + private static final String DEFAULT_SUFFIX = "_preferences"; + private static final String LENGTH = "#LENGTH"; + private static android.content.SharedPreferences mPrefs; + + /** + * Initialize the Prefs helper class to keep a reference to the SharedPreference for this + * application the SharedPreference will use the package name of the application as the Key. + * This method is deprecated please us the new builder. + * + * @param context the Application context. + */ + @Deprecated + public static void initPrefs(Context context) { + new Builder().setContext(context).build(); + } + + private static void initPrefs(Context context, String prefsName, int mode) { + mPrefs = context.getSharedPreferences(prefsName, mode); + } + + /** + * Returns the underlying SharedPreference instance + * + * @return an instance of the SharedPreference + * @throws RuntimeException if SharedPreference instance has not been instantiated yet. + */ + public static android.content.SharedPreferences getPreferences() { + if (mPrefs != null) { + return mPrefs; + } + throw new RuntimeException( + "Prefs class not correctly instantiated. Please call Builder.setContext().build() in the Application class onCreate."); + } + + /** + * @return Returns a map containing a list of pairs key/value representing + * the preferences. + * @see android.content.SharedPreferences#getAll() + */ + public static Map getAll() { + return getPreferences().getAll(); + } + + /** + * Retrieves a stored int value. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * @return Returns the preference value if it exists, or defValue. + * @throws ClassCastException if there is a preference with this name that is not + * an int. + * @see android.content.SharedPreferences#getInt(String, int) + */ + public static int getInt(final String key, final int defValue) { + return getPreferences().getInt(key, defValue); + } + + /** + * Retrieves a stored boolean value. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * @return Returns the preference value if it exists, or defValue. + * @throws ClassCastException if there is a preference with this name that is not a boolean. + * @see android.content.SharedPreferences#getBoolean(String, boolean) + */ + public static boolean getBoolean(final String key, final boolean defValue) { + return getPreferences().getBoolean(key, defValue); + } + + /** + * Retrieves a stored long value. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * @return Returns the preference value if it exists, or defValue. + * @throws ClassCastException if there is a preference with this name that is not a long. + * @see android.content.SharedPreferences#getLong(String, long) + */ + public static long getLong(final String key, final long defValue) { + return getPreferences().getLong(key, defValue); + } + + /** + * Returns the double that has been saved as a long raw bits value in the long preferences. + * + * @param key The name of the preference to retrieve. + * @param defValue the double Value to return if this preference does not exist. + * @return Returns the preference value if it exists, or defValue. + * @throws ClassCastException if there is a preference with this name that is not a long. + * @see android.content.SharedPreferences#getLong(String, long) + */ + public static double getDouble(final String key, final double defValue) { + return Double.longBitsToDouble(getPreferences().getLong(key, Double.doubleToLongBits(defValue))); + } + + /** + * Retrieves a stored float value. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * @return Returns the preference value if it exists, or defValue. + * @throws ClassCastException if there is a preference with this name that is not a float. + * @see android.content.SharedPreferences#getFloat(String, float) + */ + public static float getFloat(final String key, final float defValue) { + return getPreferences().getFloat(key, defValue); + } + + /** + * Retrieves a stored String value. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * @return Returns the preference value if it exists, or defValue. + * @throws ClassCastException if there is a preference with this name that is not a String. + * @see android.content.SharedPreferences#getString(String, String) + */ + public static String getString(final String key, final String defValue) { + return getPreferences().getString(key, defValue); + } + + /** + * Retrieves a Set of Strings as stored by {@link #putStringSet(String, Set)}. On Honeycomb and + * later this will call the native implementation in SharedPreferences, on older SDKs this will + * call {@link #getOrderedStringSet(String, Set)}. + * Note that the native implementation of {@link android.content.SharedPreferences#getStringSet(String, + * Set)} does not reliably preserve the order of the Strings in the Set. + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * @return Returns the preference values if they exist, or defValues otherwise. + * @throws ClassCastException if there is a preference with this name that is not a Set. + * @see android.content.SharedPreferences#getStringSet(String, Set) + * @see #getOrderedStringSet(String, Set) + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static Set getStringSet(final String key, final Set defValue) { + android.content.SharedPreferences prefs = getPreferences(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + return prefs.getStringSet(key, defValue); + } else { + // Workaround for pre-HC's missing getStringSet + return getOrderedStringSet(key, defValue); + } + } + + /** + * Retrieves a Set of Strings as stored by {@link #putOrderedStringSet(String, Set)}, + * preserving the original order. Note that this implementation is heavier than the native + * {@link #getStringSet(String, Set)} method (which does not guarantee to preserve order). + * + * @param key The name of the preference to retrieve. + * @param defValue Value to return if this preference does not exist. + * @return Returns the preference value if it exists, or defValues otherwise. + * @throws ClassCastException if there is a preference with this name that is not a Set of + * Strings. + * @see #getStringSet(String, Set) + */ + public static Set getOrderedStringSet(String key, final Set defValue) { + android.content.SharedPreferences prefs = getPreferences(); + if (prefs.contains(key + LENGTH)) { + LinkedHashSet set = new LinkedHashSet<>(); + int stringSetLength = prefs.getInt(key + LENGTH, -1); + if (stringSetLength >= 0) { + for (int i = 0; i < stringSetLength; i++) { + set.add(prefs.getString(key + "[" + i + "]", null)); + } + } + return set; + } + return defValue; + } + + /** + * Stores a long value. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * @see Editor#putLong(String, long) + */ + public static void putLong(final String key, final long value) { + final Editor editor = getPreferences().edit(); + editor.putLong(key, value); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + editor.commit(); + } else { + editor.apply(); + } + } + + /** + * Stores an integer value. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * @see Editor#putInt(String, int) + */ + public static void putInt(final String key, final int value) { + final Editor editor = getPreferences().edit(); + editor.putInt(key, value); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + editor.commit(); + } else { + editor.apply(); + } + } + + /** + * Stores a double value as a long raw bits value. + * + * @param key The name of the preference to modify. + * @param value The double value to be save in the preferences. + * @see Editor#putLong(String, long) + */ + public static void putDouble(final String key, final double value) { + final Editor editor = getPreferences().edit(); + editor.putLong(key, Double.doubleToRawLongBits(value)); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + editor.commit(); + } else { + editor.apply(); + } + } + + /** + * Stores a float value. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * @see Editor#putFloat(String, float) + */ + public static void putFloat(final String key, final float value) { + final Editor editor = getPreferences().edit(); + editor.putFloat(key, value); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + editor.commit(); + } else { + editor.apply(); + } + } + + /** + * Stores a boolean value. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * @see Editor#putBoolean(String, boolean) + */ + public static void putBoolean(final String key, final boolean value) { + final Editor editor = getPreferences().edit(); + editor.putBoolean(key, value); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + editor.commit(); + } else { + editor.apply(); + } + } + + /** + * Stores a String value. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * @see Editor#putString(String, String) + */ + public static void putString(final String key, final String value) { + final Editor editor = getPreferences().edit(); + editor.putString(key, value); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + editor.commit(); + } else { + editor.apply(); + } + } + + /** + * Stores a Set of Strings. On Honeycomb and later this will call the native implementation in + * SharedPreferences.Editor, on older SDKs this will call {@link #putOrderedStringSet(String, + * Set)}. + * Note that the native implementation of {@link Editor#putStringSet(String, + * Set)} does not reliably preserve the order of the Strings in the Set. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * @see Editor#putStringSet(String, Set) + * @see #putOrderedStringSet(String, Set) + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static void putStringSet(final String key, final Set value) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + final Editor editor = getPreferences().edit(); + editor.putStringSet(key, value); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + editor.commit(); + } else { + editor.apply(); + } + } else { + // Workaround for pre-HC's lack of StringSets + putOrderedStringSet(key, value); + } + } + + /** + * Stores a Set of Strings, preserving the order. + * Note that this method is heavier that the native implementation {@link #putStringSet(String, + * Set)} (which does not reliably preserve the order of the Set). To preserve the order of the + * items in the Set, the Set implementation must be one that as an iterator with predictable + * order, such as {@link LinkedHashSet}. + * + * @param key The name of the preference to modify. + * @param value The new value for the preference. + * @see #putStringSet(String, Set) + * @see #getOrderedStringSet(String, Set) + */ + public static void putOrderedStringSet(String key, Set value) { + final Editor editor = getPreferences().edit(); + int stringSetLength = 0; + if (mPrefs.contains(key + LENGTH)) { + // First read what the value was + stringSetLength = mPrefs.getInt(key + LENGTH, -1); + } + editor.putInt(key + LENGTH, value.size()); + int i = 0; + for (String aValue : value) { + editor.putString(key + "[" + i + "]", aValue); + i++; + } + for (; i < stringSetLength; i++) { + // Remove any remaining values + editor.remove(key + "[" + i + "]"); + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + editor.commit(); + } else { + editor.apply(); + } + } + + /** + * Removes a preference value. + * + * @param key The name of the preference to remove. + * @see Editor#remove(String) + */ + public static void remove(final String key) { + android.content.SharedPreferences prefs = getPreferences(); + final Editor editor = prefs.edit(); + if (prefs.contains(key + LENGTH)) { + // Workaround for pre-HC's lack of StringSets + int stringSetLength = prefs.getInt(key + LENGTH, -1); + if (stringSetLength >= 0) { + editor.remove(key + LENGTH); + for (int i = 0; i < stringSetLength; i++) { + editor.remove(key + "[" + i + "]"); + } + } + } + editor.remove(key); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + editor.commit(); + } else { + editor.apply(); + } + } + + /** + * Checks if a value is stored for the given key. + * + * @param key The name of the preference to check. + * @return {@code true} if the storage contains this key value, {@code false} otherwise. + * @see android.content.SharedPreferences#contains(String) + */ + public static boolean contains(final String key) { + return getPreferences().contains(key); + } + + /** + * Removed all the stored keys and values. + * + * @return the {@link Editor} for chaining. The changes have already been committed/applied + * through the execution of this method. + * @see Editor#clear() + */ + public static Editor clear() { + final Editor editor = getPreferences().edit().clear(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + editor.commit(); + } else { + editor.apply(); + } + return editor; + } + + /** + * Returns the Editor of the underlying SharedPreferences instance. + * + * @return An Editor + */ + public static Editor edit() { + return getPreferences().edit(); + } + + /** + * Builder class for the EasyPrefs instance. You only have to call this once in the Application + * onCreate. And in the rest of the code base you can call Prefs.method name. + */ + public final static class Builder { + + private String mKey; + private Context mContext; + private int mMode = -1; + private boolean mUseDefault = false; + + /** + * Set the filename of the SharedPreference instance. Usually this is the application's + * packagename.xml but it can be modified for migration purposes or customization. + * + * @param prefsName the filename used for the SharedPreference + * @return the {@link SharedPreferences} object. + */ + public Builder setPrefsName(final String prefsName) { + mKey = prefsName; + return this; + } + + /** + * Set the Context used to instantiate the SharedPreferences + * + * @param context the application context + * @return the {@link SharedPreferences} object. + */ + public Builder setContext(final Context context) { + mContext = context; + return this; + } + + /** + * Set the mode of the SharedPreference instance. + * + * @param mode Operating mode. Use 0 or {@link Context#MODE_PRIVATE} for the + * default operation, {@link Context#MODE_WORLD_READABLE} + * @return the {@link SharedPreferences} object. + * @see Context#getSharedPreferences + */ + public Builder setMode(final int mode) { + if (mode == ContextWrapper.MODE_PRIVATE || mode == ContextWrapper.MODE_WORLD_READABLE || mode == ContextWrapper.MODE_WORLD_WRITEABLE || mode == ContextWrapper.MODE_MULTI_PROCESS) { + mMode = mode; + } else { + throw new RuntimeException("The mode in the SharedPreference can only be set too ContextWrapper.MODE_PRIVATE, ContextWrapper.MODE_WORLD_READABLE, ContextWrapper.MODE_WORLD_WRITEABLE or ContextWrapper.MODE_MULTI_PROCESS"); + } + + return this; + } + + /** + * Set the default SharedPreference file name. Often the package name of the application is + * used, but if the {@link android.preference.PreferenceActivity} or {@link + * android.preference.PreferenceFragment} is used the system will append that with + * _preference. + * + * @param defaultSharedPreference true if default SharedPreference name should used. + * @return the {@link SharedPreferences} object. + */ + public Builder setUseDefaultSharedPreference(boolean defaultSharedPreference) { + mUseDefault = defaultSharedPreference; + return this; + } + + /** + * Initialize the SharedPreference instance to used in the application. + * + * @throws RuntimeException if Context has not been set. + */ + public void build() { + if (mContext == null) { + throw new RuntimeException("Context not set, please set context before building the Prefs instance."); + } + + if (TextUtils.isEmpty(mKey)) { + mKey = mContext.getPackageName(); + } + + if (mUseDefault) { + mKey += DEFAULT_SUFFIX; + } + + if (mMode == -1) { + mMode = ContextWrapper.MODE_PRIVATE; + } + + SharedPreferences.initPrefs(mContext, mKey, mMode); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/validation/ValidationUtility.kt b/src/main/java/com/nativedevps/support/utility/validation/ValidationUtility.kt new file mode 100644 index 0000000..6186ccb --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/validation/ValidationUtility.kt @@ -0,0 +1,62 @@ +package com.nativedevps.support.utility.validation + +import android.content.Context +import android.util.Patterns +import android.widget.Toast +import java.util.regex.Pattern + +/** + * Created by pchub on 29-09-2017. + * + * A Simple ValidationUtil class that can validate your inputs + * and can display the error messages on its own so that you + * don't have to bother adding multiple if-else-if statements + * and Toasts for your Validations. + * + * Of course you can modify this file according to your needs + * and implement more methods here to improve the validation of this class. + * + * *Note* + *

+ *     Do not save your activity's context in this class as it will lead to memory leak.
+ *     Only pass your activity's context to the parameters and avoid keeping reference
+ *     of the activity stored inside this class.
+ * 
+ */ +object ValidationUtility { + + fun showToast(context: Context, message: String) = Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + + private fun isNullOrEmpty(input: String?): Boolean = input == null || input.isEmpty() + + fun isValidUsername(username: String?, regex: String = "^[a-zA-Z0-9._-]{3,20}$"): Boolean { + return !isNullOrEmpty(username) && + Pattern.matches(regex, username) + } + + fun isValidEmail(email: String?): Boolean { + return !isNullOrEmpty(email) && Patterns.EMAIL_ADDRESS.matcher(email).matches() + } + + fun isValidMobile(mobile: String?, regex: String = "^[0-9]{10}$"): Boolean { + return !isNullOrEmpty(mobile) && Pattern.matches(regex, mobile) + } + + fun isValidPassword(password: String?): String? { + return when { + isNullOrEmpty(password) -> "Please enter Password first." + password!!.length < 6 -> "Password length should not be less than 6 characters" + password.length > 30 -> "Password length should not be greater than 30 characters" + else -> return null + } + } + + fun isValidName(txtInput: String?): String? { + return when { + isNullOrEmpty(txtInput) -> "Please enter Name first." + txtInput!!.length < 4 -> "Name length should not be less than 4 characters" + txtInput.length > 30 -> "Name length should not be greater than 30 characters" + else -> return null + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/view/DialogBox.kt b/src/main/java/com/nativedevps/support/utility/view/DialogBox.kt new file mode 100644 index 0000000..452a47f --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/view/DialogBox.kt @@ -0,0 +1,68 @@ +package com.nativedevps.support.utility.view + +import android.app.Activity +import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.nativedevps.support.custom_views.ArrayDrawableListViewAdapter +import nativedevps.support.databinding.DialogListBinding + +object DialogBox { + fun Activity.confirmationDialog( + title: String = "Alert", + message: String = "Are you sure?", + isCancellable: Boolean = true, + negativeText: String = "Cancel", + positiveText: String = "Ok", + callback: (success: Boolean) -> Unit, + ): AlertDialog { + val materialAlertDialogBuilder = MaterialAlertDialogBuilder(this) + materialAlertDialogBuilder.setTitle(title) + materialAlertDialogBuilder.setCancelable(isCancellable) + message.let { + materialAlertDialogBuilder.setMessage(it) + } + materialAlertDialogBuilder.setNegativeButton(negativeText) { dialog, which -> + materialAlertDialogBuilder.create().dismiss() + callback(false) + } + materialAlertDialogBuilder.setPositiveButton(positiveText) { dialog, which -> + materialAlertDialogBuilder.create().dismiss() + callback(true) + } + return materialAlertDialogBuilder.show() + } + + fun Activity.listDialog( + title: String? = "Alert", + isCancellable: Boolean = true, + stringList: List>, + negativeText: String? = "Cancel", + callback: (success: Boolean, selection: Pair?) -> Unit, + ): AlertDialog { + var alertDialog: AlertDialog? = null + val binding = DialogListBinding.inflate(layoutInflater) + binding.itemsListView.adapter = ArrayDrawableListViewAdapter(this, stringList) + binding.itemsListView.setOnItemClickListener { adapterView, view, position, id -> + alertDialog?.dismiss() + callback(true, Pair(position, stringList[position].second)) + } + val materialAlertDialogBuilder = MaterialAlertDialogBuilder(this) + materialAlertDialogBuilder.setView(binding.root) + materialAlertDialogBuilder.setTitle(title) + materialAlertDialogBuilder.setCancelable(isCancellable) + + if (negativeText != null) { + materialAlertDialogBuilder.setNegativeButton(negativeText) { dialog, which -> + materialAlertDialogBuilder.create().dismiss() + callback(false, null) + } + } + + return materialAlertDialogBuilder.show().apply { + alertDialog = this + alertDialog?.setOnCancelListener { + callback(false, null) + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nativedevps/support/utility/view/ViewUtils.kt b/src/main/java/com/nativedevps/support/utility/view/ViewUtils.kt new file mode 100644 index 0000000..f7d9ff0 --- /dev/null +++ b/src/main/java/com/nativedevps/support/utility/view/ViewUtils.kt @@ -0,0 +1,137 @@ +package com.nativedevps.support.utility.view + +import android.app.Activity +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Build +import android.os.SystemClock +import android.view.* +import android.widget.FrameLayout +import android.widget.PopupWindow +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.annotation.RequiresApi + + +object ViewUtils { + private const val TAG = "ViewUtils" + fun getViewFromLayout(mActivity: Activity, mContainerID: Int, mLayoutID: Int): View { + val activityContainer = mActivity.findViewById(mContainerID) + activityContainer.removeAllViews() + val vi = mActivity.layoutInflater.inflate(mLayoutID, null) + activityContainer.addView(vi) + return activityContainer + } + + fun getViewFromLayout( + mActivity: Activity, + activityContainer: FrameLayout, + mLayoutID: Int, + ): View { + activityContainer.removeAllViews() + val vi = mActivity.layoutInflater.inflate(mLayoutID, null) + activityContainer.addView(vi) + return activityContainer + } + + fun getViewFromLayout(mActivity: Activity, mLayoutID: Int): View { + return mActivity.layoutInflater.inflate(mLayoutID, null, false) + } + + fun getSmoothAnimation(activity: Activity) { + activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) + } + + fun setViewAndChildrenEnabled(view: View, enabled: Boolean) { + view.isEnabled = enabled + if (view is ViewGroup) { + val viewGroup = view + for (i in 0 until viewGroup.childCount) { + val child = viewGroup.getChildAt(i) + setViewAndChildrenEnabled(child, enabled) + } + } + } + + fun getPopup( + mInstance: Activity?, + popupWindow: PopupWindow?, + contentView: View?, + anchorView: View?, + ): PopupWindow { + var popupWindow = popupWindow + if (popupWindow == null) { + popupWindow = PopupWindow(mInstance) + } + popupWindow.isFocusable = true + popupWindow.contentView = contentView + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + popupWindow.elevation = 5f + } + popupWindow.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + popupWindow.isOutsideTouchable = true + popupWindow.showAsDropDown(anchorView) // where u want show on view click event popupwindow.showAsDropDown(view, x, y); + return popupWindow + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + fun setStatusBarColor(activity: Activity, color: Int) { + val window: Window = activity.window + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + window.setStatusBarColor(color) + } + + fun TextView.setLeftDrawable(@DrawableRes drawable: Int) { + setCompoundDrawablesWithIntrinsicBounds(drawable, 0, 0, 0); + } + + fun TextView.setTopDrawable(@DrawableRes drawable: Int) { + setCompoundDrawablesWithIntrinsicBounds(0, drawable, 0, 0); + } + + fun TextView.setRightDrawable(@DrawableRes drawable: Int) { + setCompoundDrawablesWithIntrinsicBounds(0, 0, drawable, 0); + } + + fun TextView.setBottomDrawable(@DrawableRes drawable: Int) { + setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, drawable); + } + + fun View.setClickListener(onClickEvent: (view: View) -> Unit) { + this.setOnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + onClickEvent(v) + } + return@setOnTouchListener true + } + } + + fun View.click() { + val downTime: Long = SystemClock.uptimeMillis() + val eventTime: Long = SystemClock.uptimeMillis() + 100 + val x = 0.0f + val y = 0.0f + val metaState = 0 + val motionEvent = MotionEvent.obtain( + downTime, + eventTime, + MotionEvent.ACTION_DOWN, + x, + y, + metaState + ) + dispatchTouchEvent(motionEvent) + } + + fun View.gone() { + this.visibility = View.GONE + } + + fun View.visible() { + this.visibility = View.VISIBLE + } + + fun View.invisible() { + this.visibility = View.INVISIBLE + } +} \ No newline at end of file diff --git a/src/main/res/drawable/ic_baseline_account_balance_24.xml b/src/main/res/drawable/ic_baseline_account_balance_24.xml new file mode 100644 index 0000000..97869be --- /dev/null +++ b/src/main/res/drawable/ic_baseline_account_balance_24.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/src/main/res/drawable/ic_baseline_account_balance_wallet_24.xml b/src/main/res/drawable/ic_baseline_account_balance_wallet_24.xml new file mode 100644 index 0000000..d90de7c --- /dev/null +++ b/src/main/res/drawable/ic_baseline_account_balance_wallet_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_add_24.xml b/src/main/res/drawable/ic_baseline_add_24.xml new file mode 100644 index 0000000..bdd99f4 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_add_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_card_membership_24.xml b/src/main/res/drawable/ic_baseline_card_membership_24.xml new file mode 100644 index 0000000..030c8ab --- /dev/null +++ b/src/main/res/drawable/ic_baseline_card_membership_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_credit_card_24.xml b/src/main/res/drawable/ic_baseline_credit_card_24.xml new file mode 100644 index 0000000..9c0fb1f --- /dev/null +++ b/src/main/res/drawable/ic_baseline_credit_card_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_delete_24.xml b/src/main/res/drawable/ic_baseline_delete_24.xml new file mode 100644 index 0000000..3c4030b --- /dev/null +++ b/src/main/res/drawable/ic_baseline_delete_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_language_24.xml b/src/main/res/drawable/ic_baseline_language_24.xml new file mode 100644 index 0000000..3f70646 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_language_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_list_alt_24.xml b/src/main/res/drawable/ic_baseline_list_alt_24.xml new file mode 100644 index 0000000..91683dc --- /dev/null +++ b/src/main/res/drawable/ic_baseline_list_alt_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_logout_24.xml b/src/main/res/drawable/ic_baseline_logout_24.xml new file mode 100644 index 0000000..2059dea --- /dev/null +++ b/src/main/res/drawable/ic_baseline_logout_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_schema_24.xml b/src/main/res/drawable/ic_baseline_schema_24.xml new file mode 100644 index 0000000..0257349 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_schema_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_settings_24.xml b/src/main/res/drawable/ic_baseline_settings_24.xml new file mode 100644 index 0000000..41a82ed --- /dev/null +++ b/src/main/res/drawable/ic_baseline_settings_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_round_contact_page_24.xml b/src/main/res/drawable/ic_round_contact_page_24.xml new file mode 100644 index 0000000..b461e4c --- /dev/null +++ b/src/main/res/drawable/ic_round_contact_page_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/main/res/drawable/ic_round_person_24.xml b/src/main/res/drawable/ic_round_person_24.xml new file mode 100644 index 0000000..64194a2 --- /dev/null +++ b/src/main/res/drawable/ic_round_person_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/layout/activity_actionbar.xml b/src/main/res/layout/activity_actionbar.xml new file mode 100644 index 0000000..82c2cd8 --- /dev/null +++ b/src/main/res/layout/activity_actionbar.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/d_progress_bar.xml b/src/main/res/layout/d_progress_bar.xml new file mode 100644 index 0000000..e88dfa9 --- /dev/null +++ b/src/main/res/layout/d_progress_bar.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/dialog_list.xml b/src/main/res/layout/dialog_list.xml new file mode 100644 index 0000000..c23ecf2 --- /dev/null +++ b/src/main/res/layout/dialog_list.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/item_simple_list_view.xml b/src/main/res/layout/item_simple_list_view.xml new file mode 100644 index 0000000..04e895a --- /dev/null +++ b/src/main/res/layout/item_simple_list_view.xml @@ -0,0 +1,29 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml new file mode 100644 index 0000000..4a893b7 --- /dev/null +++ b/src/main/res/values/colors.xml @@ -0,0 +1,19 @@ + + + #00000000 + #CACACA + #88979797 + #B3000000 + #FFF0E8 + #88FFF9F5 + #F7F7F7\ + #66000000 + #40000000 + #454242 + #32e6e6e6 + #FFFFFF + #00C853 + #eceff1 + #000000 + #0645AD + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml new file mode 100644 index 0000000..a54ed69 --- /dev/null +++ b/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + support + \ No newline at end of file diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml new file mode 100644 index 0000000..6a774d1 --- /dev/null +++ b/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + +