diff --git a/app/build.gradle b/app/build.gradle
index d018d27c9..fc76db8b2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -81,6 +81,8 @@ dependencies {
annotationProcessor 'androidx.room:room-compiler:2.3.0'
implementation "androidx.core:core-ktx:1.6.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
@@ -88,6 +90,7 @@ dependencies {
implementation "com.squareup.okhttp3:logging-interceptor:4.7.2"
implementation 'com.github.transferwise:sequence-layout:1.1.1'
implementation "androidx.browser:browser:1.3.0"
+ implementation 'com.facebook.shimmer:shimmer:0.1.0@aar'
}
repositories {
mavenCentral()
diff --git a/app/schemas/com.parishod.watomatic.model.logs.MessageLogsDB/3.json b/app/schemas/com.parishod.watomatic.model.logs.MessageLogsDB/3.json
new file mode 100644
index 000000000..e7b848ea7
--- /dev/null
+++ b/app/schemas/com.parishod.watomatic.model.logs.MessageLogsDB/3.json
@@ -0,0 +1,149 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 3,
+ "identityHash": "7e7d32963dbe325d983a4c069be65e75",
+ "entities": [
+ {
+ "tableName": "message_logs",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `index` INTEGER NOT NULL, `notif_id` TEXT, `notif_title` TEXT, `notif_arrived_time` INTEGER NOT NULL, `notif_is_replied` INTEGER NOT NULL, `notif_replied_msg` TEXT, `notif_reply_time` INTEGER NOT NULL, FOREIGN KEY(`index`) REFERENCES `app_packages`(`index`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "index",
+ "columnName": "index",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notifId",
+ "columnName": "notif_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "notifTitle",
+ "columnName": "notif_title",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "notifArrivedTime",
+ "columnName": "notif_arrived_time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notifIsReplied",
+ "columnName": "notif_is_replied",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notifRepliedMsg",
+ "columnName": "notif_replied_msg",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "notifReplyTime",
+ "columnName": "notif_reply_time",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_message_logs_index",
+ "unique": false,
+ "columnNames": [
+ "index"
+ ],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_message_logs_index` ON `${TABLE_NAME}` (`index`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "app_packages",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "index"
+ ],
+ "referencedColumns": [
+ "index"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "app_packages",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`index` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `package_name` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "index",
+ "columnName": "index",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "packageName",
+ "columnName": "package_name",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "index"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "supported_apps",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`app_name` TEXT NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`package_name`))",
+ "fields": [
+ {
+ "fieldPath": "name",
+ "columnName": "app_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "packageName",
+ "columnName": "package_name",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "package_name"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7e7d32963dbe325d983a4c069be65e75')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7242233a0..0aae04450 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,13 +6,12 @@
-
+
-
-
-
-
+
+
+
+
+
): RecyclerView.Adapter() {
+ lateinit var dbUtils: DbUtils
+ var newlyAddedApps: MutableList = ArrayList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppsViewHolder {
+ val itemView = LayoutInflater.from(parent.context)
+ .inflate(R.layout.installed_apps_list, parent, false)
+
+ dbUtils = DbUtils(parent.context)
+ newlyAddedApps = ArrayList(dbUtils.supportedApps)
+
+ return AppsViewHolder(itemView)
+ }
+
+ override fun onBindViewHolder(holder: AppsViewHolder, position: Int) {
+ holder.setData(installedAppsList[position])
+ }
+
+ override fun getItemCount(): Int {
+ return installedAppsList.size
+ }
+
+ inner class AppsViewHolder(view: View) : RecyclerView.ViewHolder(view){
+
+ fun setData(app: App){
+ try {
+ val icon: Drawable = itemView.context.packageManager.getApplicationIcon(app.packageName)
+ itemView.appIcon.setImageDrawable(icon)
+ } catch (e: PackageManager.NameNotFoundException) {
+ e.printStackTrace()
+ val matrix = ColorMatrix()
+ matrix.setSaturation(0f) //0 means grayscale
+ val cf = ColorMatrixColorFilter(matrix)
+ itemView.appIcon.colorFilter = cf
+ }
+ itemView.appName.text = app.name
+ itemView.appNameCheckBox.tag = app
+ itemView.appNameCheckBox.isChecked = newlyAddedApps.contains(app)
+ itemView.appNameCheckBox.setOnClickListener{
+ val view = it as CheckBox
+ if(view.isChecked){
+ addToList(view.tag as App)
+ }else{
+ removeFromList(view.tag as App)
+ }
+ }
+ itemView.setOnClickListener {
+ val view = itemView.appNameCheckBox as CheckBox
+ view.isChecked = !view.isChecked //Toggle checkbox
+ if(view.isChecked){
+ addToList(view.tag as App)
+ }else{
+ removeFromList(view.tag as App)
+ }
+ }
+ }
+
+ fun addToList(app: App){
+ if(!dbUtils.isPackageAlreadyAdded(app.packageName)) {
+ dbUtils.insertSupportedApp(app)
+ itemView.context?.let {
+ Toast.makeText(it, "${app.name} added to list.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ fun removeFromList(app: App){
+ dbUtils.removeSupportedApp(app)
+ itemView.context?.let {
+ Toast.makeText(it, "${app.name} removed from list.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/parishod/watomatic/adapter/SupportedAppsAdapter.kt b/app/src/main/java/com/parishod/watomatic/adapter/SupportedAppsAdapter.kt
index 85e431195..43b4ecd41 100644
--- a/app/src/main/java/com/parishod/watomatic/adapter/SupportedAppsAdapter.kt
+++ b/app/src/main/java/com/parishod/watomatic/adapter/SupportedAppsAdapter.kt
@@ -1,17 +1,21 @@
package com.parishod.watomatic.adapter
+import android.content.Intent
+import android.content.Intent.ACTION_VIEW
import android.content.pm.PackageManager
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.drawable.Drawable
+import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.snackbar.Snackbar
import com.google.android.material.switchmaterial.SwitchMaterial
import com.parishod.watomatic.R
-import com.parishod.watomatic.model.App
+import com.parishod.watomatic.model.logs.App
import com.parishod.watomatic.model.preferences.PreferencesManager
import com.parishod.watomatic.model.utils.Constants
import kotlinx.android.synthetic.main.supported_apps_list.view.*
@@ -46,6 +50,7 @@ class SupportedAppsAdapter(private val listType: Constants.EnabledAppsDisplayTyp
class AppsViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun setData(app: App, listType: Constants.EnabledAppsDisplayType, onClickListener: View.OnClickListener?) {
+ var isAppInstalled = true;
try {
val icon: Drawable = itemView.context.packageManager.getApplicationIcon(app.packageName)
itemView.appIcon.setImageDrawable(icon)
@@ -56,6 +61,7 @@ class SupportedAppsAdapter(private val listType: Constants.EnabledAppsDisplayTyp
}
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
+ isAppInstalled = false
if (listType == Constants.EnabledAppsDisplayType.VERTICAL) {
val matrix = ColorMatrix()
matrix.setSaturation(0f) //0 means grayscale
@@ -63,11 +69,14 @@ class SupportedAppsAdapter(private val listType: Constants.EnabledAppsDisplayTyp
itemView.appIcon.colorFilter = cf
(itemView.appEnableSwitch as SwitchMaterial).setOnClickListener {
- Toast.makeText(itemView.context, itemView.context.resources.getString(R.string.app_not_installed_text), Toast.LENGTH_SHORT).show()
+ showSnackBar(itemView, app.packageName, itemView.context.resources.getString(R.string.app_not_installed_text))
itemView.appEnableSwitch.isChecked = false
+ PreferencesManager.getPreferencesInstance(itemView.context).saveEnabledApps(itemView.appEnableSwitch.tag as App, itemView.appEnableSwitch.isChecked)
}
itemView.appIcon.setOnClickListener {
- Toast.makeText(itemView.context, itemView.context.resources.getString(R.string.app_not_installed_text), Toast.LENGTH_SHORT).show()
+ showSnackBar(itemView, app.packageName, itemView.context.resources.getString(R.string.app_not_installed_text))
+ itemView.appEnableSwitch.isChecked = false
+ PreferencesManager.getPreferencesInstance(itemView.context).saveEnabledApps(itemView.appEnableSwitch.tag as App, itemView.appEnableSwitch.isChecked)
}
}
}
@@ -76,21 +85,40 @@ class SupportedAppsAdapter(private val listType: Constants.EnabledAppsDisplayTyp
itemView.appEnableSwitch.text = app.name
itemView.appEnableSwitch.tag = app
itemView.appEnableSwitch.isChecked = PreferencesManager.getPreferencesInstance(itemView.context).isAppEnabled(app)
- itemView.appEnableSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
- val preferencesManager = PreferencesManager.getPreferencesInstance(itemView.context)
- if (!isChecked && preferencesManager.enabledApps.size <= 1) { // Keep at-least one app selected
- // Keep at-least one app selected
- Toast.makeText(
+ if(isAppInstalled) {
+ (itemView.appEnableSwitch as SwitchMaterial).setOnClickListener {
+ val preferencesManager =
+ PreferencesManager.getPreferencesInstance(itemView.context)
+ if (!itemView.appEnableSwitch.isChecked && preferencesManager.enabledApps.size <= 1) { // Keep at-least one app selected
+ // Keep at-least one app selected
+ Toast.makeText(
itemView.context,
itemView.context.resources.getString(R.string.error_atleast_single_app_must_be_selected),
Toast.LENGTH_SHORT
- ).show()
- buttonView.isChecked = true
- } else {
- preferencesManager.saveEnabledApps(buttonView.tag as App, isChecked)
+ ).show()
+ itemView.appEnableSwitch.isChecked = true
+ }else {
+ preferencesManager.saveEnabledApps(
+ itemView.appEnableSwitch.tag as App,
+ itemView.appEnableSwitch.isChecked
+ )
+ }
}
}
}
}
+
+ private fun showSnackBar(itemView: View, packageName: String, msg: String) {
+ val snackBar = Snackbar.make(itemView.rootView.findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG)
+ snackBar.setAction(itemView.context.resources.getString(R.string.install)) {
+ itemView.context.startActivity(
+ Intent(
+ ACTION_VIEW,
+ Uri.parse("https://play.google.com/store/apps/details?id=$packageName")
+ )
+ )
+ }
+ snackBar.show()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/parishod/watomatic/fragment/CustomAppsAdditionFragment.kt b/app/src/main/java/com/parishod/watomatic/fragment/CustomAppsAdditionFragment.kt
new file mode 100644
index 000000000..6e6fbbee0
--- /dev/null
+++ b/app/src/main/java/com/parishod/watomatic/fragment/CustomAppsAdditionFragment.kt
@@ -0,0 +1,94 @@
+package com.parishod.watomatic.fragment
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.LauncherApps
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.UserManager
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.parishod.watomatic.R
+import com.parishod.watomatic.adapter.InstalledAppsAdapter
+import com.parishod.watomatic.model.logs.App
+import com.parishod.watomatic.model.utils.Constants
+import com.parishod.watomatic.model.utils.DbUtils
+import kotlinx.android.synthetic.main.fragment_custom_apps.view.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.util.*
+import kotlin.collections.ArrayList
+
+class CustomAppsAdditionFragment: Fragment(){
+ lateinit var fragmentView: View
+ lateinit var installedApps: List
+ lateinit var installedAppsAdapter: InstalledAppsAdapter
+ val dbUtils: DbUtils by lazy {
+ DbUtils(context)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ fragmentView = inflater.inflate(R.layout.fragment_custom_apps, container, false)
+ fragmentView.shimmerFrameLayout.startShimmerAnimation()
+
+ CoroutineScope(Dispatchers.Main).launch {
+ installedApps = getInstalledApps(context)
+ showInstalledApps()
+ }
+
+ return fragmentView
+ }
+
+ private fun showInstalledApps() {
+ fragmentView.shimmerFrameLayout.stopShimmerAnimation()
+ fragmentView.shimmerFrameLayout.visibility = View.GONE
+
+ val layoutManager = LinearLayoutManager(context)
+ installedAppsAdapter = InstalledAppsAdapter(installedApps)
+ fragmentView.installedAppsList.layoutManager = layoutManager
+ fragmentView.installedAppsList.adapter = installedAppsAdapter
+ }
+
+ private suspend fun getInstalledApps(context: Context?): List =
+ withContext(Dispatchers.Default) {
+ val apps: MutableList = ArrayList()
+ context?.let {
+ val pm: PackageManager = context.packageManager
+ val manager = context.getSystemService(Context.USER_SERVICE) as UserManager?
+ val launcher = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps?
+
+ // Handle multi-profile support introduced in Android 5
+ manager?.let {
+ launcher?.let {
+ for (profile in manager.userProfiles) {
+ for (activityInfo in launcher.getActivityList(null, profile)) {
+ val i = Intent(Intent.ACTION_MAIN)
+ i.component = activityInfo.componentName
+ i.addCategory(Intent.CATEGORY_LAUNCHER)
+ val resolveInfo = pm.resolveActivity(i, 0)
+ resolveInfo?.let {
+ if((resolveInfo.activityInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 1) {
+ val app = App(resolveInfo.loadLabel(pm) as String, resolveInfo.activityInfo.packageName)//AppInfo(resolveInfo.loadLabel(pm) as String, resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.loadIcon(pm))
+ if(!Constants.SUPPORTED_APPS.contains(app) && !apps.contains(app)) {
+ apps.add(app)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return@withContext apps.sortedWith(compareBy { !ArrayList(dbUtils.supportedApps).contains(it) }.thenBy { it.name.lowercase() })
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/parishod/watomatic/fragment/EnabledAppsFragment.kt b/app/src/main/java/com/parishod/watomatic/fragment/EnabledAppsFragment.kt
index 46e0dfb7f..943cfaf2b 100644
--- a/app/src/main/java/com/parishod/watomatic/fragment/EnabledAppsFragment.kt
+++ b/app/src/main/java/com/parishod/watomatic/fragment/EnabledAppsFragment.kt
@@ -1,5 +1,6 @@
package com.parishod.watomatic.fragment
+import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -7,23 +8,56 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.parishod.watomatic.R
+import com.parishod.watomatic.activity.customapp.CustomAppsAdditionActivity
import com.parishod.watomatic.adapter.SupportedAppsAdapter
-import com.parishod.watomatic.model.App
+import com.parishod.watomatic.model.logs.App
import com.parishod.watomatic.model.utils.Constants
+import com.parishod.watomatic.model.utils.CustomDialog
+import com.parishod.watomatic.model.utils.DbUtils
import kotlinx.android.synthetic.main.fragment_enabled_apps.view.*
class EnabledAppsFragment : Fragment() {
-
+ private lateinit var fragmentView: View
+ private lateinit var dbUtils: DbUtils
+ private lateinit var supportedAppsAdapter: SupportedAppsAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- val view: View = inflater.inflate(R.layout.fragment_enabled_apps, container, false)
+ fragmentView = inflater.inflate(R.layout.fragment_enabled_apps, container, false)
val layoutManager = LinearLayoutManager(context)
- val supportedAppsAdapter = SupportedAppsAdapter(Constants.EnabledAppsDisplayType.VERTICAL, ArrayList(Constants.SUPPORTED_APPS), null)
- view.supportedAppsList.layoutManager = layoutManager
- view.supportedAppsList.adapter = supportedAppsAdapter
+ dbUtils = DbUtils(context)
+ supportedAppsAdapter = SupportedAppsAdapter(Constants.EnabledAppsDisplayType.VERTICAL, ArrayList(dbUtils.supportedApps), null)
+ fragmentView.supportedAppsList.layoutManager = layoutManager
+ fragmentView.supportedAppsList.adapter = supportedAppsAdapter
+
+ fragmentView.addCustomPackageButton.setOnClickListener {
+ val bundle = Bundle()
+ bundle.putString(
+ Constants.BETA_FEATURE_ALERT_DIALOG_TITLE,
+ getString(R.string.beta_feature_alert_dialog_title)
+ )
+ bundle.putString(
+ Constants.BETA_FEATURE_ALERT_DIALOG_MSG,
+ getString(R.string.beta_feature_alert_dialog_msg)
+ )
+ CustomDialog(activity).showDialog(bundle, null
+ ) { _, which ->
+ if (which == -2) {
+ //Decline
+ } else {
+ //Accept
+ startActivity(Intent(activity, CustomAppsAdditionActivity::class.java))
+ }
+ }
+ }
+ return fragmentView
+ }
+
+ override fun onResume() {
+ super.onResume()
- return view
+ supportedAppsAdapter = SupportedAppsAdapter(Constants.EnabledAppsDisplayType.VERTICAL, ArrayList(dbUtils.supportedApps), null)
+ fragmentView.supportedAppsList.adapter = supportedAppsAdapter
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/parishod/watomatic/fragment/MainFragment.java b/app/src/main/java/com/parishod/watomatic/fragment/MainFragment.java
index 72580138b..3b39e8cae 100644
--- a/app/src/main/java/com/parishod/watomatic/fragment/MainFragment.java
+++ b/app/src/main/java/com/parishod/watomatic/fragment/MainFragment.java
@@ -41,7 +41,7 @@
import com.parishod.watomatic.activity.enabledapps.EnabledAppsActivity;
import com.parishod.watomatic.activity.settings.SettingsActivity;
import com.parishod.watomatic.adapter.SupportedAppsAdapter;
-import com.parishod.watomatic.model.App;
+import com.parishod.watomatic.model.logs.App;
import com.parishod.watomatic.model.CustomRepliesData;
import com.parishod.watomatic.model.preferences.PreferencesManager;
import com.parishod.watomatic.model.utils.Constants;
@@ -81,6 +81,8 @@ public class MainFragment extends Fragment {
private Activity mActivity;
private SupportedAppsAdapter supportedAppsAdapter;
private List enabledApps = new ArrayList<>();
+ private Set supportedApps;
+ private DbUtils dbUtils;
@Nullable
@Override
@@ -91,6 +93,16 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
mActivity = getActivity();
+ dbUtils = new DbUtils(mActivity);
+ supportedApps = dbUtils.getSupportedApps();
+ if(supportedApps.isEmpty()){
+ supportedApps = Constants.SUPPORTED_APPS;
+ for (App app: supportedApps
+ ) {
+ dbUtils.insertSupportedApp(app);
+ }
+ }
+
customRepliesData = CustomRepliesData.getInstance(mActivity);
preferencesManager = PreferencesManager.getPreferencesInstance(mActivity);
@@ -188,8 +200,8 @@ private List getEnabledApps() {
enabledApps.clear();
}
enabledApps = new ArrayList<>();
- for (App app : Constants.SUPPORTED_APPS) {
- if (preferencesManager.isAppEnabled(app)) {
+ for (App app : supportedApps) {
+ if(preferencesManager.isAppEnabled(app)){
enabledApps.add(app);
}
}
@@ -282,7 +294,6 @@ public static boolean isAppInstalledFromStore(Context context) {
}
private boolean isAppUsedSufficientlyToAskRating() {
- DbUtils dbUtils = new DbUtils(mActivity);
long firstRepliedTime = dbUtils.getFirstRepliedTime();
return firstRepliedTime > 0 && System.currentTimeMillis() - firstRepliedTime > 2 * 24 * 60 * 60 * 1000L && dbUtils.getNunReplies() >= MIN_REPLIES_TO_ASK_APP_RATING;
}
diff --git a/app/src/main/java/com/parishod/watomatic/model/App.kt b/app/src/main/java/com/parishod/watomatic/model/App.kt
deleted file mode 100644
index c43d93362..000000000
--- a/app/src/main/java/com/parishod/watomatic/model/App.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.parishod.watomatic.model
-
-data class App(
- val name: String,
- val packageName: String,
-)
\ No newline at end of file
diff --git a/app/src/main/java/com/parishod/watomatic/model/logs/App.kt b/app/src/main/java/com/parishod/watomatic/model/logs/App.kt
new file mode 100644
index 000000000..1bdf492b2
--- /dev/null
+++ b/app/src/main/java/com/parishod/watomatic/model/logs/App.kt
@@ -0,0 +1,11 @@
+package com.parishod.watomatic.model.logs
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "supported_apps")
+data class App(
+ @ColumnInfo(name = "app_name") val name:String,
+ @PrimaryKey @ColumnInfo(name = "package_name") val packageName:String,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/parishod/watomatic/model/logs/MessageLogsDB.java b/app/src/main/java/com/parishod/watomatic/model/logs/MessageLogsDB.java
index f784bbeef..5583fef46 100644
--- a/app/src/main/java/com/parishod/watomatic/model/logs/MessageLogsDB.java
+++ b/app/src/main/java/com/parishod/watomatic/model/logs/MessageLogsDB.java
@@ -8,7 +8,7 @@
import com.parishod.watomatic.model.utils.Constants;
-@Database(entities = {MessageLog.class, AppPackage.class}, version = 2)
+@Database(entities = {MessageLog.class, AppPackage.class, App.class}, version = 3)
public abstract class MessageLogsDB extends RoomDatabase {
private static final String DB_NAME = Constants.LOGS_DB_NAME;
private static MessageLogsDB _instance;
@@ -26,4 +26,6 @@ public static synchronized MessageLogsDB getInstance(Context context) {
public abstract MessageLogsDao logsDao();
public abstract AppPackageDao appPackageDao();
+
+ public abstract SupportedAppsDao supportedAppsDao();
}
diff --git a/app/src/main/java/com/parishod/watomatic/model/logs/SupportedAppsDao.java b/app/src/main/java/com/parishod/watomatic/model/logs/SupportedAppsDao.java
new file mode 100644
index 000000000..f66ccd837
--- /dev/null
+++ b/app/src/main/java/com/parishod/watomatic/model/logs/SupportedAppsDao.java
@@ -0,0 +1,20 @@
+package com.parishod.watomatic.model.logs;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.Query;
+
+import java.util.List;
+
+@Dao
+public interface SupportedAppsDao {
+ @Query("SELECT * FROM supported_apps")
+ List getSupportedApps();
+ @Insert
+ void insertSupportedApp(App app);
+ @Query("SELECT * FROM supported_apps WHERE package_name=:packageName")
+ App getAppData(String packageName);
+ @Delete
+ void removeSupportedApp(App app);
+}
diff --git a/app/src/main/java/com/parishod/watomatic/model/preferences/PreferencesManager.java b/app/src/main/java/com/parishod/watomatic/model/preferences/PreferencesManager.java
index 285da77c4..cc561f540 100644
--- a/app/src/main/java/com/parishod/watomatic/model/preferences/PreferencesManager.java
+++ b/app/src/main/java/com/parishod/watomatic/model/preferences/PreferencesManager.java
@@ -9,9 +9,9 @@
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.parishod.watomatic.R;
-import com.parishod.watomatic.model.App;
+import com.parishod.watomatic.model.logs.App;
import com.parishod.watomatic.model.utils.AppUtils;
-import com.parishod.watomatic.model.utils.Constants;
+import com.parishod.watomatic.model.utils.DbUtils;
import java.lang.reflect.Type;
import java.util.Collection;
@@ -69,7 +69,7 @@ private void init() {
&& !_sharedPrefs.contains(KEY_SELECTED_APPS_ARR);
if (newInstall) {
// Enable all supported apps for new install
- setAppsAsEnabled(Constants.SUPPORTED_APPS);
+ setAppsAsEnabled(new DbUtils(thisAppContext).getSupportedApps());
// Set notifications ON for new installs
setShowNotificationPref(true);
@@ -124,7 +124,7 @@ public Set getEnabledApps() {
// For upgrading users, preserve functionality by enabling only WhatsApp
// (remove this when time most users would have updated. May be in 3 weeks after deploying this?)
if (enabledAppsJsonStr == null || enabledAppsJsonStr.equals("[]")) {
- enabledAppsJsonStr = setAppsAsEnabled(Collections.singleton(new App("WhatsApp", "com.whatsapp")));
+ enabledAppsJsonStr = setAppsAsEnabled(Collections.singleton(new App( "WhatsApp", "com.whatsapp")));
}
Type type = new TypeToken>() {
diff --git a/app/src/main/java/com/parishod/watomatic/model/utils/Constants.kt b/app/src/main/java/com/parishod/watomatic/model/utils/Constants.kt
index 0894b702d..180e69526 100644
--- a/app/src/main/java/com/parishod/watomatic/model/utils/Constants.kt
+++ b/app/src/main/java/com/parishod/watomatic/model/utils/Constants.kt
@@ -1,6 +1,6 @@
package com.parishod.watomatic.model.utils
-import com.parishod.watomatic.model.App
+import com.parishod.watomatic.model.logs.App
object Constants {
const val PERMISSION_DIALOG_TITLE = "permission_dialog_title"
@@ -8,6 +8,8 @@ object Constants {
const val PERMISSION_DIALOG_DENIED_TITLE = "permission_dialog_denied_title"
const val PERMISSION_DIALOG_DENIED_MSG = "permission_dialog_denied_msg"
const val PERMISSION_DIALOG_DENIED = "permission_dialog_denied"
+ const val BETA_FEATURE_ALERT_DIALOG_TITLE = "beta_feature_alert_dialog_title"
+ const val BETA_FEATURE_ALERT_DIALOG_MSG = "beta_feature_alert_dialog_msg"
const val LOGS_DB_NAME = "logs_messages_db"
const val NOTIFICATION_CHANNEL_ID = "watomatic"
const val NOTIFICATION_CHANNEL_NAME = "watomatic_channel"
diff --git a/app/src/main/java/com/parishod/watomatic/model/utils/CustomDialog.java b/app/src/main/java/com/parishod/watomatic/model/utils/CustomDialog.java
index 4aa57a48b..33a7da5eb 100644
--- a/app/src/main/java/com/parishod/watomatic/model/utils/CustomDialog.java
+++ b/app/src/main/java/com/parishod/watomatic/model/utils/CustomDialog.java
@@ -60,6 +60,14 @@ public void showDialog(Bundle bundle, String type, DialogInterface.OnClickListen
materialAlertDialogBuilder
.setNegativeButton(mContext.getResources().getString(R.string.sure), onClickListener)
.setPositiveButton(mContext.getResources().getString(R.string.retry), onClickListener);
+ } else if (bundle.containsKey(Constants.BETA_FEATURE_ALERT_DIALOG_TITLE)) {
+ materialAlertDialogBuilder = new MaterialAlertDialogBuilder(mContext)
+ .setTitle(bundle.getString(Constants.BETA_FEATURE_ALERT_DIALOG_TITLE))
+ .setIcon(ContextCompat.getDrawable(mContext, R.drawable.ic_alert))
+ .setMessage(bundle.getString(Constants.BETA_FEATURE_ALERT_DIALOG_MSG));
+ materialAlertDialogBuilder
+ .setNegativeButton(mContext.getResources().getString(R.string.decline_auto_start_setting), onClickListener)
+ .setPositiveButton(mContext.getResources().getString(R.string.enable_auto_start_setting), onClickListener);
} else {
materialAlertDialogBuilder = new MaterialAlertDialogBuilder(mContext)
.setTitle(bundle.getString(Constants.PERMISSION_DIALOG_TITLE))
diff --git a/app/src/main/java/com/parishod/watomatic/model/utils/DbUtils.java b/app/src/main/java/com/parishod/watomatic/model/utils/DbUtils.java
index 463f0685a..837f52aec 100644
--- a/app/src/main/java/com/parishod/watomatic/model/utils/DbUtils.java
+++ b/app/src/main/java/com/parishod/watomatic/model/utils/DbUtils.java
@@ -4,10 +4,14 @@
import android.service.notification.StatusBarNotification;
import com.parishod.watomatic.model.CustomRepliesData;
+import com.parishod.watomatic.model.logs.App;
import com.parishod.watomatic.model.logs.AppPackage;
import com.parishod.watomatic.model.logs.MessageLog;
import com.parishod.watomatic.model.logs.MessageLogsDB;
+import java.util.HashSet;
+import java.util.Set;
+
public class DbUtils {
private final Context mContext;
@@ -47,4 +51,24 @@ public long getFirstRepliedTime() {
MessageLogsDB messageLogsDB = MessageLogsDB.getInstance(mContext.getApplicationContext());
return messageLogsDB.logsDao().getFirstRepliedTime();
}
+
+ public Set getSupportedApps(){
+ MessageLogsDB messageLogsDB = MessageLogsDB.getInstance(mContext.getApplicationContext());
+ return new HashSet<>(messageLogsDB.supportedAppsDao().getSupportedApps());
+ }
+
+ public void insertSupportedApp(App app){
+ MessageLogsDB messageLogsDB = MessageLogsDB.getInstance(mContext.getApplicationContext());
+ messageLogsDB.supportedAppsDao().insertSupportedApp(app);
+ }
+
+ public boolean isPackageAlreadyAdded(String packageName){
+ MessageLogsDB messageLogsDB = MessageLogsDB.getInstance(mContext.getApplicationContext());
+ return messageLogsDB.supportedAppsDao().getAppData(packageName) != null;
+ }
+
+ public void removeSupportedApp(App app){
+ MessageLogsDB messageLogsDB = MessageLogsDB.getInstance(mContext.getApplicationContext());
+ messageLogsDB.supportedAppsDao().removeSupportedApp(app);
+ }
}
diff --git a/app/src/main/java/com/parishod/watomatic/model/utils/NotificationHelper.java b/app/src/main/java/com/parishod/watomatic/model/utils/NotificationHelper.java
index b7fb7559e..3b9937a37 100644
--- a/app/src/main/java/com/parishod/watomatic/model/utils/NotificationHelper.java
+++ b/app/src/main/java/com/parishod/watomatic/model/utils/NotificationHelper.java
@@ -14,16 +14,20 @@
import com.parishod.watomatic.BuildConfig;
import com.parishod.watomatic.R;
import com.parishod.watomatic.activity.notification.NotificationIntentActivity;
-import com.parishod.watomatic.model.App;
+import com.parishod.watomatic.model.logs.App;
import org.json.JSONException;
import org.json.JSONObject;
+import java.util.Set;
+
public class NotificationHelper {
final private Context appContext;
private static NotificationHelper _INSTANCE;
private static NotificationManager notificationManager;
private static final JSONObject appsList = new JSONObject();
+ private Set supportedApps;
+ private DbUtils dbUtils;
private NotificationHelper(Context appContext) {
this.appContext = appContext;
@@ -38,7 +42,9 @@ private void init() {
notificationManager.createNotificationChannel(notificationChannel);
}
- for (App supportedApp : Constants.SUPPORTED_APPS) {
+ dbUtils = new DbUtils(appContext);
+ supportedApps = dbUtils.getSupportedApps();
+ for (App supportedApp : supportedApps) {
try {
appsList.put(supportedApp.getPackageName(), false);
} catch (JSONException e) {
@@ -55,7 +61,7 @@ public static NotificationHelper getInstance(Context context) {
}
public void sendNotification(String title, String message, String packageName) {
- for (App supportedApp : Constants.SUPPORTED_APPS) {
+ for (App supportedApp : supportedApps) {
if (supportedApp.getPackageName().equalsIgnoreCase(packageName)) {
title = supportedApp.getName() + ":" + title;
break;
@@ -79,7 +85,7 @@ public void sendNotification(String title, String message, String packageName) {
//logic to detect if notifications exists else generate summary notification
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
StatusBarNotification[] notifications = notificationManager.getActiveNotifications();
- for (App supportedApp : Constants.SUPPORTED_APPS) {
+ for (App supportedApp : supportedApps) {
try {
appsList.put(supportedApp.getPackageName(), false);
} catch (JSONException e) {
diff --git a/app/src/main/res/drawable/ic_check_circle.xml b/app/src/main/res/drawable/ic_check_circle.xml
new file mode 100644
index 000000000..02c9464af
--- /dev/null
+++ b/app/src/main/res/drawable/ic_check_circle.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_plus_24.xml b/app/src/main/res/drawable/ic_plus_24.xml
new file mode 100644
index 000000000..70046c48f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_plus_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_custom_apps.xml b/app/src/main/res/layout/activity_custom_apps.xml
new file mode 100644
index 000000000..2cce64df0
--- /dev/null
+++ b/app/src/main/res/layout/activity_custom_apps.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_custom_apps.xml b/app/src/main/res/layout/fragment_custom_apps.xml
new file mode 100644
index 000000000..e4cf5abb1
--- /dev/null
+++ b/app/src/main/res/layout/fragment_custom_apps.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_enabled_apps.xml b/app/src/main/res/layout/fragment_enabled_apps.xml
index 42ac1f42c..ac463aa66 100644
--- a/app/src/main/res/layout/fragment_enabled_apps.xml
+++ b/app/src/main/res/layout/fragment_enabled_apps.xml
@@ -1,22 +1,39 @@
-
-
-
+ android:layout_height="match_parent">
+
+
+
+
+
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/installed_apps_list.xml b/app/src/main/res/layout/installed_apps_list.xml
new file mode 100644
index 000000000..05d93fa87
--- /dev/null
+++ b/app/src/main/res/layout/installed_apps_list.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/shimmer_placeholder_layout.xml b/app/src/main/res/layout/shimmer_placeholder_layout.xml
new file mode 100644
index 000000000..aeca69c2d
--- /dev/null
+++ b/app/src/main/res/layout/shimmer_placeholder_layout.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 406503ba6..29daf7dd2 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -13,4 +13,5 @@
#FF018786
#FF000000
#FFFFFFFF
+ #E5E4E2
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 97f246f0e..b1d71cf50 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -190,6 +190,9 @@
of monthly goal
Liberapay
Paypal
+ CustomApps
+ Install
+ Add Custom Package(Beta)
Add custom contact name
Contact read access is required in order to filter replies by contact names. If the access is not enabled, only manually entered names will be allowed. Do you want to enable it?
Enable contact permission
@@ -203,4 +206,6 @@
Current donation progress
Name cannot be blank
Name already present
+ Warning!
+ This Feature is in Beta. Not tested to work with apps in the list and can cause unintended issues.
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..fae925cd3
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file