Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create custom compose switch and replace material switch with it #362

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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'
Expand Down
88 changes: 47 additions & 41 deletions app/src/main/java/com/parishod/watomatic/fragment/MainFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -50,6 +49,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;
Expand All @@ -75,7 +75,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;
Expand All @@ -88,6 +87,7 @@ public class MainFragment extends Fragment {
private GridLayoutManager layoutManager;
private SupportedAppsAdapter supportedAppsAdapter;
private List<App> enabledApps = new ArrayList<>();
private ComposeSwitchView mainAutoReplySwitch, groupReplySwitch;

@Nullable
@Override
Expand All @@ -103,7 +103,47 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c

// Assign Views
mainAutoReplySwitch = view.findViewById(R.id.mainAutoReplySwitch);
mainAutoReplySwitch.setOnClick(() -> {
// Toast.makeText(mActivity, "switch clicked " + !mainAutoReplySwitch1.getChecked(), Toast.LENGTH_LONG).show();
preferencesManager.setServicePref(!mainAutoReplySwitch.getChecked());
setSwitchState();
return null;
});

mainAutoReplySwitch.setOnChecked(() -> {
// Toast.makeText(mActivity, "switch clicked " + mainAutoReplySwitch1.getChecked(), Toast.LENGTH_LONG).show();
preferencesManager.setServicePref(mainAutoReplySwitch.getChecked());
setSwitchState();
return null;
});

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(!groupReplySwitch.getChecked());
groupReplySwitch.setChecked(!groupReplySwitch.getChecked());
}else{
Toast.makeText(mActivity, R.string.group_reply_on_info_message, Toast.LENGTH_SHORT).show();
}
return null;
});

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(groupReplySwitch.getChecked());
return null;
});

autoReplyTextPreviewCard = view.findViewById(R.id.mainAutoReplyTextCardView);
autoReplyTextPreview = view.findViewById(R.id.textView4);
supportedAppsCard = view.findViewById(R.id.supportedAppsSelectorCardView);
Expand Down Expand Up @@ -131,43 +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
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);
});
groupReplySwitch.setSwitchEnabled(mainAutoReplySwitch.getChecked());

imgMinus.setOnClickListener(v -> {
if(days > MIN_DAYS){
Expand Down Expand Up @@ -244,6 +248,7 @@ public void onResume() {
preferencesManager.setServicePref(false);
}

mainAutoReplySwitch.setChecked(preferencesManager.isServiceEnabled());
/*if(!preferencesManager.isServiceEnabled()){
enableService(false);
}*/
Expand Down Expand Up @@ -419,9 +424,10 @@ private Intent rateIntentForUrl(String url)
}

private void setSwitchState(){
mainAutoReplySwitch.setTitleText(preferencesManager.isServiceEnabled() ? getString(R.string.mainAutoReplySwitchOnLabel) : getString(R.string.mainAutoReplySwitchOffLabel));
mainAutoReplySwitch.setChecked(preferencesManager.isServiceEnabled());
groupReplySwitch.setEnabled(preferencesManager.isServiceEnabled());
enableOrDisableEnabledAppsCheckboxes(mainAutoReplySwitch.isChecked());
groupReplySwitch.setSwitchEnabled(preferencesManager.isServiceEnabled());
enableOrDisableEnabledAppsCheckboxes(mainAutoReplySwitch.getChecked());
}

//https://stackoverflow.com/questions/20141727/check-if-user-has-granted-notificationlistener-access-to-my-app/28160115
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
})
}
}
}
}
39 changes: 13 additions & 26 deletions app/src/main/res/layout/fragment_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,24 @@
app:layout_constraintTop_toBottomOf="@+id/textView" />

<!-- Main Auto Reply enable switch -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/mainAutoReplySwitchCardView"
<com.parishod.watomatic.views.customviews.ComposeSwitchView
android:id="@+id/mainAutoReplySwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toTopOf="@id/mainAutoReplyTextCardView"
app:layout_constraintTop_toBottomOf="@+id/textView5"
tools:layout_editor_absoluteX="154dp">

<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/mainAutoReplySwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="@string/mainAutoReplySwitchOffLabel" />
</com.google.android.material.card.MaterialCardView>
android:padding="2dp"
tools:layout_editor_absoluteX="154dp"/>

<!-- Custom auto reply text card -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/mainAutoReplyTextCardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:layout_constraintTop_toBottomOf="@+id/mainAutoReplySwitchCardView"
app:layout_constraintTop_toBottomOf="@+id/mainAutoReplySwitch"
tools:layout_editor_absoluteX="134dp">

<androidx.constraintlayout.widget.ConstraintLayout
Expand Down Expand Up @@ -156,29 +150,22 @@
</com.google.android.material.card.MaterialCardView>

<!-- Group reply switch card -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/groupReplySwitchCardView"
<com.parishod.watomatic.views.customviews.ComposeSwitchView
android:id="@+id/groupReplySwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
app:layout_constraintTop_toBottomOf="@+id/supportedAppsSelectorCardView"
tools:layout_editor_absoluteX="154dp">

<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/groupReplySwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="@string/groupReplySwitchLabel" />
</com.google.android.material.card.MaterialCardView>
tools:layout_editor_absoluteX="154dp"/>

<!-- Reply frequency Time picker -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/replyFrequencyTimePickerCardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:layout_constraintTop_toBottomOf="@+id/groupReplySwitchCardView"
app:layout_constraintTop_toBottomOf="@+id/groupReplySwitch"
tools:layout_editor_absoluteX="134dp">

<androidx.constraintlayout.widget.ConstraintLayout
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.5.20'
ext.kotlin_version = '1.5.21'
repositories {
google()
jcenter()
Expand Down