From 6b032b751324a30120cfaabe88940f95171df11f Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 29 Dec 2020 00:54:08 +0100 Subject: new year cleanup: restructure messy project --- .../base/views/IconCheckboxEntry.java | 86 +++++++++++++++ .../bitmaskclient/base/views/IconSwitchEntry.java | 116 +++++++++++++++++++++ .../bitmaskclient/base/views/IconTextEntry.java | 106 +++++++++++++++++++ .../bitmaskclient/base/views/IconTextView.java | 96 +++++++++++++++++ .../base/views/ProviderHeaderView.java | 109 +++++++++++++++++++ .../bitmaskclient/base/views/VpnStateImage.java | 99 ++++++++++++++++++ 6 files changed, 612 insertions(+) create mode 100644 app/src/main/java/se/leap/bitmaskclient/base/views/IconCheckboxEntry.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/base/views/IconTextEntry.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/base/views/IconTextView.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/base/views/ProviderHeaderView.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/base/views/VpnStateImage.java (limited to 'app/src/main/java/se/leap/bitmaskclient/base/views') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/IconCheckboxEntry.java b/app/src/main/java/se/leap/bitmaskclient/base/views/IconCheckboxEntry.java new file mode 100644 index 00000000..fdbd7dbd --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/IconCheckboxEntry.java @@ -0,0 +1,86 @@ +package se.leap.bitmaskclient.base.views; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.drawable.Drawable; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.appcompat.widget.AppCompatImageView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import butterknife.ButterKnife; +import butterknife.InjectView; +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.fragments.TetheringDialog; + + +public class IconCheckboxEntry extends LinearLayout { + + @InjectView(android.R.id.text1) + TextView textView; + + @InjectView(R.id.material_icon) + AppCompatImageView iconView; + + @InjectView(R.id.checked_icon) + AppCompatImageView checkedIcon; + + public IconCheckboxEntry(Context context) { + super(context); + initLayout(context, null); + } + + public IconCheckboxEntry(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initLayout(context, attrs); + } + + public IconCheckboxEntry(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initLayout(context, attrs); + } + + @TargetApi(21) + public IconCheckboxEntry(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initLayout(context, attrs); + } + + void initLayout(Context context, AttributeSet attrs) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View rootview = inflater.inflate(R.layout.v_icon_select_text_list_item, this, true); + ButterKnife.inject(this, rootview); + + + + } + + public void bind(TetheringDialog.DialogListAdapter.ViewModel model) { + this.setEnabled(model.enabled); + textView.setText(model.text); + textView.setEnabled(model.enabled); + + Drawable checkIcon = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_check_bold)).mutate(); + if (model.enabled) { + DrawableCompat.setTint(checkIcon, ContextCompat.getColor(getContext(), R.color.colorSuccess)); + } else { + DrawableCompat.setTint(checkIcon, ContextCompat.getColor(getContext(), R.color.colorDisabled)); + } + + iconView.setImageDrawable(model.image); + checkedIcon.setImageDrawable(checkIcon); + setChecked(model.checked); + } + + public void setChecked(boolean checked) { + checkedIcon.setVisibility(checked ? VISIBLE : GONE); + checkedIcon.setContentDescription(checked ? "selected" : "unselected"); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java b/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java new file mode 100644 index 00000000..1160986e --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java @@ -0,0 +1,116 @@ +package se.leap.bitmaskclient.base.views; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import androidx.annotation.DrawableRes; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.appcompat.widget.SwitchCompat; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import se.leap.bitmaskclient.R; + +public class IconSwitchEntry extends LinearLayout { + + private TextView textView; + private TextView subtitleView; + private AppCompatImageView iconView; + private SwitchCompat switchView; + private CompoundButton.OnCheckedChangeListener checkedChangeListener; + + public IconSwitchEntry(Context context) { + super(context); + initLayout(context, null); + } + + public IconSwitchEntry(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initLayout(context, attrs); + } + + public IconSwitchEntry(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initLayout(context, attrs); + } + + @TargetApi(21) + public IconSwitchEntry(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initLayout(context, attrs); + } + + void initLayout(Context context, AttributeSet attrs) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View rootview = inflater.inflate(R.layout.v_switch_list_item, this, true); + textView = rootview.findViewById(android.R.id.text1); + subtitleView = rootview.findViewById(R.id.subtitle); + iconView = rootview.findViewById(R.id.material_icon); + switchView = rootview.findViewById(R.id.option_switch); + + if (attrs != null) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IconSwitchEntry); + + String entryText = typedArray.getString(R.styleable.IconTextEntry_text); + if (entryText != null) { + textView.setText(entryText); + } + + String subtitle = typedArray.getString(R.styleable.IconTextEntry_subtitle); + if (subtitle != null) { + subtitleView.setText(subtitle); + subtitleView.setVisibility(VISIBLE); + } + + Drawable drawable = typedArray.getDrawable(R.styleable.IconTextEntry_icon); + if (drawable != null) { + iconView.setImageDrawable(drawable); + } + + typedArray.recycle(); + } + } + + public void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener) { + checkedChangeListener = listener; + switchView.setOnCheckedChangeListener(checkedChangeListener); + } + + public void setText(@StringRes int id) { + textView.setText(id); + } + + public void showSubtitle(boolean show) { + subtitleView.setVisibility(show ? VISIBLE : GONE); + } + + public void setIcon(@DrawableRes int id) { + iconView.setImageResource(id); + } + + public void setChecked(boolean isChecked) { + switchView.setChecked(isChecked); + } + + public void setCheckedQuietly(boolean isChecked) { + switchView.setOnCheckedChangeListener(null); + switchView.setChecked(isChecked); + switchView.setOnCheckedChangeListener(checkedChangeListener); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + switchView.setVisibility(enabled ? VISIBLE : GONE); + textView.setTextColor(getResources().getColor(enabled ? android.R.color.black : R.color.colorDisabled)); + iconView.setImageAlpha(enabled ? 255 : 128); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/IconTextEntry.java b/app/src/main/java/se/leap/bitmaskclient/base/views/IconTextEntry.java new file mode 100644 index 00000000..6b9bd760 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/IconTextEntry.java @@ -0,0 +1,106 @@ +package se.leap.bitmaskclient.base.views; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import se.leap.bitmaskclient.R; + + +public class IconTextEntry extends LinearLayout { + + private TextView textView; + private ImageView iconView; + private TextView subtitleView; + + public IconTextEntry(Context context) { + super(context); + initLayout(context, null); + } + + public IconTextEntry(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initLayout(context, attrs); + } + + public IconTextEntry(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initLayout(context, attrs); + } + + @TargetApi(21) + public IconTextEntry(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initLayout(context, attrs); + } + + void initLayout(Context context, AttributeSet attrs) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View rootview = inflater.inflate(R.layout.v_icon_text_list_item, this, true); + textView = rootview.findViewById(android.R.id.text1); + subtitleView = rootview.findViewById(R.id.subtitle); + iconView = rootview.findViewById(R.id.material_icon); + + if (attrs != null) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IconTextEntry); + + String entryText = typedArray.getString(R.styleable.IconTextEntry_text); + if (entryText != null) { + textView.setText(entryText); + } + + String subtitle = typedArray.getString(R.styleable.IconTextEntry_subtitle); + if (subtitle != null) { + subtitleView.setText(subtitle); + subtitleView.setVisibility(VISIBLE); + } + + Drawable drawable = typedArray.getDrawable(R.styleable.IconTextEntry_icon); + if (drawable != null) { + iconView.setImageDrawable(drawable); + } + + typedArray.recycle(); + } + + + } + + public void setText(@StringRes int id) { + textView.setText(id); + } + + public void setSubtitle(String text) { + subtitleView.setText(text); + subtitleView.setVisibility(VISIBLE); + } + + public void hideSubtitle() { + subtitleView.setVisibility(GONE); + } + + public void setSubtitleColor(@ColorRes int color) { + subtitleView.setTextColor(getContext().getResources().getColor(color)); + } + + public void setText(CharSequence text) { + textView.setText(text); + } + + public void setIcon(@DrawableRes int id) { + iconView.setImageResource(id); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/IconTextView.java b/app/src/main/java/se/leap/bitmaskclient/base/views/IconTextView.java new file mode 100644 index 00000000..1f64e483 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/IconTextView.java @@ -0,0 +1,96 @@ +package se.leap.bitmaskclient.base.views; + + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import androidx.appcompat.widget.AppCompatTextView; +import android.text.Spannable; +import android.text.style.ImageSpan; +import android.util.AttributeSet; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class IconTextView extends AppCompatTextView { + + private int imageResource = 0; + /** + * Regex pattern that looks for embedded images of the format: [img src=imageName/] + */ + public static final String PATTERN = "\\Q[img src]\\E"; + + public IconTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public IconTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public IconTextView(Context context) { + super(context); + } + + @Override + public void setText(CharSequence text, BufferType type) { + final Spannable spannable = getTextWithImages(getContext(), text, getLineHeight(), getCurrentTextColor()); + super.setText(spannable, BufferType.SPANNABLE); + } + + public void setIcon(int imageResource) { + this.imageResource = imageResource; + } + + private Spannable getTextWithImages(Context context, CharSequence text, int lineHeight, int colour) { + final Spannable spannable = Spannable.Factory.getInstance().newSpannable(text); + addImages(context, spannable, lineHeight, colour); + return spannable; + } + + private void addImages(Context context, Spannable spannable, int lineHeight, int colour) { + final Pattern refImg = Pattern.compile(PATTERN); + + final Matcher matcher = refImg.matcher(spannable); + while (matcher.find()) { + boolean set = true; + for (ImageSpan span : spannable.getSpans(matcher.start(), matcher.end(), ImageSpan.class)) { + if (spannable.getSpanStart(span) >= matcher.start() + && spannable.getSpanEnd(span) <= matcher.end()) { + spannable.removeSpan(span); + } else { + set = false; + break; + } + } + if (set && imageResource != 0) { + spannable.setSpan(makeImageSpan(context, imageResource, lineHeight, colour), + matcher.start(), + matcher.end(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ); + } + } + } + + /** + * Create an ImageSpan for the given icon drawable. This also sets the image size and colour. + * Works best with a white, square icon because of the colouring and resizing. + * + * @param context The Android Context. + * @param drawableResId A drawable resource Id. + * @param size The desired size (i.e. width and height) of the image icon in pixels. + * Use the lineHeight of the TextView to make the image inline with the + * surrounding text. + * @param colour The colour (careful: NOT a resource Id) to apply to the image. + * @return An ImageSpan, aligned with the bottom of the text. + */ + private ImageSpan makeImageSpan(Context context, int drawableResId, int size, int colour) { + final Drawable drawable = context.getResources().getDrawable(drawableResId); + drawable.mutate(); + drawable.setColorFilter(colour, PorterDuff.Mode.MULTIPLY); + drawable.setBounds(0, 0, size, size); + return new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM); + } + +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/ProviderHeaderView.java b/app/src/main/java/se/leap/bitmaskclient/base/views/ProviderHeaderView.java new file mode 100644 index 00000000..811a54a2 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/ProviderHeaderView.java @@ -0,0 +1,109 @@ +package se.leap.bitmaskclient.base.views; + +import android.content.Context; +import androidx.annotation.DrawableRes; +import androidx.annotation.RequiresApi; +import androidx.annotation.StringRes; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.appcompat.widget.AppCompatTextView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.RelativeLayout; + +import se.leap.bitmaskclient.R; + +import static se.leap.bitmaskclient.base.utils.ViewHelper.convertDimensionToPx; + +/** + * Created by cyberta on 29.06.18. + */ + +public class ProviderHeaderView extends RelativeLayout { + private int stdPadding; + private int compactPadding; + private int stdImageSize; + private int compactImageSize; + + AppCompatImageView providerHeaderLogo; + AppCompatTextView providerHeaderText; + + public ProviderHeaderView(Context context) { + super(context); + initLayout(context); + } + + public ProviderHeaderView(Context context, AttributeSet attrs) { + super(context, attrs); + initLayout(context); + } + + public ProviderHeaderView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initLayout(context); + } + + @RequiresApi(21) + public ProviderHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initLayout(context); + } + + + void initLayout(Context context) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View rootview = inflater.inflate(R.layout.v_provider_header, this, true); + providerHeaderLogo = rootview.findViewById(R.id.provider_header_logo); + providerHeaderText = rootview.findViewById(R.id.provider_header_text); + + stdPadding = convertDimensionToPx(context, R.dimen.stdpadding); + compactPadding = convertDimensionToPx(context, R.dimen.compact_padding); + stdImageSize = convertDimensionToPx(context, R.dimen.bitmask_logo); + compactImageSize = convertDimensionToPx(context, R.dimen.bitmask_logo_compact); + } + + public void setTitle(String title) { + providerHeaderText.setText(title); + } + + public void setTitle(@StringRes int stringRes) { + providerHeaderText.setText(stringRes); + } + + public void setLogo(@DrawableRes int drawableRes) { + providerHeaderLogo.setImageResource(drawableRes); + } + + public void showCompactLayout() { + LayoutParams logoLayoutParams = (LayoutParams) providerHeaderLogo.getLayoutParams(); + logoLayoutParams.width = compactImageSize; + logoLayoutParams.height = compactImageSize; + providerHeaderLogo.setLayoutParams(logoLayoutParams); + + LayoutParams textLayoutParams = (LayoutParams) providerHeaderText.getLayoutParams(); + textLayoutParams.addRule(RIGHT_OF, R.id.provider_header_logo); + textLayoutParams.addRule(BELOW, 0); + textLayoutParams.addRule(ALIGN_TOP, R.id.provider_header_logo); + textLayoutParams.setMargins(compactPadding, compactPadding, compactPadding, compactPadding); + + providerHeaderText.setLayoutParams(textLayoutParams); + providerHeaderText.setMaxLines(2); + } + + public void showStandardLayout() { + LayoutParams logoLayoutParams = (LayoutParams) providerHeaderLogo.getLayoutParams(); + logoLayoutParams.width = stdImageSize; + logoLayoutParams.height = stdImageSize; + providerHeaderLogo.setLayoutParams(logoLayoutParams); + + LayoutParams textLayoutParams = (LayoutParams) providerHeaderText.getLayoutParams(); + textLayoutParams.addRule(RIGHT_OF, 0); + textLayoutParams.addRule(BELOW, R.id.provider_header_logo); + textLayoutParams.addRule(ALIGN_TOP, 0); + textLayoutParams.setMargins(stdPadding, stdPadding, stdPadding, stdPadding); + providerHeaderText.setLayoutParams(textLayoutParams); + providerHeaderText.setMaxLines(1); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/VpnStateImage.java b/app/src/main/java/se/leap/bitmaskclient/base/views/VpnStateImage.java new file mode 100644 index 00000000..2f8a4448 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/VpnStateImage.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient.base.views; + +import android.content.Context; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.appcompat.widget.AppCompatImageView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.widget.ProgressBar; + +import se.leap.bitmaskclient.R; + +/** + * Created by cyberta on 12.02.18. + */ + + +public class VpnStateImage extends ConstraintLayout { + + ProgressBar progressBar; + AppCompatImageView stateIcon; + + public VpnStateImage(Context context) { + super(context); + initLayout(context); + } + + public VpnStateImage(Context context, AttributeSet attrs) { + super(context, attrs); + initLayout(context); + } + + public VpnStateImage(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initLayout(context); + } + + void initLayout(Context context) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View rootview = inflater.inflate(R.layout.v_main_button, this, true); + stateIcon = rootview.findViewById(R.id.vpn_state_key); + progressBar = rootview.findViewById(R.id.progressBar); + progressBar.setIndeterminate(true); + } + + public void showProgress() { + progressBar.setVisibility(VISIBLE); + } + + + public void stopProgress(boolean animated) { + if (!animated) { + progressBar.setVisibility(GONE); + return; + } + + AlphaAnimation fadeOutAnimation = new AlphaAnimation(1.0f, 0.0f); + fadeOutAnimation.setDuration(1000); + fadeOutAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + + @Override + public void onAnimationEnd(Animation animation) { + progressBar.setVisibility(GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) {} + }); + + progressBar.startAnimation(fadeOutAnimation); + } + + public void setStateIcon(int resource) { + stateIcon.setImageResource(resource); + } + + +} -- cgit v1.2.3