extracting common classes to another project
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 493 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1006 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 933 B |
@ -1,50 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
~ Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
~ For more information, please, contact se.solovyev@gmail.com
|
|
||||||
~ or visit http://se.solovyev.org
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
|
||||||
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<selector xmlns:a="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<item a:state_pressed="false"
|
|
||||||
a:state_enabled="true"
|
|
||||||
a:state_focused="false"
|
|
||||||
a:drawable="@drawable/timepicker_down_normal" />
|
|
||||||
|
|
||||||
<item a:state_pressed="true"
|
|
||||||
a:state_enabled="true"
|
|
||||||
a:drawable="@drawable/timepicker_down_pressed" />
|
|
||||||
|
|
||||||
<item a:state_pressed="false"
|
|
||||||
a:state_enabled="true"
|
|
||||||
a:state_focused="true"
|
|
||||||
a:drawable="@drawable/timepicker_down_selected" />
|
|
||||||
|
|
||||||
<item a:state_pressed="false"
|
|
||||||
a:state_enabled="false"
|
|
||||||
a:state_focused="false"
|
|
||||||
a:drawable="@drawable/timepicker_down_disabled" />
|
|
||||||
|
|
||||||
<item a:state_pressed="false"
|
|
||||||
a:state_enabled="false"
|
|
||||||
a:state_focused="true"
|
|
||||||
a:drawable="@drawable/timepicker_down_disabled_focused" />
|
|
||||||
|
|
||||||
</selector>
|
|
Before Width: | Height: | Size: 422 B |
Before Width: | Height: | Size: 580 B |
Before Width: | Height: | Size: 795 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
@ -1,36 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
~ For more information, please, contact se.solovyev@gmail.com
|
|
||||||
~ or visit http://se.solovyev.org
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
|
||||||
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<item android:state_pressed="false" android:state_enabled="true"
|
|
||||||
android:state_focused="false" android:drawable="@drawable/timepicker_input_normal" />
|
|
||||||
<item android:state_pressed="true" android:state_enabled="true"
|
|
||||||
android:drawable="@drawable/timepicker_input_pressed" />
|
|
||||||
<item android:state_pressed="false" android:state_enabled="true"
|
|
||||||
android:state_focused="true" android:drawable="@drawable/timepicker_input_selected" />
|
|
||||||
<item android:state_pressed="false" android:state_enabled="false"
|
|
||||||
android:state_focused="false" android:drawable="@drawable/timepicker_input_disabled" />
|
|
||||||
<item android:state_pressed="false" android:state_enabled="false"
|
|
||||||
android:state_focused="true" android:drawable="@drawable/timepicker_input_normal" />
|
|
||||||
|
|
||||||
</selector>
|
|
Before Width: | Height: | Size: 280 B |
Before Width: | Height: | Size: 582 B |
Before Width: | Height: | Size: 604 B |
Before Width: | Height: | Size: 517 B |
@ -1,37 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
~ Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
~ For more information, please, contact se.solovyev@gmail.com
|
|
||||||
~ or visit http://se.solovyev.org
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
|
||||||
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<item android:state_pressed="false" android:state_enabled="true"
|
|
||||||
android:state_focused="false" android:drawable="@drawable/timepicker_up_normal"/>
|
|
||||||
<item android:state_pressed="true" android:state_enabled="true"
|
|
||||||
android:drawable="@drawable/timepicker_up_pressed"/>
|
|
||||||
<item android:state_pressed="false" android:state_enabled="true"
|
|
||||||
android:state_focused="true" android:drawable="@drawable/timepicker_up_selected"/>
|
|
||||||
<item android:state_pressed="false" android:state_enabled="false"
|
|
||||||
android:state_focused="false" android:drawable="@drawable/timepicker_up_disabled"/>
|
|
||||||
<item android:state_pressed="false" android:state_enabled="false"
|
|
||||||
android:state_focused="true" android:drawable="@drawable/timepicker_up_disabled_focused"/>
|
|
||||||
|
|
||||||
</selector>
|
|
Before Width: | Height: | Size: 491 B |
Before Width: | Height: | Size: 728 B |
Before Width: | Height: | Size: 989 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.7 KiB |
@ -1,22 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<declare-styleable name="DirectionDragButton">
|
|
||||||
<attr name="textUp" format="string"/>
|
|
||||||
<attr name="textDown" format="string"/>
|
|
||||||
<attr name="textRight" format="string"/>
|
|
||||||
<attr name="textLeft" format="string"/>
|
|
||||||
<attr name="directionTextScale" format="string"/>
|
|
||||||
<attr name="directionTextAlpha" format="integer"/>
|
|
||||||
</declare-styleable>
|
|
||||||
|
|
||||||
<declare-styleable name="DragButton">
|
|
||||||
<attr name="hTextPosition" format="float"/>
|
|
||||||
</declare-styleable>
|
|
||||||
|
|
||||||
<declare-styleable name="NumberRangeSeekBar">
|
|
||||||
<attr name="boundaries" format="string"/>
|
|
||||||
<attr name="steps" format="integer"/>
|
|
||||||
</declare-styleable>
|
|
||||||
|
|
||||||
<attr name="digitButtonStyle" format="reference" />
|
<attr name="digitButtonStyle" format="reference" />
|
||||||
<attr name="controlButtonStyle" format="reference" />
|
<attr name="controlButtonStyle" format="reference" />
|
||||||
|
@ -1,207 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TabHost;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import com.google.ads.AdRequest;
|
|
||||||
import com.google.ads.AdSize;
|
|
||||||
import com.google.ads.AdView;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/21/11
|
|
||||||
* Time: 11:54 PM
|
|
||||||
*/
|
|
||||||
public final class AndroidUtils {
|
|
||||||
|
|
||||||
// not intended for instantiation
|
|
||||||
private AndroidUtils() {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void centerAndWrapTabsFor(@NotNull TabHost tabHost) {
|
|
||||||
if (allowCenterAndWrappingTabs()) {
|
|
||||||
int tabCount = tabHost.getTabWidget().getTabCount();
|
|
||||||
for (int i = 0; i < tabCount; i++) {
|
|
||||||
final View view = tabHost.getTabWidget().getChildTabViewAt(i);
|
|
||||||
if (view != null) {
|
|
||||||
if (view.getLayoutParams().height > 0) {
|
|
||||||
// reduce height of the tab
|
|
||||||
view.getLayoutParams().height *= 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get title text view
|
|
||||||
final View textView = view.findViewById(android.R.id.title);
|
|
||||||
if (textView instanceof TextView) {
|
|
||||||
// just in case check the type
|
|
||||||
|
|
||||||
// center text
|
|
||||||
((TextView) textView).setGravity(Gravity.CENTER);
|
|
||||||
// wrap text
|
|
||||||
((TextView) textView).setSingleLine(false);
|
|
||||||
|
|
||||||
// explicitly set layout parameters
|
|
||||||
textView.getLayoutParams().height = ViewGroup.LayoutParams.FILL_PARENT;
|
|
||||||
textView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean allowCenterAndWrappingTabs() {
|
|
||||||
boolean result = true;
|
|
||||||
|
|
||||||
String deviceModel = Build.MODEL;
|
|
||||||
if (deviceModel != null) {
|
|
||||||
deviceModel = deviceModel.toUpperCase();
|
|
||||||
if (deviceModel.contains("M1") || deviceModel.contains("MIONE") || deviceModel.contains("MI-ONE")) {
|
|
||||||
// Xiaomi Phone MiOne => do not allow to center and wrap tabs
|
|
||||||
result = false;
|
|
||||||
Log.i(AndroidUtils.class.getName(), "Device model doesn't support center and wrap of tabs: " + Build.MODEL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
String buildId = Build.DISPLAY;
|
|
||||||
if (buildId != null) {
|
|
||||||
buildId = buildId.toUpperCase();
|
|
||||||
if (buildId.contains("MIUI")) {
|
|
||||||
// fix for MIUI ROM
|
|
||||||
result = false;
|
|
||||||
Log.i(AndroidUtils.class.getName(), "Device build doesn't support center and wrap of tabs: " + Build.DISPLAY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void addTab(@NotNull Context context,
|
|
||||||
@NotNull TabHost tabHost,
|
|
||||||
@NotNull String tabId,
|
|
||||||
int tabCaptionId,
|
|
||||||
@NotNull Class<? extends Activity> activityClass) {
|
|
||||||
|
|
||||||
TabHost.TabSpec spec;
|
|
||||||
|
|
||||||
final Intent intent = new Intent().setClass(context, activityClass);
|
|
||||||
|
|
||||||
// Initialize a TabSpec for each tab and add it to the TabHost
|
|
||||||
spec = tabHost.newTabSpec(tabId).setIndicator(context.getString(tabCaptionId)).setContent(intent);
|
|
||||||
|
|
||||||
tabHost.addTab(spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param context context
|
|
||||||
* @param appPackageName - full name of the package of an app, 'com.example.app' for example.
|
|
||||||
* @return version number we are currently in
|
|
||||||
*/
|
|
||||||
public static int getAppVersionCode(@NotNull Context context, @NotNull String appPackageName) {
|
|
||||||
try {
|
|
||||||
return context.getPackageManager().getPackageInfo(appPackageName, 0).versionCode;
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
// App not installed!
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static AdView createAndInflateAdView(@NotNull Activity activity,
|
|
||||||
@NotNull String admobAccountId,
|
|
||||||
@Nullable ViewGroup parentView,
|
|
||||||
int layoutId,
|
|
||||||
@NotNull List<String> keywords) {
|
|
||||||
final ViewGroup layout = parentView != null ? parentView : (ViewGroup) activity.findViewById(layoutId);
|
|
||||||
|
|
||||||
// Create the adView
|
|
||||||
final AdView adView = new AdView(activity, AdSize.BANNER, admobAccountId);
|
|
||||||
|
|
||||||
// Add the adView to it
|
|
||||||
layout.addView(adView);
|
|
||||||
|
|
||||||
// Initiate a generic request to load it with an ad
|
|
||||||
final AdRequest adRequest = new AdRequest();
|
|
||||||
|
|
||||||
// todo serso: revert - only for tests
|
|
||||||
//adRequest.addTestDevice(AdRequest.TEST_EMULATOR);
|
|
||||||
//adRequest.addTestDevice("DB3C2F605A1296971898F0E60224A927");
|
|
||||||
|
|
||||||
for (String keyword : keywords) {
|
|
||||||
adRequest.addKeyword(keyword);
|
|
||||||
}
|
|
||||||
adView.loadAd(adRequest);
|
|
||||||
|
|
||||||
return adView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> void processViewsOfType(@NotNull View view, @NotNull Class<T> clazz, @NotNull ViewProcessor<T> viewProcessor) {
|
|
||||||
processViewsOfType0(view, clazz, viewProcessor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void processViews(@NotNull View view, @NotNull ViewProcessor<View> viewProcessor) {
|
|
||||||
processViewsOfType0(view, null, viewProcessor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> void processViewsOfType0(@NotNull View view, @Nullable Class<T> clazz, @NotNull ViewProcessor<T> viewProcessor) {
|
|
||||||
if (view instanceof ViewGroup) {
|
|
||||||
final ViewGroup viewGroup = (ViewGroup) view;
|
|
||||||
|
|
||||||
if (clazz == null || ViewGroup.class.isAssignableFrom(clazz)) {
|
|
||||||
//noinspection unchecked
|
|
||||||
viewProcessor.process((T) viewGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int index = 0; index < viewGroup.getChildCount(); index++) {
|
|
||||||
processViewsOfType0(viewGroup.getChildAt(index), clazz, viewProcessor);
|
|
||||||
}
|
|
||||||
} else if (clazz == null || view.getClass().isAssignableFrom(clazz)) {
|
|
||||||
//noinspection unchecked
|
|
||||||
viewProcessor.process((T) view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static interface ViewProcessor<V> {
|
|
||||||
void process(@NotNull V view);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void restartActivity(@NotNull Activity activity) {
|
|
||||||
final Intent intent = activity.getIntent();
|
|
||||||
/*
|
|
||||||
for compatibility with android_1.6_compatibility
|
|
||||||
overridePendingTransition(0, 0);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);*/
|
|
||||||
|
|
||||||
Log.d(activity.getClass().getName(), "Finishing current activity!");
|
|
||||||
activity.finish();
|
|
||||||
|
|
||||||
/*
|
|
||||||
for compatibility with android_1.6_compatibility
|
|
||||||
|
|
||||||
overridePendingTransition(0, 0);*/
|
|
||||||
Log.d(activity.getClass().getName(), "Starting new activity!");
|
|
||||||
activity.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,233 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.android.view.widgets.DragButton;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 11/25/11
|
|
||||||
* Time: 1:52 PM
|
|
||||||
*/
|
|
||||||
public enum ResourceCache {
|
|
||||||
|
|
||||||
instance;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
|
|
||||||
|
|
||||||
// ids of drag buttons in R.class
|
|
||||||
private List<Integer> dragButtonIds = null;
|
|
||||||
|
|
||||||
// ids of buttons in R.class
|
|
||||||
private List<Integer> buttonIds = null;
|
|
||||||
|
|
||||||
// first map: key: language id, value: map of captions and translations
|
|
||||||
// second mal: key: caption id, value: translation
|
|
||||||
private final Map<String, Map<String, String>> captions = new HashMap<String, Map<String, String>>();
|
|
||||||
|
|
||||||
private Class<?> resourceClass;
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final NameToIdCache nameToIdCache = new NameToIdCache();
|
|
||||||
|
|
||||||
public List<Integer> getDragButtonIds() {
|
|
||||||
return dragButtonIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Integer> getButtonIds() {
|
|
||||||
return buttonIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method load captions for default locale using android R class
|
|
||||||
*
|
|
||||||
* @param context STATIC CONTEXT
|
|
||||||
* @param resourceClass class of captions in android (SUBCLASS of R class)
|
|
||||||
*/
|
|
||||||
public void initCaptions(@NotNull Context context, @NotNull Class<?> resourceClass) {
|
|
||||||
initCaptions(context, resourceClass, Locale.getDefault());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method load captions for specified locale using android R class
|
|
||||||
*
|
|
||||||
* @param context STATIC CONTEXT
|
|
||||||
* @param resourceClass class of captions in android (SUBCLASS of R class)
|
|
||||||
* @param locale language to be used for translation
|
|
||||||
*/
|
|
||||||
public void initCaptions(@NotNull Context context, @NotNull Class<?> resourceClass, @NotNull Locale locale) {
|
|
||||||
assert this.resourceClass == null || this.resourceClass.equals(resourceClass);
|
|
||||||
|
|
||||||
this.context = context;
|
|
||||||
this.resourceClass = resourceClass;
|
|
||||||
|
|
||||||
if (!initialized(locale)) {
|
|
||||||
final Map<String, String> captionsByLanguage = new HashMap<String, String>();
|
|
||||||
|
|
||||||
final Locale defaultLocale = Locale.getDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!defaultLocale.getLanguage().equals(locale.getLanguage())) {
|
|
||||||
updateConfiguration(context, locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Field field : resourceClass.getDeclaredFields()) {
|
|
||||||
int modifiers = field.getModifiers();
|
|
||||||
if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) {
|
|
||||||
try {
|
|
||||||
int captionId = field.getInt(resourceClass);
|
|
||||||
captionsByLanguage.put(field.getName(), context.getString(captionId));
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Log.e(ResourceCache.class.getName(), e.getMessage());
|
|
||||||
} catch (Resources.NotFoundException e) {
|
|
||||||
Log.e(ResourceCache.class.getName(), "Caption with name " + field.getName() + " was not found for " + locale.getLanguage() + " language: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (!defaultLocale.getLanguage().equals(locale.getLanguage())) {
|
|
||||||
updateConfiguration(context, defaultLocale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
captions.put(locale.getLanguage(), captionsByLanguage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void updateConfiguration(@NotNull Context context, @NotNull Locale locale) {
|
|
||||||
Locale.setDefault(locale);
|
|
||||||
final Configuration config = new Configuration();
|
|
||||||
config.locale = locale;
|
|
||||||
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean initialized(@NotNull Locale locale) {
|
|
||||||
return captions.containsKey(locale.getLanguage());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param captionId id of caption to be translated
|
|
||||||
* @return translation by caption id in default language, null if no translation in default language present
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public String getCaption(@NotNull String captionId) {
|
|
||||||
return getCaption(captionId, Locale.getDefault());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param captionId id of caption to be translated
|
|
||||||
* @param locale language to be used for translation
|
|
||||||
* @return translation by caption id in specified language, null if no translation in specified language present
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public String getCaption(@NotNull String captionId, @NotNull final Locale locale) {
|
|
||||||
Map<String, String> captionsByLanguage = captions.get(locale.getLanguage());
|
|
||||||
if (captionsByLanguage != null) {
|
|
||||||
return getCaption(captionsByLanguage, captionId, locale);
|
|
||||||
} else {
|
|
||||||
assert resourceClass != null && context != null;
|
|
||||||
|
|
||||||
initCaptions(context, resourceClass, locale);
|
|
||||||
|
|
||||||
captionsByLanguage = captions.get(locale.getLanguage());
|
|
||||||
if (captionsByLanguage != null) {
|
|
||||||
return getCaption(captionsByLanguage, captionId, locale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String getCaption(@NotNull Map<String, String> captionsByLanguage, @NotNull String captionId, @NotNull Locale locale) {
|
|
||||||
String result = captionsByLanguage.get(captionId);
|
|
||||||
if (result == null && !locale.getLanguage().equals(DEFAULT_LOCALE.getLanguage())) {
|
|
||||||
result = getCaption(captionId, DEFAULT_LOCALE);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init(@NotNull Class<?> resourceClass, @NotNull Activity activity) {
|
|
||||||
dragButtonIds = new ArrayList<Integer>();
|
|
||||||
buttonIds = new ArrayList<Integer>();
|
|
||||||
|
|
||||||
for (Field field : resourceClass.getDeclaredFields()) {
|
|
||||||
int modifiers = field.getModifiers();
|
|
||||||
if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) {
|
|
||||||
try {
|
|
||||||
int viewId = field.getInt(resourceClass);
|
|
||||||
final View view = activity.findViewById(viewId);
|
|
||||||
if (view instanceof DragButton) {
|
|
||||||
dragButtonIds.add(viewId);
|
|
||||||
}
|
|
||||||
if (view instanceof Button) {
|
|
||||||
buttonIds.add(viewId);
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Log.e(ResourceCache.class.getName(), e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Map<String, Integer> getNameToIdCache(@NotNull Class<?> clazz) {
|
|
||||||
return this.nameToIdCache.getCache(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class NameToIdCache {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Map<Class<?>, Map<String, Integer>> caches = new HashMap<Class<?>, Map<String, Integer>>(3);
|
|
||||||
|
|
||||||
// not intended for instantiation
|
|
||||||
private NameToIdCache() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Map<String, Integer> getCache(@NotNull Class<?> clazz) {
|
|
||||||
Map<String, Integer> result = caches.get(clazz);
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
result = new HashMap<String, Integer>();
|
|
||||||
|
|
||||||
for (Field field : clazz.getDeclaredFields()) {
|
|
||||||
int modifiers = field.getModifiers();
|
|
||||||
if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) {
|
|
||||||
try {
|
|
||||||
result.put(field.getName(), field.getInt(clazz));
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
Log.e(ResourceCache.class.getName(), e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
caches.put(clazz, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,17 +1,8 @@
|
|||||||
package org.solovyev.android.calculator;
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import com.google.ads.AdView;
|
|
||||||
import net.robotmedia.billing.BillingController;
|
import net.robotmedia.billing.BillingController;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.solovyev.android.ads.AdsController;
|
||||||
import org.solovyev.android.AndroidUtils;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User: serso
|
* User: serso
|
||||||
@ -37,53 +28,11 @@ public class CalculatorApplication extends android.app.Application {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAdFreePurchased(@NotNull Context context) {
|
|
||||||
return BillingController.isPurchased(context.getApplicationContext(), AD_FREE_PRODUCT_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean transactionsRestored = false;
|
|
||||||
|
|
||||||
public static boolean isAdFree(@NotNull Context context) {
|
|
||||||
// check if user already bought this product
|
|
||||||
boolean purchased = isAdFreePurchased(context);
|
|
||||||
if (!purchased && !transactionsRestored) {
|
|
||||||
// we must to restore all transactions done by user to guarantee that product was purchased or not
|
|
||||||
BillingController.restoreTransactions(context);
|
|
||||||
|
|
||||||
transactionsRestored = true;
|
|
||||||
|
|
||||||
// todo serso: may be call net.robotmedia.billing.BillingController.restoreTransactions() always before first check and get rid of second check
|
|
||||||
// check the billing one more time
|
|
||||||
purchased = isAdFreePurchased(context);
|
|
||||||
}
|
|
||||||
return purchased;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static AdView inflateAd(@NotNull Activity activity) {
|
|
||||||
return inflateAd(activity, null, R.id.ad_parent_view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static AdView inflateAd(@NotNull Activity activity, @Nullable ViewGroup parentView, int parentViewId) {
|
|
||||||
AdView result = null;
|
|
||||||
if ( !isAdFree(activity) ) {
|
|
||||||
Log.d(activity.getClass().getName(), "Application is not ad free - inflating ad!");
|
|
||||||
final List<String> keywords = Collections.emptyList();
|
|
||||||
result = AndroidUtils.createAndInflateAdView(activity, ADMOB_USER_ID, parentView, parentViewId, keywords);
|
|
||||||
} else {
|
|
||||||
Log.d(activity.getClass().getName(), "Application is ad free - no ads!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
//BillingController.setDebug(true);
|
AdsController.getInstance().init(ADMOB_USER_ID, AD_FREE_PRODUCT_ID, new BillingController.IConfiguration() {
|
||||||
BillingController.setConfiguration(new BillingController.IConfiguration() {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getObfuscationSalt() {
|
public byte[] getObfuscationSalt() {
|
||||||
|
@ -19,7 +19,6 @@ import org.solovyev.android.calculator.jscl.JsclOperation;
|
|||||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||||
import org.solovyev.android.calculator.model.TextProcessor;
|
import org.solovyev.android.calculator.model.TextProcessor;
|
||||||
import org.solovyev.android.view.AMenuBuilder;
|
|
||||||
import org.solovyev.android.view.AMenuItem;
|
import org.solovyev.android.view.AMenuItem;
|
||||||
import org.solovyev.android.view.AutoResizeTextView;
|
import org.solovyev.android.view.AutoResizeTextView;
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import net.robotmedia.billing.BillingRequest;
|
|||||||
import net.robotmedia.billing.IBillingObserver;
|
import net.robotmedia.billing.IBillingObserver;
|
||||||
import net.robotmedia.billing.model.Transaction;
|
import net.robotmedia.billing.model.Transaction;
|
||||||
import org.solovyev.android.AndroidUtils;
|
import org.solovyev.android.AndroidUtils;
|
||||||
|
import org.solovyev.android.ads.AdsController;
|
||||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||||
import org.solovyev.android.view.widgets.VibratorContainer;
|
import org.solovyev.android.view.widgets.VibratorContainer;
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ public class CalculatorPreferencesActivity extends PreferenceActivity implements
|
|||||||
private void setAdFreeAction() {
|
private void setAdFreeAction() {
|
||||||
final Preference adFreePreference = findPreference(CalculatorApplication.AD_FREE_P_KEY);
|
final Preference adFreePreference = findPreference(CalculatorApplication.AD_FREE_P_KEY);
|
||||||
|
|
||||||
if (!CalculatorApplication.isAdFree(this)) {
|
if (!AdsController.getInstance().isAdFree(this)) {
|
||||||
Log.d(CalculatorPreferencesActivity.class.getName(), "Ad free is not purchased - enable preference!");
|
Log.d(CalculatorPreferencesActivity.class.getName(), "Ad free is not purchased - enable preference!");
|
||||||
|
|
||||||
adFreePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
adFreePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
@ -76,7 +77,7 @@ public class CalculatorPreferencesActivity extends PreferenceActivity implements
|
|||||||
new AlertDialog.Builder(CalculatorPreferencesActivity.this).setTitle(R.string.c_error).setMessage(R.string.c_billing_error).create().show();
|
new AlertDialog.Builder(CalculatorPreferencesActivity.this).setTitle(R.string.c_error).setMessage(R.string.c_billing_error).create().show();
|
||||||
} else {
|
} else {
|
||||||
Log.d(CalculatorPreferencesActivity.class.getName(), "Billing is supported - continue!");
|
Log.d(CalculatorPreferencesActivity.class.getName(), "Billing is supported - continue!");
|
||||||
if (!CalculatorApplication.isAdFree(CalculatorPreferencesActivity.this)) {
|
if (!AdsController.getInstance().isAdFree(CalculatorPreferencesActivity.this)) {
|
||||||
Log.d(CalculatorPreferencesActivity.class.getName(), "Item not purchased - try to purchase!");
|
Log.d(CalculatorPreferencesActivity.class.getName(), "Item not purchased - try to purchase!");
|
||||||
|
|
||||||
// not purchased => purchasing
|
// not purchased => purchasing
|
||||||
|
@ -20,6 +20,16 @@ public final class CalculatorSecurity {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static String getPK() {
|
public static String getPK() {
|
||||||
return "org.solovyev.android.calculator";
|
final StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
result.append("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A");
|
||||||
|
result.append("MIIBCgKCAQEAquP2a7dEhTaJEQeXtSyreH5dCmTDOd");
|
||||||
|
result.append("dElCfg0ijOeB8JTxBiJTXLWnLA0kMaT/sRXswUaYI61YCQOoik82");
|
||||||
|
result.append("qrFH7W4+OFtiLb8WGX+YPEpQQ/IBZu9qm3xzS9Nolu79EBff0/CLa1FuT9RtjO");
|
||||||
|
result.append("iTW8Q0VP9meQdJEkfqJEyVCgHain+MGoQaRXI45EzkYmkz8TBx6X6aJF5NBAXnAWeyD0wPX1");
|
||||||
|
result.append("uedHH7+LgLcjnPVw82YjyJSzYnaaD2GX0Y7PGoFe6J5K4yJGGX5mih45pe2HWcG5lAkQhu1uX2hCcCBdF3");
|
||||||
|
result.append("W7paRq9mJvCsbn+BNTh9gq8QKui0ltmiWpa5U+/9L+FQIDAQAB");
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import android.app.Activity;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import com.google.ads.AdView;
|
import com.google.ads.AdView;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.solovyev.android.calculator.CalculatorApplication;
|
import org.solovyev.android.ads.AdsController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User: serso
|
* User: serso
|
||||||
@ -35,7 +35,7 @@ public class AbstractHelpActivity extends Activity {
|
|||||||
setContentView(layoutId);
|
setContentView(layoutId);
|
||||||
|
|
||||||
// do not inflate ad in help (as some problems were encountered dut to ScrollView - no space for ad banner)
|
// do not inflate ad in help (as some problems were encountered dut to ScrollView - no space for ad banner)
|
||||||
adView = CalculatorApplication.inflateAd(this);
|
adView = AdsController.getInstance().inflateAd(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -19,7 +19,7 @@ import android.widget.ListView;
|
|||||||
import com.google.ads.AdView;
|
import com.google.ads.AdView;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.solovyev.android.calculator.CalculatorApplication;
|
import org.solovyev.android.ads.AdsController;
|
||||||
import org.solovyev.android.calculator.CalculatorModel;
|
import org.solovyev.android.calculator.CalculatorModel;
|
||||||
import org.solovyev.android.calculator.R;
|
import org.solovyev.android.calculator.R;
|
||||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||||
@ -67,7 +67,7 @@ public abstract class AbstractHistoryActivity extends ListActivity {
|
|||||||
|
|
||||||
setContentView(R.layout.history_activity);
|
setContentView(R.layout.history_activity);
|
||||||
|
|
||||||
adView = CalculatorApplication.inflateAd(this);
|
adView = AdsController.getInstance().inflateAd(this);
|
||||||
|
|
||||||
adapter = new HistoryArrayAdapter(this, getLayoutId(), R.id.history_item, new ArrayList<CalculatorHistoryState>());
|
adapter = new HistoryArrayAdapter(this, getLayoutId(), R.id.history_item, new ArrayList<CalculatorHistoryState>());
|
||||||
setListAdapter(adapter);
|
setListAdapter(adapter);
|
||||||
|
@ -18,7 +18,7 @@ import android.widget.*;
|
|||||||
import com.google.ads.AdView;
|
import com.google.ads.AdView;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.solovyev.android.calculator.CalculatorApplication;
|
import org.solovyev.android.ads.AdsController;
|
||||||
import org.solovyev.android.calculator.CalculatorModel;
|
import org.solovyev.android.calculator.CalculatorModel;
|
||||||
import org.solovyev.android.calculator.R;
|
import org.solovyev.android.calculator.R;
|
||||||
import org.solovyev.android.calculator.model.AndroidMathRegistry;
|
import org.solovyev.android.calculator.model.AndroidMathRegistry;
|
||||||
@ -90,7 +90,7 @@ public abstract class AbstractMathEntityListActivity<T extends MathEntity> exten
|
|||||||
|
|
||||||
setContentView(getLayoutId());
|
setContentView(getLayoutId());
|
||||||
|
|
||||||
adView = CalculatorApplication.inflateAd(this);
|
adView = AdsController.getInstance().inflateAd(this);
|
||||||
|
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
if ( intent != null ) {
|
if ( intent != null ) {
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.msg;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.android.ResourceCache;
|
|
||||||
import org.solovyev.common.msg.AbstractMessage;
|
|
||||||
import org.solovyev.common.msg.MessageType;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 10/18/11
|
|
||||||
* Time: 11:57 PM
|
|
||||||
*/
|
|
||||||
public class AndroidMessage extends AbstractMessage {
|
|
||||||
|
|
||||||
public AndroidMessage(@NotNull String messageCode,
|
|
||||||
@NotNull MessageType messageType,
|
|
||||||
@org.jetbrains.annotations.Nullable Object... arguments) {
|
|
||||||
super(messageCode, messageType, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AndroidMessage(@NotNull String messageCode,
|
|
||||||
@NotNull MessageType messageType,
|
|
||||||
@NotNull List<?> arguments) {
|
|
||||||
super(messageCode, messageType, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getMessagePattern(@NotNull Locale locale) {
|
|
||||||
return ResourceCache.instance.getCaption(getMessageCode());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/18/11
|
|
||||||
* Time: 1:30 PM
|
|
||||||
*/
|
|
||||||
public interface AMenu<T extends AMenuItem<D>, D> {
|
|
||||||
@Nullable
|
|
||||||
T itemAt(int i);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
CharSequence[] getMenuCaptions(@NotNull final Context context);
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
package org.solovyev.android.view;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/19/11
|
|
||||||
* Time: 10:54 AM
|
|
||||||
*/
|
|
||||||
public class AMenuBuilder<T extends AMenuItem<D>, D>{
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final AlertDialog.Builder menuBuilder;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final AMenu<T, D> menu;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static <T extends Enum & AMenuItem<D>, D> AMenuBuilder<T, D> newInstance(@NotNull Context context, @NotNull Class<T> enumClass) {
|
|
||||||
return new AMenuBuilder<T, D>(context, EnumMenu.<T, D>newInstance(enumClass));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static <T extends AMenuItem<D>, D> AMenuBuilder<T, D> newInstance(@NotNull Context context, @NotNull AMenu<T, D> menu) {
|
|
||||||
return new AMenuBuilder<T, D>(context, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AMenuBuilder(@NotNull Context context, @NotNull AMenu<T, D> menu) {
|
|
||||||
this.context = context;
|
|
||||||
this.menuBuilder = new AlertDialog.Builder(context);
|
|
||||||
this.menu = menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public AlertDialog.Builder getMenuBuilder() {
|
|
||||||
return menuBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public AlertDialog create(@NotNull final D data) {
|
|
||||||
menuBuilder.setItems(menu.getMenuCaptions(context), new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int item) {
|
|
||||||
final AMenuItem<D> menuItem = menu.itemAt(item);
|
|
||||||
if (menuItem != null) {
|
|
||||||
menuItem.doAction(data, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return menuBuilder.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/18/11
|
|
||||||
* Time: 1:29 PM
|
|
||||||
*/
|
|
||||||
public interface AMenuItem<T> {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
String getCaption(@NotNull Context context);
|
|
||||||
|
|
||||||
void doAction(@NotNull T data, @NotNull Context context);
|
|
||||||
}
|
|
@ -1,301 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.Layout.Alignment;
|
|
||||||
import android.text.StaticLayout;
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text view that auto adjusts text size to fit within the view.
|
|
||||||
* If the text size equals the minimum text size and still does not
|
|
||||||
* fit, append with an ellipsis.
|
|
||||||
*
|
|
||||||
* @author Chase Colburn
|
|
||||||
* @since Apr 4, 2011
|
|
||||||
*/
|
|
||||||
public class AutoResizeTextView extends TextView {
|
|
||||||
|
|
||||||
// Minimum text size for this text view
|
|
||||||
public static final float MIN_TEXT_SIZE = 20;
|
|
||||||
|
|
||||||
private float initialTextSize = 100;
|
|
||||||
|
|
||||||
// Interface for resize notifications
|
|
||||||
public interface OnTextResizeListener {
|
|
||||||
public void onTextResize(TextView textView, float oldSize, float newSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Off screen canvas for text size rendering
|
|
||||||
private static final Canvas textResizeCanvas = new Canvas();
|
|
||||||
|
|
||||||
// Our ellipse string
|
|
||||||
private static final String ellipsis = "...";
|
|
||||||
|
|
||||||
// Registered resize listener
|
|
||||||
private OnTextResizeListener textResizeListener;
|
|
||||||
|
|
||||||
// Flag for text and/or size changes to force a resize
|
|
||||||
private boolean needsResize = false;
|
|
||||||
|
|
||||||
// Lower bounds for text size
|
|
||||||
private float minTextSize = MIN_TEXT_SIZE;
|
|
||||||
|
|
||||||
// Text view line spacing multiplier
|
|
||||||
private float spacingMult = 1.0f;
|
|
||||||
|
|
||||||
// Text view additional line spacing
|
|
||||||
private float spacingAdd = 0.0f;
|
|
||||||
|
|
||||||
// Add ellipsis to text that overflows at the smallest text size
|
|
||||||
private boolean addEllipsis = true;
|
|
||||||
|
|
||||||
// Default constructor override
|
|
||||||
public AutoResizeTextView(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default constructor when inflating from XML file
|
|
||||||
public AutoResizeTextView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default constructor override
|
|
||||||
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When text changes, set the force resize flag to true and resetInterpreter the text size.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
|
|
||||||
needsResize = true;
|
|
||||||
// Since this view may be reused, it is good to resetInterpreter the text size
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the text view size changed, set the force resize flag to true
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
||||||
if (w != oldw || h != oldh) {
|
|
||||||
needsResize = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register listener to receive resize notifications
|
|
||||||
*
|
|
||||||
* @param listener
|
|
||||||
*/
|
|
||||||
public void setOnResizeListener(OnTextResizeListener listener) {
|
|
||||||
textResizeListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override the set text size to update our internal reference values
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setTextSize(int unit, float size) {
|
|
||||||
super.setTextSize(unit, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override the set line spacing to update our internal reference values
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setLineSpacing(float add, float mult) {
|
|
||||||
super.setLineSpacing(add, mult);
|
|
||||||
spacingMult = mult;
|
|
||||||
spacingAdd = add;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the lower text size limit and invalidate the view
|
|
||||||
*
|
|
||||||
* @param minTextSize
|
|
||||||
*/
|
|
||||||
public void setMinTextSize(float minTextSize) {
|
|
||||||
this.minTextSize = minTextSize;
|
|
||||||
requestLayout();
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return lower text size limit
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public float getMinTextSize() {
|
|
||||||
return minTextSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set flag to add ellipsis to text that overflows at the smallest text size
|
|
||||||
*
|
|
||||||
* @param addEllipsis
|
|
||||||
*/
|
|
||||||
public void setAddEllipsis(boolean addEllipsis) {
|
|
||||||
this.addEllipsis = addEllipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return flag to add ellipsis to text that overflows at the smallest text size
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean getAddEllipsis() {
|
|
||||||
return addEllipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resize text after measuring
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
||||||
if (changed || needsResize) {
|
|
||||||
int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
|
|
||||||
int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
|
|
||||||
resizeText(widthLimit, heightLimit, getText());
|
|
||||||
}
|
|
||||||
super.onLayout(changed, left, top, right, bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resize the text size with default width and height
|
|
||||||
*/
|
|
||||||
public void resizeText() {
|
|
||||||
resizeText(getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resizeText(final CharSequence text) {
|
|
||||||
int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
|
|
||||||
int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
|
|
||||||
resizeText(widthLimit, heightLimit, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resize the text size with specified width and height
|
|
||||||
*
|
|
||||||
* @param width
|
|
||||||
* @param height
|
|
||||||
* @param text
|
|
||||||
*/
|
|
||||||
private void resizeText(int width, int height, CharSequence text) {
|
|
||||||
Log.d(this.getClass().getName(), "Resizing: w=" + width + ", h=" + height + ", text='" + text + "'");
|
|
||||||
|
|
||||||
// Do not resize if the view does not have dimensions or there is no text
|
|
||||||
if (text == null || text.length() == 0 || height <= 0 || width <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the text view's paint object
|
|
||||||
TextPaint textPaint = getPaint();
|
|
||||||
|
|
||||||
// Store the current text size
|
|
||||||
float oldTextSize = textPaint.getTextSize();
|
|
||||||
Log.d(this.getClass().getName(), "Old text size: " + oldTextSize);
|
|
||||||
|
|
||||||
// If there is a max text size set, use the lesser of that and the default text size
|
|
||||||
// todo serso: +2 is a workaround => to be checked boundary constraints
|
|
||||||
float newTextSize = initialTextSize + 2;
|
|
||||||
|
|
||||||
int newTextHeight;
|
|
||||||
|
|
||||||
if (text instanceof Editable) {
|
|
||||||
((Editable) text).append("|");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Get the required text height
|
|
||||||
newTextHeight = getTextRect(text, textPaint, width, newTextSize);
|
|
||||||
|
|
||||||
logDimensions(newTextSize, newTextHeight);
|
|
||||||
if (newTextHeight > height) {
|
|
||||||
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
|
|
||||||
while (newTextHeight > height) {
|
|
||||||
if (newTextSize <= minTextSize) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
newTextSize = Math.max(newTextSize - 1, minTextSize);
|
|
||||||
newTextHeight = getTextRect(text, textPaint, width, newTextSize);
|
|
||||||
logDimensions(newTextSize, newTextHeight);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while (newTextHeight < height) {
|
|
||||||
if (newTextSize <= minTextSize) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
newTextSize = Math.max(newTextSize + 1, minTextSize);
|
|
||||||
newTextHeight = getTextRect(text, textPaint, width, newTextSize);
|
|
||||||
logDimensions(newTextSize, newTextHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (text instanceof Editable) {
|
|
||||||
((Editable) text).delete(text.length() - 1, text.length());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initialTextSize = newTextSize;
|
|
||||||
|
|
||||||
// If we had reached our minimum text size and still don't fit, append an ellipsis
|
|
||||||
if (addEllipsis && newTextSize == minTextSize && newTextHeight > height) {
|
|
||||||
// Draw using a static layout
|
|
||||||
StaticLayout layout = new StaticLayout(text, textPaint, width, Alignment.ALIGN_NORMAL, spacingMult, spacingAdd, false);
|
|
||||||
layout.draw(textResizeCanvas);
|
|
||||||
int lastLine = layout.getLineForVertical(height) - 1;
|
|
||||||
int start = layout.getLineStart(lastLine);
|
|
||||||
int end = layout.getLineEnd(lastLine);
|
|
||||||
float lineWidth = layout.getLineWidth(lastLine);
|
|
||||||
float ellipseWidth = textPaint.measureText(ellipsis);
|
|
||||||
|
|
||||||
// Trim characters off until we have enough room to draw the ellipsis
|
|
||||||
while (width < lineWidth + ellipseWidth) {
|
|
||||||
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
|
|
||||||
}
|
|
||||||
setText(text.subSequence(0, end) + ellipsis);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some devices try to auto adjust line spacing, so force default line spacing
|
|
||||||
// and invalidate the layout as a side effect
|
|
||||||
textPaint.setTextSize(newTextSize);
|
|
||||||
setLineSpacing(spacingAdd, spacingMult);
|
|
||||||
|
|
||||||
// Notify the listener if registered
|
|
||||||
if (textResizeListener != null) {
|
|
||||||
textResizeListener.onTextResize(this, oldTextSize, newTextSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset force resize flag
|
|
||||||
needsResize = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logDimensions(float newTextSize, int newTextHeight) {
|
|
||||||
Log.d(this.getClass().getName(), "Nex text size: " + newTextSize + ", new text height: " + newTextHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
|
|
||||||
private int getTextRect(CharSequence source, TextPaint paint, int width, float textSize) {
|
|
||||||
// Update the text paint object
|
|
||||||
paint.setTextSize(textSize);
|
|
||||||
// Draw using a static layout
|
|
||||||
StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, spacingMult, spacingAdd, false);
|
|
||||||
layout.draw(textResizeCanvas);
|
|
||||||
return layout.getHeight();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/13/11
|
|
||||||
* Time: 12:08 AM
|
|
||||||
*/
|
|
||||||
public interface CursorControl {
|
|
||||||
|
|
||||||
public void setCursorOnStart();
|
|
||||||
|
|
||||||
public void setCursorOnEnd();
|
|
||||||
|
|
||||||
public void moveCursorLeft();
|
|
||||||
|
|
||||||
public void moveCursorRight();
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/18/11
|
|
||||||
* Time: 1:34 PM
|
|
||||||
*/
|
|
||||||
public class EnumMenu<T extends Enum & AMenuItem<D>, D> implements AMenu<T, D> {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final AMenu<T, D> menu;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static <T extends Enum & AMenuItem<D>, D> AMenu<T, D> newInstance(@NotNull Class<T> enumClass) {
|
|
||||||
return new EnumMenu<T, D>(enumClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EnumMenu(Class<T> enumClass) {
|
|
||||||
this.menu = MenuImpl.newInstance(enumClass.getEnumConstants());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T itemAt(int i) {
|
|
||||||
return this.menu.itemAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public CharSequence[] getMenuCaptions(@NotNull final Context context) {
|
|
||||||
return this.menu.getMenuCaptions(context);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view;
|
|
||||||
|
|
||||||
import android.widget.TextView;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/10/11
|
|
||||||
* Time: 7:21 PM
|
|
||||||
*/
|
|
||||||
public interface FontSizeAdjuster {
|
|
||||||
|
|
||||||
void adjustFontSize(@NotNull TextView textView);
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.common.utils.history.HistoryAction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/16/11
|
|
||||||
* Time: 11:42 PM
|
|
||||||
*/
|
|
||||||
public interface HistoryControl<T> {
|
|
||||||
|
|
||||||
void doHistoryAction(@NotNull HistoryAction historyAction);
|
|
||||||
|
|
||||||
void setCurrentHistoryState(@NotNull T editorHistoryState);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
T getCurrentHistoryState();
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2012. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view;
|
|
||||||
|
|
||||||
import android.app.TabActivity;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.widget.TabHost;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.android.view.prefs.StringPreference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 1/9/12
|
|
||||||
* Time: 6:17 PM
|
|
||||||
*/
|
|
||||||
public class LastTabSaver implements TabHost.OnTabChangeListener {
|
|
||||||
|
|
||||||
private static final String LAST_OPENED_TAB_P_KEY = "last_opened_tab_";
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final StringPreference<String> preference;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final TabActivity tabActivity;
|
|
||||||
|
|
||||||
public LastTabSaver(@NotNull TabActivity tabActivity, @NotNull String defaultTabId) {
|
|
||||||
this.tabActivity = tabActivity;
|
|
||||||
this.preference = StringPreference.newInstance(getPreferenceKey(), defaultTabId);
|
|
||||||
|
|
||||||
final TabHost tabHost = tabActivity.getTabHost();
|
|
||||||
tabHost.setCurrentTabByTag(this.getLastOpenedTabId());
|
|
||||||
tabHost.setOnTabChangedListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroy() {
|
|
||||||
final TabHost tabHost = tabActivity.getTabHost();
|
|
||||||
tabHost.setOnTabChangedListener(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTabChanged(String tabId) {
|
|
||||||
preference.putPreference(PreferenceManager.getDefaultSharedPreferences(tabActivity), tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getLastOpenedTabId() {
|
|
||||||
return preference.getPreference(PreferenceManager.getDefaultSharedPreferences(tabActivity));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String getPreferenceKey() {
|
|
||||||
return LAST_OPENED_TAB_P_KEY + tabActivity.getClass().getName();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.CollectionsUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/18/11
|
|
||||||
* Time: 1:31 PM
|
|
||||||
*/
|
|
||||||
public class MenuImpl<T extends AMenuItem<D>, D> implements AMenu<T, D> {
|
|
||||||
|
|
||||||
private final List<T> menuItems = new ArrayList<T>();
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static <T extends AMenuItem<D>, D> AMenu<T, D> newInstance(T... menuItems) {
|
|
||||||
return new MenuImpl<T, D>(menuItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static <T extends AMenuItem<D>, D> AMenu<T, D> newInstance(@NotNull List<T> menuItems) {
|
|
||||||
return new MenuImpl<T, D>(menuItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
private MenuImpl(T... menuItems) {
|
|
||||||
this(CollectionsUtils.asList(menuItems));
|
|
||||||
}
|
|
||||||
|
|
||||||
private MenuImpl(@NotNull List<T> menuItems) {
|
|
||||||
this.menuItems.addAll(menuItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public T itemAt(int i) {
|
|
||||||
if (i >= 0 && i < menuItems.size()) {
|
|
||||||
return menuItems.get(i);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NotNull
|
|
||||||
public CharSequence[] getMenuCaptions(@NotNull final Context context) {
|
|
||||||
final CharSequence[] result = new CharSequence[this.menuItems.size()];
|
|
||||||
for (int i = 0; i < this.menuItems.size(); i++) {
|
|
||||||
result[i] = this.menuItems.get(i).getCaption(context);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,180 +0,0 @@
|
|||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.preference.DialogPreference;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.Mapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/19/11
|
|
||||||
* Time: 3:17 PM
|
|
||||||
*/
|
|
||||||
public abstract class AbstractDialogPreference<T> extends DialogPreference {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected final static String localNameSpace = "http://schemas.android.com/apk/res/org.solovyev.android.calculator";
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected final static String androidns = "http://schemas.android.com/apk/res/android";
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected TextView valueTextView;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected String valueText;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected final Context context;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected String description;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected T value;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private T defaultValue;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final String defaultStringValue;
|
|
||||||
|
|
||||||
private final boolean needValueText;
|
|
||||||
|
|
||||||
public AbstractDialogPreference(Context context, AttributeSet attrs, @Nullable String defaultStringValue, boolean needValueText) {
|
|
||||||
super(context, attrs);
|
|
||||||
this.context = context;
|
|
||||||
this.defaultStringValue = defaultStringValue;
|
|
||||||
this.needValueText = needValueText;
|
|
||||||
|
|
||||||
final String defaultValueFromAttrs = attrs.getAttributeValue(androidns, "defaultValue");
|
|
||||||
if ( defaultValueFromAttrs != null ) {
|
|
||||||
defaultValue = getMapper().parseValue(defaultValueFromAttrs);
|
|
||||||
} else if (defaultStringValue != null) {
|
|
||||||
defaultValue = getMapper().parseValue(defaultStringValue);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
description = attrs.getAttributeValue(androidns, "dialogMessage");
|
|
||||||
valueText = attrs.getAttributeValue(androidns, "text");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NotNull
|
|
||||||
protected final LinearLayout onCreateDialogView() {
|
|
||||||
if (shouldPersist()) {
|
|
||||||
value = getPersistedValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
final LinearLayout result = new LinearLayout(context);
|
|
||||||
result.setOrientation(LinearLayout.VERTICAL);
|
|
||||||
result.setPadding(6, 6, 6, 6);
|
|
||||||
|
|
||||||
if (description != null) {
|
|
||||||
final TextView splashText = new TextView(context);
|
|
||||||
splashText.setText(description);
|
|
||||||
result.addView(splashText);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needValueText) {
|
|
||||||
valueTextView = new TextView(context);
|
|
||||||
valueTextView.setGravity(Gravity.CENTER_HORIZONTAL);
|
|
||||||
valueTextView.setTextSize(32);
|
|
||||||
|
|
||||||
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.FILL_PARENT,
|
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT);
|
|
||||||
result.addView(valueTextView, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
final View v = createPreferenceView();
|
|
||||||
initPreferenceView(v);
|
|
||||||
|
|
||||||
final LinearLayout.LayoutParams params = getParams();
|
|
||||||
if (params != null) {
|
|
||||||
result.addView(v, params);
|
|
||||||
} else {
|
|
||||||
result.addView(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected abstract LinearLayout.LayoutParams getParams();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSetInitialValue(boolean restore, Object defaultValue) {
|
|
||||||
super.onSetInitialValue(restore, defaultValue);
|
|
||||||
|
|
||||||
if (restore) {
|
|
||||||
if (shouldPersist()) {
|
|
||||||
value = getPersistedValue();
|
|
||||||
} else {
|
|
||||||
value = this.defaultValue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value = (T) defaultValue;
|
|
||||||
if (shouldPersist()) {
|
|
||||||
persist(this.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onBindDialogView(View v) {
|
|
||||||
super.onBindDialogView(v);
|
|
||||||
initPreferenceView(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected abstract View createPreferenceView();
|
|
||||||
|
|
||||||
protected abstract void initPreferenceView(@Nullable View v);
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private T getPersistedValue() {
|
|
||||||
String persistedString = getPersistedString(defaultStringValue);
|
|
||||||
if ( persistedString == defaultStringValue ) {
|
|
||||||
return defaultValue;
|
|
||||||
} else {
|
|
||||||
return getMapper().parseValue(persistedString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void persistValue(@Nullable T value) {
|
|
||||||
Log.d(AbstractDialogPreference.class.getName(), "Trying to persist value: " + value);
|
|
||||||
this.value = value;
|
|
||||||
|
|
||||||
Log.d(AbstractDialogPreference.class.getName(), "android.preference.Preference.callChangeListener()");
|
|
||||||
if (callChangeListener(value)) {
|
|
||||||
Log.d(AbstractDialogPreference.class.getName(), "android.preference.Preference.shouldPersist()");
|
|
||||||
if (shouldPersist()) {
|
|
||||||
Log.d(AbstractDialogPreference.class.getName(), "org.solovyev.android.view.prefs.AbstractDialogPreference.persist()");
|
|
||||||
persist(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void persist(@Nullable T value) {
|
|
||||||
if (value != null) {
|
|
||||||
final String toBePersistedString = getMapper().formatValue(value);
|
|
||||||
if (toBePersistedString != null) {
|
|
||||||
if ( callChangeListener(toBePersistedString) ) {
|
|
||||||
persistString(toBePersistedString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected abstract Mapper<T> getMapper();
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/25/11
|
|
||||||
* Time: 12:23 PM
|
|
||||||
*/
|
|
||||||
public abstract class AbstractPreference<T> implements Preference<T> {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final String key;
|
|
||||||
|
|
||||||
private final T defaultValue;
|
|
||||||
|
|
||||||
protected AbstractPreference(@NotNull String key, @Nullable T defaultValue) {
|
|
||||||
this.key = key;
|
|
||||||
this.defaultValue = defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getKey() {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getDefaultValue() {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final T getPreference(@NotNull SharedPreferences preferences) {
|
|
||||||
if ( preferences.contains(this.key) ) {
|
|
||||||
return getPersistedValue(preferences);
|
|
||||||
} else {
|
|
||||||
return this.defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected abstract T getPersistedValue(@NotNull SharedPreferences preferences);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putDefault(@NotNull SharedPreferences preferences) {
|
|
||||||
putPreference(preferences, this.defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putPreference(@NotNull SharedPreferences preferences, @Nullable T value) {
|
|
||||||
if (value != null) {
|
|
||||||
final SharedPreferences.Editor editor = preferences.edit();
|
|
||||||
putPersistedValue(editor, value);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull T value);
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2012. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import com.google.ads.AdView;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.android.calculator.CalculatorApplication;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 1/14/12
|
|
||||||
* Time: 6:47 PM
|
|
||||||
*/
|
|
||||||
public class AdViewPreference extends android.preference.Preference {
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private AdView adView;
|
|
||||||
|
|
||||||
public AdViewPreference(Context context) {
|
|
||||||
super(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AdViewPreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected View onCreateView(ViewGroup parent) {
|
|
||||||
// this will create the linear layout defined in ads_layout.xml
|
|
||||||
View view = super.onCreateView(parent);
|
|
||||||
|
|
||||||
if (view instanceof ViewGroup) {
|
|
||||||
adView = CalculatorApplication.inflateAd((Activity) getContext(), ((ViewGroup) view), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/25/11
|
|
||||||
* Time: 1:06 PM
|
|
||||||
*/
|
|
||||||
public class BooleanPreference extends AbstractPreference<Boolean>{
|
|
||||||
|
|
||||||
public BooleanPreference(@NotNull String key, @Nullable Boolean defaultValue) {
|
|
||||||
super(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean getPersistedValue(@NotNull SharedPreferences preferences) {
|
|
||||||
return preferences.getBoolean(getKey(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull Boolean value) {
|
|
||||||
editor.putBoolean(getKey(), value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.Mapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/25/11
|
|
||||||
* Time: 1:17 PM
|
|
||||||
*/
|
|
||||||
public class EnumMapper<T extends Enum> implements Mapper<T>{
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Class<T> enumClass;
|
|
||||||
|
|
||||||
public EnumMapper(@NotNull Class<T> enumClass) {
|
|
||||||
this.enumClass = enumClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T extends Enum> Mapper<T> newInstance(@NotNull Class<T> enumClass) {
|
|
||||||
return new EnumMapper<T>(enumClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String formatValue(@Nullable T value) throws IllegalArgumentException {
|
|
||||||
return value == null ? null : value.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T parseValue(@Nullable String value) throws IllegalArgumentException {
|
|
||||||
return value == null ? null : (T)Enum.valueOf(enumClass, value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/25/11
|
|
||||||
* Time: 1:08 PM
|
|
||||||
*/
|
|
||||||
public class FloatPreference extends AbstractPreference<Float> {
|
|
||||||
|
|
||||||
protected FloatPreference(@NotNull String key, @Nullable Float defaultValue) {
|
|
||||||
super(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Float getPersistedValue(@NotNull SharedPreferences preferences) {
|
|
||||||
return preferences.getFloat(getKey(), -1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull Float value) {
|
|
||||||
editor.putFloat(getKey(), value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.common.NumberIntervalMapper;
|
|
||||||
import org.solovyev.common.utils.Interval;
|
|
||||||
import org.solovyev.common.utils.Mapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/21/11
|
|
||||||
* Time: 11:41 PM
|
|
||||||
*/
|
|
||||||
public class FloatRangeSeekBarPreference extends RangeSeekBarPreference<Float> {
|
|
||||||
|
|
||||||
public FloatRangeSeekBarPreference(@NotNull Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected Mapper<Interval<Float>> getMapper() {
|
|
||||||
return new NumberIntervalMapper<Float>(Float.class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/25/11
|
|
||||||
* Time: 12:47 PM
|
|
||||||
*/
|
|
||||||
public class IntegerPreference extends AbstractPreference<Integer> {
|
|
||||||
|
|
||||||
public IntegerPreference(@NotNull String key, @Nullable Integer defaultValue) {
|
|
||||||
super(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Integer getPersistedValue(@NotNull SharedPreferences preferences) {
|
|
||||||
return preferences.getInt(getKey(), -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull Integer value) {
|
|
||||||
editor.putInt(getKey(), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.common.NumberIntervalMapper;
|
|
||||||
import org.solovyev.common.utils.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/19/11
|
|
||||||
* Time: 10:04 PM
|
|
||||||
*/
|
|
||||||
public class IntegerRangeSeekBarPreference extends RangeSeekBarPreference<Integer> {
|
|
||||||
|
|
||||||
public IntegerRangeSeekBarPreference(@NotNull Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected Mapper<Interval<Integer>> getMapper() {
|
|
||||||
return new NumberIntervalMapper<Integer>(Integer.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/25/11
|
|
||||||
* Time: 1:07 PM
|
|
||||||
*/
|
|
||||||
public class LongPreference extends AbstractPreference<Long> {
|
|
||||||
|
|
||||||
protected LongPreference(@NotNull String key, @Nullable Long defaultValue) {
|
|
||||||
super(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Long getPersistedValue(@NotNull SharedPreferences preferences) {
|
|
||||||
return preferences.getLong(getKey(), -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull Long value) {
|
|
||||||
editor.putLong(getKey(), value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.android.view.widgets.NumberPicker;
|
|
||||||
import org.solovyev.common.NumberIntervalMapper;
|
|
||||||
import org.solovyev.common.utils.Interval;
|
|
||||||
import org.solovyev.common.utils.Mapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/26/11
|
|
||||||
* Time: 10:31 PM
|
|
||||||
*/
|
|
||||||
public class NumberPickerDialogPreference extends AbstractDialogPreference<Integer> implements NumberPicker.OnChangedListener {
|
|
||||||
|
|
||||||
private static final NumberIntervalMapper<Integer> INTEGER_NUMBER_INTERVAL_MAPPER = new NumberIntervalMapper<Integer>(Integer.class);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private NumberPicker numberPicker;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Interval<Integer> boundaries;
|
|
||||||
|
|
||||||
public NumberPickerDialogPreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs, null, false);
|
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
boundaries = INTEGER_NUMBER_INTERVAL_MAPPER.parseValue(attrs.getAttributeValue(localNameSpace, "boundaries"));
|
|
||||||
|
|
||||||
createPreferenceView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LinearLayout.LayoutParams getParams() {
|
|
||||||
final LinearLayout.LayoutParams result = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
|
||||||
result.gravity = Gravity.CENTER;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected View createPreferenceView() {
|
|
||||||
this.numberPicker = new NumberPicker(context);
|
|
||||||
this.numberPicker.setOnChangeListener(this);
|
|
||||||
|
|
||||||
initPreferenceView(this.numberPicker);
|
|
||||||
|
|
||||||
return numberPicker;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initPreferenceView(@Nullable View v) {
|
|
||||||
|
|
||||||
if ( v == null ) {
|
|
||||||
v = numberPicker;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value != null) {
|
|
||||||
((NumberPicker) v).setRange(boundaries.getLeftBorder(), boundaries.getRightBorder());
|
|
||||||
((NumberPicker) v).setCurrent(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected Mapper<Integer> getMapper() {
|
|
||||||
return INTEGER_NUMBER_INTERVAL_MAPPER.getMapper();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChanged(NumberPicker picker, int oldVal, int newVal) {
|
|
||||||
persistValue(newVal);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/25/11
|
|
||||||
* Time: 12:21 PM
|
|
||||||
*/
|
|
||||||
|
|
||||||
public interface Preference<T> {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
String getKey();
|
|
||||||
|
|
||||||
T getDefaultValue();
|
|
||||||
|
|
||||||
T getPreference(@NotNull SharedPreferences preferences);
|
|
||||||
|
|
||||||
void putPreference(@NotNull SharedPreferences preferences, @Nullable T value);
|
|
||||||
|
|
||||||
void putDefault(@NotNull SharedPreferences preferences);
|
|
||||||
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.android.view.widgets.AbstractRangeSeekBar;
|
|
||||||
import org.solovyev.android.view.widgets.NumberRangeSeekBar;
|
|
||||||
import org.solovyev.common.utils.Interval;
|
|
||||||
import org.solovyev.common.utils.NumberInterval;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/19/11
|
|
||||||
* Time: 12:27 PM
|
|
||||||
*/
|
|
||||||
public abstract class RangeSeekBarPreference<T extends Number> extends AbstractDialogPreference<Interval<T>> implements AbstractRangeSeekBar.OnRangeSeekBarChangeListener<T> {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private AbstractRangeSeekBar<T> rangeSeekBar;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Interval<T> boundaries;
|
|
||||||
|
|
||||||
private Integer steps;
|
|
||||||
|
|
||||||
public RangeSeekBarPreference(@NotNull Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs, null, true);
|
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
boundaries = getMapper().parseValue(attrs.getAttributeValue(localNameSpace, "boundaries"));
|
|
||||||
|
|
||||||
steps = attrs.getAttributeIntValue(localNameSpace, "steps", -1);
|
|
||||||
if ( steps.equals(-1) ) {
|
|
||||||
steps = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert steps == null || steps >= 2;
|
|
||||||
|
|
||||||
createPreferenceView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected View createPreferenceView() {
|
|
||||||
this.rangeSeekBar = new NumberRangeSeekBar<T>(boundaries, steps, context);
|
|
||||||
this.rangeSeekBar.setNotifyWhileDragging(true);
|
|
||||||
this.rangeSeekBar.setOnRangeSeekBarChangeListener(this);
|
|
||||||
|
|
||||||
initPreferenceView(this.rangeSeekBar);
|
|
||||||
|
|
||||||
return this.rangeSeekBar;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LinearLayout.LayoutParams getParams() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initPreferenceView(@Nullable View v) {
|
|
||||||
if ( v == null ) {
|
|
||||||
v = rangeSeekBar;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value != null) {
|
|
||||||
((AbstractRangeSeekBar<T>) v).setSelectedMinValue(value.getLeftBorder());
|
|
||||||
((AbstractRangeSeekBar<T>) v).setSelectedMaxValue(value.getRightBorder());
|
|
||||||
setValueText(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void rangeSeekBarValuesChanged(T minValue, T maxValue, boolean changeComplete) {
|
|
||||||
final Interval<T> interval = new NumberInterval<T>(minValue, maxValue);
|
|
||||||
|
|
||||||
if (changeComplete) {
|
|
||||||
persistValue(interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
setValueText(interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setValueText(@NotNull Interval<T> interval) {
|
|
||||||
final String t = String.valueOf(interval);
|
|
||||||
valueTextView.setText(valueText == null ? t : t.concat(valueText));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.SeekBar;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.NumberMapper;
|
|
||||||
import org.solovyev.common.utils.Mapper;
|
|
||||||
|
|
||||||
|
|
||||||
/* The following code was written by Matthew Wiggins
|
|
||||||
* and is released under the APACHE 2.0 license
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class SeekBarPreference extends AbstractDialogPreference<Integer> implements SeekBar.OnSeekBarChangeListener {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private SeekBar seekBar;
|
|
||||||
|
|
||||||
private int max = 0;
|
|
||||||
|
|
||||||
public SeekBarPreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs, "50", true);
|
|
||||||
|
|
||||||
max = attrs.getAttributeIntValue(androidns, "max", 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected LinearLayout.LayoutParams getParams() {
|
|
||||||
return new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected View createPreferenceView() {
|
|
||||||
seekBar = new SeekBar(context);
|
|
||||||
seekBar.setOnSeekBarChangeListener(this);
|
|
||||||
|
|
||||||
return seekBar;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initPreferenceView(@Nullable View v ) {
|
|
||||||
if ( v == null) {
|
|
||||||
v = seekBar;
|
|
||||||
}
|
|
||||||
|
|
||||||
((SeekBar) v).setMax(max);
|
|
||||||
if (value != null) {
|
|
||||||
((SeekBar) v).setProgress(value);
|
|
||||||
setValueText(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected Mapper<Integer> getMapper() {
|
|
||||||
return new NumberMapper<Integer>(Integer.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
|
|
||||||
setValueText(value);
|
|
||||||
|
|
||||||
persistValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setValueText(int value) {
|
|
||||||
String t = String.valueOf(value);
|
|
||||||
valueTextView.setText(valueText == null ? t : t.concat(valueText));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onStartTrackingTouch(SeekBar seek) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onStopTrackingTouch(SeekBar seek) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMax(int max) {
|
|
||||||
this.max = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMax() {
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProgress(int progress) {
|
|
||||||
value = progress;
|
|
||||||
seekBar.setProgress(progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getProgress() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.prefs;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.Mapper;
|
|
||||||
import org.solovyev.common.utils.StringMapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/25/11
|
|
||||||
* Time: 12:37 PM
|
|
||||||
*/
|
|
||||||
public class StringPreference<T> extends AbstractPreference<T> {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Mapper<T> mapper;
|
|
||||||
|
|
||||||
public StringPreference(@NotNull String id, @Nullable T defaultValue, @NotNull Mapper<T> mapper) {
|
|
||||||
super(id, defaultValue);
|
|
||||||
this.mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static StringPreference<String> newInstance(@NotNull String id, @Nullable String defaultValue) {
|
|
||||||
return new StringPreference<String>(id, defaultValue, new StringMapper());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static <T> StringPreference<T> newInstance(@NotNull String id, @Nullable String defaultValue, @NotNull Mapper<T> parser) {
|
|
||||||
return new StringPreference<T>(id, parser.parseValue(defaultValue), parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static <T extends Enum> StringPreference<T> newInstance(@NotNull String id, @Nullable T defaultValue, @NotNull Class<T> enumType) {
|
|
||||||
return new StringPreference<T>(id, defaultValue, new EnumMapper<T>(enumType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected T getPersistedValue(@NotNull SharedPreferences preferences) {
|
|
||||||
return mapper.parseValue(preferences.getString(getKey(), null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void putPersistedValue(@NotNull SharedPreferences.Editor editor, @NotNull T value) {
|
|
||||||
editor.putString(getKey(), mapper.formatValue(value));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,422 +0,0 @@
|
|||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/19/11
|
|
||||||
* Time: 3:30 PM
|
|
||||||
*/
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.*;
|
|
||||||
import android.graphics.Paint.Style;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.android.calculator.R;
|
|
||||||
import org.solovyev.common.math.LinearNormalizer;
|
|
||||||
import org.solovyev.common.math.Normalizer;
|
|
||||||
import org.solovyev.common.utils.Converter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Widget that lets users select a minimum and maximum value on a given numerical range.
|
|
||||||
* The range value types can be one of Long, Double, Integer, Float, Short, Byte or BigDecimal.
|
|
||||||
*
|
|
||||||
* @param <T> The Number type of the range values. One of Long, Double, Integer, Float, Short, Byte or BigDecimal.
|
|
||||||
* @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
|
|
||||||
*/
|
|
||||||
public abstract class AbstractRangeSeekBar<T> extends ImageView {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Paint paint = new Paint();
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final ThumbContainer tc;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Converter<T, Double> toDoubleConverter;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Converter<Double, T> toTConverter;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final T minValue, maxValue;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Normalizer fromValueNormalizer;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Normalizer fromScreenNormalizer;
|
|
||||||
|
|
||||||
private double normalizedMinValue = 0d;
|
|
||||||
|
|
||||||
private double normalizedMaxValue = 1d;
|
|
||||||
|
|
||||||
private Thumb pressedThumb = null;
|
|
||||||
|
|
||||||
private boolean notifyWhileDragging = false;
|
|
||||||
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private OnRangeSeekBarChangeListener<T> listener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new RangeSeekBar.
|
|
||||||
*
|
|
||||||
* @param minValue The minimum value of the selectable range.
|
|
||||||
* @param maxValue The maximum value of the selectable range.
|
|
||||||
* @param steps number of steps to be used in range seek bar
|
|
||||||
* @param context parent context
|
|
||||||
* @throws IllegalArgumentException Will be thrown if min/max value types are not one of Long, Double, Integer, Float, Short, Byte or BigDecimal.
|
|
||||||
*/
|
|
||||||
public AbstractRangeSeekBar(@NotNull T minValue, @NotNull T maxValue, @Nullable Integer steps, Context context) throws IllegalArgumentException {
|
|
||||||
super(context);
|
|
||||||
|
|
||||||
this.minValue = minValue;
|
|
||||||
this.maxValue = maxValue;
|
|
||||||
|
|
||||||
this.toDoubleConverter = getToDoubleConverter();
|
|
||||||
this.toTConverter = getToTConverter();
|
|
||||||
|
|
||||||
fromValueNormalizer = new LinearNormalizer(toDoubleConverter.convert(minValue), toDoubleConverter.convert(maxValue));
|
|
||||||
|
|
||||||
tc = new ThumbContainer();
|
|
||||||
|
|
||||||
fromScreenNormalizer = new Normalizer() {
|
|
||||||
@Override
|
|
||||||
public double normalize(double value) {
|
|
||||||
int width = getWidth();
|
|
||||||
if (width <= 2 * tc.padding) {
|
|
||||||
// prevent division by zero, simply return 0.
|
|
||||||
return 0d;
|
|
||||||
} else {
|
|
||||||
double result = (value - tc.padding) / (width - 2 * tc.padding);
|
|
||||||
return Math.min(1d, Math.max(0d, result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double denormalize(double value) {
|
|
||||||
return (float) (tc.padding + value * (getWidth() - 2 * tc.padding));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected abstract Converter<Double,T> getToTConverter();
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected abstract Converter<T,Double> getToDoubleConverter();
|
|
||||||
|
|
||||||
public boolean isNotifyWhileDragging() {
|
|
||||||
return notifyWhileDragging;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should the widget notify the listener callback while the user is still dragging a thumb? Default is false.
|
|
||||||
*
|
|
||||||
* @param flag
|
|
||||||
*/
|
|
||||||
public void setNotifyWhileDragging(boolean flag) {
|
|
||||||
this.notifyWhileDragging = flag;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the absolute minimum value of the range that has been set at construction time.
|
|
||||||
*
|
|
||||||
* @return The absolute minimum value of the range.
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
public T getMinValue() {
|
|
||||||
return minValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the absolute maximum value of the range that has been set at construction time.
|
|
||||||
*
|
|
||||||
* @return The absolute maximum value of the range.
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
public T getMaxValue() {
|
|
||||||
return maxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the currently selected min value.
|
|
||||||
*
|
|
||||||
* @return The currently selected min value.
|
|
||||||
*/
|
|
||||||
public T getSelectedMinValue() {
|
|
||||||
return denormalizeValue(normalizedMinValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the currently selected minimum value. The widget will be invalidated and redrawn.
|
|
||||||
*
|
|
||||||
* @param value The Number value to set the minimum value to. Will be clamped to given absolute minimum/maximum range.
|
|
||||||
*/
|
|
||||||
public void setSelectedMinValue(@NotNull T value) {
|
|
||||||
setNormalizedMinValue(normalizeValue(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the currently selected max value.
|
|
||||||
*
|
|
||||||
* @return The currently selected max value.
|
|
||||||
*/
|
|
||||||
public T getSelectedMaxValue() {
|
|
||||||
return denormalizeValue(normalizedMaxValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the currently selected maximum value. The widget will be invalidated and redrawn.
|
|
||||||
*
|
|
||||||
* @param value The Number value to set the maximum value to. Will be clamped to given absolute minimum/maximum range.
|
|
||||||
*/
|
|
||||||
public void setSelectedMaxValue(@NotNull T value) {
|
|
||||||
setNormalizedMaxValue(normalizeValue(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers given listener callback to notify about changed selected values.
|
|
||||||
*
|
|
||||||
* @param listener The listener to notify about changed selected values.
|
|
||||||
*/
|
|
||||||
public void setOnRangeSeekBarChangeListener(OnRangeSeekBarChangeListener<T> listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles thumb selection and movement. Notifies listener callback on certain events.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
pressedThumb = evalPressedThumb(event.getX());
|
|
||||||
invalidate();
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
if (pressedThumb != null) {
|
|
||||||
|
|
||||||
double value = convertToNormalizedValue(event.getX());
|
|
||||||
|
|
||||||
if (Thumb.MIN.equals(pressedThumb)) {
|
|
||||||
setNormalizedMinValue(value);
|
|
||||||
} else if (Thumb.MAX.equals(pressedThumb)) {
|
|
||||||
setNormalizedMaxValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notifyWhileDragging && listener != null) {
|
|
||||||
listener.rangeSeekBarValuesChanged(getSelectedMinValue(), getSelectedMaxValue(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
case MotionEvent.ACTION_CANCEL:
|
|
||||||
pressedThumb = null;
|
|
||||||
invalidate();
|
|
||||||
if (listener != null) {
|
|
||||||
listener.rangeSeekBarValuesChanged(getSelectedMinValue(), getSelectedMaxValue(), true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures correct size of the widget.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
||||||
int width = 200;
|
|
||||||
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
|
|
||||||
width = MeasureSpec.getSize(widthMeasureSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
int height = tc.thumbImage.getHeight();
|
|
||||||
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
|
|
||||||
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
|
|
||||||
}
|
|
||||||
setMeasuredDimension(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the widget on the given canvas.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas canvas) {
|
|
||||||
super.onDraw(canvas);
|
|
||||||
// draw seek bar background line
|
|
||||||
final RectF rect = tc.getRect();
|
|
||||||
paint.setStyle(Style.FILL);
|
|
||||||
paint.setColor(Color.GRAY);
|
|
||||||
canvas.drawRect(rect, paint);
|
|
||||||
// draw seek bar active range line
|
|
||||||
rect.left = convertToScreenValue(normalizedMinValue);
|
|
||||||
rect.right = convertToScreenValue(normalizedMaxValue);
|
|
||||||
// orange color
|
|
||||||
paint.setColor(Color.rgb(255, 165, 0));
|
|
||||||
canvas.drawRect(rect, paint);
|
|
||||||
|
|
||||||
// draw minimum thumb
|
|
||||||
drawThumb(convertToScreenValue(normalizedMinValue), Thumb.MIN == pressedThumb, canvas);
|
|
||||||
|
|
||||||
// draw maximum thumb
|
|
||||||
drawThumb(convertToScreenValue(normalizedMaxValue), Thumb.MAX == pressedThumb, canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the "normal" resp. "pressed" thumb image on specified x-coordinate.
|
|
||||||
*
|
|
||||||
* @param normalizedToScreenValue The x-coordinate in screen space where to draw the image.
|
|
||||||
* @param pressed Is the thumb currently in "pressed" state?
|
|
||||||
* @param canvas The canvas to draw upon.
|
|
||||||
*/
|
|
||||||
private void drawThumb(float normalizedToScreenValue, boolean pressed, Canvas canvas) {
|
|
||||||
canvas.drawBitmap(tc.getImage(pressed), normalizedToScreenValue - tc.thumbHalfWidth, (float) ((0.5f * getHeight()) - tc.thumbHalfHeight), paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decides which (if any) thumb is touched by the given x-coordinate.
|
|
||||||
*
|
|
||||||
* @param touchX The x-coordinate of a touch event in screen space.
|
|
||||||
* @return The pressed thumb or null if none has been touched.
|
|
||||||
*/
|
|
||||||
private Thumb evalPressedThumb(float touchX) {
|
|
||||||
Thumb result = null;
|
|
||||||
boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue);
|
|
||||||
boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue);
|
|
||||||
if (minThumbPressed && maxThumbPressed) {
|
|
||||||
// if both thumbs are pressed (they lie on top of each other), choose the one with more room to drag. this avoids "stalling" the thumbs in a corner, not being able to drag them apart anymore.
|
|
||||||
result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
|
|
||||||
} else if (minThumbPressed) {
|
|
||||||
result = Thumb.MIN;
|
|
||||||
} else if (maxThumbPressed) {
|
|
||||||
result = Thumb.MAX;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decides if given x-coordinate in screen space needs to be interpreted as "within" the normalized thumb x-coordinate.
|
|
||||||
*
|
|
||||||
* @param touchX The x-coordinate in screen space to check.
|
|
||||||
* @param normalizedThumbValue The normalized x-coordinate of the thumb to check.
|
|
||||||
* @return true if x-coordinate is in thumb range, false otherwise.
|
|
||||||
*/
|
|
||||||
private boolean isInThumbRange(float touchX, double normalizedThumbValue) {
|
|
||||||
return Math.abs(touchX - convertToScreenValue(normalizedThumbValue)) <= tc.thumbHalfWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets normalized min value to value so that 0 <= value <= normalized max value <= 1.
|
|
||||||
* The View will get invalidated when calling this method.
|
|
||||||
*
|
|
||||||
* @param value The new normalized min value to set.
|
|
||||||
*/
|
|
||||||
private void setNormalizedMinValue(double value) {
|
|
||||||
normalizedMinValue = Math.max(0d, Math.min(1d, Math.min(value, normalizedMaxValue)));
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets normalized max value to value so that 0 <= normalized min value <= value <= 1.
|
|
||||||
* The View will get invalidated when calling this method.
|
|
||||||
*
|
|
||||||
* @param value The new normalized max value to set.
|
|
||||||
*/
|
|
||||||
private void setNormalizedMaxValue(double value) {
|
|
||||||
normalizedMaxValue = Math.max(0d, Math.min(1d, Math.max(value, normalizedMinValue)));
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a normalized value to a Number object in the value space between absolute minimum and maximum.
|
|
||||||
*
|
|
||||||
* @param normalized
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private T denormalizeValue(double normalized) {
|
|
||||||
return toTConverter.convert(fromValueNormalizer.denormalize(normalized));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given Number value to a normalized double.
|
|
||||||
*
|
|
||||||
* @param value The Number value to normalize.
|
|
||||||
* @return The normalized double.
|
|
||||||
*/
|
|
||||||
private double normalizeValue(T value) {
|
|
||||||
return fromValueNormalizer.normalize(toDoubleConverter.convert(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a normalized value into screen space.
|
|
||||||
*
|
|
||||||
* @param normalizedValue The normalized value to convert.
|
|
||||||
* @return The converted value in screen space.
|
|
||||||
*/
|
|
||||||
private float convertToScreenValue(double normalizedValue) {
|
|
||||||
return (float)this.fromScreenNormalizer.denormalize(normalizedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts screen space x-coordinates into normalized values.
|
|
||||||
*
|
|
||||||
* @param screenValue The x-coordinate in screen space to convert.
|
|
||||||
* @return The normalized value.
|
|
||||||
*/
|
|
||||||
private double convertToNormalizedValue(float screenValue) {
|
|
||||||
return this.fromScreenNormalizer.normalize(screenValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback listener interface to notify about changed range values.
|
|
||||||
*
|
|
||||||
* @param <T> The Number type the RangeSeekBar has been declared with.
|
|
||||||
* @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
|
|
||||||
*/
|
|
||||||
public interface OnRangeSeekBarChangeListener<T> {
|
|
||||||
|
|
||||||
void rangeSeekBarValuesChanged(T minValue, T maxValue, boolean changeComplete);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thumb constants (min and max).
|
|
||||||
*
|
|
||||||
* @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
|
|
||||||
*/
|
|
||||||
private static enum Thumb {
|
|
||||||
MIN, MAX
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ThumbContainer {
|
|
||||||
@NotNull
|
|
||||||
private final Bitmap thumbImage = BitmapFactory.decodeResource(getResources(), R.drawable.seek_thumb_normal);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Bitmap thumbPressedImage = BitmapFactory.decodeResource(getResources(), R.drawable.seek_thumb_pressed);
|
|
||||||
|
|
||||||
private final float thumbWidth = thumbImage.getWidth();
|
|
||||||
|
|
||||||
private final float thumbHalfWidth = 0.5f * thumbWidth;
|
|
||||||
|
|
||||||
private final float thumbHalfHeight = 0.5f * thumbImage.getHeight();
|
|
||||||
|
|
||||||
private final float lineHeight = 0.3f * thumbHalfHeight;
|
|
||||||
|
|
||||||
private final float padding = thumbHalfWidth;
|
|
||||||
|
|
||||||
public RectF getRect() {
|
|
||||||
return new RectF(padding, 0.5f * (getHeight() - lineHeight), getWidth() - padding, 0.5f * (getHeight() + lineHeight));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bitmap getImage(boolean pressed) {
|
|
||||||
return pressed ? thumbPressedImage : thumbImage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,254 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2008 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Paint.Style;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Vibrator;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.widget.Button;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.android.calculator.R;
|
|
||||||
import org.solovyev.android.view.FontSizeAdjuster;
|
|
||||||
import org.solovyev.common.utils.CollectionsUtils;
|
|
||||||
import org.solovyev.common.utils.Point2d;
|
|
||||||
import org.solovyev.common.utils.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE: copied from com.android.calculator2.ColorButton
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Button with click-animation effect.
|
|
||||||
*/
|
|
||||||
public class ColorButton extends Button {
|
|
||||||
|
|
||||||
private int CLICK_FEEDBACK_COLOR;
|
|
||||||
private static final int CLICK_FEEDBACK_INTERVAL = 10;
|
|
||||||
private static final int CLICK_FEEDBACK_DURATION = 350;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Point2d textPosition;
|
|
||||||
private long animationStart;
|
|
||||||
private Paint feedbackPaint;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final OnClickListenerVibrator onClickListener;
|
|
||||||
|
|
||||||
private static final float H_TEXT_POSITION_DEFAULT_VALUE = 0.5f;
|
|
||||||
|
|
||||||
private float hTextPosition = H_TEXT_POSITION_DEFAULT_VALUE;
|
|
||||||
|
|
||||||
public ColorButton(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColorButton(Context context, AttributeSet attrs, boolean init) {
|
|
||||||
super(context, attrs);
|
|
||||||
|
|
||||||
TypedArray a = context.obtainStyledAttributes(attrs, org.solovyev.android.calculator.R.styleable.DragButton);
|
|
||||||
final int N = a.getIndexCount();
|
|
||||||
for (int i = 0; i < N; i++) {
|
|
||||||
int attr = a.getIndex(i);
|
|
||||||
|
|
||||||
final String attrValue = a.getString(attr);
|
|
||||||
|
|
||||||
if (!StringUtils.isEmpty(attrValue)) {
|
|
||||||
switch (attr) {
|
|
||||||
case R.styleable.DragButton_hTextPosition:
|
|
||||||
this.hTextPosition = Float.valueOf(attrValue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (init) {
|
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onClickListener = new OnClickListenerVibrator((Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE), PreferenceManager.getDefaultSharedPreferences(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void init(Context context) {
|
|
||||||
final Resources resources = getResources();
|
|
||||||
|
|
||||||
CLICK_FEEDBACK_COLOR = resources.getColor(org.solovyev.android.calculator.R.color.magic_flame);
|
|
||||||
feedbackPaint = new Paint();
|
|
||||||
feedbackPaint.setStyle(Style.STROKE);
|
|
||||||
feedbackPaint.setStrokeWidth(2);
|
|
||||||
|
|
||||||
|
|
||||||
if (CollectionsUtils.contains(getText().toString(), Arrays.asList("+", "-", "/", "×", "*", "∙"))) {
|
|
||||||
getPaint().setColor(resources.getColor(R.color.button_operator_text_color));
|
|
||||||
} else if (getText().toString().equals("C")) {
|
|
||||||
getPaint().setColor(resources.getColor(R.color.button_ce_text_color));
|
|
||||||
} else {
|
|
||||||
getPaint().setColor(resources.getColor(R.color.button_text_color));
|
|
||||||
}
|
|
||||||
|
|
||||||
animationStart = -1;
|
|
||||||
|
|
||||||
if (context instanceof FontSizeAdjuster) {
|
|
||||||
((FontSizeAdjuster) context).adjustFontSize(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSizeChanged(int w, int h, int oldW, int oldH) {
|
|
||||||
measureText();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void measureText() {
|
|
||||||
Paint paint = getPaint();
|
|
||||||
|
|
||||||
if (getText() != null) {
|
|
||||||
textPosition = getTextPosition(paint, getText());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point2d getTextPosition(@NotNull Paint paint, @NotNull CharSequence text) {
|
|
||||||
final Point2d result = new Point2d();
|
|
||||||
|
|
||||||
result.setX(hTextPosition * getWidth() - 0.5f * paint.measureText(text.toString()));
|
|
||||||
|
|
||||||
float height = getHeight() - paint.ascent() - paint.descent();
|
|
||||||
|
|
||||||
result.setY(height / 2);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onTextChanged(CharSequence text, int start, int before, int after) {
|
|
||||||
measureText();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void drawMagicFlame(int duration, Canvas canvas) {
|
|
||||||
int alpha = 255 - 255 * duration / CLICK_FEEDBACK_DURATION;
|
|
||||||
int color = CLICK_FEEDBACK_COLOR | (alpha << 24);
|
|
||||||
|
|
||||||
feedbackPaint.setColor(color);
|
|
||||||
canvas.drawRect(1, 1, getWidth() - 1, getHeight() - 1, feedbackPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDraw(Canvas canvas) {
|
|
||||||
if (animationStart != -1) {
|
|
||||||
int animDuration = (int) (System.currentTimeMillis() - animationStart);
|
|
||||||
|
|
||||||
if (animDuration >= CLICK_FEEDBACK_DURATION) {
|
|
||||||
animationStart = -1;
|
|
||||||
} else {
|
|
||||||
drawMagicFlame(animDuration, canvas);
|
|
||||||
postInvalidateDelayed(CLICK_FEEDBACK_INTERVAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CharSequence text = getText();
|
|
||||||
if (!StringUtils.isEmpty(text) && textPosition != null) {
|
|
||||||
canvas.drawText(text, 0, text.length(), textPosition.getX(), textPosition.getY(), getPaint());
|
|
||||||
} else {
|
|
||||||
drawDrawables(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawDrawables(Canvas canvas) {
|
|
||||||
final int compoundPaddingLeft = getCompoundPaddingLeft();
|
|
||||||
final int compoundPaddingTop = getCompoundPaddingTop();
|
|
||||||
final int compoundPaddingRight = getCompoundPaddingRight();
|
|
||||||
final int compoundPaddingBottom = getCompoundPaddingBottom();
|
|
||||||
|
|
||||||
final int scrollX = getScrollX();
|
|
||||||
final int scrollY = getScrollY();
|
|
||||||
|
|
||||||
final int right = getRight();
|
|
||||||
final int left = getLeft();
|
|
||||||
final int bottom = getBottom();
|
|
||||||
final int top = getTop();
|
|
||||||
|
|
||||||
final Drawable[] drawables = getCompoundDrawables();
|
|
||||||
if (drawables != null) {
|
|
||||||
|
|
||||||
int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
|
|
||||||
int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
|
|
||||||
|
|
||||||
Drawable topDr = drawables[1];
|
|
||||||
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
|
|
||||||
// Make sure to update invalidateDrawable() when changing this code.
|
|
||||||
if (topDr != null) {
|
|
||||||
canvas.save();
|
|
||||||
canvas.translate(scrollX + compoundPaddingLeft + (hspace - topDr.getBounds().width()) / 2,
|
|
||||||
scrollY + getPaddingTop() + vspace / 2);
|
|
||||||
topDr.draw(canvas);
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void animateClickFeedback() {
|
|
||||||
animationStart = System.currentTimeMillis();
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean performClick() {
|
|
||||||
vibrate();
|
|
||||||
return super.performClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean performLongClick() {
|
|
||||||
vibrate();
|
|
||||||
return super.performLongClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void vibrate() {
|
|
||||||
this.onClickListener.onClick(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
|
||||||
boolean result = super.onTouchEvent(event);
|
|
||||||
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
animateClickFeedback();
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
case MotionEvent.ACTION_CANCEL:
|
|
||||||
invalidate();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,234 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2007 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* 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.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.*;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
public class ColorPickerDialog extends Dialog {
|
|
||||||
|
|
||||||
public interface OnColorChangedListener {
|
|
||||||
void colorChanged(int color);
|
|
||||||
}
|
|
||||||
|
|
||||||
private OnColorChangedListener mListener;
|
|
||||||
private int mInitialColor;
|
|
||||||
|
|
||||||
private static class ColorPickerView extends View {
|
|
||||||
private Paint mPaint;
|
|
||||||
private Paint mCenterPaint;
|
|
||||||
private final int[] mColors;
|
|
||||||
private OnColorChangedListener mListener;
|
|
||||||
|
|
||||||
ColorPickerView(Context c, OnColorChangedListener l, int color) {
|
|
||||||
super(c);
|
|
||||||
mListener = l;
|
|
||||||
mColors = new int[]{
|
|
||||||
0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00,
|
|
||||||
0xFFFFFF00, 0xFFFF0000
|
|
||||||
};
|
|
||||||
Shader s = new SweepGradient(0, 0, mColors, null);
|
|
||||||
|
|
||||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
||||||
mPaint.setShader(s);
|
|
||||||
mPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
mPaint.setStrokeWidth(32);
|
|
||||||
|
|
||||||
mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
||||||
mCenterPaint.setColor(color);
|
|
||||||
mCenterPaint.setStrokeWidth(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean mTrackingCenter;
|
|
||||||
private boolean mHighlightCenter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas canvas) {
|
|
||||||
float r = CENTER_X - mPaint.getStrokeWidth() * 0.5f;
|
|
||||||
|
|
||||||
canvas.translate(CENTER_X, CENTER_X);
|
|
||||||
|
|
||||||
canvas.drawOval(new RectF(-r, -r, r, r), mPaint);
|
|
||||||
canvas.drawCircle(0, 0, CENTER_RADIUS, mCenterPaint);
|
|
||||||
|
|
||||||
if (mTrackingCenter) {
|
|
||||||
int c = mCenterPaint.getColor();
|
|
||||||
mCenterPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
|
|
||||||
if (mHighlightCenter) {
|
|
||||||
mCenterPaint.setAlpha(0xFF);
|
|
||||||
} else {
|
|
||||||
mCenterPaint.setAlpha(0x80);
|
|
||||||
}
|
|
||||||
canvas.drawCircle(0, 0,
|
|
||||||
CENTER_RADIUS + mCenterPaint.getStrokeWidth(),
|
|
||||||
mCenterPaint);
|
|
||||||
|
|
||||||
mCenterPaint.setStyle(Paint.Style.FILL);
|
|
||||||
mCenterPaint.setColor(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
||||||
setMeasuredDimension(CENTER_X * 2, CENTER_Y * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int CENTER_X = 100;
|
|
||||||
private static final int CENTER_Y = 100;
|
|
||||||
private static final int CENTER_RADIUS = 32;
|
|
||||||
|
|
||||||
private int floatToByte(float x) {
|
|
||||||
int n = java.lang.Math.round(x);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int pinToByte(int n) {
|
|
||||||
if (n < 0) {
|
|
||||||
n = 0;
|
|
||||||
} else if (n > 255) {
|
|
||||||
n = 255;
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int ave(int s, int d, float p) {
|
|
||||||
return s + java.lang.Math.round(p * (d - s));
|
|
||||||
}
|
|
||||||
|
|
||||||
private int interpColor(int colors[], float unit) {
|
|
||||||
if (unit <= 0) {
|
|
||||||
return colors[0];
|
|
||||||
}
|
|
||||||
if (unit >= 1) {
|
|
||||||
return colors[colors.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
float p = unit * (colors.length - 1);
|
|
||||||
int i = (int) p;
|
|
||||||
p -= i;
|
|
||||||
|
|
||||||
// now p is just the fractional part [0...1) and i is the index
|
|
||||||
int c0 = colors[i];
|
|
||||||
int c1 = colors[i + 1];
|
|
||||||
int a = ave(Color.alpha(c0), Color.alpha(c1), p);
|
|
||||||
int r = ave(Color.red(c0), Color.red(c1), p);
|
|
||||||
int g = ave(Color.green(c0), Color.green(c1), p);
|
|
||||||
int b = ave(Color.blue(c0), Color.blue(c1), p);
|
|
||||||
|
|
||||||
return Color.argb(a, r, g, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int rotateColor(int color, float rad) {
|
|
||||||
float deg = rad * 180 / 3.1415927f;
|
|
||||||
int r = Color.red(color);
|
|
||||||
int g = Color.green(color);
|
|
||||||
int b = Color.blue(color);
|
|
||||||
|
|
||||||
ColorMatrix cm = new ColorMatrix();
|
|
||||||
ColorMatrix tmp = new ColorMatrix();
|
|
||||||
|
|
||||||
cm.setRGB2YUV();
|
|
||||||
tmp.setRotate(0, deg);
|
|
||||||
cm.postConcat(tmp);
|
|
||||||
tmp.setYUV2RGB();
|
|
||||||
cm.postConcat(tmp);
|
|
||||||
|
|
||||||
final float[] a = cm.getArray();
|
|
||||||
|
|
||||||
int ir = floatToByte(a[0] * r + a[1] * g + a[2] * b);
|
|
||||||
int ig = floatToByte(a[5] * r + a[6] * g + a[7] * b);
|
|
||||||
int ib = floatToByte(a[10] * r + a[11] * g + a[12] * b);
|
|
||||||
|
|
||||||
return Color.argb(Color.alpha(color), pinToByte(ir),
|
|
||||||
pinToByte(ig), pinToByte(ib));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final float PI = 3.1415926f;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
|
||||||
float x = event.getX() - CENTER_X;
|
|
||||||
float y = event.getY() - CENTER_Y;
|
|
||||||
boolean inCenter = java.lang.Math.sqrt(x * x + y * y) <= CENTER_RADIUS;
|
|
||||||
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
mTrackingCenter = inCenter;
|
|
||||||
if (inCenter) {
|
|
||||||
mHighlightCenter = true;
|
|
||||||
invalidate();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
if (mTrackingCenter) {
|
|
||||||
if (mHighlightCenter != inCenter) {
|
|
||||||
mHighlightCenter = inCenter;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
float angle = (float) java.lang.Math.atan2(y, x);
|
|
||||||
// need to turn angle [-PI ... PI] into unit [0....1]
|
|
||||||
float unit = angle / (2 * PI);
|
|
||||||
if (unit < 0) {
|
|
||||||
unit += 1;
|
|
||||||
}
|
|
||||||
mCenterPaint.setColor(interpColor(mColors, unit));
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
if (mTrackingCenter) {
|
|
||||||
if (inCenter) {
|
|
||||||
mListener.colorChanged(mCenterPaint.getColor());
|
|
||||||
}
|
|
||||||
mTrackingCenter = false; // so we draw w/o halo
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ColorPickerDialog(Context context,
|
|
||||||
OnColorChangedListener listener,
|
|
||||||
int initialColor) {
|
|
||||||
super(context);
|
|
||||||
|
|
||||||
mListener = listener;
|
|
||||||
mInitialColor = initialColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
OnColorChangedListener l = new OnColorChangedListener() {
|
|
||||||
public void colorChanged(int color) {
|
|
||||||
mListener.colorChanged(color);
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setContentView(new ColorPickerView(getContext(), l, mInitialColor));
|
|
||||||
setTitle("Pick a Color");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,395 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.android.calculator.R;
|
|
||||||
import org.solovyev.common.NumberParser;
|
|
||||||
import org.solovyev.common.utils.CollectionsUtils;
|
|
||||||
import org.solovyev.common.utils.Point2d;
|
|
||||||
import org.solovyev.common.utils.StringUtils;
|
|
||||||
|
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 7/17/11
|
|
||||||
* Time: 10:25 PM
|
|
||||||
*/
|
|
||||||
public class DirectionDragButton extends DragButton {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final static Float DEFAULT_DIRECTION_TEXT_SCALE_FLOAT = 0.33f;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final static Integer DEFAULT_DIRECTION_TEXT_ALPHA = 140;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final static String DEFAULT_DIRECTION_TEXT_SCALE = "0.33;0.33;0.33;0.33";
|
|
||||||
|
|
||||||
protected static class DirectionTextData {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final GuiDragDirection guiDragDirection;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String text;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Point2d position;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private TextPaint paint;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Float textScale;
|
|
||||||
|
|
||||||
private DirectionTextData(@NotNull GuiDragDirection guiDragDirection, @NotNull String text) {
|
|
||||||
this.guiDragDirection = guiDragDirection;
|
|
||||||
this.text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public GuiDragDirection getGuiDragDirection() {
|
|
||||||
return guiDragDirection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getText() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setText(@NotNull String text) {
|
|
||||||
this.text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Point2d getPosition() {
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPosition(@NotNull Point2d position) {
|
|
||||||
this.position = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public TextPaint getPaint() {
|
|
||||||
return paint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPaint(@NotNull TextPaint paint) {
|
|
||||||
this.paint = paint;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Float getTextScale() {
|
|
||||||
return textScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTextScale(@NotNull Float textScale) {
|
|
||||||
this.textScale = textScale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static enum GuiDragDirection {
|
|
||||||
up(DragDirection.up, 0) {
|
|
||||||
@Override
|
|
||||||
public int getAttributeId() {
|
|
||||||
return R.styleable.DirectionDragButton_textUp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, int w, int h) {
|
|
||||||
return getUpDownTextPosition(paint, basePaint, text, baseText, 1, w, h);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
down(DragDirection.down, 2) {
|
|
||||||
@Override
|
|
||||||
public int getAttributeId() {
|
|
||||||
return R.styleable.DirectionDragButton_textDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, int w, int h) {
|
|
||||||
return getUpDownTextPosition(paint, basePaint, text, baseText, -1, w, h);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
left(DragDirection.left, 3) {
|
|
||||||
@Override
|
|
||||||
public int getAttributeId() {
|
|
||||||
return R.styleable.DirectionDragButton_textLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, int w, int h) {
|
|
||||||
return getLeftRightTextPosition(paint, basePaint, text, baseText, w, h, true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
right(DragDirection.right, 1) {
|
|
||||||
@Override
|
|
||||||
public int getAttributeId() {
|
|
||||||
return R.styleable.DirectionDragButton_textRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, int w, int h) {
|
|
||||||
return getLeftRightTextPosition(paint, basePaint, text, baseText, w, h, false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final DragDirection dragDirection;
|
|
||||||
|
|
||||||
private final int attributePosition;
|
|
||||||
|
|
||||||
GuiDragDirection(@NotNull DragDirection dragDirection, int attributePosition) {
|
|
||||||
this.dragDirection = dragDirection;
|
|
||||||
this.attributePosition = attributePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract int getAttributeId();
|
|
||||||
|
|
||||||
public int getAttributePosition() {
|
|
||||||
return attributePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public abstract Point2d getTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, int w, int h);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static Point2d getLeftRightTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, CharSequence text, @NotNull CharSequence baseText, int w, int h, boolean left) {
|
|
||||||
final Point2d result = new Point2d();
|
|
||||||
|
|
||||||
if (left) {
|
|
||||||
float width = paint.measureText(" ");
|
|
||||||
result.setX(width);
|
|
||||||
} else {
|
|
||||||
float width = paint.measureText(text.toString() + " ");
|
|
||||||
result.setX(w - width);
|
|
||||||
}
|
|
||||||
|
|
||||||
float selfHeight = paint.ascent() + paint.descent();
|
|
||||||
|
|
||||||
basePaint.measureText(StringUtils.getNotEmpty(baseText, "|"));
|
|
||||||
|
|
||||||
result.setY(h / 2 - selfHeight / 2);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static Point2d getUpDownTextPosition(@NotNull Paint paint, @NotNull Paint basePaint, @NotNull CharSequence text, CharSequence baseText, float direction, int w, int h) {
|
|
||||||
final Point2d result = new Point2d();
|
|
||||||
|
|
||||||
float width = paint.measureText(text.toString() + " ");
|
|
||||||
result.setX(w - width);
|
|
||||||
|
|
||||||
float selfHeight = paint.ascent() + paint.descent();
|
|
||||||
|
|
||||||
basePaint.measureText(StringUtils.getNotEmpty(baseText, "|"));
|
|
||||||
|
|
||||||
if (direction < 0) {
|
|
||||||
result.setY(h / 2 + h / 3 - selfHeight / 2);
|
|
||||||
} else {
|
|
||||||
result.setY(h / 2 - h / 3 - selfHeight / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static GuiDragDirection valueOf(@NotNull DragDirection dragDirection) {
|
|
||||||
for (GuiDragDirection guiDragDirection : values()) {
|
|
||||||
if (guiDragDirection.dragDirection == dragDirection) {
|
|
||||||
return guiDragDirection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Map<GuiDragDirection, DirectionTextData> directionTextDataMap = new EnumMap<GuiDragDirection, DirectionTextData>(GuiDragDirection.class);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String directionTextScale = DEFAULT_DIRECTION_TEXT_SCALE;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Integer directionTextAlpha = DEFAULT_DIRECTION_TEXT_ALPHA;
|
|
||||||
|
|
||||||
private boolean initialized = false;
|
|
||||||
|
|
||||||
public DirectionDragButton(Context context, @NotNull AttributeSet attrs) {
|
|
||||||
super(context, attrs, false);
|
|
||||||
init(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(@NotNull Context context, @NotNull AttributeSet attrs) {
|
|
||||||
|
|
||||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DirectionDragButton);
|
|
||||||
|
|
||||||
final int N = a.getIndexCount();
|
|
||||||
for (int i = 0; i < N; i++) {
|
|
||||||
int attr = a.getIndex(i);
|
|
||||||
|
|
||||||
final String attrValue = a.getString(attr);
|
|
||||||
|
|
||||||
if (!StringUtils.isEmpty(attrValue)) {
|
|
||||||
switch (attr) {
|
|
||||||
case R.styleable.DirectionDragButton_directionTextScale:
|
|
||||||
this.directionTextScale = attrValue;
|
|
||||||
break;
|
|
||||||
case R.styleable.DirectionDragButton_directionTextAlpha:
|
|
||||||
this.directionTextAlpha = Integer.valueOf(attrValue);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// try drag direction text
|
|
||||||
for (GuiDragDirection guiDragDirection : GuiDragDirection.values()) {
|
|
||||||
if (guiDragDirection.getAttributeId() == attr) {
|
|
||||||
this.directionTextDataMap.put(guiDragDirection, new DirectionTextData(guiDragDirection, attrValue));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<GuiDragDirection, Float> entry : getDirectionTextScales().entrySet()) {
|
|
||||||
final DirectionTextData dtd = directionTextDataMap.get(entry.getKey());
|
|
||||||
if (dtd != null) {
|
|
||||||
dtd.setTextScale(entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.init(context);
|
|
||||||
initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void measureText() {
|
|
||||||
super.measureText();
|
|
||||||
|
|
||||||
if (initialized) {
|
|
||||||
final Paint basePaint = getPaint();
|
|
||||||
final Resources resources = getResources();
|
|
||||||
|
|
||||||
for (DirectionTextData directionTextData : directionTextDataMap.values()) {
|
|
||||||
initDirectionTextPaint(basePaint, directionTextData, resources);
|
|
||||||
|
|
||||||
final GuiDragDirection guiDragDirection = directionTextData.getGuiDragDirection();
|
|
||||||
final String directionText = directionTextData.getText();
|
|
||||||
final Paint directionPaint = directionTextData.getPaint();
|
|
||||||
|
|
||||||
directionTextData.setPosition(guiDragDirection.getTextPosition(directionPaint, basePaint, directionText, getText(), getWidth(), getHeight()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDraw(Canvas canvas) {
|
|
||||||
super.onDraw(canvas);
|
|
||||||
|
|
||||||
final TextPaint paint = getPaint();
|
|
||||||
final Resources resources = getResources();
|
|
||||||
|
|
||||||
for (DirectionTextData directionTextData : directionTextDataMap.values()) {
|
|
||||||
initDirectionTextPaint(paint, directionTextData, resources);
|
|
||||||
final String text = directionTextData.getText();
|
|
||||||
final Point2d position = directionTextData.getPosition();
|
|
||||||
canvas.drawText(text, 0, text.length(), position.getX(), position.getY(), directionTextData.getPaint());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void initDirectionTextPaint(@NotNull Paint basePaint,
|
|
||||||
@NotNull DirectionTextData directionTextData,
|
|
||||||
@NotNull Resources resources) {
|
|
||||||
final TextPaint directionTextPaint = new TextPaint(basePaint);
|
|
||||||
|
|
||||||
directionTextPaint.setColor(resources.getColor(R.color.button_text_color));
|
|
||||||
directionTextPaint.setAlpha(getDirectionTextAlpha());
|
|
||||||
directionTextPaint.setTextSize(basePaint.getTextSize() * directionTextData.getTextScale());
|
|
||||||
|
|
||||||
directionTextData.setPaint(directionTextPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int getDirectionTextAlpha() {
|
|
||||||
return directionTextAlpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getTextUp() {
|
|
||||||
return getText(GuiDragDirection.up);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getTextDown() {
|
|
||||||
return getText(GuiDragDirection.down);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getText(@NotNull DragDirection direction) {
|
|
||||||
final GuiDragDirection guiDragDirection = GuiDragDirection.valueOf(direction);
|
|
||||||
return guiDragDirection == null ? null : getText(guiDragDirection);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String getText(@NotNull GuiDragDirection direction) {
|
|
||||||
DirectionTextData directionTextData = this.directionTextDataMap.get(direction);
|
|
||||||
return directionTextData == null ? null : directionTextData.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getDirectionTextScale() {
|
|
||||||
return directionTextScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Map<GuiDragDirection, Float> getDirectionTextScales() {
|
|
||||||
final List<Float> scales = CollectionsUtils.split(getDirectionTextScale(), ";", new NumberParser<Float>(Float.class));
|
|
||||||
|
|
||||||
final Map<GuiDragDirection, Float> result = new HashMap<GuiDragDirection, Float>();
|
|
||||||
for (GuiDragDirection guiDragDirection : GuiDragDirection.values()) {
|
|
||||||
result.put(guiDragDirection, DEFAULT_DIRECTION_TEXT_SCALE_FLOAT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scales.size() == 1) {
|
|
||||||
final Float scale = scales.get(0);
|
|
||||||
for (Map.Entry<GuiDragDirection, Float> entry : result.entrySet()) {
|
|
||||||
entry.setValue(scale);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < scales.size(); i++) {
|
|
||||||
for (GuiDragDirection guiDragDirection : GuiDragDirection.values()) {
|
|
||||||
if (guiDragDirection.getAttributePosition() == i) {
|
|
||||||
result.put(guiDragDirection, scales.get(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.Point2d;
|
|
||||||
|
|
||||||
public class DragButton extends ColorButton {
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private Point2d startPoint = null;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private org.solovyev.android.view.widgets.OnDragListener onDragListener;
|
|
||||||
|
|
||||||
private final OnTouchListener onTouchListener = new OnTouchListenerImpl();
|
|
||||||
|
|
||||||
public DragButton(Context context, @NotNull AttributeSet attrs) {
|
|
||||||
this(context, attrs, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DragButton(Context context, @NotNull AttributeSet attrs, boolean init) {
|
|
||||||
super(context, attrs, false);
|
|
||||||
setOnTouchListener(this.onTouchListener);
|
|
||||||
if ( init ) {
|
|
||||||
super.init(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnDragListener(@Nullable org.solovyev.android.view.widgets.OnDragListener onDragListener) {
|
|
||||||
this.onDragListener = onDragListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public org.solovyev.android.view.widgets.OnDragListener getOnDragListener() {
|
|
||||||
return onDragListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OnTouchListener implementation that fires onDrag()
|
|
||||||
*
|
|
||||||
* @author serso
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private final class OnTouchListenerImpl implements OnTouchListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(@NotNull View v, @NotNull MotionEvent event) {
|
|
||||||
// processing on touch event
|
|
||||||
|
|
||||||
// in order to avoid possible NPEs
|
|
||||||
final Point2d localStartPoint = startPoint;
|
|
||||||
final org.solovyev.android.view.widgets.OnDragListener localOnDragListener = onDragListener;
|
|
||||||
|
|
||||||
if (localOnDragListener != null) {
|
|
||||||
// only if onDrag() listener specified
|
|
||||||
|
|
||||||
Log.d(String.valueOf(getId()), "onTouch() for: " + getId() + " . Motion event: " + event);
|
|
||||||
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
// start tracking: set start point
|
|
||||||
startPoint = new Point2d(event.getX(), event.getY());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
// stop tracking
|
|
||||||
|
|
||||||
if (localStartPoint != null && localOnDragListener.onDrag(DragButton.this, new DragEvent(localStartPoint, event))) {
|
|
||||||
if (localOnDragListener.isSuppressOnClickEvent()) {
|
|
||||||
// prevent on click action
|
|
||||||
setPressed(false);
|
|
||||||
|
|
||||||
// sometimes setPressed(false); doesn't work so to prevent onClick action button disables
|
|
||||||
if (v instanceof Button) {
|
|
||||||
final Button button = (Button) v;
|
|
||||||
|
|
||||||
button.setEnabled(false);
|
|
||||||
|
|
||||||
new Handler().postDelayed(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
button.setEnabled(true);
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startPoint = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
public enum DragDirection {
|
|
||||||
|
|
||||||
up,
|
|
||||||
down,
|
|
||||||
left,
|
|
||||||
right;
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.common.utils.Point2d;
|
|
||||||
|
|
||||||
public class DragEvent {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Point2d startPoint;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final MotionEvent motionEvent;
|
|
||||||
|
|
||||||
public DragEvent(@NotNull Point2d startPoint, @NotNull MotionEvent motionEvent) {
|
|
||||||
this.startPoint = startPoint;
|
|
||||||
this.motionEvent = motionEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return motion event started at start point
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
public MotionEvent getMotionEvent() {
|
|
||||||
return motionEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return start point of dragging
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
public Point2d getStartPoint() {
|
|
||||||
return startPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.EventListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/18/11
|
|
||||||
* Time: 8:48 PM
|
|
||||||
*/
|
|
||||||
public interface DragPreferencesChangeListener extends EventListener{
|
|
||||||
|
|
||||||
void onDragPreferencesChange(@NotNull SimpleOnDragListener.Preferences preferences );
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.android.view.HistoryControl;
|
|
||||||
import org.solovyev.common.utils.Point2d;
|
|
||||||
import org.solovyev.common.utils.StringUtils;
|
|
||||||
import org.solovyev.common.utils.history.HistoryAction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/16/11
|
|
||||||
* Time: 11:36 PM
|
|
||||||
*/
|
|
||||||
public class HistoryDragProcessor<T> implements SimpleOnDragListener.DragProcessor {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final HistoryControl<T> historyControl;
|
|
||||||
|
|
||||||
public HistoryDragProcessor(@NotNull HistoryControl<T> historyControl) {
|
|
||||||
this.historyControl = historyControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) {
|
|
||||||
boolean result = false;
|
|
||||||
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "History on drag event start: " + dragDirection);
|
|
||||||
|
|
||||||
final HistoryAction historyAction;
|
|
||||||
if ( dragDirection == DragDirection.up ) {
|
|
||||||
historyAction = HistoryAction.undo;
|
|
||||||
} else if ( dragDirection == DragDirection.down ) {
|
|
||||||
historyAction = HistoryAction.redo;
|
|
||||||
} else {
|
|
||||||
historyAction = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (historyAction != null) {
|
|
||||||
result = true;
|
|
||||||
historyControl.doHistoryAction(historyAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,538 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/18/11
|
|
||||||
* Time: 10:03 PM
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* Copyright (C) 2008 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.text.InputFilter;
|
|
||||||
import android.text.InputType;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.method.NumberKeyListener;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import org.solovyev.android.calculator.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A view for selecting a number
|
|
||||||
*
|
|
||||||
* For a dialog using this view, see {@link android.app.TimePickerDialog}.
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public class NumberPicker extends LinearLayout {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The callback interface used to indicate the number value has been adjusted.
|
|
||||||
*/
|
|
||||||
public interface OnChangedListener {
|
|
||||||
/**
|
|
||||||
* @param picker The NumberPicker associated with this listener.
|
|
||||||
* @param oldVal The previous value.
|
|
||||||
* @param newVal The new value.
|
|
||||||
*/
|
|
||||||
void onChanged(NumberPicker picker, int oldVal, int newVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface used to format the number into a string for presentation
|
|
||||||
*/
|
|
||||||
public interface Formatter {
|
|
||||||
String toString(int value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Use a custom NumberPicker formatting callback to use two-digit
|
|
||||||
* minutes strings like "01". Keeping a static formatter etc. is the
|
|
||||||
* most efficient way to do this; it avoids creating temporary objects
|
|
||||||
* on every call to format().
|
|
||||||
*/
|
|
||||||
public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER =
|
|
||||||
new NumberPicker.Formatter() {
|
|
||||||
final StringBuilder mBuilder = new StringBuilder();
|
|
||||||
final java.util.Formatter mFmt = new java.util.Formatter(
|
|
||||||
mBuilder, java.util.Locale.US);
|
|
||||||
final Object[] mArgs = new Object[1];
|
|
||||||
public String toString(int value) {
|
|
||||||
mArgs[0] = value;
|
|
||||||
mBuilder.delete(0, mBuilder.length());
|
|
||||||
mFmt.format("%02d", mArgs);
|
|
||||||
return mFmt.toString();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Handler mHandler;
|
|
||||||
private final Runnable mRunnable = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (mIncrement) {
|
|
||||||
changeCurrent(mCurrent + 1);
|
|
||||||
mHandler.postDelayed(this, mSpeed);
|
|
||||||
} else if (mDecrement) {
|
|
||||||
changeCurrent(mCurrent - 1);
|
|
||||||
mHandler.postDelayed(this, mSpeed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final EditText mText;
|
|
||||||
private final InputFilter mNumberInputFilter;
|
|
||||||
|
|
||||||
private String[] mDisplayedValues;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lower value of the range of numbers allowed for the NumberPicker
|
|
||||||
*/
|
|
||||||
private int mStart;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upper value of the range of numbers allowed for the NumberPicker
|
|
||||||
*/
|
|
||||||
private int mEnd;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current value of this NumberPicker
|
|
||||||
*/
|
|
||||||
private int mCurrent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Previous value of this NumberPicker.
|
|
||||||
*/
|
|
||||||
private int mPrevious;
|
|
||||||
private OnChangedListener mListener;
|
|
||||||
private Formatter mFormatter;
|
|
||||||
private long mSpeed = 300;
|
|
||||||
|
|
||||||
private boolean mIncrement;
|
|
||||||
private boolean mDecrement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new number picker
|
|
||||||
* @param context the application environment
|
|
||||||
*/
|
|
||||||
public NumberPicker(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new number picker
|
|
||||||
* @param context the application environment
|
|
||||||
* @param attrs a collection of attributes
|
|
||||||
*/
|
|
||||||
public NumberPicker(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
setOrientation(VERTICAL);
|
|
||||||
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
inflater.inflate(R.layout.number_picker, this, true);
|
|
||||||
mHandler = new Handler();
|
|
||||||
|
|
||||||
OnClickListener clickListener = new OnClickListener() {
|
|
||||||
public void onClick(View v) {
|
|
||||||
validateInput(mText);
|
|
||||||
if (!mText.hasFocus()) mText.requestFocus();
|
|
||||||
|
|
||||||
// now perform the increment/decrement
|
|
||||||
if (R.id.increment == v.getId()) {
|
|
||||||
changeCurrent(mCurrent + 1);
|
|
||||||
} else if (R.id.decrement == v.getId()) {
|
|
||||||
changeCurrent(mCurrent - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
OnFocusChangeListener focusListener = new OnFocusChangeListener() {
|
|
||||||
public void onFocusChange(View v, boolean hasFocus) {
|
|
||||||
|
|
||||||
/* When focus is lost check that the text field
|
|
||||||
* has valid values.
|
|
||||||
*/
|
|
||||||
if (!hasFocus) {
|
|
||||||
validateInput(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
OnLongClickListener longClickListener = new OnLongClickListener() {
|
|
||||||
/**
|
|
||||||
* We start the long click here but rely on the {@link NumberPickerButton}
|
|
||||||
* to inform us when the long click has ended.
|
|
||||||
*/
|
|
||||||
public boolean onLongClick(View v) {
|
|
||||||
/* The text view may still have focus so clear it's focus which will
|
|
||||||
* trigger the on focus changed and any typed values to be pulled.
|
|
||||||
*/
|
|
||||||
mText.clearFocus();
|
|
||||||
|
|
||||||
if (R.id.increment == v.getId()) {
|
|
||||||
mIncrement = true;
|
|
||||||
mHandler.post(mRunnable);
|
|
||||||
} else if (R.id.decrement == v.getId()) {
|
|
||||||
mDecrement = true;
|
|
||||||
mHandler.post(mRunnable);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
InputFilter inputFilter = new NumberPickerInputFilter();
|
|
||||||
mNumberInputFilter = new NumberRangeKeyListener();
|
|
||||||
mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
|
|
||||||
mIncrementButton.setOnClickListener(clickListener);
|
|
||||||
mIncrementButton.setOnLongClickListener(longClickListener);
|
|
||||||
mIncrementButton.setNumberPicker(this);
|
|
||||||
|
|
||||||
mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement);
|
|
||||||
mDecrementButton.setOnClickListener(clickListener);
|
|
||||||
mDecrementButton.setOnLongClickListener(longClickListener);
|
|
||||||
mDecrementButton.setNumberPicker(this);
|
|
||||||
|
|
||||||
mText = (EditText) findViewById(R.id.timepicker_input);
|
|
||||||
mText.setOnFocusChangeListener(focusListener);
|
|
||||||
mText.setFilters(new InputFilter[] {inputFilter});
|
|
||||||
mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
|
|
||||||
|
|
||||||
if (!isEnabled()) {
|
|
||||||
setEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the enabled state of this view. The interpretation of the enabled
|
|
||||||
* state varies by subclass.
|
|
||||||
*
|
|
||||||
* @param enabled True if this view is enabled, false otherwise.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setEnabled(boolean enabled) {
|
|
||||||
super.setEnabled(enabled);
|
|
||||||
mIncrementButton.setEnabled(enabled);
|
|
||||||
mDecrementButton.setEnabled(enabled);
|
|
||||||
mText.setEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the callback that indicates the number has been adjusted by the user.
|
|
||||||
* @param listener the callback, should not be null.
|
|
||||||
*/
|
|
||||||
public void setOnChangeListener(OnChangedListener listener) {
|
|
||||||
mListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the formatter that will be used to format the number for presentation
|
|
||||||
* @param formatter the formatter object. If formatter is null, String.valueOf()
|
|
||||||
* will be used
|
|
||||||
*/
|
|
||||||
public void setFormatter(Formatter formatter) {
|
|
||||||
mFormatter = formatter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the range of numbers allowed for the number picker. The current
|
|
||||||
* value will be automatically set to the start.
|
|
||||||
*
|
|
||||||
* @param start the start of the range (inclusive)
|
|
||||||
* @param end the end of the range (inclusive)
|
|
||||||
*/
|
|
||||||
public void setRange(int start, int end) {
|
|
||||||
setRange(start, end, null/*displayedValues*/);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the range of numbers allowed for the number picker. The current
|
|
||||||
* value will be automatically set to the start. Also provide a mapping
|
|
||||||
* for values used to display to the user.
|
|
||||||
*
|
|
||||||
* @param start the start of the range (inclusive)
|
|
||||||
* @param end the end of the range (inclusive)
|
|
||||||
* @param displayedValues the values displayed to the user.
|
|
||||||
*/
|
|
||||||
public void setRange(int start, int end, String[] displayedValues) {
|
|
||||||
mDisplayedValues = displayedValues;
|
|
||||||
mStart = start;
|
|
||||||
mEnd = end;
|
|
||||||
mCurrent = start;
|
|
||||||
updateView();
|
|
||||||
|
|
||||||
if (displayedValues != null) {
|
|
||||||
// Allow text entry rather than strictly numeric entry.
|
|
||||||
mText.setRawInputType(InputType.TYPE_CLASS_TEXT/* |
|
|
||||||
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS*/);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the current value for the number picker.
|
|
||||||
*
|
|
||||||
* @param current the current value the start of the range (inclusive)
|
|
||||||
* @throws IllegalArgumentException when current is not within the range
|
|
||||||
* of of the number picker
|
|
||||||
*/
|
|
||||||
public void setCurrent(int current) {
|
|
||||||
if (current < mStart || current > mEnd) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"current should be >= start and <= end");
|
|
||||||
}
|
|
||||||
mCurrent = current;
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the speed at which the numbers will scroll when the +/-
|
|
||||||
* buttons are longpressed
|
|
||||||
*
|
|
||||||
* @param speed The speed (in milliseconds) at which the numbers will scroll
|
|
||||||
* default 300ms
|
|
||||||
*/
|
|
||||||
public void setSpeed(long speed) {
|
|
||||||
mSpeed = speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formatNumber(int value) {
|
|
||||||
return (mFormatter != null)
|
|
||||||
? mFormatter.toString(value)
|
|
||||||
: String.valueOf(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the current value of this NumberPicker, and sets mPrevious to the previous
|
|
||||||
* value. If current is greater than mEnd less than mStart, the value of mCurrent
|
|
||||||
* is wrapped around.
|
|
||||||
*
|
|
||||||
* Subclasses can override this to change the wrapping behavior
|
|
||||||
*
|
|
||||||
* @param current the new value of the NumberPicker
|
|
||||||
*/
|
|
||||||
protected void changeCurrent(int current) {
|
|
||||||
// Wrap around the values if we go past the start or end
|
|
||||||
if (current > mEnd) {
|
|
||||||
current = mStart;
|
|
||||||
} else if (current < mStart) {
|
|
||||||
current = mEnd;
|
|
||||||
}
|
|
||||||
mPrevious = mCurrent;
|
|
||||||
mCurrent = current;
|
|
||||||
notifyChange();
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the listener, if registered, of a change of the value of this
|
|
||||||
* NumberPicker.
|
|
||||||
*/
|
|
||||||
private void notifyChange() {
|
|
||||||
if (mListener != null) {
|
|
||||||
mListener.onChanged(this, mPrevious, mCurrent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the view of this NumberPicker. If displayValues were specified
|
|
||||||
* in {@link #setRange}, the string corresponding to the index specified by
|
|
||||||
* the current value will be returned. Otherwise, the formatter specified
|
|
||||||
* in will be used to format the number.
|
|
||||||
*/
|
|
||||||
private void updateView() {
|
|
||||||
/* If we don't have displayed values then use the
|
|
||||||
* current number else find the correct value in the
|
|
||||||
* displayed values for the current number.
|
|
||||||
*/
|
|
||||||
if (mDisplayedValues == null) {
|
|
||||||
mText.setText(formatNumber(mCurrent));
|
|
||||||
} else {
|
|
||||||
mText.setText(mDisplayedValues[mCurrent - mStart]);
|
|
||||||
}
|
|
||||||
mText.setSelection(mText.getText().length());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateCurrentView(CharSequence str) {
|
|
||||||
int val = getSelectedPos(str.toString());
|
|
||||||
if ((val >= mStart) && (val <= mEnd)) {
|
|
||||||
if (mCurrent != val) {
|
|
||||||
mPrevious = mCurrent;
|
|
||||||
mCurrent = val;
|
|
||||||
notifyChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateInput(View v) {
|
|
||||||
String str = String.valueOf(((TextView) v).getText());
|
|
||||||
if ("".equals(str)) {
|
|
||||||
|
|
||||||
// Restore to the old value as we don't allow empty values
|
|
||||||
updateView();
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Check the new value and ensure it's in range
|
|
||||||
validateCurrentView(str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public void cancelIncrement() {
|
|
||||||
mIncrement = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public void cancelDecrement() {
|
|
||||||
mDecrement = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final char[] DIGIT_CHARACTERS = new char[] {
|
|
||||||
'-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
|
|
||||||
};
|
|
||||||
|
|
||||||
private NumberPickerButton mIncrementButton;
|
|
||||||
private NumberPickerButton mDecrementButton;
|
|
||||||
|
|
||||||
private class NumberPickerInputFilter implements InputFilter {
|
|
||||||
public CharSequence filter(CharSequence source, int start, int end,
|
|
||||||
Spanned dest, int dstart, int dend) {
|
|
||||||
if (mDisplayedValues == null) {
|
|
||||||
return mNumberInputFilter.filter(source, start, end, dest, dstart, dend);
|
|
||||||
}
|
|
||||||
CharSequence filtered = String.valueOf(source.subSequence(start, end));
|
|
||||||
String result = String.valueOf(dest.subSequence(0, dstart))
|
|
||||||
+ filtered
|
|
||||||
+ dest.subSequence(dend, dest.length());
|
|
||||||
String str = String.valueOf(result).toLowerCase();
|
|
||||||
for (String val : mDisplayedValues) {
|
|
||||||
val = val.toLowerCase();
|
|
||||||
if (val.startsWith(str)) {
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class NumberRangeKeyListener extends NumberKeyListener {
|
|
||||||
|
|
||||||
// XXX This doesn't allow for range limits when controlled by a
|
|
||||||
// soft input method!
|
|
||||||
public int getInputType() {
|
|
||||||
return InputType.TYPE_CLASS_NUMBER;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected char[] getAcceptedChars() {
|
|
||||||
return DIGIT_CHARACTERS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence filter(CharSequence source, int start, int end,
|
|
||||||
Spanned dest, int dstart, int dend) {
|
|
||||||
|
|
||||||
CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
|
|
||||||
if (filtered == null) {
|
|
||||||
filtered = source.subSequence(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
String result = String.valueOf(dest.subSequence(0, dstart))
|
|
||||||
+ filtered
|
|
||||||
+ dest.subSequence(dend, dest.length());
|
|
||||||
|
|
||||||
if ("".equals(result)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int val = getSelectedPos(result);
|
|
||||||
|
|
||||||
/* Ensure the user can't type in a value greater
|
|
||||||
* than the max allowed. We have to allow less than min
|
|
||||||
* as the user might want to delete some numbers
|
|
||||||
* and then type a new number.
|
|
||||||
*/
|
|
||||||
if (val > mEnd) {
|
|
||||||
return "";
|
|
||||||
} else {
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getSelectedPos(String str) {
|
|
||||||
if (mDisplayedValues == null) {
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(str);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
/* Ignore as if it's not a number we don't care */
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < mDisplayedValues.length; i++) {
|
|
||||||
/* Don't force the user to type in jan when ja will do */
|
|
||||||
str = str.toLowerCase();
|
|
||||||
if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
|
|
||||||
return mStart + i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The user might have typed in a number into the month field i.e.
|
|
||||||
* 10 instead of OCT so support that too.
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(str);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
|
|
||||||
/* Ignore as if it's not a number we don't care */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current value of the NumberPicker
|
|
||||||
* @return the current value.
|
|
||||||
*/
|
|
||||||
public int getCurrent() {
|
|
||||||
return mCurrent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the upper value of the range of the NumberPicker
|
|
||||||
* @return the uppper number of the range.
|
|
||||||
*/
|
|
||||||
protected int getEndRange() {
|
|
||||||
return mEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the lower value of the range of the NumberPicker
|
|
||||||
* @return the lower number of the range.
|
|
||||||
*/
|
|
||||||
protected int getBeginRange() {
|
|
||||||
return mStart;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/18/11
|
|
||||||
* Time: 10:04 PM
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* Copyright (C) 2008 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import org.solovyev.android.calculator.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class exists purely to cancel long click events, that got
|
|
||||||
* started in NumberPicker
|
|
||||||
*/
|
|
||||||
class NumberPickerButton extends ImageButton {
|
|
||||||
|
|
||||||
private NumberPicker mNumberPicker;
|
|
||||||
|
|
||||||
public NumberPickerButton(Context context, AttributeSet attrs,
|
|
||||||
int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NumberPickerButton(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NumberPickerButton(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNumberPicker(NumberPicker picker) {
|
|
||||||
mNumberPicker = picker;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
|
||||||
cancelLongpressIfRequired(event);
|
|
||||||
return super.onTouchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTrackballEvent(MotionEvent event) {
|
|
||||||
cancelLongpressIfRequired(event);
|
|
||||||
return super.onTrackballEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
||||||
if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
|
|
||||||
|| (keyCode == KeyEvent.KEYCODE_ENTER)) {
|
|
||||||
cancelLongpress();
|
|
||||||
}
|
|
||||||
return super.onKeyUp(keyCode, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cancelLongpressIfRequired(MotionEvent event) {
|
|
||||||
if ((event.getAction() == MotionEvent.ACTION_CANCEL)
|
|
||||||
|| (event.getAction() == MotionEvent.ACTION_UP)) {
|
|
||||||
cancelLongpress();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cancelLongpress() {
|
|
||||||
if (R.id.increment == getId()) {
|
|
||||||
mNumberPicker.cancelIncrement();
|
|
||||||
} else if (R.id.decrement == getId()) {
|
|
||||||
mNumberPicker.cancelDecrement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
|
||||||
super.onWindowFocusChanged(hasWindowFocus);
|
|
||||||
if (!hasWindowFocus) {
|
|
||||||
cancelLongpress();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.Converter;
|
|
||||||
import org.solovyev.common.utils.Interval;
|
|
||||||
import org.solovyev.common.utils.NumberValuer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/19/11
|
|
||||||
* Time: 4:26 PM
|
|
||||||
*/
|
|
||||||
public class NumberRangeSeekBar<T extends Number> extends AbstractRangeSeekBar<T> {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final NumberType numberType;
|
|
||||||
|
|
||||||
|
|
||||||
public NumberRangeSeekBar(@NotNull Interval<T> boundaries, @Nullable Integer steps, Context context) throws IllegalArgumentException {
|
|
||||||
this(boundaries.getLeftBorder(), boundaries.getRightBorder(), steps, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new RangeSeekBar.
|
|
||||||
*
|
|
||||||
* @param minValue The minimum value of the selectable range.
|
|
||||||
* @param maxValue The maximum value of the selectable range.
|
|
||||||
* @param steps number of steps of range
|
|
||||||
* @param context parent context
|
|
||||||
* @throws IllegalArgumentException Will be thrown if min/max value types are not one of Long, Double, Integer, Float, Short, Byte or BigDecimal.
|
|
||||||
*/
|
|
||||||
public NumberRangeSeekBar(@NotNull T minValue, @NotNull T maxValue, @Nullable Integer steps, Context context) throws IllegalArgumentException {
|
|
||||||
super(minValue, maxValue, steps, context);
|
|
||||||
|
|
||||||
numberType = NumberType.fromNumber(minValue);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected Converter<Double, T> getToTConverter() {
|
|
||||||
return new Converter<Double, T>() {
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public T convert(@NotNull Double value) {
|
|
||||||
return (T) numberType.toNumber(value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected Converter<T, Double> getToDoubleConverter() {
|
|
||||||
return new NumberValuer<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility enumeration used to convert between Numbers and doubles.
|
|
||||||
*
|
|
||||||
* @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
|
|
||||||
*/
|
|
||||||
enum NumberType {
|
|
||||||
|
|
||||||
LONG(Long.class),
|
|
||||||
DOUBLE(Double.class),
|
|
||||||
INTEGER(Integer.class),
|
|
||||||
FLOAT(Float.class),
|
|
||||||
SHORT(Short.class),
|
|
||||||
BYTE(Byte.class),
|
|
||||||
BIG_DECIMAL(BigDecimal.class);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Class underlyingClass;
|
|
||||||
|
|
||||||
NumberType(@NotNull Class underlyingClass) {
|
|
||||||
this.underlyingClass = underlyingClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static <E extends Number> NumberType fromNumber(E value) throws IllegalArgumentException {
|
|
||||||
|
|
||||||
for (NumberType numberType : NumberType.values()) {
|
|
||||||
if (numberType.underlyingClass.isInstance(value)) {
|
|
||||||
return numberType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T extends Number> T toNumber(double value) {
|
|
||||||
|
|
||||||
switch (this) {
|
|
||||||
case LONG:
|
|
||||||
return (T)new Long((long) value);
|
|
||||||
case DOUBLE:
|
|
||||||
return (T)new Double(value);
|
|
||||||
case INTEGER:
|
|
||||||
return (T)new Integer((int) value);
|
|
||||||
case FLOAT:
|
|
||||||
return (T)new Float((float) value);
|
|
||||||
case SHORT:
|
|
||||||
return (T)new Short((short) value);
|
|
||||||
case BYTE:
|
|
||||||
return (T)new Byte((byte) value);
|
|
||||||
case BIG_DECIMAL:
|
|
||||||
return (T)new BigDecimal(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InstantiationError("can't convert " + this + " to a Number object");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Vibrator;
|
|
||||||
import android.view.View;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 10/26/11
|
|
||||||
* Time: 11:25 PM
|
|
||||||
*/
|
|
||||||
public class OnClickListenerVibrator implements View.OnClickListener {
|
|
||||||
|
|
||||||
private static final float VIBRATION_TIME_SCALE = 1.0f;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private VibratorContainer vibrator;
|
|
||||||
|
|
||||||
public OnClickListenerVibrator(@Nullable Vibrator vibrator,
|
|
||||||
@NotNull SharedPreferences preferences) {
|
|
||||||
this.vibrator = new VibratorContainer(vibrator, preferences, VIBRATION_TIME_SCALE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
vibrator.vibrate();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 10/26/11
|
|
||||||
* Time: 10:55 PM
|
|
||||||
*/
|
|
||||||
public class OnClickListenerWrapper implements View.OnClickListener{
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final View.OnClickListener onClickListener;
|
|
||||||
|
|
||||||
public OnClickListenerWrapper(@NotNull View.OnClickListener onClickListener) {
|
|
||||||
this.onClickListener = onClickListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
this.onClick(v);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.android.view.widgets.DragButton;
|
|
||||||
import org.solovyev.android.view.widgets.DragEvent;
|
|
||||||
|
|
||||||
import java.util.EventListener;
|
|
||||||
|
|
||||||
|
|
||||||
public interface OnDragListener extends EventListener{
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return 'true': if drag event has taken place (i.e. onDrag() method returned true) then click action will be suppresed
|
|
||||||
*/
|
|
||||||
boolean isSuppressOnClickEvent();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param dragButton drag button object for which onDrag listener was set
|
|
||||||
* @param event drag event
|
|
||||||
*
|
|
||||||
* @return 'true' if drag event occurred, 'false' otherwise
|
|
||||||
*/
|
|
||||||
boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event);
|
|
||||||
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 10/26/11
|
|
||||||
* Time: 10:37 PM
|
|
||||||
*/
|
|
||||||
public class OnDragListenerWrapper implements OnDragListener {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final OnDragListener onDragListener;
|
|
||||||
|
|
||||||
public OnDragListenerWrapper(@NotNull OnDragListener onDragListener) {
|
|
||||||
this.onDragListener = onDragListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSuppressOnClickEvent() {
|
|
||||||
return this.onDragListener.isSuppressOnClickEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event) {
|
|
||||||
return this.onDragListener.onDrag(dragButton, event);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,305 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.android.calculator.R;
|
|
||||||
import org.solovyev.common.NumberIntervalMapper;
|
|
||||||
import org.solovyev.common.utils.*;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class SimpleOnDragListener implements OnDragListener, DragPreferencesChangeListener {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static final Point2d axis = new Point2d(0, 1);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private DragProcessor dragProcessor;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Preferences preferences;
|
|
||||||
|
|
||||||
public SimpleOnDragListener(@NotNull Preferences preferences) {
|
|
||||||
this.preferences = preferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SimpleOnDragListener(@NotNull DragProcessor dragProcessor, @NotNull Preferences preferences) {
|
|
||||||
this.dragProcessor = dragProcessor;
|
|
||||||
this.preferences = preferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onDrag(@NotNull DragButton dragButton, @NotNull DragEvent event) {
|
|
||||||
boolean result = false;
|
|
||||||
|
|
||||||
logDragEvent(dragButton, event);
|
|
||||||
|
|
||||||
final Point2d startPoint = event.getStartPoint();
|
|
||||||
final MotionEvent motionEvent = event.getMotionEvent();
|
|
||||||
|
|
||||||
// init end point
|
|
||||||
final Point2d endPoint = new Point2d(motionEvent.getX(), motionEvent.getY());
|
|
||||||
|
|
||||||
final float distance = MathUtils.getDistance(startPoint, endPoint);
|
|
||||||
|
|
||||||
final MutableObject<Boolean> right = new MutableObject<Boolean>();
|
|
||||||
final double angle = Math.toDegrees(MathUtils.getAngle(startPoint, MathUtils.sum(startPoint, axis), endPoint, right));
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Angle: " + angle);
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Is right?: " + right.getObject());
|
|
||||||
|
|
||||||
final double duration = motionEvent.getEventTime() - motionEvent.getDownTime();
|
|
||||||
|
|
||||||
final Preference distancePreferences = preferences.getPreferencesMap().get(PreferenceType.distance);
|
|
||||||
final Preference anglePreferences = preferences.getPreferencesMap().get(PreferenceType.angle);
|
|
||||||
|
|
||||||
DragDirection direction = null;
|
|
||||||
for (Map.Entry<DragDirection, DragPreference> directionEntry : distancePreferences.getDirectionPreferences().entrySet()) {
|
|
||||||
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Drag direction: " + directionEntry.getKey());
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Trying direction interval: " + directionEntry.getValue().getInterval());
|
|
||||||
|
|
||||||
if (isInInterval(directionEntry.getValue().getInterval(), distance)) {
|
|
||||||
final DragPreference anglePreference = anglePreferences.getDirectionPreferences().get(directionEntry.getKey());
|
|
||||||
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Trying angle interval: " + anglePreference.getInterval());
|
|
||||||
|
|
||||||
if (directionEntry.getKey() == DragDirection.left && right.getObject()) {
|
|
||||||
} else if (directionEntry.getKey() == DragDirection.right && !right.getObject()) {
|
|
||||||
} else {
|
|
||||||
if (isInInterval(anglePreference.getInterval(), (float) angle)) {
|
|
||||||
direction = directionEntry.getKey();
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "MATCH! Direction: " + direction);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (direction != null) {
|
|
||||||
final Preference durationPreferences = preferences.getPreferencesMap().get(PreferenceType.duration);
|
|
||||||
|
|
||||||
final DragPreference durationDragPreferences = durationPreferences.getDirectionPreferences().get(direction);
|
|
||||||
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Trying time interval: " + durationDragPreferences.getInterval());
|
|
||||||
if (isInInterval(durationDragPreferences.getInterval(), (float) duration)) {
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "MATCH!");
|
|
||||||
result = dragProcessor.processDragEvent(direction, dragButton, startPoint, motionEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isInInterval(@NotNull Interval<Float> interval, float value) {
|
|
||||||
return interval.getLeftBorder() - MathUtils.MIN_AMOUNT <= value && value <= interval.getRightBorder() + MathUtils.MIN_AMOUNT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSuppressOnClickEvent() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logDragEvent(@NotNull DragButton dragButton, @NotNull DragEvent event) {
|
|
||||||
final Point2d startPoint = event.getStartPoint();
|
|
||||||
final MotionEvent motionEvent = event.getMotionEvent();
|
|
||||||
final Point2d endPoint = new Point2d(motionEvent.getX(), motionEvent.getY());
|
|
||||||
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Start point: " + startPoint + ", End point: " + endPoint);
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Distance: " + MathUtils.getDistance(startPoint, endPoint));
|
|
||||||
final MutableObject<Boolean> right = new MutableObject<Boolean>();
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Angle: " + Math.toDegrees(MathUtils.getAngle(startPoint, MathUtils.sum(startPoint, axis), endPoint, right)));
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Is right angle? " + right);
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Axis: " + axis + " Vector: " + MathUtils.subtract(endPoint, startPoint));
|
|
||||||
Log.d(String.valueOf(dragButton.getId()), "Total time: " + (motionEvent.getEventTime() - motionEvent.getDownTime()) + " ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public DragProcessor getDragProcessor() {
|
|
||||||
return dragProcessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDragProcessor(@NotNull DragProcessor dragProcessor) {
|
|
||||||
this.dragProcessor = dragProcessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDragPreferencesChange(@NotNull Preferences preferences) {
|
|
||||||
this.preferences = preferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface DragProcessor {
|
|
||||||
|
|
||||||
boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo serso: currently we do not use direction
|
|
||||||
public static String getPreferenceId(@NotNull PreferenceType preferenceType, @NotNull DragDirection direction) {
|
|
||||||
return "org.solovyev.android.calculator.DragButtonCalibrationActivity" + "_" + preferenceType.name() /*+ "_" + direction.name()*/;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static Preferences getPreferences(@NotNull final SharedPreferences preferences, @NotNull Context context) {
|
|
||||||
|
|
||||||
final Mapper<Interval<Float>> mapper = new NumberIntervalMapper<Float>(Float.class);
|
|
||||||
|
|
||||||
final Preferences result = new Preferences();
|
|
||||||
|
|
||||||
for (PreferenceType preferenceType : PreferenceType.values()) {
|
|
||||||
for (DragDirection dragDirection : DragDirection.values()) {
|
|
||||||
|
|
||||||
final String preferenceId = getPreferenceId(preferenceType, dragDirection);
|
|
||||||
|
|
||||||
final String defaultValue;
|
|
||||||
switch (preferenceType) {
|
|
||||||
case angle:
|
|
||||||
defaultValue = context.getResources().getString(R.string.p_drag_angle);
|
|
||||||
break;
|
|
||||||
case distance:
|
|
||||||
defaultValue = context.getResources().getString(R.string.p_drag_distance);
|
|
||||||
break;
|
|
||||||
case duration:
|
|
||||||
defaultValue = context.getResources().getString(R.string.p_drag_duration);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
defaultValue = null;
|
|
||||||
Log.e(SimpleOnDragListener.class.getName(), "New preference type added: default preferences should be defined. Preference id: " + preferenceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String value = preferences.getString(preferenceId, defaultValue);
|
|
||||||
|
|
||||||
if (defaultValue != null) {
|
|
||||||
final Interval<Float> intervalPref = mapper.parseValue(value);
|
|
||||||
assert intervalPref != null;
|
|
||||||
|
|
||||||
transformInterval(preferenceType, dragDirection, intervalPref);
|
|
||||||
|
|
||||||
Log.d(SimpleOnDragListener.class.getName(), "Preference loaded for " + dragDirection +". Id: " + preferenceId + ", value: " + intervalPref.toString());
|
|
||||||
|
|
||||||
final DragPreference directionPreference = new DragPreference(dragDirection, intervalPref);
|
|
||||||
|
|
||||||
Preference preference = result.getPreferencesMap().get(preferenceType);
|
|
||||||
if (preference == null) {
|
|
||||||
preference = new Preference(preferenceType);
|
|
||||||
result.getPreferencesMap().put(preferenceType, preference);
|
|
||||||
}
|
|
||||||
|
|
||||||
preference.getDirectionPreferences().put(dragDirection, directionPreference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static Interval<Float> transformInterval(@NotNull PreferenceType preferenceType, @NotNull DragDirection dragDirection, @NotNull Interval<Float> interval) {
|
|
||||||
if (preferenceType == PreferenceType.angle) {
|
|
||||||
final Float leftBorder = interval.getLeftBorder();
|
|
||||||
final Float rightBorder = interval.getRightBorder();
|
|
||||||
|
|
||||||
if (dragDirection == DragDirection.up) {
|
|
||||||
interval.setLeftBorder(180f - rightBorder);
|
|
||||||
interval.setRightBorder(180f - leftBorder);
|
|
||||||
} else if (dragDirection == DragDirection.left ) {
|
|
||||||
interval.setLeftBorder(90f - rightBorder);
|
|
||||||
interval.setRightBorder(90f + rightBorder);
|
|
||||||
} else if ( dragDirection == DragDirection.right ) {
|
|
||||||
interval.setLeftBorder(90f - rightBorder);
|
|
||||||
interval.setRightBorder(90f + rightBorder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static enum PreferenceType {
|
|
||||||
angle,
|
|
||||||
distance,
|
|
||||||
duration
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DragPreference {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private DragDirection direction;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Interval<Float> interval;
|
|
||||||
|
|
||||||
|
|
||||||
public DragPreference(@NotNull DragDirection direction, @NotNull Interval<Float> interval) {
|
|
||||||
this.direction = direction;
|
|
||||||
this.interval = interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public DragDirection getDirection() {
|
|
||||||
return direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDirection(@NotNull DragDirection direction) {
|
|
||||||
this.direction = direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Interval<Float> getInterval() {
|
|
||||||
return interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInterval(@NotNull Interval<Float> interval) {
|
|
||||||
this.interval = interval;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Preference {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private PreferenceType preferenceType;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Map<DragDirection, DragPreference> directionPreferences = new HashMap<DragDirection, DragPreference>();
|
|
||||||
|
|
||||||
|
|
||||||
public Preference(@NotNull PreferenceType preferenceType) {
|
|
||||||
this.preferenceType = preferenceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public PreferenceType getPreferenceType() {
|
|
||||||
return preferenceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPreferenceType(@NotNull PreferenceType preferenceType) {
|
|
||||||
this.preferenceType = preferenceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Map<DragDirection, DragPreference> getDirectionPreferences() {
|
|
||||||
return directionPreferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDirectionPreferences(@NotNull Map<DragDirection, DragPreference> directionPreferences) {
|
|
||||||
this.directionPreferences = directionPreferences;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Preferences {
|
|
||||||
|
|
||||||
private final Map<PreferenceType, Preference> preferencesMap = new HashMap<PreferenceType, Preference>();
|
|
||||||
|
|
||||||
public Map<PreferenceType, Preference> getPreferencesMap() {
|
|
||||||
return preferencesMap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.view.widgets;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Vibrator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.NumberMapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 10/26/11
|
|
||||||
* Time: 11:40 PM
|
|
||||||
*/
|
|
||||||
public class VibratorContainer implements SharedPreferences.OnSharedPreferenceChangeListener {
|
|
||||||
|
|
||||||
public static final String HAPTIC_FEEDBACK_P_KEY = "org.solovyev.android.calculator.CalculatorModel_haptic_feedback";
|
|
||||||
public static final boolean HAPTIC_FEEDBACK_DEFAULT = false;
|
|
||||||
public static final String HAPTIC_FEEDBACK_DURATION_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_calc_haptic_feedback_duration_key";
|
|
||||||
|
|
||||||
private final static NumberMapper<Long> mapper = new NumberMapper<Long>(Long.class);
|
|
||||||
|
|
||||||
private static final long defaultVibrationTime = 100;
|
|
||||||
|
|
||||||
private final float vibrationTimeScale;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final Vibrator vibrator;
|
|
||||||
|
|
||||||
private long time = 0;
|
|
||||||
|
|
||||||
public VibratorContainer(@Nullable Vibrator vibrator, @NotNull SharedPreferences preferences, float vibrationTimeScale) {
|
|
||||||
this.vibrator = vibrator;
|
|
||||||
this.vibrationTimeScale = vibrationTimeScale;
|
|
||||||
|
|
||||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
|
||||||
onSharedPreferenceChanged(preferences, null);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void vibrate() {
|
|
||||||
if (time > 0 && vibrator != null) {
|
|
||||||
vibrator.vibrate(time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSharedPreferenceChanged(SharedPreferences preferences, @Nullable String key) {
|
|
||||||
if (preferences.getBoolean(HAPTIC_FEEDBACK_P_KEY, VibratorContainer.HAPTIC_FEEDBACK_DEFAULT)) {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
this.time = getScaledValue(mapper.parseValue(preferences.getString(HAPTIC_FEEDBACK_DURATION_P_KEY, mapper.formatValue(getScaledValue(defaultVibrationTime)))));
|
|
||||||
} else {
|
|
||||||
this.time = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getScaledValue(long vibrationTime) {
|
|
||||||
return (long) (vibrationTime * vibrationTimeScale);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.common;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.*;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/21/11
|
|
||||||
* Time: 12:02 AM
|
|
||||||
*/
|
|
||||||
public abstract class AbstractIntervalMapper<T> implements Mapper<Interval<T>> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String formatValue(@Nullable Interval<T> interval) throws IllegalArgumentException {
|
|
||||||
if (interval != null) {
|
|
||||||
return CollectionsUtils.formatValue(Arrays.asList(interval.getLeftBorder(), interval.getRightBorder()), ";", getFormatter());
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected abstract Formatter<T> getFormatter();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Interval<T> parseValue(@Nullable String s) throws IllegalArgumentException {
|
|
||||||
//Log.d(AbstractIntervalMapper.class.getName(), "Parsing: " + s);
|
|
||||||
final List<T> list = CollectionsUtils.split(s, ";", getParser());
|
|
||||||
|
|
||||||
assert list.size() == 2;
|
|
||||||
return newInstance(list.get(0), list.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected abstract Interval<T> newInstance(@Nullable T left, @Nullable T right);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected abstract Parser<T> getParser();
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.common;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.math.ValueOfFormatter;
|
|
||||||
import org.solovyev.common.utils.Formatter;
|
|
||||||
import org.solovyev.common.utils.Mapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/26/11
|
|
||||||
* Time: 11:27 PM
|
|
||||||
*/
|
|
||||||
public class BooleanMapper implements Mapper<Boolean>{
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Formatter<Boolean> formatter = new ValueOfFormatter<Boolean>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String formatValue(@Nullable Boolean value) throws IllegalArgumentException {
|
|
||||||
return formatter.formatValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean parseValue(@Nullable String value) throws IllegalArgumentException {
|
|
||||||
return value == null ? null : Boolean.valueOf(value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.common;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.solovyev.common.utils.Formatter;
|
|
||||||
import org.solovyev.common.utils.Mapper;
|
|
||||||
import org.solovyev.common.utils.Parser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/26/11
|
|
||||||
* Time: 10:45 PM
|
|
||||||
*/
|
|
||||||
public abstract class GenericIntervalMapper<T> extends AbstractIntervalMapper<T> {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Mapper<T> mapper;
|
|
||||||
|
|
||||||
public GenericIntervalMapper(@NotNull Mapper<T> mapper) {
|
|
||||||
this.mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected Formatter<T> getFormatter() {
|
|
||||||
return mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected Parser<T> getParser() {
|
|
||||||
return mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Mapper<T> getMapper() {
|
|
||||||
return mapper;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.common;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.Interval;
|
|
||||||
import org.solovyev.common.utils.NumberInterval;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/20/11
|
|
||||||
* Time: 11:56 PM
|
|
||||||
*/
|
|
||||||
public class NumberIntervalMapper<T extends Number> extends GenericIntervalMapper<T> {
|
|
||||||
|
|
||||||
public NumberIntervalMapper(@NotNull Class<T> clazz) {
|
|
||||||
super(new NumberMapper<T>(clazz));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
protected Interval<T> newInstance(@Nullable T left, @Nullable T right) {
|
|
||||||
return new NumberInterval<T>(left, right);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.common;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.math.ValueOfFormatter;
|
|
||||||
import org.solovyev.common.utils.Formatter;
|
|
||||||
import org.solovyev.common.utils.Mapper;
|
|
||||||
import org.solovyev.common.utils.Parser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/26/11
|
|
||||||
* Time: 11:10 PM
|
|
||||||
*/
|
|
||||||
public class NumberMapper<T extends Number> implements Mapper<T>{
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Formatter<T> formatter = new ValueOfFormatter<T>();
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Parser<T> parser;
|
|
||||||
|
|
||||||
public NumberMapper(@NotNull Class<T> clazz) {
|
|
||||||
this.parser = new NumberParser<T>(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String formatValue(@Nullable T value) throws IllegalArgumentException {
|
|
||||||
return formatter.formatValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T parseValue(@Nullable String value) throws IllegalArgumentException {
|
|
||||||
return this.parser.parseValue(value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.common;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.Parser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/26/11
|
|
||||||
* Time: 11:07 PM
|
|
||||||
*/
|
|
||||||
public class NumberParser<T extends Number> implements Parser<T> {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Class<T> clazz;
|
|
||||||
|
|
||||||
public NumberParser(@NotNull Class<T> clazz) {
|
|
||||||
this.clazz = clazz;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T parseValue(@Nullable String value) throws IllegalArgumentException {
|
|
||||||
T result;
|
|
||||||
|
|
||||||
if (value != null) {
|
|
||||||
if (this.clazz.equals(Integer.class)) {
|
|
||||||
result = (T) Integer.valueOf(value);
|
|
||||||
} else if (this.clazz.equals(Float.class)) {
|
|
||||||
result = (T) Float.valueOf(value);
|
|
||||||
} else if (this.clazz.equals(Long.class)) {
|
|
||||||
result = (T) Long.valueOf(value);
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException(this.clazz + " is not supported!");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.common;
|
|
||||||
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.Equalizer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 12/17/11
|
|
||||||
* Time: 11:11 PM
|
|
||||||
*/
|
|
||||||
public enum SameEqualizer implements Equalizer {
|
|
||||||
|
|
||||||
instance;
|
|
||||||
|
|
||||||
private SameEqualizer() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(@Nullable Object first, @Nullable Object second) {
|
|
||||||
return first == second;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
package org.solovyev.common.math;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/20/11
|
|
||||||
* Time: 5:42 PM
|
|
||||||
*/
|
|
||||||
public class DiscreteNormalizer implements Normalizer {
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Normalizer normalizer;
|
|
||||||
|
|
||||||
private final double min;
|
|
||||||
|
|
||||||
private final double step;
|
|
||||||
|
|
||||||
public DiscreteNormalizer(double min, double max, int steps) {
|
|
||||||
this(min, max, steps, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiscreteNormalizer(double min, double max, int steps, @Nullable Normalizer normalizer) {
|
|
||||||
assert min <= max;
|
|
||||||
assert steps > 1;
|
|
||||||
|
|
||||||
if (normalizer != null) {
|
|
||||||
this.normalizer = normalizer;
|
|
||||||
} else {
|
|
||||||
this.normalizer = new LinearNormalizer(min, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.step = this.normalizer.normalize((max - min) / (steps - 1));
|
|
||||||
this.min = this.normalizer.normalize(min);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiscreteNormalizer(double min, double max, double step) {
|
|
||||||
assert min <= max;
|
|
||||||
assert step > 0;
|
|
||||||
|
|
||||||
this.normalizer = new LinearNormalizer(min, max);
|
|
||||||
|
|
||||||
this.step = normalizer.normalize(step);
|
|
||||||
this.min = normalizer.normalize(min);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double normalize(double value) {
|
|
||||||
double normalizedValue = normalizer.normalize(value);
|
|
||||||
|
|
||||||
double result = min;
|
|
||||||
while (true) {
|
|
||||||
if ( result + step > normalizedValue ) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
result += step;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double denormalize(double value) {
|
|
||||||
return normalizer.denormalize(value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.common.math;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/19/11
|
|
||||||
* Time: 9:31 PM
|
|
||||||
*/
|
|
||||||
public class LinearNormalizer implements Normalizer {
|
|
||||||
|
|
||||||
private final double min;
|
|
||||||
private final double max;
|
|
||||||
|
|
||||||
public LinearNormalizer(double min, double max) {
|
|
||||||
this.min = min;
|
|
||||||
this.max = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double normalize(double value){
|
|
||||||
if ((max - min) != 0d) {
|
|
||||||
return (value - min) / (max - min);
|
|
||||||
} else {
|
|
||||||
return 1d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double denormalize(double value){
|
|
||||||
return min + value * (max - min);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package org.solovyev.common.math;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/20/11
|
|
||||||
* Time: 5:30 PM
|
|
||||||
*/
|
|
||||||
public interface Normalizer {
|
|
||||||
|
|
||||||
double normalize(double value);
|
|
||||||
|
|
||||||
double denormalize(double value);
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
|
||||||
* For more information, please, contact se.solovyev@gmail.com
|
|
||||||
* or visit http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.common.math;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.solovyev.common.utils.Formatter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User: serso
|
|
||||||
* Date: 9/20/11
|
|
||||||
* Time: 10:52 PM
|
|
||||||
*/
|
|
||||||
public class ValueOfFormatter<T> implements Formatter<T>{
|
|
||||||
|
|
||||||
private final boolean processNulls;
|
|
||||||
|
|
||||||
public ValueOfFormatter() {
|
|
||||||
this(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueOfFormatter(boolean processNulls) {
|
|
||||||
this.processNulls = processNulls;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String formatValue(@Nullable T t) throws IllegalArgumentException {
|
|
||||||
return t == null ? (processNulls ? String.valueOf(t) : null) : String.valueOf(t);
|
|
||||||
}
|
|
||||||
}
|
|