diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java index 9a728fa0..04aeecde 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java @@ -34,6 +34,7 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; public abstract class AbstractAboutFragment extends Fragment { @@ -145,7 +146,7 @@ public abstract class AbstractAboutFragment extends Fragment { @Override public int compareTo(Library another) { - return name.toLowerCase().compareTo(another.name.toLowerCase()); + return name.toLowerCase(Locale.US).compareTo(another.name.toLowerCase(Locale.US)); } } } diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java new file mode 100644 index 00000000..ee4e0e60 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractDashboardActivity.java @@ -0,0 +1,115 @@ +package org.microg.tools.ui; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractDashboardActivity extends AppCompatActivity { + protected int preferencesResource = 0; + + private final List conditions = new ArrayList(); + private ViewGroup conditionContainer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.dashboard_activity); + conditionContainer = (ViewGroup) findViewById(R.id.condition_container); + + setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); + + getSupportFragmentManager().beginTransaction() + .replace(R.id.content_wrapper, getFragment()) + .commit(); + } + + @Override + protected void onResume() { + super.onResume(); + forceConditionReevaluation(); + } + + private synchronized void resetConditionViews() { + conditionContainer.removeAllViews(); + for (Condition condition : conditions) { + if (condition.isEvaluated()) { + if (condition.isActive(this)) { + addConditionToView(condition); + } + } else { + evaluateConditionAsync(condition); + } + } + } + + private void evaluateConditionAsync(final Condition condition) { + if (condition.willBeEvaluating()) { + new Thread(new Runnable() { + @Override + public void run() { + if (condition.isActive(AbstractDashboardActivity.this)) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (conditions.contains(condition) && condition.isEvaluated()) { + addConditionToView(condition); + } + } + }); + } + } + }).start(); + } + } + + protected void forceConditionReevaluation() { + for (Condition condition : conditions) { + condition.resetEvaluated(); + } + resetConditionViews(); + } + + protected void addAllConditions(Condition[] conditions) { + for (Condition condition : conditions) { + addCondition(condition); + } + } + + protected void addCondition(Condition condition) { + conditions.add(condition); + if (conditionContainer == null) return; + if (condition.isEvaluated()) { + addConditionToView(condition); + } else { + evaluateConditionAsync(condition); + } + } + + private synchronized void addConditionToView(Condition condition) { + for (int i = 0; i < conditionContainer.getChildCount(); i++) { + if (conditionContainer.getChildAt(i).getTag() == condition) return; + } + conditionContainer.addView(condition.createView(this, conditionContainer)); + } + + protected void clearConditions() { + conditions.clear(); + resetConditionViews(); + } + + protected Fragment getFragment() { + if (preferencesResource == 0) { + throw new IllegalStateException("Neither preferencesResource given, nor overriden getFragment()"); + } + ResourceSettingsFragment fragment = new ResourceSettingsFragment(); + Bundle b = new Bundle(); + b.putInt(ResourceSettingsFragment.EXTRA_PREFERENCE_RESOURCE, preferencesResource); + fragment.setArguments(b); + return fragment; + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java index ea5b9427..9f81470e 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java @@ -8,7 +8,7 @@ import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.ViewGroup; -public class AbstractSettingsActivity extends AppCompatActivity { +public abstract class AbstractSettingsActivity extends AppCompatActivity { protected boolean showHomeAsUp = false; protected int preferencesResource = 0; private ViewGroup customBarContainer; diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java new file mode 100644 index 00000000..163d4d17 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/Condition.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2013-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.ui; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; +import android.support.v4.content.res.ResourcesCompat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +public class Condition { + @DrawableRes + private final int iconRes; + private final Drawable icon; + + @StringRes + private final int titleRes; + private final CharSequence title; + + @StringRes + private final int summaryRes; + private final CharSequence summary; + + @StringRes + private final int firstActionTextRes; + private final CharSequence firstActionText; + private final View.OnClickListener firstActionListener; + + @StringRes + private final int secondActionTextRes; + private final CharSequence secondActionText; + private final View.OnClickListener secondActionListener; + + private final Evaluation evaluation; + + private boolean evaluated = false; + private boolean evaluating = false; + private boolean active; + + Condition(Builder builder) { + icon = builder.icon; + title = builder.title; + summary = builder.summary; + firstActionText = builder.firstActionText; + firstActionListener = builder.firstActionListener; + secondActionText = builder.secondActionText; + secondActionListener = builder.secondActionListener; + summaryRes = builder.summaryRes; + iconRes = builder.iconRes; + firstActionTextRes = builder.firstActionTextRes; + secondActionTextRes = builder.secondActionTextRes; + titleRes = builder.titleRes; + evaluation = builder.evaluation; + } + + View createView(final Context context, ViewGroup container) { + LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.condition_card, container, false); + Drawable icon = getIcon(context); + if (icon != null) + ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(icon); + ((TextView) view.findViewById(android.R.id.title)).setText(getTitle(context)); + ((TextView) view.findViewById(android.R.id.summary)).setText(getSummary(context)); + Button first = (Button) view.findViewById(R.id.first_action); + first.setText(getFirstActionText(context)); + first.setOnClickListener(getFirstActionListener()); + CharSequence secondActionText = getSecondActionText(context); + if (secondActionText != null) { + Button second = (Button) view.findViewById(R.id.second_action); + second.setText(secondActionText); + second.setOnClickListener(getSecondActionListener()); + second.setVisibility(View.VISIBLE); + } + final View detailGroup = view.findViewById(R.id.detail_group); + final ImageView expandIndicator = (ImageView) view.findViewById(R.id.expand_indicator); + View.OnClickListener expandListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (detailGroup.getVisibility() == View.VISIBLE) { + expandIndicator.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_expand_more, context.getTheme())); + detailGroup.setVisibility(View.GONE); + } else { + expandIndicator.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_expand_less, context.getTheme())); + detailGroup.setVisibility(View.VISIBLE); + } + } + }; + view.findViewById(R.id.collapsed_group).setOnClickListener(expandListener); + expandIndicator.setOnClickListener(expandListener); + view.setTag(this); + return view; + } + + public Drawable getIcon(Context context) { + if (iconRes != 0) { + return ResourcesCompat.getDrawable(context.getResources(), iconRes, context.getTheme()); + } + return icon; + } + + public CharSequence getTitle(Context context) { + if (titleRes != 0) { + return context.getString(titleRes); + } + return title; + } + + public CharSequence getSummary(Context context) { + if (summaryRes != 0) { + return context.getString(summaryRes); + } + return summary; + } + + public View.OnClickListener getFirstActionListener() { + return firstActionListener; + } + + public CharSequence getFirstActionText(Context context) { + if (firstActionTextRes != 0) { + return context.getString(firstActionTextRes); + } + return firstActionText; + } + + public View.OnClickListener getSecondActionListener() { + return secondActionListener; + } + + public CharSequence getSecondActionText(Context context) { + if (secondActionTextRes != 0) { + return context.getString(secondActionTextRes); + } + return secondActionText; + } + + public synchronized boolean willBeEvaluating() { + if (!evaluating && !evaluated && evaluation != null) { + return evaluating = true; + } else { + return false; + } + } + + public boolean isEvaluated() { + return evaluated || evaluation == null; + } + + public synchronized void evaluate(Context context) { + active = evaluation == null || evaluation.isActive(context); + evaluated = true; + evaluating = false; + } + + public boolean isActive(Context context) { + if (!evaluated && evaluation != null) evaluate(context); + return active; + } + + public void resetEvaluated() { + this.evaluated = false; + } + + public interface Evaluation { + boolean isActive(Context context); + } + + public static class Builder { + + @DrawableRes + private int iconRes; + private Drawable icon; + @StringRes + private int titleRes; + private CharSequence title; + @StringRes + private int summaryRes; + private CharSequence summary; + @StringRes + private int firstActionTextRes; + private CharSequence firstActionText; + private View.OnClickListener firstActionListener; + @StringRes + private int secondActionTextRes; + private CharSequence secondActionText; + private View.OnClickListener secondActionListener; + private Evaluation evaluation; + + + public Builder() { + } + + public Builder icon(Drawable val) { + icon = val; + return this; + } + + public Builder icon(@DrawableRes int val) { + iconRes = val; + return this; + } + + public Builder title(CharSequence val) { + title = val; + return this; + } + + public Builder title(@StringRes int val) { + titleRes = val; + return this; + } + + public Builder summary(CharSequence val) { + summary = val; + return this; + } + + public Builder summary(@StringRes int val) { + summaryRes = val; + return this; + } + + public Builder firstAction(CharSequence text, View.OnClickListener listener) { + firstActionText = text; + firstActionListener = listener; + return this; + } + + public Builder firstAction(@StringRes int val, View.OnClickListener listener) { + firstActionTextRes = val; + firstActionListener = listener; + return this; + } + + public Builder secondAction(CharSequence text, View.OnClickListener listener) { + secondActionText = text; + secondActionListener = listener; + return this; + } + + public Builder secondAction(@StringRes int val, View.OnClickListener listener) { + secondActionTextRes = val; + secondActionListener = listener; + return this; + } + + public Builder evaluation(Evaluation evaluation) { + this.evaluation = evaluation; + return this; + } + + public Condition build() { + return new Condition(this); + } + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java index 6cad9403..84635893 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java @@ -30,7 +30,6 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.CompoundButton; import android.widget.LinearLayout; -import android.widget.Switch; import android.widget.TextView; import java.util.ArrayList; @@ -208,8 +207,8 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC */ private SavedState(Parcel in) { super(in); - checked = (Boolean) in.readValue(null); - visible = (Boolean) in.readValue(null); + checked = (Boolean) in.readValue(Boolean.class.getClassLoader()); + visible = (Boolean) in.readValue(Boolean.class.getClassLoader()); } @Override diff --git a/microg-ui-tools/src/main/res/drawable/empty.xml b/microg-ui-tools/src/main/res/drawable/empty.xml new file mode 100644 index 00000000..90afa75f --- /dev/null +++ b/microg-ui-tools/src/main/res/drawable/empty.xml @@ -0,0 +1,18 @@ + + + + \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/drawable/ic_expand_less.xml b/microg-ui-tools/src/main/res/drawable/ic_expand_less.xml new file mode 100644 index 00000000..0541b3e6 --- /dev/null +++ b/microg-ui-tools/src/main/res/drawable/ic_expand_less.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/microg-ui-tools/src/main/res/drawable/ic_expand_more.xml b/microg-ui-tools/src/main/res/drawable/ic_expand_more.xml new file mode 100644 index 00000000..ad2b66f7 --- /dev/null +++ b/microg-ui-tools/src/main/res/drawable/ic_expand_more.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/microg-ui-tools/src/main/res/drawable/self_check.xml b/microg-ui-tools/src/main/res/drawable/self_check.xml index 5c28f0c3..557d7645 100644 --- a/microg-ui-tools/src/main/res/drawable/self_check.xml +++ b/microg-ui-tools/src/main/res/drawable/self_check.xml @@ -1,4 +1,20 @@ + + + + diff --git a/microg-ui-tools/src/main/res/layout-v14/preference_category_dashboard.xml b/microg-ui-tools/src/main/res/layout-v14/preference_category_dashboard.xml new file mode 100644 index 00000000..45d12ba7 --- /dev/null +++ b/microg-ui-tools/src/main/res/layout-v14/preference_category_dashboard.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/layout-v21/preference_material.xml b/microg-ui-tools/src/main/res/layout-v21/preference_material.xml index 2c93ad42..6e2a37ee 100644 --- a/microg-ui-tools/src/main/res/layout-v21/preference_material.xml +++ b/microg-ui-tools/src/main/res/layout-v21/preference_material.xml @@ -1,6 +1,7 @@ - + diff --git a/microg-ui-tools/src/main/res/layout/app_bar.xml b/microg-ui-tools/src/main/res/layout/app_bar.xml index d10f3e79..13408f25 100644 --- a/microg-ui-tools/src/main/res/layout/app_bar.xml +++ b/microg-ui-tools/src/main/res/layout/app_bar.xml @@ -1,18 +1,19 @@ + ~ Copyright (C) 2014 The Android Open Source Project + ~ Copyright (C) 2014-2016 microG Project Team + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + + + + + + + + + + + + + + + + + + + + + + + + +