From 4aceb4aa4f173d2e9aa5456f30277837048eadeb Mon Sep 17 00:00:00 2001 From: spuday90 Date: Fri, 27 Aug 2021 09:01:50 +0530 Subject: [PATCH 1/2] create custom compose switch and replace material switch with it --- app/build.gradle | 23 +++- .../watomatic/fragment/MainFragment.java | 95 ++++++++------- .../views/customviews/ComposeSwitchView.kt | 113 ++++++++++++++++++ app/src/main/res/layout/fragment_main.xml | 39 ++---- build.gradle | 2 +- 5 files changed, 200 insertions(+), 72 deletions(-) create mode 100644 app/src/main/java/com/parishod/watomatic/views/customviews/ComposeSwitchView.kt diff --git a/app/build.gradle b/app/build.gradle index b44501656..5d098e317 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -56,7 +56,17 @@ android { buildFeatures { viewBinding = true dataBinding = true + compose true } + kotlinOptions { + jvmTarget = '1.8' + } + + composeOptions { + kotlinCompilerVersion kotlin_version + kotlinCompilerExtensionVersion '1.0.1' + } + flavorDimensions 'version' productFlavors { GooglePlay { @@ -75,11 +85,22 @@ dependencies { implementation 'com.google.android.material:material:1.4.0-rc01' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.room:room-runtime:2.3.0' + implementation 'androidx.compose.ui:ui:1.0.1' + // Compose Material Design + implementation 'androidx.compose.material:material:1.0.1' + // Tooling support (Previews, etc.) + implementation 'androidx.compose.ui:ui-tooling:1.0.1' + // Animations + implementation 'androidx.compose.animation:animation:1.0.1' + //Integration with ViewModels + implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07' + // When using a AppCompat theme + implementation "com.google.accompanist:accompanist-appcompat-theme:0.16.0" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' annotationProcessor 'androidx.room:room-compiler:2.3.0' - implementation "androidx.core:core-ktx:1.5.0" + implementation "androidx.core:core-ktx:1.6.0" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.squareup.retrofit2:retrofit:2.9.0' 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 0a805432b..15071e4d2 100644 --- a/app/src/main/java/com/parishod/watomatic/fragment/MainFragment.java +++ b/app/src/main/java/com/parishod/watomatic/fragment/MainFragment.java @@ -50,6 +50,7 @@ import com.parishod.watomatic.model.utils.CustomDialog; import com.parishod.watomatic.model.utils.DbUtils; import com.parishod.watomatic.model.utils.ServieUtils; +import com.parishod.watomatic.views.customviews.ComposeSwitchView; import java.util.ArrayList; import java.util.Arrays; @@ -75,7 +76,6 @@ public class MainFragment extends Fragment { TextView autoReplyTextPreview, timeSelectedTextPreview, timePickerSubTitleTextPreview; CustomRepliesData customRepliesData; String autoReplyTextPlaceholder; - SwitchMaterial mainAutoReplySwitch, groupReplySwitch; CardView supportedAppsCard; private PreferencesManager preferencesManager; private int days = 0; @@ -88,6 +88,7 @@ public class MainFragment extends Fragment { private GridLayoutManager layoutManager; private SupportedAppsAdapter supportedAppsAdapter; private List enabledApps = new ArrayList<>(); + private ComposeSwitchView mainAutoReplySwitch1, groupReplySwitch1; @Nullable @Override @@ -102,8 +103,48 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c preferencesManager = PreferencesManager.getPreferencesInstance(mActivity); // Assign Views - mainAutoReplySwitch = view.findViewById(R.id.mainAutoReplySwitch); - groupReplySwitch = view.findViewById(R.id.groupReplySwitch); + mainAutoReplySwitch1 = view.findViewById(R.id.mainAutoReplySwitch1); + mainAutoReplySwitch1.setOnClick(() -> { +// Toast.makeText(mActivity, "switch clicked " + !mainAutoReplySwitch1.getChecked(), Toast.LENGTH_LONG).show(); + preferencesManager.setServicePref(!mainAutoReplySwitch1.getChecked()); + setSwitchState(); + return null; + }); + + mainAutoReplySwitch1.setOnChecked(() -> { +// Toast.makeText(mActivity, "switch clicked " + mainAutoReplySwitch1.getChecked(), Toast.LENGTH_LONG).show(); + preferencesManager.setServicePref(mainAutoReplySwitch1.getChecked()); + setSwitchState(); + return null; + }); + + groupReplySwitch1 = view.findViewById(R.id.groupReplySwitch1); + groupReplySwitch1.setTitleText(getString(R.string.groupReplySwitchLabel)); + groupReplySwitch1.setOnClick(() -> { + if(groupReplySwitch1.getSwitchEnabled()) { + if (!groupReplySwitch1.getChecked()) { + Toast.makeText(mActivity, R.string.group_reply_on_info_message, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(mActivity, R.string.group_reply_off_info_message, Toast.LENGTH_SHORT).show(); + } + preferencesManager.setGroupReplyPref(!groupReplySwitch1.getChecked()); + groupReplySwitch1.setChecked(!groupReplySwitch1.getChecked()); + }else{ + Toast.makeText(mActivity, R.string.group_reply_on_info_message, Toast.LENGTH_SHORT).show(); + } + return null; + }); + + groupReplySwitch1.setOnChecked(() -> { + if(groupReplySwitch1.getChecked()){ + Toast.makeText(mActivity, R.string.group_reply_on_info_message, Toast.LENGTH_SHORT).show(); + }else{ + Toast.makeText(mActivity, R.string.group_reply_off_info_message, Toast.LENGTH_SHORT).show(); + } + preferencesManager.setGroupReplyPref(groupReplySwitch1.getChecked()); + return null; + }); + autoReplyTextPreviewCard = view.findViewById(R.id.mainAutoReplyTextCardView); autoReplyTextPreview = view.findViewById(R.id.textView4); supportedAppsCard = view.findViewById(R.id.supportedAppsSelectorCardView); @@ -131,43 +172,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c autoReplyTextPreviewCard.setOnClickListener(this::openCustomReplyEditorActivity); autoReplyTextPreview.setText(customRepliesData.getTextToSendOrElse(autoReplyTextPlaceholder)); // Enable group chat switch only if main switch id ON - groupReplySwitch.setEnabled(mainAutoReplySwitch.isChecked()); - - mainAutoReplySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - if(isChecked && !isListenerEnabled(mActivity, NotificationService.class)){ -// launchNotificationAccessSettings(); - showPermissionsDialog(); - }else { - preferencesManager.setServicePref(isChecked); - if(isChecked){ - startNotificationService(); - }else{ - stopNotificationService(); - } - mainAutoReplySwitch.setText( - isChecked - ? R.string.mainAutoReplySwitchOnLabel - : R.string.mainAutoReplySwitchOffLabel - ); - - setSwitchState(); - - // Enable group chat switch only if main switch id ON - groupReplySwitch.setEnabled(isChecked); - } - }); - - groupReplySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - // Ignore if this is not triggered by user action but just UI update in onResume() #62 - if (preferencesManager.isGroupReplyEnabled() == isChecked) { return;} - - if(isChecked){ - Toast.makeText(mActivity, R.string.group_reply_on_info_message, Toast.LENGTH_SHORT).show(); - }else{ - Toast.makeText(mActivity, R.string.group_reply_off_info_message, Toast.LENGTH_SHORT).show(); - } - preferencesManager.setGroupReplyPref(isChecked); - }); + groupReplySwitch1.setSwitchEnabled(mainAutoReplySwitch1.getChecked()); imgMinus.setOnClickListener(v -> { if(days > MIN_DAYS){ @@ -244,13 +249,14 @@ public void onResume() { preferencesManager.setServicePref(false); } + mainAutoReplySwitch1.setChecked(preferencesManager.isServiceEnabled()); /*if(!preferencesManager.isServiceEnabled()){ enableService(false); }*/ setSwitchState(); // set group chat switch state - groupReplySwitch.setChecked(preferencesManager.isGroupReplyEnabled()); + groupReplySwitch1.setChecked(preferencesManager.isGroupReplyEnabled()); // Set user auto reply text autoReplyTextPreview.setText(customRepliesData.getTextToSendOrElse(autoReplyTextPlaceholder)); @@ -419,9 +425,10 @@ private Intent rateIntentForUrl(String url) } private void setSwitchState(){ - mainAutoReplySwitch.setChecked(preferencesManager.isServiceEnabled()); - groupReplySwitch.setEnabled(preferencesManager.isServiceEnabled()); - enableOrDisableEnabledAppsCheckboxes(mainAutoReplySwitch.isChecked()); + mainAutoReplySwitch1.setTitleText(preferencesManager.isServiceEnabled() ? getString(R.string.mainAutoReplySwitchOnLabel) : getString(R.string.mainAutoReplySwitchOffLabel)); + mainAutoReplySwitch1.setChecked(preferencesManager.isServiceEnabled()); + groupReplySwitch1.setSwitchEnabled(preferencesManager.isServiceEnabled()); + enableOrDisableEnabledAppsCheckboxes(mainAutoReplySwitch1.getChecked()); } //https://stackoverflow.com/questions/20141727/check-if-user-has-granted-notificationlistener-access-to-my-app/28160115 diff --git a/app/src/main/java/com/parishod/watomatic/views/customviews/ComposeSwitchView.kt b/app/src/main/java/com/parishod/watomatic/views/customviews/ComposeSwitchView.kt new file mode 100644 index 000000000..0112328e8 --- /dev/null +++ b/app/src/main/java/com/parishod/watomatic/views/customviews/ComposeSwitchView.kt @@ -0,0 +1,113 @@ +package com.parishod.watomatic.views.customviews + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.AbstractComposeView +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.accompanist.appcompattheme.AppCompatTheme + +//REF: https://compose.academy/blog/migrating_to_compose_-_composeview +class ComposeSwitchView@JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : AbstractComposeView(context, attrs, defStyleAttr) { + + + // Create a State for the title text so any changes can be observed and reflected automatically + // in our Composable Text. + var titleText by mutableStateOf("") + var onClick by mutableStateOf({}) + var onChecked by mutableStateOf({}) + var checked by mutableStateOf(false) + var switchEnabled by mutableStateOf(true) + + @ExperimentalMaterialApi + @Composable + override fun Content() { + AppCompatTheme{ + MaterialSwitchComponent(onClick) + } + } + + // We represent a Composable function by annotating it with the @Composable annotation. Composable +// functions can only be called from within the scope of other composable functions. We should +// think of composable functions to be similar to lego blocks - each composable function is in turn +// built up of smaller composable functions. + @ExperimentalMaterialApi + @SuppressLint("UnrememberedMutableState") + @Composable + fun MaterialSwitchComponent( + onClick: () -> Unit, + ) { + // Reacting to state changes is the core behavior of Compose. You will notice a couple new + // keywords that are compose related - remember & mutableStateOf.remember{} is a helper + // composable that calculates the value passed to it only during the first composition. It then + // returns the same value for every subsequent composition. Next, you can think of + // mutableStateOf as an observable value where updates to this variable will redraw all + // the composable functions that access it. We don't need to explicitly subscribe at all. Any + // composable that reads its value will be recomposed any time the value + // changes. This ensures that only the composables that depend on this will be redraw while the + // rest remain unchanged. This ensures efficiency and is a performance optimization. It + // is inspired from existing frameworks like React. + //var checked by remember { mutableStateOf(false) } + + // Card composable is a predefined composable that is meant to represent the card surface as + // specified by the Material Design specification. We also configure it to have rounded + // corners and apply a modifier. + + // You can think of Modifiers as implementations of the decorators pattern that are used to + // modify the composable that its applied to. In the example below, we add a padding of + // 8dp to the Card composable. In addition, we configure it out occupy the entire available + // width using the Modifier.fillMaxWidth() modifier. + Card( + modifier = Modifier + .padding(8.dp) + .fillMaxWidth(), + border = BorderStroke(1.5.dp, + when{ + !switchEnabled -> Color(192, 192, 192) + checked -> MaterialTheme.colors.secondaryVariant + else -> Color(255, 69, 0) + }), + shape = RoundedCornerShape(5.dp), + onClick = onClick, + backgroundColor = Color(255, 255, 255) + ) { + // Row is a composable that places its children in a horizontal sequence. You can think of it + // similar to a LinearLayout with the horizontal orientation. In addition, we pass a modifier + // to the Row composable. + Row(modifier = Modifier.padding(16.dp), Arrangement.SpaceBetween) { + // The Text composable is pre-defined by the Compose UI library; you can use this + // composable to render text on the screen + Text(text = titleText, modifier = Modifier.padding(8.dp)) + // A pre-defined composable that's capable of rendering a switch. It honors the Material + // Design specification. + Switch( + modifier = Modifier + .padding(8.dp), + enabled = switchEnabled, + checked = checked, + onCheckedChange = { + checked = !checked + onChecked.invoke() + }) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index b7b1229cd..765244faf 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -32,22 +32,16 @@ app:layout_constraintTop_toBottomOf="@+id/textView" /> - - - - + android:padding="2dp" + tools:layout_editor_absoluteX="154dp"/> - - - - + tools:layout_editor_absoluteX="154dp"/> Date: Wed, 1 Sep 2021 07:38:51 +0530 Subject: [PATCH 2/2] refactor rename switches --- .../watomatic/fragment/MainFragment.java | 47 +++++++++---------- app/src/main/res/layout/fragment_main.xml | 8 ++-- 2 files changed, 27 insertions(+), 28 deletions(-) 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 15071e4d2..213879bbb 100644 --- a/app/src/main/java/com/parishod/watomatic/fragment/MainFragment.java +++ b/app/src/main/java/com/parishod/watomatic/fragment/MainFragment.java @@ -34,7 +34,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.checkbox.MaterialCheckBox; -import com.google.android.material.switchmaterial.SwitchMaterial; import com.parishod.watomatic.BuildConfig; import com.parishod.watomatic.NotificationService; import com.parishod.watomatic.R; @@ -88,7 +87,7 @@ public class MainFragment extends Fragment { private GridLayoutManager layoutManager; private SupportedAppsAdapter supportedAppsAdapter; private List enabledApps = new ArrayList<>(); - private ComposeSwitchView mainAutoReplySwitch1, groupReplySwitch1; + private ComposeSwitchView mainAutoReplySwitch, groupReplySwitch; @Nullable @Override @@ -103,45 +102,45 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c preferencesManager = PreferencesManager.getPreferencesInstance(mActivity); // Assign Views - mainAutoReplySwitch1 = view.findViewById(R.id.mainAutoReplySwitch1); - mainAutoReplySwitch1.setOnClick(() -> { + mainAutoReplySwitch = view.findViewById(R.id.mainAutoReplySwitch); + mainAutoReplySwitch.setOnClick(() -> { // Toast.makeText(mActivity, "switch clicked " + !mainAutoReplySwitch1.getChecked(), Toast.LENGTH_LONG).show(); - preferencesManager.setServicePref(!mainAutoReplySwitch1.getChecked()); + preferencesManager.setServicePref(!mainAutoReplySwitch.getChecked()); setSwitchState(); return null; }); - mainAutoReplySwitch1.setOnChecked(() -> { + mainAutoReplySwitch.setOnChecked(() -> { // Toast.makeText(mActivity, "switch clicked " + mainAutoReplySwitch1.getChecked(), Toast.LENGTH_LONG).show(); - preferencesManager.setServicePref(mainAutoReplySwitch1.getChecked()); + preferencesManager.setServicePref(mainAutoReplySwitch.getChecked()); setSwitchState(); return null; }); - groupReplySwitch1 = view.findViewById(R.id.groupReplySwitch1); - groupReplySwitch1.setTitleText(getString(R.string.groupReplySwitchLabel)); - groupReplySwitch1.setOnClick(() -> { - if(groupReplySwitch1.getSwitchEnabled()) { - if (!groupReplySwitch1.getChecked()) { + groupReplySwitch = view.findViewById(R.id.groupReplySwitch); + groupReplySwitch.setTitleText(getString(R.string.groupReplySwitchLabel)); + groupReplySwitch.setOnClick(() -> { + if(groupReplySwitch.getSwitchEnabled()) { + if (!groupReplySwitch.getChecked()) { Toast.makeText(mActivity, R.string.group_reply_on_info_message, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(mActivity, R.string.group_reply_off_info_message, Toast.LENGTH_SHORT).show(); } - preferencesManager.setGroupReplyPref(!groupReplySwitch1.getChecked()); - groupReplySwitch1.setChecked(!groupReplySwitch1.getChecked()); + preferencesManager.setGroupReplyPref(!groupReplySwitch.getChecked()); + groupReplySwitch.setChecked(!groupReplySwitch.getChecked()); }else{ Toast.makeText(mActivity, R.string.group_reply_on_info_message, Toast.LENGTH_SHORT).show(); } return null; }); - groupReplySwitch1.setOnChecked(() -> { - if(groupReplySwitch1.getChecked()){ + groupReplySwitch.setOnChecked(() -> { + if(groupReplySwitch.getChecked()){ Toast.makeText(mActivity, R.string.group_reply_on_info_message, Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(mActivity, R.string.group_reply_off_info_message, Toast.LENGTH_SHORT).show(); } - preferencesManager.setGroupReplyPref(groupReplySwitch1.getChecked()); + preferencesManager.setGroupReplyPref(groupReplySwitch.getChecked()); return null; }); @@ -172,7 +171,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c autoReplyTextPreviewCard.setOnClickListener(this::openCustomReplyEditorActivity); autoReplyTextPreview.setText(customRepliesData.getTextToSendOrElse(autoReplyTextPlaceholder)); // Enable group chat switch only if main switch id ON - groupReplySwitch1.setSwitchEnabled(mainAutoReplySwitch1.getChecked()); + groupReplySwitch.setSwitchEnabled(mainAutoReplySwitch.getChecked()); imgMinus.setOnClickListener(v -> { if(days > MIN_DAYS){ @@ -249,14 +248,14 @@ public void onResume() { preferencesManager.setServicePref(false); } - mainAutoReplySwitch1.setChecked(preferencesManager.isServiceEnabled()); + mainAutoReplySwitch.setChecked(preferencesManager.isServiceEnabled()); /*if(!preferencesManager.isServiceEnabled()){ enableService(false); }*/ setSwitchState(); // set group chat switch state - groupReplySwitch1.setChecked(preferencesManager.isGroupReplyEnabled()); + groupReplySwitch.setChecked(preferencesManager.isGroupReplyEnabled()); // Set user auto reply text autoReplyTextPreview.setText(customRepliesData.getTextToSendOrElse(autoReplyTextPlaceholder)); @@ -425,10 +424,10 @@ private Intent rateIntentForUrl(String url) } private void setSwitchState(){ - mainAutoReplySwitch1.setTitleText(preferencesManager.isServiceEnabled() ? getString(R.string.mainAutoReplySwitchOnLabel) : getString(R.string.mainAutoReplySwitchOffLabel)); - mainAutoReplySwitch1.setChecked(preferencesManager.isServiceEnabled()); - groupReplySwitch1.setSwitchEnabled(preferencesManager.isServiceEnabled()); - enableOrDisableEnabledAppsCheckboxes(mainAutoReplySwitch1.getChecked()); + mainAutoReplySwitch.setTitleText(preferencesManager.isServiceEnabled() ? getString(R.string.mainAutoReplySwitchOnLabel) : getString(R.string.mainAutoReplySwitchOffLabel)); + mainAutoReplySwitch.setChecked(preferencesManager.isServiceEnabled()); + groupReplySwitch.setSwitchEnabled(preferencesManager.isServiceEnabled()); + enableOrDisableEnabledAppsCheckboxes(mainAutoReplySwitch.getChecked()); } //https://stackoverflow.com/questions/20141727/check-if-user-has-granted-notificationlistener-access-to-my-app/28160115 diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 765244faf..c5f043712 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -33,7 +33,7 @@