diff --git a/external/ColorPickerPreference/.classpath b/external/ColorPickerPreference/.classpath new file mode 100644 index 00000000..a4763d1e --- /dev/null +++ b/external/ColorPickerPreference/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/external/ColorPickerPreference/.gitattributes b/external/ColorPickerPreference/.gitattributes new file mode 100644 index 00000000..dfe07704 --- /dev/null +++ b/external/ColorPickerPreference/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/external/ColorPickerPreference/.gitignore b/external/ColorPickerPreference/.gitignore new file mode 100644 index 00000000..4b1a60ae --- /dev/null +++ b/external/ColorPickerPreference/.gitignore @@ -0,0 +1,4 @@ +/bin +/gen +.classpath +.project \ No newline at end of file diff --git a/external/ColorPickerPreference/.project b/external/ColorPickerPreference/.project new file mode 100644 index 00000000..b3e7744d --- /dev/null +++ b/external/ColorPickerPreference/.project @@ -0,0 +1,33 @@ + + + ColorPickerPreference + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/external/ColorPickerPreference/AndroidManifest.xml b/external/ColorPickerPreference/AndroidManifest.xml new file mode 100644 index 00000000..e09215a9 --- /dev/null +++ b/external/ColorPickerPreference/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/ColorPickerPreference/CHANGELOG.rst b/external/ColorPickerPreference/CHANGELOG.rst new file mode 100644 index 00000000..5342d2ac --- /dev/null +++ b/external/ColorPickerPreference/CHANGELOG.rst @@ -0,0 +1,26 @@ +================================ +ColorPickerPreference Change Log +================================ + +2011-02-11 v1.11: +---------------- +fix: color controls not visible in landscape orientation +fix: colorPickerDialog constructor was protected + +2011-01-25 v1.1: +---------------- +* new: Alpha Slider is disabled by default +* new: Alpha Slider can be enabled: + * with preference XML using attribute alphaSlider="true" + * with function setAlphaSliderEnabled(true) +* new: defaultValue in preference XML now accepts HEX color code: + * #FF00FF, rgb + * #FF00FF00, argb + +2011-01-20 v1.01: +----------------- +fix: sometimes preview color disappear + +2011-01-19 v1.0: +---------------- +release \ No newline at end of file diff --git a/external/ColorPickerPreference/LICENSE b/external/ColorPickerPreference/LICENSE new file mode 100644 index 00000000..da9cd5cb --- /dev/null +++ b/external/ColorPickerPreference/LICENSE @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2011 Sergey Margaritov & Daniel Nilsson + * + * 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. + */ \ No newline at end of file diff --git a/external/ColorPickerPreference/README.rst b/external/ColorPickerPreference/README.rst new file mode 100644 index 00000000..bffa417a --- /dev/null +++ b/external/ColorPickerPreference/README.rst @@ -0,0 +1,47 @@ +===================== +ColorPickerPreference +===================== + +Generally used classes by Daniel Nilsson. +ColorPickerPreference class by Sergey Margaritov. +Packed by Sergey Margaritov. + +Features +======== + +* Color Area +* Hue Slider +* Alpha Slider (disabled by default) +* Old & New Color +* Color Preview in Preferences List + +Requirements +============ + +Tested with APIv7, but maybe will work with early versions + +Usage +===== + +You can see some tests inside + +:: + + + alphaSlider="true" + /> + +To enable Alpha Slider in your code use function: +:: + setAlphaSliderEnabled(boolean enable) + +Screens +======= + +* .. image:: https://github.com/attenzione/android-ColorPickerPreference/raw/master/screen_1.png + +* .. image:: https://github.com/attenzione/android-ColorPickerPreference/raw/master/screen_2.png \ No newline at end of file diff --git a/external/ColorPickerPreference/proguard.cfg b/external/ColorPickerPreference/proguard.cfg new file mode 100644 index 00000000..8ad7d335 --- /dev/null +++ b/external/ColorPickerPreference/proguard.cfg @@ -0,0 +1,34 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/external/ColorPickerPreference/project.properties b/external/ColorPickerPreference/project.properties new file mode 100644 index 00000000..616f300c --- /dev/null +++ b/external/ColorPickerPreference/project.properties @@ -0,0 +1,12 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +android.library=true +# Project target. +target=android-16 diff --git a/external/ColorPickerPreference/res/drawable-hdpi/icon.png b/external/ColorPickerPreference/res/drawable-hdpi/icon.png new file mode 100644 index 00000000..8074c4c5 Binary files /dev/null and b/external/ColorPickerPreference/res/drawable-hdpi/icon.png differ diff --git a/external/ColorPickerPreference/res/drawable-ldpi/icon.png b/external/ColorPickerPreference/res/drawable-ldpi/icon.png new file mode 100644 index 00000000..1095584e Binary files /dev/null and b/external/ColorPickerPreference/res/drawable-ldpi/icon.png differ diff --git a/external/ColorPickerPreference/res/drawable-mdpi/icon.png b/external/ColorPickerPreference/res/drawable-mdpi/icon.png new file mode 100644 index 00000000..a07c69fa Binary files /dev/null and b/external/ColorPickerPreference/res/drawable-mdpi/icon.png differ diff --git a/external/ColorPickerPreference/res/layout-land/dialog_color_picker.xml b/external/ColorPickerPreference/res/layout-land/dialog_color_picker.xml new file mode 100644 index 00000000..cf567016 --- /dev/null +++ b/external/ColorPickerPreference/res/layout-land/dialog_color_picker.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/ColorPickerPreference/res/layout/dialog_color_picker.xml b/external/ColorPickerPreference/res/layout/dialog_color_picker.xml new file mode 100644 index 00000000..00ee3753 --- /dev/null +++ b/external/ColorPickerPreference/res/layout/dialog_color_picker.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/ColorPickerPreference/res/values/integer.xml b/external/ColorPickerPreference/res/values/integer.xml new file mode 100644 index 00000000..e3626064 --- /dev/null +++ b/external/ColorPickerPreference/res/values/integer.xml @@ -0,0 +1,5 @@ + + + 0xff000000 + 0xff00ff00 + \ No newline at end of file diff --git a/external/ColorPickerPreference/res/values/strings.xml b/external/ColorPickerPreference/res/values/strings.xml new file mode 100644 index 00000000..251a66f4 --- /dev/null +++ b/external/ColorPickerPreference/res/values/strings.xml @@ -0,0 +1,21 @@ + + + + Hello World, Main! + ColorPickerPreference + + + Color Picker + Press on Color to apply + + + Category + Color 1 + black color by default, set by reference + Color 2 + not persistent color\nalpha slider added via code + Color 3 + picker with alpha slider + Color 4 + color set with HEX code in xml + diff --git a/external/ColorPickerPreference/res/xml/settings.xml b/external/ColorPickerPreference/res/xml/settings.xml new file mode 100644 index 00000000..0cd87af9 --- /dev/null +++ b/external/ColorPickerPreference/res/xml/settings.xml @@ -0,0 +1,32 @@ + + + + + + + + + \ No newline at end of file diff --git a/external/ColorPickerPreference/screen_1.png b/external/ColorPickerPreference/screen_1.png new file mode 100644 index 00000000..4798dae7 Binary files /dev/null and b/external/ColorPickerPreference/screen_1.png differ diff --git a/external/ColorPickerPreference/screen_2.png b/external/ColorPickerPreference/screen_2.png new file mode 100644 index 00000000..3bb2d671 Binary files /dev/null and b/external/ColorPickerPreference/screen_2.png differ diff --git a/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/AlphaPatternDrawable.java b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/AlphaPatternDrawable.java new file mode 100644 index 00000000..ff9c3c84 --- /dev/null +++ b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/AlphaPatternDrawable.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * + * 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 net.margaritov.preference.colorpicker; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +/** + * This drawable that draws a simple white and gray chessboard pattern. + * It's pattern you will often see as a background behind a + * partly transparent image in many applications. + * @author Daniel Nilsson + */ +public class AlphaPatternDrawable extends Drawable { + + private int mRectangleSize = 10; + + private Paint mPaint = new Paint(); + private Paint mPaintWhite = new Paint(); + private Paint mPaintGray = new Paint(); + + private int numRectanglesHorizontal; + private int numRectanglesVertical; + + /** + * Bitmap in which the pattern will be cahched. + */ + private Bitmap mBitmap; + + public AlphaPatternDrawable(int rectangleSize) { + mRectangleSize = rectangleSize; + mPaintWhite.setColor(0xffffffff); + mPaintGray.setColor(0xffcbcbcb); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap(mBitmap, null, getBounds(), mPaint); + } + + @Override + public int getOpacity() { + return 0; + } + + @Override + public void setAlpha(int alpha) { + throw new UnsupportedOperationException("Alpha is not supported by this drawwable."); + } + + @Override + public void setColorFilter(ColorFilter cf) { + throw new UnsupportedOperationException("ColorFilter is not supported by this drawwable."); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + + int height = bounds.height(); + int width = bounds.width(); + + numRectanglesHorizontal = (int) Math.ceil((width / mRectangleSize)); + numRectanglesVertical = (int) Math.ceil(height / mRectangleSize); + + generatePatternBitmap(); + + } + + /** + * This will generate a bitmap with the pattern + * as big as the rectangle we were allow to draw on. + * We do this to chache the bitmap so we don't need to + * recreate it each time draw() is called since it + * takes a few milliseconds. + */ + private void generatePatternBitmap(){ + + if(getBounds().width() <= 0 || getBounds().height() <= 0){ + return; + } + + mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888); + Canvas canvas = new Canvas(mBitmap); + + Rect r = new Rect(); + boolean verticalStartWhite = true; + for (int i = 0; i <= numRectanglesVertical; i++) { + + boolean isWhite = verticalStartWhite; + for (int j = 0; j <= numRectanglesHorizontal; j++) { + + r.top = i * mRectangleSize; + r.left = j * mRectangleSize; + r.bottom = r.top + mRectangleSize; + r.right = r.left + mRectangleSize; + + canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray); + + isWhite = !isWhite; + } + + verticalStartWhite = !verticalStartWhite; + + } + + } + +} \ No newline at end of file diff --git a/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerDialog.java b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerDialog.java new file mode 100644 index 00000000..a63d48c3 --- /dev/null +++ b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerDialog.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * + * 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 net.margaritov.preference.colorpicker; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; + +public class ColorPickerDialog + extends + Dialog + implements + ColorPickerView.OnColorChangedListener, + View.OnClickListener { + + private ColorPickerView mColorPicker; + + private ColorPickerPanelView mOldColor; + private ColorPickerPanelView mNewColor; + + private OnColorChangedListener mListener; + + public interface OnColorChangedListener { + public void onColorChanged(int color); + } + + public ColorPickerDialog(Context context, int initialColor) { + super(context); + + init(initialColor); + } + + private void init(int color) { + // To fight color banding. + getWindow().setFormat(PixelFormat.RGBA_8888); + + setUp(color); + + } + + private void setUp(int color) { + + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + View layout = inflater.inflate(R.layout.dialog_color_picker, null); + + setContentView(layout); + + setTitle(R.string.dialog_color_picker); + + mColorPicker = (ColorPickerView) layout.findViewById(R.id.color_picker_view); + mOldColor = (ColorPickerPanelView) layout.findViewById(R.id.old_color_panel); + mNewColor = (ColorPickerPanelView) layout.findViewById(R.id.new_color_panel); + + ((LinearLayout) mOldColor.getParent()).setPadding( + Math.round(mColorPicker.getDrawingOffset()), + 0, + Math.round(mColorPicker.getDrawingOffset()), + 0 + ); + + mOldColor.setOnClickListener(this); + mNewColor.setOnClickListener(this); + mColorPicker.setOnColorChangedListener(this); + mOldColor.setColor(color); + mColorPicker.setColor(color, true); + + } + + @Override + public void onColorChanged(int color) { + + mNewColor.setColor(color); + + /* + if (mListener != null) { + mListener.onColorChanged(color); + } + */ + + } + + public void setAlphaSliderVisible(boolean visible) { + mColorPicker.setAlphaSliderVisible(visible); + } + + /** + * Set a OnColorChangedListener to get notified when the color + * selected by the user has changed. + * @param listener + */ + public void setOnColorChangedListener(OnColorChangedListener listener){ + mListener = listener; + } + + public int getColor() { + return mColorPicker.getColor(); + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.new_color_panel) { + if (mListener != null) { + mListener.onColorChanged(mNewColor.getColor()); + } + } + dismiss(); + } + + @Override + public Bundle onSaveInstanceState() { + Bundle state = super.onSaveInstanceState(); + state.putInt("old_color", mOldColor.getColor()); + state.putInt("new_color", mNewColor.getColor()); + return state; + } + + @Override + public void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mOldColor.setColor(savedInstanceState.getInt("old_color")); + mColorPicker.setColor(savedInstanceState.getInt("new_color"), true); + } +} diff --git a/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerPanelView.java b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerPanelView.java new file mode 100644 index 00000000..b0003662 --- /dev/null +++ b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerPanelView.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * + * 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 net.margaritov.preference.colorpicker; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; + +/** + * This class draws a panel which which will be filled with a color which can be set. + * It can be used to show the currently selected color which you will get from + * the {@link ColorPickerView}. + * @author Daniel Nilsson + * + */ +public class ColorPickerPanelView extends View { + + /** + * The width in pixels of the border + * surrounding the color panel. + */ + private final static float BORDER_WIDTH_PX = 1; + + private float mDensity = 1f; + + private int mBorderColor = 0xff6E6E6E; + private int mColor = 0xff000000; + + private Paint mBorderPaint; + private Paint mColorPaint; + + private RectF mDrawingRect; + private RectF mColorRect; + + private AlphaPatternDrawable mAlphaPattern; + + + public ColorPickerPanelView(Context context){ + this(context, null); + } + + public ColorPickerPanelView(Context context, AttributeSet attrs){ + this(context, attrs, 0); + } + + public ColorPickerPanelView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init(){ + mBorderPaint = new Paint(); + mColorPaint = new Paint(); + mDensity = getContext().getResources().getDisplayMetrics().density; + } + + + @Override + protected void onDraw(Canvas canvas) { + + final RectF rect = mColorRect; + + if(BORDER_WIDTH_PX > 0){ + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect, mBorderPaint); + } + + if(mAlphaPattern != null){ + mAlphaPattern.draw(canvas); + } + + mColorPaint.setColor(mColor); + + canvas.drawRect(rect, mColorPaint); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = getPaddingLeft(); + mDrawingRect.right = w - getPaddingRight(); + mDrawingRect.top = getPaddingTop(); + mDrawingRect.bottom = h - getPaddingBottom(); + + setUpColorRect(); + + } + + private void setUpColorRect(){ + final RectF dRect = mDrawingRect; + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mColorRect = new RectF(left,top, right, bottom); + + mAlphaPattern = new AlphaPatternDrawable((int)(5 * mDensity)); + + mAlphaPattern.setBounds( + Math.round(mColorRect.left), + Math.round(mColorRect.top), + Math.round(mColorRect.right), + Math.round(mColorRect.bottom) + ); + + } + + /** + * Set the color that should be shown by this view. + * @param color + */ + public void setColor(int color){ + mColor = color; + invalidate(); + } + + /** + * Get the color currently show by this view. + * @return + */ + public int getColor(){ + return mColor; + } + + /** + * Set the color of the border surrounding the panel. + * @param color + */ + public void setBorderColor(int color){ + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding the panel. + */ + public int getBorderColor(){ + return mBorderColor; + } + +} \ No newline at end of file diff --git a/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerPreference.java b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerPreference.java new file mode 100644 index 00000000..8dded67a --- /dev/null +++ b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerPreference.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2011 Sergey Margaritov + * + * 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 net.margaritov.preference.colorpicker; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; + +/** + * A preference type that allows a user to choose a time + * @author Sergey Margaritov + */ +public class ColorPickerPreference + extends + Preference + implements + Preference.OnPreferenceClickListener, + ColorPickerDialog.OnColorChangedListener { + + View mView; + ColorPickerDialog mDialog; + private int mValue = Color.BLACK; + private float mDensity = 0; + private boolean mAlphaSliderEnabled = false; + + public ColorPickerPreference(Context context) { + super(context); + init(context, null); + } + + public ColorPickerPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public ColorPickerPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getColor(index, Color.BLACK); + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + onColorChanged(restoreValue ? getPersistedInt(mValue) : (Integer) defaultValue); + } + + private void init(Context context, AttributeSet attrs) { + mDensity = getContext().getResources().getDisplayMetrics().density; + setOnPreferenceClickListener(this); + if (attrs != null) { + mAlphaSliderEnabled = attrs.getAttributeBooleanValue(null, "alphaSlider", false); + } + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + mView = view; + setPreviewColor(); + } + + private void setPreviewColor() { + if (mView == null) return; + ImageView iView = new ImageView(getContext()); + LinearLayout widgetFrameView = ((LinearLayout)mView.findViewById(android.R.id.widget_frame)); + if (widgetFrameView == null) return; + widgetFrameView.setVisibility(View.VISIBLE); + widgetFrameView.setPadding( + widgetFrameView.getPaddingLeft(), + widgetFrameView.getPaddingTop(), + (int)(mDensity * 8), + widgetFrameView.getPaddingBottom() + ); + // remove already create preview image + int count = widgetFrameView.getChildCount(); + if (count > 0) { + widgetFrameView.removeViews(0, count); + } + widgetFrameView.addView(iView); + widgetFrameView.setMinimumWidth(0); + iView.setBackgroundDrawable(new AlphaPatternDrawable((int)(5 * mDensity))); + iView.setImageBitmap(getPreviewBitmap()); + } + + private Bitmap getPreviewBitmap() { + int d = (int) (mDensity * 31); //30dip + int color = mValue; + Bitmap bm = Bitmap.createBitmap(d, d, Config.ARGB_8888); + int w = bm.getWidth(); + int h = bm.getHeight(); + int c = color; + for (int i = 0; i < w; i++) { + for (int j = i; j < h; j++) { + c = (i <= 1 || j <= 1 || i >= w-2 || j >= h-2) ? Color.GRAY : color; + bm.setPixel(i, j, c); + if (i != j) { + bm.setPixel(j, i, c); + } + } + } + + return bm; + } + + @Override + public void onColorChanged(int color) { + if (isPersistent()) { + persistInt(color); + } + mValue = color; + setPreviewColor(); + try { + getOnPreferenceChangeListener().onPreferenceChange(this, color); + } catch (NullPointerException e) { + + } + } + + public boolean onPreferenceClick(Preference preference) { + showDialog(null); + return false; + } + + protected void showDialog(Bundle state) { + mDialog = new ColorPickerDialog(getContext(), mValue); + mDialog.setOnColorChangedListener(this); + if (mAlphaSliderEnabled) { + mDialog.setAlphaSliderVisible(true); + } + if (state != null) { + mDialog.onRestoreInstanceState(state); + } + mDialog.show(); + } + + /** + * Toggle Alpha Slider visibility (by default it's disabled) + * @param enable + */ + public void setAlphaSliderEnabled(boolean enable) { + mAlphaSliderEnabled = enable; + } + + /** + * For custom purposes. Not used by ColorPickerPreferrence + * @param color + * @author Unknown + */ + public static String convertToARGB(int color) { + String alpha = Integer.toHexString(Color.alpha(color)); + String red = Integer.toHexString(Color.red(color)); + String green = Integer.toHexString(Color.green(color)); + String blue = Integer.toHexString(Color.blue(color)); + + if (alpha.length() == 1) { + alpha = "0" + alpha; + } + + if (red.length() == 1) { + red = "0" + red; + } + + if (green.length() == 1) { + green = "0" + green; + } + + if (blue.length() == 1) { + blue = "0" + blue; + } + + return "#" + alpha + red + green + blue; + } + + /** + * For custom purposes. Not used by ColorPickerPreferrence + * @param argb + * @throws NumberFormatException + * @author Unknown + */ + public static int convertToColorInt(String argb) throws NumberFormatException { + + if (argb.startsWith("#")) { + argb = argb.replace("#", ""); + } + + int alpha = -1, red = -1, green = -1, blue = -1; + + if (argb.length() == 8) { + alpha = Integer.parseInt(argb.substring(0, 2), 16); + red = Integer.parseInt(argb.substring(2, 4), 16); + green = Integer.parseInt(argb.substring(4, 6), 16); + blue = Integer.parseInt(argb.substring(6, 8), 16); + } + else if (argb.length() == 6) { + alpha = 255; + red = Integer.parseInt(argb.substring(0, 2), 16); + green = Integer.parseInt(argb.substring(2, 4), 16); + blue = Integer.parseInt(argb.substring(4, 6), 16); + } + + return Color.argb(alpha, red, green, blue); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (mDialog == null || !mDialog.isShowing()) { + return superState; + } + + final SavedState myState = new SavedState(superState); + myState.dialogBundle = mDialog.onSaveInstanceState(); + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !(state instanceof SavedState)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + showDialog(myState.dialogBundle); + } + + private static class SavedState extends BaseSavedState { + Bundle dialogBundle; + + public SavedState(Parcel source) { + super(source); + dialogBundle = source.readBundle(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeBundle(dialogBundle); + } + + public SavedState(Parcelable superState) { + super(superState); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} \ No newline at end of file diff --git a/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerView.java b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerView.java new file mode 100644 index 00000000..c0ec0531 --- /dev/null +++ b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/ColorPickerView.java @@ -0,0 +1,952 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * + * 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 net.margaritov.preference.colorpicker; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ComposeShader; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; +import android.graphics.Shader.TileMode; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +/** + * Displays a color picker to the user and allow them + * to select a color. A slider for the alpha channel is + * also available. Enable it by setting + * setAlphaSliderVisible(boolean) to true. + * @author Daniel Nilsson + */ +public class ColorPickerView extends View { + + private final static int PANEL_SAT_VAL = 0; + private final static int PANEL_HUE = 1; + private final static int PANEL_ALPHA = 2; + + /** + * The width in pixels of the border + * surrounding all color panels. + */ + private final static float BORDER_WIDTH_PX = 1; + + /** + * The width in dp of the hue panel. + */ + private float HUE_PANEL_WIDTH = 30f; + /** + * The height in dp of the alpha panel + */ + private float ALPHA_PANEL_HEIGHT = 20f; + /** + * The distance in dp between the different + * color panels. + */ + private float PANEL_SPACING = 10f; + /** + * The radius in dp of the color palette tracker circle. + */ + private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f; + /** + * The dp which the tracker of the hue or alpha panel + * will extend outside of its bounds. + */ + private float RECTANGLE_TRACKER_OFFSET = 2f; + + + private float mDensity = 1f; + + private OnColorChangedListener mListener; + + private Paint mSatValPaint; + private Paint mSatValTrackerPaint; + + private Paint mHuePaint; + private Paint mHueTrackerPaint; + + private Paint mAlphaPaint; + private Paint mAlphaTextPaint; + + private Paint mBorderPaint; + + private Shader mValShader; + private Shader mSatShader; + private Shader mHueShader; + private Shader mAlphaShader; + + private int mAlpha = 0xff; + private float mHue = 360f; + private float mSat = 0f; + private float mVal = 0f; + + private String mAlphaSliderText = ""; + private int mSliderTrackerColor = 0xff1c1c1c; + private int mBorderColor = 0xff6E6E6E; + private boolean mShowAlphaPanel = false; + + /* + * To remember which panel that has the "focus" when + * processing hardware button data. + */ + private int mLastTouchedPanel = PANEL_SAT_VAL; + + /** + * Offset from the edge we must have or else + * the finger tracker will get clipped when + * it is drawn outside of the view. + */ + private float mDrawingOffset; + + + /* + * Distance form the edges of the view + * of where we are allowed to draw. + */ + private RectF mDrawingRect; + + private RectF mSatValRect; + private RectF mHueRect; + private RectF mAlphaRect; + + private AlphaPatternDrawable mAlphaPattern; + + private Point mStartTouchPoint = null; + + public interface OnColorChangedListener { + public void onColorChanged(int color); + } + + public ColorPickerView(Context context){ + this(context, null); + } + + public ColorPickerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPickerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init(){ + mDensity = getContext().getResources().getDisplayMetrics().density; + PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity; + RECTANGLE_TRACKER_OFFSET *= mDensity; + HUE_PANEL_WIDTH *= mDensity; + ALPHA_PANEL_HEIGHT *= mDensity; + PANEL_SPACING = PANEL_SPACING * mDensity; + + mDrawingOffset = calculateRequiredOffset(); + + initPaintTools(); + + //Needed for receiving trackball motion events. + setFocusable(true); + setFocusableInTouchMode(true); + } + + private void initPaintTools(){ + + mSatValPaint = new Paint(); + mSatValTrackerPaint = new Paint(); + mHuePaint = new Paint(); + mHueTrackerPaint = new Paint(); + mAlphaPaint = new Paint(); + mAlphaTextPaint = new Paint(); + mBorderPaint = new Paint(); + + + mSatValTrackerPaint.setStyle(Style.STROKE); + mSatValTrackerPaint.setStrokeWidth(2f * mDensity); + mSatValTrackerPaint.setAntiAlias(true); + + mHueTrackerPaint.setColor(mSliderTrackerColor); + mHueTrackerPaint.setStyle(Style.STROKE); + mHueTrackerPaint.setStrokeWidth(2f * mDensity); + mHueTrackerPaint.setAntiAlias(true); + + mAlphaTextPaint.setColor(0xff1c1c1c); + mAlphaTextPaint.setTextSize(14f * mDensity); + mAlphaTextPaint.setAntiAlias(true); + mAlphaTextPaint.setTextAlign(Align.CENTER); + mAlphaTextPaint.setFakeBoldText(true); + + + } + + private float calculateRequiredOffset(){ + float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET); + offset = Math.max(offset, BORDER_WIDTH_PX * mDensity); + + return offset * 1.5f; + } + + private int[] buildHueColorArray(){ + + int[] hue = new int[361]; + + int count = 0; + for(int i = hue.length -1; i >= 0; i--, count++){ + hue[count] = Color.HSVToColor(new float[]{i, 1f, 1f}); + } + + return hue; + } + + + @Override + protected void onDraw(Canvas canvas) { + + if(mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) return; + + drawSatValPanel(canvas); + drawHuePanel(canvas); + drawAlphaPanel(canvas); + + } + + private void drawSatValPanel(Canvas canvas){ + + final RectF rect = mSatValRect; + + if(BORDER_WIDTH_PX > 0){ + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, rect.bottom + BORDER_WIDTH_PX, mBorderPaint); + } + + if (mValShader == null) { + mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, + 0xffffffff, 0xff000000, TileMode.CLAMP); + } + + int rgb = Color.HSVToColor(new float[]{mHue,1f,1f}); + + mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + 0xffffffff, rgb, TileMode.CLAMP); + ComposeShader mShader = new ComposeShader(mValShader, mSatShader, PorterDuff.Mode.MULTIPLY); + mSatValPaint.setShader(mShader); + + canvas.drawRect(rect, mSatValPaint); + + Point p = satValToPoint(mSat, mVal); + + mSatValTrackerPaint.setColor(0xff000000); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity, mSatValTrackerPaint); + + mSatValTrackerPaint.setColor(0xffdddddd); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint); + + } + + private void drawHuePanel(Canvas canvas){ + + final RectF rect = mHueRect; + + if(BORDER_WIDTH_PX > 0){ + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + + if (mHueShader == null) { + mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, buildHueColorArray(), null, TileMode.CLAMP); + mHuePaint.setShader(mHueShader); + } + + canvas.drawRect(rect, mHuePaint); + + float rectHeight = 4 * mDensity / 2; + + Point p = hueToPoint(mHue); + + RectF r = new RectF(); + r.left = rect.left - RECTANGLE_TRACKER_OFFSET; + r.right = rect.right + RECTANGLE_TRACKER_OFFSET; + r.top = p.y - rectHeight; + r.bottom = p.y + rectHeight; + + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + + } + + private void drawAlphaPanel(Canvas canvas){ + + if(!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) return; + + final RectF rect = mAlphaRect; + + if(BORDER_WIDTH_PX > 0){ + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + + + mAlphaPattern.draw(canvas); + + float[] hsv = new float[]{mHue,mSat,mVal}; + int color = Color.HSVToColor(hsv); + int acolor = Color.HSVToColor(0, hsv); + + mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + color, acolor, TileMode.CLAMP); + + + mAlphaPaint.setShader(mAlphaShader); + + canvas.drawRect(rect, mAlphaPaint); + + if(mAlphaSliderText != null && mAlphaSliderText!= ""){ + canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity, mAlphaTextPaint); + } + + float rectWidth = 4 * mDensity / 2; + + Point p = alphaToPoint(mAlpha); + + RectF r = new RectF(); + r.left = p.x - rectWidth; + r.right = p.x + rectWidth; + r.top = rect.top - RECTANGLE_TRACKER_OFFSET; + r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET; + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + + } + + + private Point hueToPoint(float hue){ + + final RectF rect = mHueRect; + final float height = rect.height(); + + Point p = new Point(); + + p.y = (int) (height - (hue * height / 360f) + rect.top); + p.x = (int) rect.left; + + return p; + } + + private Point satValToPoint(float sat, float val){ + + final RectF rect = mSatValRect; + final float height = rect.height(); + final float width = rect.width(); + + Point p = new Point(); + + p.x = (int) (sat * width + rect.left); + p.y = (int) ((1f - val) * height + rect.top); + + return p; + } + + private Point alphaToPoint(int alpha){ + + final RectF rect = mAlphaRect; + final float width = rect.width(); + + Point p = new Point(); + + p.x = (int) (width - (alpha * width / 0xff) + rect.left); + p.y = (int) rect.top; + + return p; + + } + + private float[] pointToSatVal(float x, float y){ + + final RectF rect = mSatValRect; + float[] result = new float[2]; + + float width = rect.width(); + float height = rect.height(); + + if (x < rect.left){ + x = 0f; + } + else if(x > rect.right){ + x = width; + } + else{ + x = x - rect.left; + } + + if (y < rect.top){ + y = 0f; + } + else if(y > rect.bottom){ + y = height; + } + else{ + y = y - rect.top; + } + + + result[0] = 1.f / width * x; + result[1] = 1.f - (1.f / height * y); + + return result; + } + + private float pointToHue(float y){ + + final RectF rect = mHueRect; + + float height = rect.height(); + + if (y < rect.top){ + y = 0f; + } + else if(y > rect.bottom){ + y = height; + } + else{ + y = y - rect.top; + } + + return 360f - (y * 360f / height); + } + + private int pointToAlpha(int x){ + + final RectF rect = mAlphaRect; + final int width = (int) rect.width(); + + if(x < rect.left){ + x = 0; + } + else if(x > rect.right){ + x = width; + } + else{ + x = x - (int)rect.left; + } + + return 0xff - (x * 0xff / width); + + } + + + @Override + public boolean onTrackballEvent(MotionEvent event) { + + float x = event.getX(); + float y = event.getY(); + + boolean update = false; + + + if(event.getAction() == MotionEvent.ACTION_MOVE){ + + switch(mLastTouchedPanel){ + + case PANEL_SAT_VAL: + + float sat, val; + + sat = mSat + x/50f; + val = mVal - y/50f; + + if(sat < 0f){ + sat = 0f; + } + else if(sat > 1f){ + sat = 1f; + } + + if(val < 0f){ + val = 0f; + } + else if(val > 1f){ + val = 1f; + } + + mSat = sat; + mVal = val; + + update = true; + + break; + + case PANEL_HUE: + + float hue = mHue - y * 10f; + + if(hue < 0f){ + hue = 0f; + } + else if(hue > 360f){ + hue = 360f; + } + + mHue = hue; + + update = true; + + break; + + case PANEL_ALPHA: + + if(!mShowAlphaPanel || mAlphaRect == null){ + update = false; + } + else{ + + int alpha = (int) (mAlpha - x*10); + + if(alpha < 0){ + alpha = 0; + } + else if(alpha > 0xff){ + alpha = 0xff; + } + + mAlpha = alpha; + + + update = true; + } + + break; + } + + + } + + + if(update){ + + if(mListener != null){ + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } + + invalidate(); + return true; + } + + + return super.onTrackballEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + boolean update = false; + + switch(event.getAction()){ + + case MotionEvent.ACTION_DOWN: + + mStartTouchPoint = new Point((int)event.getX(), (int)event.getY()); + + update = moveTrackersIfNeeded(event); + + break; + + case MotionEvent.ACTION_MOVE: + + update = moveTrackersIfNeeded(event); + + break; + + case MotionEvent.ACTION_UP: + + mStartTouchPoint = null; + + update = moveTrackersIfNeeded(event); + + break; + + } + + if(update){ + + if(mListener != null){ + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } + + invalidate(); + return true; + } + + + return super.onTouchEvent(event); + } + + private boolean moveTrackersIfNeeded(MotionEvent event){ + + if(mStartTouchPoint == null) return false; + + boolean update = false; + + int startX = mStartTouchPoint.x; + int startY = mStartTouchPoint.y; + + + if(mHueRect.contains(startX, startY)){ + mLastTouchedPanel = PANEL_HUE; + + mHue = pointToHue(event.getY()); + + update = true; + } + else if(mSatValRect.contains(startX, startY)){ + + mLastTouchedPanel = PANEL_SAT_VAL; + + float[] result = pointToSatVal(event.getX(), event.getY()); + + mSat = result[0]; + mVal = result[1]; + + update = true; + } + else if(mAlphaRect != null && mAlphaRect.contains(startX, startY)){ + + mLastTouchedPanel = PANEL_ALPHA; + + mAlpha = pointToAlpha((int)event.getX()); + + update = true; + } + + + return update; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int width = 0; + int height = 0; + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + int widthAllowed = MeasureSpec.getSize(widthMeasureSpec); + int heightAllowed = MeasureSpec.getSize(heightMeasureSpec); + + widthAllowed = chooseWidth(widthMode, widthAllowed); + heightAllowed = chooseHeight(heightMode, heightAllowed); + + if(!mShowAlphaPanel){ + + height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH); + + //If calculated height (based on the width) is more than the allowed height. + if(height > heightAllowed || getTag().equals("landscape")) { + height = heightAllowed; + width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH); + } + else{ + width = widthAllowed; + } + } + else{ + + width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH); + + if(width > widthAllowed){ + width = widthAllowed; + height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT); + } + else{ + height = heightAllowed; + } + + } + + setMeasuredDimension(width, height); + } + + private int chooseWidth(int mode, int size){ + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedWidth(); + } + } + + private int chooseHeight(int mode, int size){ + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedHeight(); + } + } + + private int getPrefferedWidth(){ + + int width = getPrefferedHeight(); + + if(mShowAlphaPanel){ + width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT); + } + + + return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING); + + } + + private int getPrefferedHeight(){ + + int height = (int)(200 * mDensity); + + if(mShowAlphaPanel){ + height += PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + return height; + } + + + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = mDrawingOffset + getPaddingLeft(); + mDrawingRect.right = w - mDrawingOffset - getPaddingRight(); + mDrawingRect.top = mDrawingOffset + getPaddingTop(); + mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom(); + + setUpSatValRect(); + setUpHueRect(); + setUpAlphaRect(); + } + + private void setUpSatValRect(){ + + final RectF dRect = mDrawingRect; + float panelSide = dRect.height() - BORDER_WIDTH_PX * 2; + + if(mShowAlphaPanel){ + panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = top + panelSide; + float right = left + panelSide; + + mSatValRect = new RectF(left,top, right, bottom); + } + + private void setUpHueRect(){ + final RectF dRect = mDrawingRect; + + float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0); + float right = dRect.right - BORDER_WIDTH_PX; + + mHueRect = new RectF(left, top, right, bottom); + } + + private void setUpAlphaRect() { + + if(!mShowAlphaPanel) return; + + final RectF dRect = mDrawingRect; + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mAlphaRect = new RectF(left, top, right, bottom); + + mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); + mAlphaPattern.setBounds( + Math.round(mAlphaRect.left), + Math.round(mAlphaRect.top), + Math.round(mAlphaRect.right), + Math.round(mAlphaRect.bottom) + ); + + } + + + /** + * Set a OnColorChangedListener to get notified when the color + * selected by the user has changed. + * @param listener + */ + public void setOnColorChangedListener(OnColorChangedListener listener){ + mListener = listener; + } + + /** + * Set the color of the border surrounding all panels. + * @param color + */ + public void setBorderColor(int color){ + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding all panels. + */ + public int getBorderColor(){ + return mBorderColor; + } + + /** + * Get the current color this view is showing. + * @return the current color. + */ + public int getColor(){ + return Color.HSVToColor(mAlpha, new float[]{mHue,mSat,mVal}); + } + + /** + * Set the color the view should show. + * @param color The color that should be selected. + */ + public void setColor(int color){ + setColor(color, false); + } + + /** + * Set the color this view should show. + * @param color The color that should be selected. + * @param callback If you want to get a callback to + * your OnColorChangedListener. + */ + public void setColor(int color, boolean callback){ + + int alpha = Color.alpha(color); + int red = Color.red(color); + int blue = Color.blue(color); + int green = Color.green(color); + + float[] hsv = new float[3]; + + Color.RGBToHSV(red, green, blue, hsv); + + mAlpha = alpha; + mHue = hsv[0]; + mSat = hsv[1]; + mVal = hsv[2]; + + if(callback && mListener != null){ + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } + + invalidate(); + } + + /** + * Get the drawing offset of the color picker view. + * The drawing offset is the distance from the side of + * a panel to the side of the view minus the padding. + * Useful if you want to have your own panel below showing + * the currently selected color and want to align it perfectly. + * @return The offset in pixels. + */ + public float getDrawingOffset(){ + return mDrawingOffset; + } + + /** + * Set if the user is allowed to adjust the alpha panel. Default is false. + * If it is set to false no alpha will be set. + * @param visible + */ + public void setAlphaSliderVisible(boolean visible){ + + if(mShowAlphaPanel != visible){ + mShowAlphaPanel = visible; + + /* + * Reset all shader to force a recreation. + * Otherwise they will not look right after + * the size of the view has changed. + */ + mValShader = null; + mSatShader = null; + mHueShader = null; + mAlphaShader = null;; + + requestLayout(); + } + + } + + public void setSliderTrackerColor(int color){ + mSliderTrackerColor = color; + + mHueTrackerPaint.setColor(mSliderTrackerColor); + + invalidate(); + } + + public int getSliderTrackerColor(){ + return mSliderTrackerColor; + } + + /** + * Set the text that should be shown in the + * alpha slider. Set to null to disable text. + * @param res string resource id. + */ + public void setAlphaSliderText(int res){ + String text = getContext().getString(res); + setAlphaSliderText(text); + } + + /** + * Set the text that should be shown in the + * alpha slider. Set to null to disable text. + * @param text Text that should be shown. + */ + public void setAlphaSliderText(String text){ + mAlphaSliderText = text; + invalidate(); + } + + /** + * Get the current value of the text + * that will be shown in the alpha + * slider. + * @return + */ + public String getAlphaSliderText(){ + return mAlphaSliderText; + } +} \ No newline at end of file diff --git a/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/Test.java b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/Test.java new file mode 100644 index 00000000..e5e167d9 --- /dev/null +++ b/external/ColorPickerPreference/src/net/margaritov/preference/colorpicker/Test.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011 Sergey Margaritov + * + * 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 net.margaritov.preference.colorpicker; + +import net.margaritov.preference.colorpicker.R; + +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.PreferenceActivity; + +public class Test extends PreferenceActivity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings); + ((ColorPickerPreference)findPreference("color2")).setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + preference.setSummary(ColorPickerPreference.convertToARGB(Integer.valueOf(String.valueOf(newValue)))); + return true; + } + + }); + ((ColorPickerPreference)findPreference("color2")).setAlphaSliderEnabled(true); + } +} \ No newline at end of file diff --git a/lib/src/org/transdroid/daemon/BitComet/BitCometAdapter.java b/lib/src/org/transdroid/daemon/BitComet/BitCometAdapter.java index 732a4e8a..525c9c0c 100644 --- a/lib/src/org/transdroid/daemon/BitComet/BitCometAdapter.java +++ b/lib/src/org/transdroid/daemon/BitComet/BitCometAdapter.java @@ -480,7 +480,8 @@ public class BitCometAdapter implements IDaemonAdapter { comment, dateAdded, null, - null)); + null, + settings.getType())); } } } @@ -557,7 +558,8 @@ public class BitCometAdapter implements IDaemonAdapter { label, dateAdded, null, - null)); // Not supported in the web interface + null, // Not supported in the web interface + settings.getType())); id++; // Stop/start/etc. requests are made by ID, which is the order number in the returned XML list :-S diff --git a/lib/src/org/transdroid/daemon/Bitflu/BitfluAdapter.java b/lib/src/org/transdroid/daemon/Bitflu/BitfluAdapter.java index 14e618bd..cfcc2fe0 100644 --- a/lib/src/org/transdroid/daemon/Bitflu/BitfluAdapter.java +++ b/lib/src/org/transdroid/daemon/Bitflu/BitfluAdapter.java @@ -214,7 +214,8 @@ public class BitfluAdapter implements IDaemonAdapter { null, // label null, // Not available null, // Not available - null)); // Not available + null, // Not available + settings.getType())); } } // Return the list diff --git a/lib/src/org/transdroid/daemon/BuffaloNas/BuffaloNasAdapter.java b/lib/src/org/transdroid/daemon/BuffaloNas/BuffaloNasAdapter.java index dbe6bea8..06db1af9 100644 --- a/lib/src/org/transdroid/daemon/BuffaloNas/BuffaloNasAdapter.java +++ b/lib/src/org/transdroid/daemon/BuffaloNas/BuffaloNasAdapter.java @@ -281,7 +281,8 @@ public class BuffaloNasAdapter implements IDaemonAdapter { null, null, null, - null)); + null, + settings.getType())); } // Return the list diff --git a/lib/src/org/transdroid/daemon/DLinkRouterBT/DLinkRouterBTAdapter.java b/lib/src/org/transdroid/daemon/DLinkRouterBT/DLinkRouterBTAdapter.java index 521333b7..b9eabbaf 100644 --- a/lib/src/org/transdroid/daemon/DLinkRouterBT/DLinkRouterBTAdapter.java +++ b/lib/src/org/transdroid/daemon/DLinkRouterBT/DLinkRouterBTAdapter.java @@ -376,7 +376,8 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter { null, null, null, - null); + null, + settings.getType()); torrents.add(new_t); } diff --git a/lib/src/org/transdroid/daemon/Deluge/DelugeAdapter.java b/lib/src/org/transdroid/daemon/Deluge/DelugeAdapter.java index 8a7fa785..6ce49d45 100644 --- a/lib/src/org/transdroid/daemon/Deluge/DelugeAdapter.java +++ b/lib/src/org/transdroid/daemon/Deluge/DelugeAdapter.java @@ -593,8 +593,9 @@ public class DelugeAdapter implements IDaemonAdapter { 0f, // Not available tor.has(RPC_LABEL)? tor.getString(RPC_LABEL): null, tor.has(RPC_TIMEADDED)? new Date(tor.getInt(RPC_TIMEADDED) * 1000L): null, - null, - tor.getString(RPC_MESSAGE))); // Not available + null, // Not available + tor.getString(RPC_MESSAGE), + settings.getType())); } } diff --git a/lib/src/org/transdroid/daemon/Ktorrent/StatsParser.java b/lib/src/org/transdroid/daemon/Ktorrent/StatsParser.java index fb55a519..b6dd76f7 100644 --- a/lib/src/org/transdroid/daemon/Ktorrent/StatsParser.java +++ b/lib/src/org/transdroid/daemon/Ktorrent/StatsParser.java @@ -5,6 +5,7 @@ import java.io.Reader; import java.util.ArrayList; import java.util.List; +import org.transdroid.daemon.Daemon; import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.Torrent; import org.transdroid.daemon.TorrentStatus; @@ -85,7 +86,8 @@ public class StatsParser { null, // Not supported in the web interface null, // Not supported in the web interface null, // Not supported in the web interface - null)); // Not supported in the web interface + null, // Not supported in the web interface + Daemon.KTorrent)); id++; // Stop/start/etc. requests are made by ID, which is the order number in the returned XML list :-S } else if (next == XmlPullParser.START_TAG && name.equals("torrent")){ diff --git a/lib/src/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java b/lib/src/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java index 0a7b4fda..40bfceb1 100644 --- a/lib/src/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java +++ b/lib/src/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java @@ -409,7 +409,8 @@ public class QbittorrentAdapter implements IDaemonAdapter { null, null, // Only available in /json/propertiesGeneral on a per-torrent basis, unfortunately null, - null)); + null, + settings.getType())); } // Return the list diff --git a/lib/src/org/transdroid/daemon/Rtorrent/RtorrentAdapter.java b/lib/src/org/transdroid/daemon/Rtorrent/RtorrentAdapter.java index 7e06af29..fb71300b 100644 --- a/lib/src/org/transdroid/daemon/Rtorrent/RtorrentAdapter.java +++ b/lib/src/org/transdroid/daemon/Rtorrent/RtorrentAdapter.java @@ -350,10 +350,11 @@ public class RtorrentAdapter implements IDaemonAdapter { (Long)info[10], // totalSize ((Long)info[8]).floatValue() / ((Long)info[10]).floatValue(), // partDone 0f, // TODO: Add availability data - label, // See remark on rTorrent/groups above + label, added, finished, - error)); + error, + settings.getType())); } else { @@ -379,10 +380,11 @@ public class RtorrentAdapter implements IDaemonAdapter { (Integer)info[10], // totalSize ((Integer)info[8]).floatValue() / ((Integer)info[10]).floatValue(), // partDone 0f, // TODO: Add availability data - label, // See remark on rTorrent/groups above + label, added, finished, - error)); + error, + settings.getType())); } } diff --git a/lib/src/org/transdroid/daemon/Tfb4rt/StatsParser.java b/lib/src/org/transdroid/daemon/Tfb4rt/StatsParser.java index af7f5cc7..31dc7ae7 100644 --- a/lib/src/org/transdroid/daemon/Tfb4rt/StatsParser.java +++ b/lib/src/org/transdroid/daemon/Tfb4rt/StatsParser.java @@ -5,6 +5,7 @@ import java.io.Reader; import java.util.ArrayList; import java.util.List; +import org.transdroid.daemon.Daemon; import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.Torrent; import org.transdroid.daemon.TorrentStatus; @@ -80,7 +81,8 @@ public class StatsParser { null, // Not supported in the XML stats null, null, - null)); + null, + Daemon.Tfb4rt)); } else if (next == XmlPullParser.START_TAG && name.equals("transfer")){ diff --git a/lib/src/org/transdroid/daemon/Torrent.java b/lib/src/org/transdroid/daemon/Torrent.java index 63622186..4a3c0f3e 100644 --- a/lib/src/org/transdroid/daemon/Torrent.java +++ b/lib/src/org/transdroid/daemon/Torrent.java @@ -55,6 +55,7 @@ public final class Torrent implements Parcelable, Comparable { final private Date dateAdded; final private Date dateDone; final private String error; + final private Daemon daemon; //public long getID() { return id; } //public String getHash() { return hash; } @@ -80,6 +81,7 @@ public final class Torrent implements Parcelable, Comparable { public Date getDateAdded() { return dateAdded; } public Date getDateDone() { return dateDone; } public String getError() { return error; } + public Daemon getDaemon() { return daemon; } private Torrent(Parcel in) { this.id = in.readLong(); @@ -108,12 +110,13 @@ public final class Torrent implements Parcelable, Comparable { long lDateDone = in.readLong(); this.dateDone = (lDateDone == -1)? null: new Date(lDateDone); this.error = in.readString(); + this.daemon = Daemon.valueOf(in.readString()); } public Torrent(long id, String hash, String name, TorrentStatus statusCode, String locationDir, int rateDownload, int rateUpload, int peersGettingFromUs, int peersSendingToUs, int peersConnected, int peersKnown, int eta, long downloadedEver, long uploadedEver, long totalSize, float partDone, float available, String label, - Date dateAdded, Date realDateDone, String error) { + Date dateAdded, Date realDateDone, String error, Daemon daemon) { this.id = id; this.hash = hash; this.name = name; @@ -148,6 +151,7 @@ public final class Torrent implements Parcelable, Comparable { } } this.error = error; + this.daemon = daemon; } /** @@ -295,6 +299,7 @@ public final class Torrent implements Parcelable, Comparable { dest.writeLong((dateAdded == null)? -1: dateAdded.getTime()); dest.writeLong((dateDone == null)? -1: dateDone.getTime()); dest.writeString(error); + dest.writeString(daemon.name()); } } diff --git a/lib/src/org/transdroid/daemon/Transmission/TransmissionAdapter.java b/lib/src/org/transdroid/daemon/Transmission/TransmissionAdapter.java index aa4bb4e1..0aeb6d1b 100644 --- a/lib/src/org/transdroid/daemon/Transmission/TransmissionAdapter.java +++ b/lib/src/org/transdroid/daemon/Transmission/TransmissionAdapter.java @@ -500,7 +500,8 @@ public class TransmissionAdapter implements IDaemonAdapter { null, // No label/category/group support in the RPC API for now new Date(tor.getLong(RPC_DATEADDED) * 1000L), new Date(tor.getLong(RPC_DATEDONE) * 1000L), - errorString)); + errorString, + settings.getType())); } // Return the list diff --git a/lib/src/org/transdroid/daemon/Utorrent/UtorrentAdapter.java b/lib/src/org/transdroid/daemon/Utorrent/UtorrentAdapter.java index 40b9214b..982fa079 100644 --- a/lib/src/org/transdroid/daemon/Utorrent/UtorrentAdapter.java +++ b/lib/src/org/transdroid/daemon/Utorrent/UtorrentAdapter.java @@ -515,7 +515,8 @@ public class UtorrentAdapter implements IDaemonAdapter { addedOnDate, completedOnDate, // uTorrent doesn't give the error message, so just remind that there is some error - status == TorrentStatus.Error? "See GUI for error message": null)); + status == TorrentStatus.Error? "See GUI for error message": null, + settings.getType())); } return torrents; diff --git a/lib/src/org/transdroid/daemon/Vuze/VuzeAdapter.java b/lib/src/org/transdroid/daemon/Vuze/VuzeAdapter.java index b19ca94d..ba55e981 100644 --- a/lib/src/org/transdroid/daemon/Vuze/VuzeAdapter.java +++ b/lib/src/org/transdroid/daemon/Vuze/VuzeAdapter.java @@ -31,6 +31,7 @@ import java.util.Map; import org.apache.openjpa.lib.util.Base16Encoder; import org.transdroid.daemon.Daemon; import org.transdroid.daemon.DaemonException; +import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonMethod; import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.IDaemonAdapter; @@ -38,7 +39,6 @@ import org.transdroid.daemon.Priority; import org.transdroid.daemon.Torrent; import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentStatus; -import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByUrlTask; import org.transdroid.daemon.task.DaemonTask; @@ -99,18 +99,27 @@ public class VuzeAdapter implements IDaemonAdapter { case AddByFile: byte[] bytes; + FileInputStream in = null; try { // Request to add a torrent by local .torrent file String file = ((AddByFileTask)task).getFile(); - FileInputStream in = new FileInputStream(new File(URI.create(file))); + in = new FileInputStream(new File(URI.create(file))); bytes = new byte[in.available()]; in.read(bytes, 0, in.available()); + in.close(); } catch (FileNotFoundException e) { return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.FileAccessError, e.toString())); } catch (IllegalArgumentException e) { return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.FileAccessError, "Invalid local URI")); } catch (Exception e) { return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.FileAccessError, e.toString())); + } finally { + try { + if (in != null) + in.close(); + } catch (IOException e) { + // Ignore; it was already closed or never opened + } } makeVuzeCall(DaemonMethod.AddByFile, "createFromBEncodedData[byte[]]", new String[] { Base16Encoder.encode(bytes) }); return new DaemonTaskSuccessResult(task); @@ -406,7 +415,8 @@ public class VuzeAdapter implements IDaemonAdapter { null, // TODO: Implement Vuze label support new Date((Long) statsinfo.get("time_started")), // dateAdded null, // Unsupported? - error)); + error, + settings.getType())); } diff --git a/lib/src/org/transdroid/daemon/util/FileSizeConverter.java b/lib/src/org/transdroid/daemon/util/FileSizeConverter.java index a31c9966..13f18e38 100644 --- a/lib/src/org/transdroid/daemon/util/FileSizeConverter.java +++ b/lib/src/org/transdroid/daemon/util/FileSizeConverter.java @@ -15,71 +15,99 @@ * along with Transdroid. If not, see . * */ - package org.transdroid.daemon.util; +package org.transdroid.daemon.util; /** * Quick and dirty file size formatter. - * * @author erickok - * */ public class FileSizeConverter { private static final String DECIMAL_FORMATTER = "%.1f"; - + /** * A quantity in which to express a file size. - * * @author erickok - * */ public enum SizeUnit { - B, - KB, - MB, - GB + B, KB, MB, GB } - + private static int INC_SIZE = 1024; - - // Returns a file size given in bytes to a different unit, as a formatted string - public static String getSize(long from, SizeUnit to) - { + + /** + * Returns a file size (in bytes) in a different unit, as a formatted string + * @param from The file size in bytes + * @param to The unit to convert to + * @return A formatted string with number (rounded to one decimal) and unit, e.g. 1177.4MB + */ + public static String getSize(long from, SizeUnit to) { String out; switch (to) { case B: out = String.valueOf(from); break; case KB: - out = String.format(DECIMAL_FORMATTER, ((double)from) / 1024); + out = String.format(DECIMAL_FORMATTER, ((double) from) / INC_SIZE); break; case MB: - out = String.format(DECIMAL_FORMATTER, ((double)from) / 1024 / 1024); + out = String.format(DECIMAL_FORMATTER, ((double) from) / INC_SIZE / INC_SIZE); break; default: - out = String.format(DECIMAL_FORMATTER, ((double)from) / 1024 / 1024 / 1024); + out = String.format(DECIMAL_FORMATTER, ((double) from) / INC_SIZE / INC_SIZE / INC_SIZE); break; } - - return (out + " " + to.toString()); + + return (out + " " + to.toString()); } - // Returns a file size in bytes in a nice readable formatted string + /** + * Returns a file size as nice readable string, with unit, e.g. 1234567890 (bytes) returns 1,15GB + * @param from The file size in bytes + * @return A formatted string with number (rounded to one decimal), with unit text + */ public static String getSize(long from) { return getSize(from, true); } // Returns a file size in bytes in a nice readable formatted string + /** + * Returns a file size as nice readable string, e.g. 1234567890 (bytes) returns 1,15 or 1,15GB + * @param from The file size in bytes + * @param withUnit Whether to also append the appropriate unit (B, KB, MB, GB) as text + * @return A formatted string with number (rounded to one decimal) and optionally unit + */ public static String getSize(long from, boolean withUnit) { if (from < INC_SIZE) { - return String.valueOf(from) + (withUnit? SizeUnit.B.toString(): ""); - } else if (from < (INC_SIZE * INC_SIZE)) { - return String.format(DECIMAL_FORMATTER, ((double)from) / INC_SIZE) + (withUnit? SizeUnit.KB.toString(): ""); + return String.valueOf(from) + (withUnit ? SizeUnit.B.toString() : ""); + } else if (from < (INC_SIZE * INC_SIZE)) { + return String.format(DECIMAL_FORMATTER, ((double) from) / INC_SIZE) + + (withUnit ? SizeUnit.KB.toString() : ""); } else if (from < (INC_SIZE * INC_SIZE * INC_SIZE)) { - return String.format(DECIMAL_FORMATTER, ((double)from) / INC_SIZE / INC_SIZE) + (withUnit? SizeUnit.MB.toString(): ""); - } else { - return String.format(DECIMAL_FORMATTER, ((double)from) / INC_SIZE / INC_SIZE / INC_SIZE) + (withUnit? SizeUnit.GB.toString(): ""); + return String.format(DECIMAL_FORMATTER, ((double) from) / INC_SIZE / INC_SIZE) + + (withUnit ? SizeUnit.MB.toString() : ""); + } else { + return String.format(DECIMAL_FORMATTER, ((double) from) / INC_SIZE / INC_SIZE / INC_SIZE) + + (withUnit ? SizeUnit.GB.toString() : ""); } } - + + /** + * Returns the unit to display some file size (as returned by getSize(long)) in, e.g. 1234567890 (bytes) returns GB + * as it is 1.2GB big + * @param from The file size in bytes + * @return The unit, i.e. B, KB, MB or GB + */ + public static SizeUnit getSizeUnit(long from) { + if (from < INC_SIZE) { + return SizeUnit.B; + } else if (from < (INC_SIZE * INC_SIZE)) { + return SizeUnit.KB; + } else if (from < (INC_SIZE * INC_SIZE * INC_SIZE)) { + return SizeUnit.MB; + } else { + return SizeUnit.GB; + } + } + } diff --git a/lite/.classpath b/lite/.classpath new file mode 100644 index 00000000..08d23ddb --- /dev/null +++ b/lite/.classpath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lite/.factorypath b/lite/.factorypath new file mode 100644 index 00000000..d528d92e --- /dev/null +++ b/lite/.factorypath @@ -0,0 +1,3 @@ + + + diff --git a/lite/.project b/lite/.project new file mode 100644 index 00000000..1b341fee --- /dev/null +++ b/lite/.project @@ -0,0 +1,33 @@ + + + Transdroid Core + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/lite/.settings/org.eclipse.jdt.apt.core.prefs b/lite/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 00000000..7d52ece5 --- /dev/null +++ b/lite/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=.apt_generated +org.eclipse.jdt.apt.reconcileEnabled=true diff --git a/lite/.settings/org.eclipse.jdt.core.prefs b/lite/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..0b3561ab --- /dev/null +++ b/lite/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.processAnnotations=enabled diff --git a/lite/AndroidManifest.xml b/lite/AndroidManifest.xml new file mode 100644 index 00000000..6b16a6ad --- /dev/null +++ b/lite/AndroidManifest.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/compile-libs/androidannotations-3.0-SNAPSHOT.jar b/lite/compile-libs/androidannotations-3.0-SNAPSHOT.jar new file mode 100644 index 00000000..310da002 Binary files /dev/null and b/lite/compile-libs/androidannotations-3.0-SNAPSHOT.jar differ diff --git a/lite/libs/androidannotations-api-3.0-SNAPSHOT.jar b/lite/libs/androidannotations-api-3.0-SNAPSHOT.jar new file mode 100644 index 00000000..bf0bfae5 Binary files /dev/null and b/lite/libs/androidannotations-api-3.0-SNAPSHOT.jar differ diff --git a/lite/proguard-project.txt b/lite/proguard-project.txt new file mode 100644 index 00000000..f2fe1559 --- /dev/null +++ b/lite/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/lite/project.properties b/lite/project.properties new file mode 100644 index 00000000..b8863ad3 --- /dev/null +++ b/lite/project.properties @@ -0,0 +1,16 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-16 +android.library.reference.1=../external/JakeWharton-ActionBarSherlock/library +android.library.reference.2=../external/ColorPickerPreference diff --git a/lite/res/drawable-hdpi/ab_bottom_solid_transdroid.9.png b/lite/res/drawable-hdpi/ab_bottom_solid_transdroid.9.png new file mode 100644 index 00000000..64793735 Binary files /dev/null and b/lite/res/drawable-hdpi/ab_bottom_solid_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/ab_solid_transdroid.9.png b/lite/res/drawable-hdpi/ab_solid_transdroid.9.png new file mode 100644 index 00000000..12ece624 Binary files /dev/null and b/lite/res/drawable-hdpi/ab_solid_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/ab_stacked_solid_transdroid.9.png b/lite/res/drawable-hdpi/ab_stacked_solid_transdroid.9.png new file mode 100644 index 00000000..005445ac Binary files /dev/null and b/lite/res/drawable-hdpi/ab_stacked_solid_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/ab_transparent_transdroid.9.png b/lite/res/drawable-hdpi/ab_transparent_transdroid.9.png new file mode 100644 index 00000000..fc241b9f Binary files /dev/null and b/lite/res/drawable-hdpi/ab_transparent_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/ic_action_discard.png b/lite/res/drawable-hdpi/ic_action_discard.png new file mode 100644 index 00000000..ffd19d9e Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_discard.png differ diff --git a/lite/res/drawable-hdpi/ic_action_labels.png b/lite/res/drawable-hdpi/ic_action_labels.png new file mode 100644 index 00000000..432e7c00 Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_labels.png differ diff --git a/lite/res/drawable-hdpi/ic_action_new.png b/lite/res/drawable-hdpi/ic_action_new.png new file mode 100644 index 00000000..ad8ada6b Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_new.png differ diff --git a/lite/res/drawable-hdpi/ic_action_pause.png b/lite/res/drawable-hdpi/ic_action_pause.png new file mode 100644 index 00000000..6b435bb0 Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_pause.png differ diff --git a/lite/res/drawable-hdpi/ic_action_refresh.png b/lite/res/drawable-hdpi/ic_action_refresh.png new file mode 100644 index 00000000..bb9d855f Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_refresh.png differ diff --git a/lite/res/drawable-hdpi/ic_action_remove.png b/lite/res/drawable-hdpi/ic_action_remove.png new file mode 100644 index 00000000..094eea58 Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_remove.png differ diff --git a/lite/res/drawable-hdpi/ic_action_resume.png b/lite/res/drawable-hdpi/ic_action_resume.png new file mode 100644 index 00000000..738aae1a Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_resume.png differ diff --git a/lite/res/drawable-hdpi/ic_action_rss.png b/lite/res/drawable-hdpi/ic_action_rss.png new file mode 100644 index 00000000..02ec51ef Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_rss.png differ diff --git a/lite/res/drawable-hdpi/ic_action_search.png b/lite/res/drawable-hdpi/ic_action_search.png new file mode 100644 index 00000000..f12e005e Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_search.png differ diff --git a/lite/res/drawable-hdpi/ic_action_sort_by_size.png b/lite/res/drawable-hdpi/ic_action_sort_by_size.png new file mode 100644 index 00000000..cbb5f451 Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_sort_by_size.png differ diff --git a/lite/res/drawable-hdpi/ic_action_start.png b/lite/res/drawable-hdpi/ic_action_start.png new file mode 100644 index 00000000..df8a2ca2 Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_start.png differ diff --git a/lite/res/drawable-hdpi/ic_action_stop.png b/lite/res/drawable-hdpi/ic_action_stop.png new file mode 100644 index 00000000..dd5d6a1c Binary files /dev/null and b/lite/res/drawable-hdpi/ic_action_stop.png differ diff --git a/lite/res/drawable-hdpi/ic_activity_torrents.png b/lite/res/drawable-hdpi/ic_activity_torrents.png new file mode 100644 index 00000000..63728265 Binary files /dev/null and b/lite/res/drawable-hdpi/ic_activity_torrents.png differ diff --git a/lite/res/drawable-hdpi/ic_empty_details.png b/lite/res/drawable-hdpi/ic_empty_details.png new file mode 100644 index 00000000..718615e5 Binary files /dev/null and b/lite/res/drawable-hdpi/ic_empty_details.png differ diff --git a/lite/res/drawable-hdpi/ic_launcher.png b/lite/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 00000000..bb8449f4 Binary files /dev/null and b/lite/res/drawable-hdpi/ic_launcher.png differ diff --git a/lite/res/drawable-hdpi/ic_priority_high.png b/lite/res/drawable-hdpi/ic_priority_high.png new file mode 100644 index 00000000..43dd253b Binary files /dev/null and b/lite/res/drawable-hdpi/ic_priority_high.png differ diff --git a/lite/res/drawable-hdpi/ic_priority_low.png b/lite/res/drawable-hdpi/ic_priority_low.png new file mode 100644 index 00000000..fa8e7bfb Binary files /dev/null and b/lite/res/drawable-hdpi/ic_priority_low.png differ diff --git a/lite/res/drawable-hdpi/ic_priority_normal.png b/lite/res/drawable-hdpi/ic_priority_normal.png new file mode 100644 index 00000000..b59edc02 Binary files /dev/null and b/lite/res/drawable-hdpi/ic_priority_normal.png differ diff --git a/lite/res/drawable-hdpi/ic_priority_off.png b/lite/res/drawable-hdpi/ic_priority_off.png new file mode 100644 index 00000000..e44ea25a Binary files /dev/null and b/lite/res/drawable-hdpi/ic_priority_off.png differ diff --git a/lite/res/drawable-hdpi/list_focused_transdroid.9.png b/lite/res/drawable-hdpi/list_focused_transdroid.9.png new file mode 100644 index 00000000..b342ccbb Binary files /dev/null and b/lite/res/drawable-hdpi/list_focused_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/menu_dropdown_panel_transdroid.9.png b/lite/res/drawable-hdpi/menu_dropdown_panel_transdroid.9.png new file mode 100644 index 00000000..bab5e3ba Binary files /dev/null and b/lite/res/drawable-hdpi/menu_dropdown_panel_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/menu_hardkey_panel_transdroid.9.png b/lite/res/drawable-hdpi/menu_hardkey_panel_transdroid.9.png new file mode 100644 index 00000000..40a8f3eb Binary files /dev/null and b/lite/res/drawable-hdpi/menu_hardkey_panel_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/progress_bg_transdroid.9.png b/lite/res/drawable-hdpi/progress_bg_transdroid.9.png new file mode 100644 index 00000000..3d5c707d Binary files /dev/null and b/lite/res/drawable-hdpi/progress_bg_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/progress_primary_transdroid.9.png b/lite/res/drawable-hdpi/progress_primary_transdroid.9.png new file mode 100644 index 00000000..44a3c436 Binary files /dev/null and b/lite/res/drawable-hdpi/progress_primary_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/progress_secondary_transdroid.9.png b/lite/res/drawable-hdpi/progress_secondary_transdroid.9.png new file mode 100644 index 00000000..32196b2b Binary files /dev/null and b/lite/res/drawable-hdpi/progress_secondary_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/spinner_ab_default_transdroid.9.png b/lite/res/drawable-hdpi/spinner_ab_default_transdroid.9.png new file mode 100644 index 00000000..4fd4aeba Binary files /dev/null and b/lite/res/drawable-hdpi/spinner_ab_default_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/spinner_ab_disabled_transdroid.9.png b/lite/res/drawable-hdpi/spinner_ab_disabled_transdroid.9.png new file mode 100644 index 00000000..d42c97b8 Binary files /dev/null and b/lite/res/drawable-hdpi/spinner_ab_disabled_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/spinner_ab_focused_transdroid.9.png b/lite/res/drawable-hdpi/spinner_ab_focused_transdroid.9.png new file mode 100644 index 00000000..4761bd26 Binary files /dev/null and b/lite/res/drawable-hdpi/spinner_ab_focused_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/spinner_ab_pressed_transdroid.9.png b/lite/res/drawable-hdpi/spinner_ab_pressed_transdroid.9.png new file mode 100644 index 00000000..bddc913e Binary files /dev/null and b/lite/res/drawable-hdpi/spinner_ab_pressed_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/tab_selected_focused_transdroid.9.png b/lite/res/drawable-hdpi/tab_selected_focused_transdroid.9.png new file mode 100644 index 00000000..36fec49a Binary files /dev/null and b/lite/res/drawable-hdpi/tab_selected_focused_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/tab_selected_pressed_transdroid.9.png b/lite/res/drawable-hdpi/tab_selected_pressed_transdroid.9.png new file mode 100644 index 00000000..aa07ad6f Binary files /dev/null and b/lite/res/drawable-hdpi/tab_selected_pressed_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/tab_selected_transdroid.9.png b/lite/res/drawable-hdpi/tab_selected_transdroid.9.png new file mode 100644 index 00000000..d59bde58 Binary files /dev/null and b/lite/res/drawable-hdpi/tab_selected_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/tab_unselected_focused_transdroid.9.png b/lite/res/drawable-hdpi/tab_unselected_focused_transdroid.9.png new file mode 100644 index 00000000..40613b47 Binary files /dev/null and b/lite/res/drawable-hdpi/tab_unselected_focused_transdroid.9.png differ diff --git a/lite/res/drawable-hdpi/tab_unselected_pressed_transdroid.9.png b/lite/res/drawable-hdpi/tab_unselected_pressed_transdroid.9.png new file mode 100644 index 00000000..ef83039c Binary files /dev/null and b/lite/res/drawable-hdpi/tab_unselected_pressed_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/ab_bottom_solid_transdroid.9.png b/lite/res/drawable-mdpi/ab_bottom_solid_transdroid.9.png new file mode 100644 index 00000000..135904dc Binary files /dev/null and b/lite/res/drawable-mdpi/ab_bottom_solid_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/ab_solid_transdroid.9.png b/lite/res/drawable-mdpi/ab_solid_transdroid.9.png new file mode 100644 index 00000000..fa3d4284 Binary files /dev/null and b/lite/res/drawable-mdpi/ab_solid_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/ab_stacked_solid_transdroid.9.png b/lite/res/drawable-mdpi/ab_stacked_solid_transdroid.9.png new file mode 100644 index 00000000..fd09cb18 Binary files /dev/null and b/lite/res/drawable-mdpi/ab_stacked_solid_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/ab_transparent_transdroid.9.png b/lite/res/drawable-mdpi/ab_transparent_transdroid.9.png new file mode 100644 index 00000000..8c3b514f Binary files /dev/null and b/lite/res/drawable-mdpi/ab_transparent_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/ic_action_discard.png b/lite/res/drawable-mdpi/ic_action_discard.png new file mode 100644 index 00000000..a8ee5f25 Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_discard.png differ diff --git a/lite/res/drawable-mdpi/ic_action_labels.png b/lite/res/drawable-mdpi/ic_action_labels.png new file mode 100644 index 00000000..b85d7c58 Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_labels.png differ diff --git a/lite/res/drawable-mdpi/ic_action_new.png b/lite/res/drawable-mdpi/ic_action_new.png new file mode 100644 index 00000000..4d5d484b Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_new.png differ diff --git a/lite/res/drawable-mdpi/ic_action_pause.png b/lite/res/drawable-mdpi/ic_action_pause.png new file mode 100644 index 00000000..a5aee6f2 Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_pause.png differ diff --git a/lite/res/drawable-mdpi/ic_action_refresh.png b/lite/res/drawable-mdpi/ic_action_refresh.png new file mode 100644 index 00000000..bd611e8e Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_refresh.png differ diff --git a/lite/res/drawable-mdpi/ic_action_remove.png b/lite/res/drawable-mdpi/ic_action_remove.png new file mode 100644 index 00000000..3336760d Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_remove.png differ diff --git a/lite/res/drawable-mdpi/ic_action_resume.png b/lite/res/drawable-mdpi/ic_action_resume.png new file mode 100644 index 00000000..28e81379 Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_resume.png differ diff --git a/lite/res/drawable-mdpi/ic_action_rss.png b/lite/res/drawable-mdpi/ic_action_rss.png new file mode 100644 index 00000000..2de867b8 Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_rss.png differ diff --git a/lite/res/drawable-mdpi/ic_action_search.png b/lite/res/drawable-mdpi/ic_action_search.png new file mode 100644 index 00000000..587d9e0b Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_search.png differ diff --git a/lite/res/drawable-mdpi/ic_action_sort_by_size.png b/lite/res/drawable-mdpi/ic_action_sort_by_size.png new file mode 100644 index 00000000..aa921e76 Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_sort_by_size.png differ diff --git a/lite/res/drawable-mdpi/ic_action_start.png b/lite/res/drawable-mdpi/ic_action_start.png new file mode 100644 index 00000000..6a40cd5f Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_start.png differ diff --git a/lite/res/drawable-mdpi/ic_action_stop.png b/lite/res/drawable-mdpi/ic_action_stop.png new file mode 100644 index 00000000..20df4158 Binary files /dev/null and b/lite/res/drawable-mdpi/ic_action_stop.png differ diff --git a/lite/res/drawable-mdpi/ic_activity_torrents.png b/lite/res/drawable-mdpi/ic_activity_torrents.png new file mode 100644 index 00000000..ce606a71 Binary files /dev/null and b/lite/res/drawable-mdpi/ic_activity_torrents.png differ diff --git a/lite/res/drawable-mdpi/ic_empty_details.png b/lite/res/drawable-mdpi/ic_empty_details.png new file mode 100644 index 00000000..5bd58335 Binary files /dev/null and b/lite/res/drawable-mdpi/ic_empty_details.png differ diff --git a/lite/res/drawable-mdpi/ic_launcher.png b/lite/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 00000000..88fb5bd7 Binary files /dev/null and b/lite/res/drawable-mdpi/ic_launcher.png differ diff --git a/lite/res/drawable-mdpi/list_focused_transdroid.9.png b/lite/res/drawable-mdpi/list_focused_transdroid.9.png new file mode 100644 index 00000000..cc6c75f9 Binary files /dev/null and b/lite/res/drawable-mdpi/list_focused_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/menu_dropdown_panel_transdroid.9.png b/lite/res/drawable-mdpi/menu_dropdown_panel_transdroid.9.png new file mode 100644 index 00000000..2b2458ef Binary files /dev/null and b/lite/res/drawable-mdpi/menu_dropdown_panel_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/menu_hardkey_panel_transdroid.9.png b/lite/res/drawable-mdpi/menu_hardkey_panel_transdroid.9.png new file mode 100644 index 00000000..19b2feb5 Binary files /dev/null and b/lite/res/drawable-mdpi/menu_hardkey_panel_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/progress_bg_transdroid.9.png b/lite/res/drawable-mdpi/progress_bg_transdroid.9.png new file mode 100644 index 00000000..9372a60f Binary files /dev/null and b/lite/res/drawable-mdpi/progress_bg_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/progress_primary_transdroid.9.png b/lite/res/drawable-mdpi/progress_primary_transdroid.9.png new file mode 100644 index 00000000..27192ef1 Binary files /dev/null and b/lite/res/drawable-mdpi/progress_primary_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/progress_secondary_transdroid.9.png b/lite/res/drawable-mdpi/progress_secondary_transdroid.9.png new file mode 100644 index 00000000..2b240eae Binary files /dev/null and b/lite/res/drawable-mdpi/progress_secondary_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/spinner_ab_default_transdroid.9.png b/lite/res/drawable-mdpi/spinner_ab_default_transdroid.9.png new file mode 100644 index 00000000..9aeafee2 Binary files /dev/null and b/lite/res/drawable-mdpi/spinner_ab_default_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/spinner_ab_disabled_transdroid.9.png b/lite/res/drawable-mdpi/spinner_ab_disabled_transdroid.9.png new file mode 100644 index 00000000..88dd4415 Binary files /dev/null and b/lite/res/drawable-mdpi/spinner_ab_disabled_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/spinner_ab_focused_transdroid.9.png b/lite/res/drawable-mdpi/spinner_ab_focused_transdroid.9.png new file mode 100644 index 00000000..63a0fda5 Binary files /dev/null and b/lite/res/drawable-mdpi/spinner_ab_focused_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/spinner_ab_pressed_transdroid.9.png b/lite/res/drawable-mdpi/spinner_ab_pressed_transdroid.9.png new file mode 100644 index 00000000..15652b11 Binary files /dev/null and b/lite/res/drawable-mdpi/spinner_ab_pressed_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/tab_selected_focused_transdroid.9.png b/lite/res/drawable-mdpi/tab_selected_focused_transdroid.9.png new file mode 100644 index 00000000..ac6ffee0 Binary files /dev/null and b/lite/res/drawable-mdpi/tab_selected_focused_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/tab_selected_pressed_transdroid.9.png b/lite/res/drawable-mdpi/tab_selected_pressed_transdroid.9.png new file mode 100644 index 00000000..690f41de Binary files /dev/null and b/lite/res/drawable-mdpi/tab_selected_pressed_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/tab_selected_transdroid.9.png b/lite/res/drawable-mdpi/tab_selected_transdroid.9.png new file mode 100644 index 00000000..1aa38d48 Binary files /dev/null and b/lite/res/drawable-mdpi/tab_selected_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/tab_unselected_focused_transdroid.9.png b/lite/res/drawable-mdpi/tab_unselected_focused_transdroid.9.png new file mode 100644 index 00000000..f9c9c6bd Binary files /dev/null and b/lite/res/drawable-mdpi/tab_unselected_focused_transdroid.9.png differ diff --git a/lite/res/drawable-mdpi/tab_unselected_pressed_transdroid.9.png b/lite/res/drawable-mdpi/tab_unselected_pressed_transdroid.9.png new file mode 100644 index 00000000..2acd0bbe Binary files /dev/null and b/lite/res/drawable-mdpi/tab_unselected_pressed_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/ab_bottom_solid_transdroid.9.png b/lite/res/drawable-xhdpi/ab_bottom_solid_transdroid.9.png new file mode 100644 index 00000000..abf5694b Binary files /dev/null and b/lite/res/drawable-xhdpi/ab_bottom_solid_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/ab_solid_transdroid.9.png b/lite/res/drawable-xhdpi/ab_solid_transdroid.9.png new file mode 100644 index 00000000..143a16f4 Binary files /dev/null and b/lite/res/drawable-xhdpi/ab_solid_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/ab_stacked_solid_transdroid.9.png b/lite/res/drawable-xhdpi/ab_stacked_solid_transdroid.9.png new file mode 100644 index 00000000..f9a1e1f2 Binary files /dev/null and b/lite/res/drawable-xhdpi/ab_stacked_solid_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/ab_transparent_transdroid.9.png b/lite/res/drawable-xhdpi/ab_transparent_transdroid.9.png new file mode 100644 index 00000000..24020e23 Binary files /dev/null and b/lite/res/drawable-xhdpi/ab_transparent_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/action_sort_by_size.png b/lite/res/drawable-xhdpi/action_sort_by_size.png new file mode 100644 index 00000000..cb2dff32 Binary files /dev/null and b/lite/res/drawable-xhdpi/action_sort_by_size.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_discard.png b/lite/res/drawable-xhdpi/ic_action_discard.png new file mode 100644 index 00000000..412b3335 Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_discard.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_labels.png b/lite/res/drawable-xhdpi/ic_action_labels.png new file mode 100644 index 00000000..8fdcd1a2 Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_labels.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_new.png b/lite/res/drawable-xhdpi/ic_action_new.png new file mode 100644 index 00000000..23b9a1c1 Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_new.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_pause.png b/lite/res/drawable-xhdpi/ic_action_pause.png new file mode 100644 index 00000000..333c1b24 Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_pause.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_refresh.png b/lite/res/drawable-xhdpi/ic_action_refresh.png new file mode 100644 index 00000000..a7fdc0df Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_refresh.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_remove.png b/lite/res/drawable-xhdpi/ic_action_remove.png new file mode 100644 index 00000000..f391760e Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_remove.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_resume.png b/lite/res/drawable-xhdpi/ic_action_resume.png new file mode 100644 index 00000000..fe6b5588 Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_resume.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_rss.png b/lite/res/drawable-xhdpi/ic_action_rss.png new file mode 100644 index 00000000..dcd88e1c Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_rss.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_search.png b/lite/res/drawable-xhdpi/ic_action_search.png new file mode 100644 index 00000000..3549f84d Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_search.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_start.png b/lite/res/drawable-xhdpi/ic_action_start.png new file mode 100644 index 00000000..51124993 Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_start.png differ diff --git a/lite/res/drawable-xhdpi/ic_action_stop.png b/lite/res/drawable-xhdpi/ic_action_stop.png new file mode 100644 index 00000000..ee5eda25 Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_action_stop.png differ diff --git a/lite/res/drawable-xhdpi/ic_activity_torrents.png b/lite/res/drawable-xhdpi/ic_activity_torrents.png new file mode 100644 index 00000000..25614e00 Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_activity_torrents.png differ diff --git a/lite/res/drawable-xhdpi/ic_empty_details.png b/lite/res/drawable-xhdpi/ic_empty_details.png new file mode 100644 index 00000000..7b446989 Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_empty_details.png differ diff --git a/lite/res/drawable-xhdpi/ic_launcher.png b/lite/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 00000000..e2a94658 Binary files /dev/null and b/lite/res/drawable-xhdpi/ic_launcher.png differ diff --git a/lite/res/drawable-xhdpi/list_focused_transdroid.9.png b/lite/res/drawable-xhdpi/list_focused_transdroid.9.png new file mode 100644 index 00000000..368d15f6 Binary files /dev/null and b/lite/res/drawable-xhdpi/list_focused_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/menu_dropdown_panel_transdroid.9.png b/lite/res/drawable-xhdpi/menu_dropdown_panel_transdroid.9.png new file mode 100644 index 00000000..2974663c Binary files /dev/null and b/lite/res/drawable-xhdpi/menu_dropdown_panel_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/menu_hardkey_panel_transdroid.9.png b/lite/res/drawable-xhdpi/menu_hardkey_panel_transdroid.9.png new file mode 100644 index 00000000..97c03063 Binary files /dev/null and b/lite/res/drawable-xhdpi/menu_hardkey_panel_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/progress_bg_transdroid.9.png b/lite/res/drawable-xhdpi/progress_bg_transdroid.9.png new file mode 100644 index 00000000..8b4853aa Binary files /dev/null and b/lite/res/drawable-xhdpi/progress_bg_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/progress_primary_transdroid.9.png b/lite/res/drawable-xhdpi/progress_primary_transdroid.9.png new file mode 100644 index 00000000..b1c9444a Binary files /dev/null and b/lite/res/drawable-xhdpi/progress_primary_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/progress_secondary_transdroid.9.png b/lite/res/drawable-xhdpi/progress_secondary_transdroid.9.png new file mode 100644 index 00000000..48cc8e57 Binary files /dev/null and b/lite/res/drawable-xhdpi/progress_secondary_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/spinner_ab_default_transdroid.9.png b/lite/res/drawable-xhdpi/spinner_ab_default_transdroid.9.png new file mode 100644 index 00000000..14b1401d Binary files /dev/null and b/lite/res/drawable-xhdpi/spinner_ab_default_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/spinner_ab_disabled_transdroid.9.png b/lite/res/drawable-xhdpi/spinner_ab_disabled_transdroid.9.png new file mode 100644 index 00000000..c9dfbd60 Binary files /dev/null and b/lite/res/drawable-xhdpi/spinner_ab_disabled_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/spinner_ab_focused_transdroid.9.png b/lite/res/drawable-xhdpi/spinner_ab_focused_transdroid.9.png new file mode 100644 index 00000000..e4eddc17 Binary files /dev/null and b/lite/res/drawable-xhdpi/spinner_ab_focused_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/spinner_ab_pressed_transdroid.9.png b/lite/res/drawable-xhdpi/spinner_ab_pressed_transdroid.9.png new file mode 100644 index 00000000..95f9b4c6 Binary files /dev/null and b/lite/res/drawable-xhdpi/spinner_ab_pressed_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/tab_selected_focused_transdroid.9.png b/lite/res/drawable-xhdpi/tab_selected_focused_transdroid.9.png new file mode 100644 index 00000000..1c6ccb7b Binary files /dev/null and b/lite/res/drawable-xhdpi/tab_selected_focused_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/tab_selected_pressed_transdroid.9.png b/lite/res/drawable-xhdpi/tab_selected_pressed_transdroid.9.png new file mode 100644 index 00000000..bed352bd Binary files /dev/null and b/lite/res/drawable-xhdpi/tab_selected_pressed_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/tab_selected_transdroid.9.png b/lite/res/drawable-xhdpi/tab_selected_transdroid.9.png new file mode 100644 index 00000000..794323de Binary files /dev/null and b/lite/res/drawable-xhdpi/tab_selected_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/tab_unselected_focused_transdroid.9.png b/lite/res/drawable-xhdpi/tab_unselected_focused_transdroid.9.png new file mode 100644 index 00000000..564f40f5 Binary files /dev/null and b/lite/res/drawable-xhdpi/tab_unselected_focused_transdroid.9.png differ diff --git a/lite/res/drawable-xhdpi/tab_unselected_pressed_transdroid.9.png b/lite/res/drawable-xhdpi/tab_unselected_pressed_transdroid.9.png new file mode 100644 index 00000000..3f885d60 Binary files /dev/null and b/lite/res/drawable-xhdpi/tab_unselected_pressed_transdroid.9.png differ diff --git a/lite/res/drawable-xxhdpi/ic_activity_torrents.png b/lite/res/drawable-xxhdpi/ic_activity_torrents.png new file mode 100644 index 00000000..a4a1a817 Binary files /dev/null and b/lite/res/drawable-xxhdpi/ic_activity_torrents.png differ diff --git a/lite/res/drawable-xxhdpi/ic_launcher.png b/lite/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..91ea69c2 Binary files /dev/null and b/lite/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/lite/res/drawable/loading_progress.xml b/lite/res/drawable/loading_progress.xml new file mode 100644 index 00000000..df2175ef --- /dev/null +++ b/lite/res/drawable/loading_progress.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/lite/res/drawable/pressed_background_transdroid.xml b/lite/res/drawable/pressed_background_transdroid.xml new file mode 100644 index 00000000..2b1eed9f --- /dev/null +++ b/lite/res/drawable/pressed_background_transdroid.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/lite/res/drawable/progress_horizontal_transdroid.xml b/lite/res/drawable/progress_horizontal_transdroid.xml new file mode 100644 index 00000000..585c2b73 --- /dev/null +++ b/lite/res/drawable/progress_horizontal_transdroid.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + diff --git a/lite/res/drawable/section_header.xml b/lite/res/drawable/section_header.xml new file mode 100644 index 00000000..d6094f63 --- /dev/null +++ b/lite/res/drawable/section_header.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/lite/res/drawable/selectable_background_transdroid.xml b/lite/res/drawable/selectable_background_transdroid.xml new file mode 100644 index 00000000..de42f6a1 --- /dev/null +++ b/lite/res/drawable/selectable_background_transdroid.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/lite/res/drawable/spinner_background_ab_transdroid.xml b/lite/res/drawable/spinner_background_ab_transdroid.xml new file mode 100644 index 00000000..32edfe7b --- /dev/null +++ b/lite/res/drawable/spinner_background_ab_transdroid.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/lite/res/drawable/tab_indicator_ab_transdroid.xml b/lite/res/drawable/tab_indicator_ab_transdroid.xml new file mode 100644 index 00000000..5345f3e5 --- /dev/null +++ b/lite/res/drawable/tab_indicator_ab_transdroid.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/lite/res/layout-v14/actionbar_progressitem.xml b/lite/res/layout-v14/actionbar_progressitem.xml new file mode 100644 index 00000000..f6dfd64a --- /dev/null +++ b/lite/res/layout-v14/actionbar_progressitem.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/lite/res/layout-w600dp/activity_torrents.xml b/lite/res/layout-w600dp/activity_torrents.xml new file mode 100644 index 00000000..da228be0 --- /dev/null +++ b/lite/res/layout-w600dp/activity_torrents.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/lite/res/layout-w720dp/activity_torrents.xml b/lite/res/layout-w720dp/activity_torrents.xml new file mode 100644 index 00000000..dfdbcad1 --- /dev/null +++ b/lite/res/layout-w720dp/activity_torrents.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/layout/actionbar_progressitem.xml b/lite/res/layout/actionbar_progressitem.xml new file mode 100644 index 00000000..5123e8df --- /dev/null +++ b/lite/res/layout/actionbar_progressitem.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/lite/res/layout/activity_details.xml b/lite/res/layout/activity_details.xml new file mode 100644 index 00000000..f1d8b7c8 --- /dev/null +++ b/lite/res/layout/activity_details.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/lite/res/layout/activity_torrents.xml b/lite/res/layout/activity_torrents.xml new file mode 100644 index 00000000..feb3cba7 --- /dev/null +++ b/lite/res/layout/activity_torrents.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/lite/res/layout/fragment_details.xml b/lite/res/layout/fragment_details.xml new file mode 100644 index 00000000..d627b53b --- /dev/null +++ b/lite/res/layout/fragment_details.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/lite/res/layout/fragment_details_header.xml b/lite/res/layout/fragment_details_header.xml new file mode 100644 index 00000000..141517d2 --- /dev/null +++ b/lite/res/layout/fragment_details_header.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/layout/fragment_filters.xml b/lite/res/layout/fragment_filters.xml new file mode 100644 index 00000000..9fa3c37a --- /dev/null +++ b/lite/res/layout/fragment_filters.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/lite/res/layout/fragment_torrents.xml b/lite/res/layout/fragment_torrents.xml new file mode 100644 index 00000000..869766dc --- /dev/null +++ b/lite/res/layout/fragment_torrents.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/layout/list_item_filter.xml b/lite/res/layout/list_item_filter.xml new file mode 100644 index 00000000..66f6ae72 --- /dev/null +++ b/lite/res/layout/list_item_filter.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/lite/res/layout/list_item_navigation.xml b/lite/res/layout/list_item_navigation.xml new file mode 100644 index 00000000..38868e27 --- /dev/null +++ b/lite/res/layout/list_item_navigation.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/lite/res/layout/list_item_separator.xml b/lite/res/layout/list_item_separator.xml new file mode 100644 index 00000000..65389da3 --- /dev/null +++ b/lite/res/layout/list_item_separator.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/lite/res/layout/list_item_torrent.xml b/lite/res/layout/list_item_torrent.xml new file mode 100644 index 00000000..c87f9742 --- /dev/null +++ b/lite/res/layout/list_item_torrent.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/lite/res/layout/list_item_torrentfile.xml b/lite/res/layout/list_item_torrentfile.xml new file mode 100644 index 00000000..b2e1bd4d --- /dev/null +++ b/lite/res/layout/list_item_torrentfile.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + diff --git a/lite/res/menu/activity_deleteableprefs.xml b/lite/res/menu/activity_deleteableprefs.xml new file mode 100644 index 00000000..ad353807 --- /dev/null +++ b/lite/res/menu/activity_deleteableprefs.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/lite/res/menu/activity_details.xml b/lite/res/menu/activity_details.xml new file mode 100644 index 00000000..d9ddd500 --- /dev/null +++ b/lite/res/menu/activity_details.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/lite/res/menu/activity_torrents.xml b/lite/res/menu/activity_torrents.xml new file mode 100644 index 00000000..0d086f85 --- /dev/null +++ b/lite/res/menu/activity_torrents.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/menu/fragment_details.xml b/lite/res/menu/fragment_details.xml new file mode 100644 index 00000000..5b32d61d --- /dev/null +++ b/lite/res/menu/fragment_details.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/menu/fragment_details_file.xml b/lite/res/menu/fragment_details_file.xml new file mode 100644 index 00000000..edc1931c --- /dev/null +++ b/lite/res/menu/fragment_details_file.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/menu/fragment_torrents_cab.xml b/lite/res/menu/fragment_torrents_cab.xml new file mode 100644 index 00000000..ae62819e --- /dev/null +++ b/lite/res/menu/fragment_torrents_cab.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/values-v11/styles_transdroid.xml b/lite/res/values-v11/styles_transdroid.xml new file mode 100644 index 00000000..d3671383 --- /dev/null +++ b/lite/res/values-v11/styles_transdroid.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/values/attrs.xml b/lite/res/values/attrs.xml new file mode 100644 index 00000000..a19eb9b9 --- /dev/null +++ b/lite/res/values/attrs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/lite/res/values/colors.xml b/lite/res/values/colors.xml new file mode 100644 index 00000000..f4cdf7fa --- /dev/null +++ b/lite/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #8acc12 + #7dbb21 + diff --git a/lite/res/values/colors_transdroid.xml b/lite/res/values/colors_transdroid.xml new file mode 100644 index 00000000..88ea9872 --- /dev/null +++ b/lite/res/values/colors_transdroid.xml @@ -0,0 +1,21 @@ + + + + + #CCaada62 + diff --git a/lite/res/values/dimens.xml b/lite/res/values/dimens.xml new file mode 100644 index 00000000..e6f730d4 --- /dev/null +++ b/lite/res/values/dimens.xml @@ -0,0 +1,6 @@ + + + 16dp + 8dp + + \ No newline at end of file diff --git a/lite/res/values/strings.xml b/lite/res/values/strings.xml new file mode 100644 index 00000000..734dca59 --- /dev/null +++ b/lite/res/values/strings.xml @@ -0,0 +1,262 @@ + + + + Transdroid + + Add + From file + From URL + Scan barcode + Search + Refresh + RSS + Enable turle mode + Disable turle mode + Sort list + Name + Status + Date done + Date added + Upload speed + Ratio + Filter list + Settings + Help + Start + Normal start + Force start + Stop + Resume + Pause + Remove + Remove torrent + Remove and delete data + Set label + Update trackers + Off + Low + Normal + High + Remote play in VLC + Download using (S)FTP + Remove settings + + Transdroid allows you to monitor and manage the torrent client you run at home or on your seedbox. Setting things up can be a bit tricky, but we offer step-by-step guides and promise it\'ll be worth it! + Connected, but no torrent are active within the current filter + Select a torrent to view its details + SERVERS + STATUS + LABELS + All + Downloading + Uploading + Active + Inactive + + Status: %1%s + Waiting to check… + Verifying local data… + Waiting to download %s + Error… + %1$s OF %2$s (%3$s) + %1$s, UPLOADED %2$s + SINCE $1%s + ~ %1$s + ETA %1$s + UNKNOWN ETA + RATIO %1$s + %1$s OF %2$s PEERS + ↑ %1$s + ↓ %1$s + Downloading + Seeding + Paused + Queued + Stopped + Unknown status + Not downloaded + Low priority + Normal priority + High priority + TRACKERS + ERRORS + FILES + + All labels + Unlabeled + New label + Setting a label is not supported by your client + + Torrent added (refreshing) + %1$s removed + %1$s removed and data deleted + %1$s resumed (refreshing) + %1$s stopped + %1$s started (refreshing) + %1$s paused + Torrents paused + Torrents resumed (refreshing) + Torrents stopped + Torrents started (refreshing) + Trackers updated + Label set to \'%1$s\' + Torrent moved to \'%1$s\' + + Torrent search + Search for torrents + + + Servers + Add new server + Search sites + Set default site + Add web search site + RSS feeds + Add RSS feed + Other settings + + Name + Optional personal name + Direct search URL + %s will be replaced by the search query + Feed URL + Requires authentication + Opens links in the webbrowser for user login + + Server type + IP or host name + Port number + User name + Password + Deluge web password + Advanced settings + Local IP or host + When connected to the specified local network + Local network + The server\'s local (wifi) network + Folder + Usually empty + SCGI mount point + Optional settings + Finished notification + Notify when a torrent finishes + New torrent notification + Nofity when a torrent was added + Server OS + Download directory + Manually set absolute path for remote connections + Connection timeout + Number of seconds before a connection attempt is aborted + Base (S)FTP url + For example ftp://me@server/downloads/ + (S)FTP password + Use SSL + Connect using https + Custom SSL thumbprint (SHA-1) + Permit only connections to this specific certificate + Accept all SSL certificates + Allow all connections from any thumbprint + + Background notifications + Enable notifications + Enables the background service + Interval + How often to check the server + Sound + Vibrate + LED colour + If supported by your device + Support AWD notifications + Show torrent counter in ADW Launcher + + System + Check for updates + Check transdroidorg for latest app version + Import settings + Export settings + Send error log + Get support or report a bug + View install guides + Recent changes + About + + Error during communication; check your connection + Internal error building request + Error parsing server response (please check your settings) + Web interface not connected to a running daemon + Access denied (please check your settings) + Can\'t read .torrent file + Error while parsing the RSS feed + This URL is not well-formed + Your web search URL is invalid: + Input is not a valid IP address or host name + Port number is always numeric + Directory paths end with a / or \ + Timeout can not be empty and is a positive number + The RSS feed item didn\'t provide an URL enclosure or link tag pointing to the .torrent file + The RSS feed item does not provide a link to browse to + URL is not a (valid) RSS feed + SD card not available to read/write + File does not seem to contain Transdroid settings + There is no settings file found + + + BitComet + Bitflu 1.2+ + BitTorrent 6+ + Buffalo NAS -1.31 + Deluge 1.2+ + DLink Router BT + Ktorrent + qBittorrent + rTorrent + Torrentflux-b4rt + Transmission + µTorrent + Vuze + + + daemon_bitcomet + daemon_bitflu + daemon_bittorrent + daemon_buffalonas + daemon_deluge + daemon_dlinkrouterbt + daemon_ktorrent + daemon_qbittorrent + daemon_rtorrent + daemon_tfb4rt + daemon_transmission + daemon_utorrent + daemon_vuze + + + Windows + Mac + Linux + + + type_windows + type_mac + type_linux + + + 1 minute + 10 minutes + 30 minutes + 1 hour + 3 hours + 12 hours + 1 day + + + 60 + 600 + 1800 + 3600 + 10800 + 43200 + 86400 + + + \ No newline at end of file diff --git a/lite/res/values/styles.xml b/lite/res/values/styles.xml new file mode 100644 index 00000000..9daedc4b --- /dev/null +++ b/lite/res/values/styles.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/values/styles_transdroid.xml b/lite/res/values/styles_transdroid.xml new file mode 100644 index 00000000..98e8c09c --- /dev/null +++ b/lite/res/values/styles_transdroid.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/xml/pref_main.xml b/lite/res/xml/pref_main.xml new file mode 100644 index 00000000..09c61a50 --- /dev/null +++ b/lite/res/xml/pref_main.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/xml/pref_notifications.xml b/lite/res/xml/pref_notifications.xml new file mode 100644 index 00000000..972a477e --- /dev/null +++ b/lite/res/xml/pref_notifications.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/xml/pref_rssfeed.xml b/lite/res/xml/pref_rssfeed.xml new file mode 100644 index 00000000..401c5aea --- /dev/null +++ b/lite/res/xml/pref_rssfeed.xml @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/xml/pref_server.xml b/lite/res/xml/pref_server.xml new file mode 100644 index 00000000..c34556f7 --- /dev/null +++ b/lite/res/xml/pref_server.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/xml/pref_system.xml b/lite/res/xml/pref_system.xml new file mode 100644 index 00000000..8968dc62 --- /dev/null +++ b/lite/res/xml/pref_system.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lite/res/xml/pref_websearch.xml b/lite/res/xml/pref_websearch.xml new file mode 100644 index 00000000..f2efdec1 --- /dev/null +++ b/lite/res/xml/pref_websearch.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/lite/res/xml/searchable.xml b/lite/res/xml/searchable.xml new file mode 100644 index 00000000..9f811c8c --- /dev/null +++ b/lite/res/xml/searchable.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/lite/src/com/actionbarsherlock/view/SherlockListView.java b/lite/src/com/actionbarsherlock/view/SherlockListView.java new file mode 100644 index 00000000..3b2df828 --- /dev/null +++ b/lite/src/com/actionbarsherlock/view/SherlockListView.java @@ -0,0 +1,337 @@ +package com.actionbarsherlock.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.util.AttributeSet; +import android.util.SparseBooleanArray; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Checkable; +import android.widget.ListView; + +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + +/** + * Provides backwards compatible multiple choice ActionMode support on Froyo+ using ActionBarSherlock. + */ +public class SherlockListView extends ListView { + // API 11+ reference, but ok because the value will be inlined. + public static final int CHOICE_MODE_MULTIPLE_MODAL_COMPAT = CHOICE_MODE_MULTIPLE_MODAL; + + /** + * Wrapper to intercept delegation of long click events, and pass to {@link #doLongPress} + */ + class OnItemLongClickListenerWrapper implements OnItemLongClickListener { + private OnItemLongClickListener wrapped; + + public void setWrapped(OnItemLongClickListener listener) { + this.wrapped = listener; + } + + @Override + public boolean onItemLongClick(AdapterView view, View child, int position, long id) { + // this would be easier if AbsListView.performLongPress wasn't package + // protected :-( + boolean handled = doLongPress(child, position, id); + if (!handled && wrapped != null) { + return wrapped.onItemLongClick(view, child, position, id); + } + return true; + } + } + + /** + * Hijack the onLongClickListener so we can intercept delegation. + */ + @Override + public void setOnItemLongClickListener(OnItemLongClickListener listener) { + if (longClickListenerWrapper == null) { + longClickListenerWrapper = new OnItemLongClickListenerWrapper(); + } + longClickListenerWrapper.setWrapped(listener); + super.setOnItemLongClickListener(longClickListenerWrapper); + } + + /** + * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}. It acts as the + * {@link ActionMode.Callback} for the selection mode and also receives + * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user selects and deselects + * list items. + */ + @SuppressWarnings("javadoc") + public interface MultiChoiceModeListenerCompat extends ActionMode.Callback { + /** + * Called when an item is checked or unchecked during selection mode. + * @param mode The {@link ActionMode} providing the selection mode + * @param position Adapter position of the item that was checked or unchecked + * @param id Adapter ID of the item that was checked or unchecked + * @param checked true if the item is now checked, false if the item is now unchecked. + */ + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked); + } + + class MultiChoiceModeWrapper implements MultiChoiceModeListenerCompat { + private MultiChoiceModeListenerCompat wrapped; + + public void setWrapped(MultiChoiceModeListenerCompat wrapped) { + this.wrapped = wrapped; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + if (wrapped == null) { + return false; + } + if (wrapped.onCreateActionMode(mode, menu)) { + // Initialize checked graphic state? + setLongClickable(false); + return true; + } + return false; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + if (wrapped == null) { + return false; + } + return wrapped.onPrepareActionMode(mode, menu); + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (wrapped == null) { + return false; + } + return wrapped.onActionItemClicked(mode, item); + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + if (wrapped == null) { + return; + } + wrapped.onDestroyActionMode(mode); + actionMode = null; + + // Ending selection mode means deselecting everything. + clearChoices(); + checkedItemCount = 0; + updateOnScreenCheckedViews(); + invalidateViews(); + setLongClickable(true); + requestLayout(); + invalidate(); + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { + if (wrapped == null) { + return; + } + wrapped.onItemCheckedStateChanged(mode, position, id, checked); + + // If there are no items selected we no longer need the selection mode. + if (checkedItemCount == 0) { + mode.finish(); + } + } + } + + private com.actionbarsherlock.view.ActionMode actionMode; + private OnItemLongClickListenerWrapper longClickListenerWrapper; + private MultiChoiceModeWrapper choiceModeListener; + private int choiceMode; + private int checkedItemCount; + + public SherlockListView(Context context) { + super(context); + init(context); + } + + public SherlockListView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public SherlockListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + void init(Context context) { + if (isInEditMode()) { + // Ignore when viewing in the UI designer + return; + } + if (!(context instanceof SherlockActivity || context instanceof SherlockFragmentActivity)) { + throw new IllegalStateException( + "This view must be hosted in a SherlockActivity or SherlockFragmentActivity"); + } + setOnItemLongClickListener(null); + } + + @Override + public void setChoiceMode(int mode) { + choiceMode = mode; + if (actionMode != null) { + actionMode.finish(); + actionMode = null; + } + if (choiceMode != CHOICE_MODE_NONE) { + if (mode == CHOICE_MODE_MULTIPLE_MODAL_COMPAT) { + clearChoices(); + checkedItemCount = 0; + setLongClickable(true); + updateOnScreenCheckedViews(); + requestLayout(); + invalidate(); + mode = CHOICE_MODE_MULTIPLE; + } + super.setChoiceMode(mode); + } + } + + @Override + public int getChoiceMode() { + return choiceMode; + } + + public void setMultiChoiceModeListener(MultiChoiceModeListenerCompat listener) { + if (choiceModeListener == null) { + choiceModeListener = new MultiChoiceModeWrapper(); + } + choiceModeListener.setWrapped(listener); + } + + @Override + public boolean performItemClick(View view, int position, long id) { + boolean handled = false; + boolean dispatchItemClick = true; + boolean checkStateChanged = false; + if (choiceMode != CHOICE_MODE_NONE) { + handled = true; + if (choiceMode == CHOICE_MODE_MULTIPLE + || (choiceMode == CHOICE_MODE_MULTIPLE_MODAL_COMPAT && actionMode != null)) { + boolean newValue = !getCheckedItemPositions().get(position); + setItemChecked(position, newValue); + if (actionMode != null) { + choiceModeListener.onItemCheckedStateChanged(actionMode, position, id, newValue); + dispatchItemClick = false; + } + checkStateChanged = true; + return false; + } else if (choiceMode == CHOICE_MODE_SINGLE) { + boolean newValue = !getCheckedItemPositions().get(position); + setItemChecked(position, newValue); + checkStateChanged = true; + } + if (checkStateChanged) { + updateOnScreenCheckedViews(); + } + } + if (dispatchItemClick) { + handled |= super.performItemClick(view, position, id); + } + return handled; + } + + /** + * Perform a quick, in-place update of the checked or activated state on all visible item views. This should only be + * called when a valid choice mode is active. + *

+ * (Taken verbatim from AbsListView.java) + */ + @TargetApi(11) + private void updateOnScreenCheckedViews() { + final int firstPos = getFirstVisiblePosition(); + final int count = getChildCount(); + final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB; + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + final int position = firstPos + i; + + if (child instanceof Checkable) { + ((Checkable) child).setChecked(getCheckedItemPositions().get(position)); + } else if (useActivated) { + child.setActivated(getCheckedItemPositions().get(position)); + } + } + } + + public ActionMode startActionMode(ActionMode.Callback callback) { + if (actionMode != null) { + return actionMode; + } + Context context = getContext(); + if (context instanceof SherlockActivity) { + actionMode = ((SherlockActivity) getContext()).startActionMode(callback); + } else if (context instanceof SherlockFragmentActivity) { + actionMode = ((SherlockFragmentActivity) context).startActionMode(callback); + } else { + throw new IllegalStateException( + "This view must be hosted in a SherlockActivity or SherlockFragmentActivity"); + } + return actionMode; + } + + boolean doLongPress(final View child, final int longPressPosition, final long longPressId) { + if (choiceMode == CHOICE_MODE_MULTIPLE_MODAL_COMPAT) { + if (actionMode == null && (actionMode = startActionMode(choiceModeListener)) != null) { + setItemChecked(longPressPosition, true); + } + return true; + } + return false; + } + + /** + * Sets the checked state of the specified position. The is only valid if the choice mode has been set to + * {@link #CHOICE_MODE_SINGLE} or {@link #CHOICE_MODE_MULTIPLE}. + * @param position The item whose checked state is to be checked + * @param value The new checked state for the item + */ + @Override + public void setItemChecked(int position, boolean value) { + if (choiceMode == CHOICE_MODE_NONE) { + return; + } + SparseBooleanArray checkStates = getCheckedItemPositions(); + + // Start selection mode if needed. We don't need to if we're unchecking + // something. + if (value && choiceMode == CHOICE_MODE_MULTIPLE_MODAL_COMPAT && actionMode == null) { + actionMode = startActionMode(choiceModeListener); + } + + if (choiceMode == CHOICE_MODE_MULTIPLE || choiceMode == CHOICE_MODE_MULTIPLE_MODAL) { + // boolean oldValue = checkStates.get(position); + checkStates.put(position, value); + if (value) { + checkedItemCount++; + } else { + checkedItemCount--; + } + if (actionMode != null) { + final long id = getAdapter().getItemId(position); + choiceModeListener.onItemCheckedStateChanged(actionMode, position, id, value); + } + } else { + if (value || isItemChecked(position)) { + checkStates.clear(); + } + // this may end up selecting the value we just cleared but this way + // we ensure length of checkStates is 1, a fact getCheckedItemPosition + // relies on + if (value) { + checkStates.put(position, true); + } + } + requestLayout(); + invalidate(); + } +} diff --git a/lite/src/com/commonsware/cwac/merge/MergeAdapter.java b/lite/src/com/commonsware/cwac/merge/MergeAdapter.java new file mode 100644 index 00000000..a713b862 --- /dev/null +++ b/lite/src/com/commonsware/cwac/merge/MergeAdapter.java @@ -0,0 +1,481 @@ +/*** + Copyright (c) 2008-2009 CommonsWare, LLC + Portions (c) 2009 Google, Inc. + + 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 com.commonsware.cwac.merge; + +import android.database.DataSetObserver; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListAdapter; +import android.widget.SectionIndexer; +import java.util.ArrayList; +import java.util.List; +import com.commonsware.cwac.sacklist.SackOfViewsAdapter; + +/** + * Adapter that merges multiple child adapters and views + * into a single contiguous whole. + * + * Adapters used as pieces within MergeAdapter must have + * view type IDs monotonically increasing from 0. Ideally, + * adapters also have distinct ranges for their row ids, as + * returned by getItemId(). + * + */ +public class MergeAdapter extends BaseAdapter implements SectionIndexer { + protected PieceStateRoster pieces=new PieceStateRoster(); + + /** + * Stock constructor, simply chaining to the superclass. + */ + public MergeAdapter() { + super(); + } + + /** + * Adds a new adapter to the roster of things to appear in + * the aggregate list. + * + * @param adapter + * Source for row views for this section + */ + public void addAdapter(ListAdapter adapter) { + pieces.add(adapter); + adapter.registerDataSetObserver(new CascadeDataSetObserver()); + } + + /** + * Adds a new View to the roster of things to appear in + * the aggregate list. + * + * @param view + * Single view to add + */ + public void addView(View view) { + addView(view, false); + } + + /** + * Adds a new View to the roster of things to appear in + * the aggregate list. + * + * @param view + * Single view to add + * @param enabled + * false if views are disabled, true if enabled + */ + public void addView(View view, boolean enabled) { + ArrayList list=new ArrayList(1); + + list.add(view); + + addViews(list, enabled); + } + + /** + * Adds a list of views to the roster of things to appear + * in the aggregate list. + * + * @param views + * List of views to add + */ + public void addViews(List views) { + addViews(views, false); + } + + /** + * Adds a list of views to the roster of things to appear + * in the aggregate list. + * + * @param views + * List of views to add + * @param enabled + * false if views are disabled, true if enabled + */ + public void addViews(List views, boolean enabled) { + if (enabled) { + addAdapter(new EnabledSackAdapter(views)); + } + else { + addAdapter(new SackOfViewsAdapter(views)); + } + } + + /** + * Get the data item associated with the specified + * position in the data set. + * + * @param position + * Position of the item whose data we want + */ + @Override + public Object getItem(int position) { + for (ListAdapter piece : getPieces()) { + int size=piece.getCount(); + + if (position < size) { + return(piece.getItem(position)); + } + + position-=size; + } + + return(null); + } + + /** + * Get the adapter associated with the specified position + * in the data set. + * + * @param position + * Position of the item whose adapter we want + */ + public ListAdapter getAdapter(int position) { + for (ListAdapter piece : getPieces()) { + int size=piece.getCount(); + + if (position < size) { + return(piece); + } + + position-=size; + } + + return(null); + } + + /** + * How many items are in the data set represented by this + * Adapter. + */ + @Override + public int getCount() { + int total=0; + + for (ListAdapter piece : getPieces()) { + total+=piece.getCount(); + } + + return(total); + } + + /** + * Returns the number of types of Views that will be + * created by getView(). + */ + @Override + public int getViewTypeCount() { + int total=0; + + for (PieceState piece : pieces.getRawPieces()) { + total+=piece.adapter.getViewTypeCount(); + } + + return(Math.max(total, 1)); // needed for + // setListAdapter() before + // content add' + } + + /** + * Get the type of View that will be created by getView() + * for the specified item. + * + * @param position + * Position of the item whose data we want + */ + @Override + public int getItemViewType(int position) { + int typeOffset=0; + int result=-1; + + for (PieceState piece : pieces.getRawPieces()) { + if (piece.isActive) { + int size=piece.adapter.getCount(); + + if (position < size) { + result=typeOffset + piece.adapter.getItemViewType(position); + break; + } + + position-=size; + } + + typeOffset+=piece.adapter.getViewTypeCount(); + } + + return(result); + } + + /** + * Are all items in this ListAdapter enabled? If yes it + * means all items are selectable and clickable. + */ + @Override + public boolean areAllItemsEnabled() { + return(false); + } + + /** + * Returns true if the item at the specified position is + * not a separator. + * + * @param position + * Position of the item whose data we want + */ + @Override + public boolean isEnabled(int position) { + for (ListAdapter piece : getPieces()) { + int size=piece.getCount(); + + if (position < size) { + return(piece.isEnabled(position)); + } + + position-=size; + } + + return(false); + } + + /** + * Get a View that displays the data at the specified + * position in the data set. + * + * @param position + * Position of the item whose data we want + * @param convertView + * View to recycle, if not null + * @param parent + * ViewGroup containing the returned View + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + for (ListAdapter piece : getPieces()) { + int size=piece.getCount(); + + if (position < size) { + + return(piece.getView(position, convertView, parent)); + } + + position-=size; + } + + return(null); + } + + /** + * Get the row id associated with the specified position + * in the list. + * + * @param position + * Position of the item whose data we want + */ + @Override + public long getItemId(int position) { + for (ListAdapter piece : getPieces()) { + int size=piece.getCount(); + + if (position < size) { + return(piece.getItemId(position)); + } + + position-=size; + } + + return(-1); + } + + @Override + public int getPositionForSection(int section) { + int position=0; + + for (ListAdapter piece : getPieces()) { + if (piece instanceof SectionIndexer) { + Object[] sections=((SectionIndexer)piece).getSections(); + int numSections=0; + + if (sections != null) { + numSections=sections.length; + } + + if (section < numSections) { + return(position + ((SectionIndexer)piece).getPositionForSection(section)); + } + else if (sections != null) { + section-=numSections; + } + } + + position+=piece.getCount(); + } + + return(0); + } + + @Override + public int getSectionForPosition(int position) { + int section=0; + + for (ListAdapter piece : getPieces()) { + int size=piece.getCount(); + + if (position < size) { + if (piece instanceof SectionIndexer) { + return(section + ((SectionIndexer)piece).getSectionForPosition(position)); + } + + return(0); + } + else { + if (piece instanceof SectionIndexer) { + Object[] sections=((SectionIndexer)piece).getSections(); + + if (sections != null) { + section+=sections.length; + } + } + } + + position-=size; + } + + return(0); + } + + @Override + public Object[] getSections() { + ArrayList sections=new ArrayList(); + + for (ListAdapter piece : getPieces()) { + if (piece instanceof SectionIndexer) { + Object[] curSections=((SectionIndexer)piece).getSections(); + + if (curSections != null) { + for (Object section : curSections) { + sections.add(section); + } + } + } + } + + if (sections.size() == 0) { + return(new String[0]); + } + + return(sections.toArray(new Object[0])); + } + + public void setActive(ListAdapter adapter, boolean isActive) { + pieces.setActive(adapter, isActive); + notifyDataSetChanged(); + } + + public void setActive(View v, boolean isActive) { + pieces.setActive(v, isActive); + notifyDataSetChanged(); + } + + protected List getPieces() { + return(pieces.getPieces()); + } + + private static class PieceState { + ListAdapter adapter; + boolean isActive=true; + + PieceState(ListAdapter adapter, boolean isActive) { + this.adapter=adapter; + this.isActive=isActive; + } + } + + private static class PieceStateRoster { + protected ArrayList pieces=new ArrayList(); + protected ArrayList active=null; + + void add(ListAdapter adapter) { + pieces.add(new PieceState(adapter, true)); + } + + void setActive(ListAdapter adapter, boolean isActive) { + for (PieceState state : pieces) { + if (state.adapter==adapter) { + state.isActive=isActive; + active=null; + break; + } + } + } + + void setActive(View v, boolean isActive) { + for (PieceState state : pieces) { + if (state.adapter instanceof SackOfViewsAdapter && + ((SackOfViewsAdapter)state.adapter).hasView(v)) { + state.isActive=isActive; + active=null; + break; + } + } + } + + List getRawPieces() { + return(pieces); + } + + List getPieces() { + if (active == null) { + active=new ArrayList(); + + for (PieceState state : pieces) { + if (state.isActive) { + active.add(state.adapter); + } + } + } + + return(active); + } + } + + private static class EnabledSackAdapter extends SackOfViewsAdapter { + public EnabledSackAdapter(List views) { + super(views); + } + + @Override + public boolean areAllItemsEnabled() { + return(true); + } + + @Override + public boolean isEnabled(int position) { + return(true); + } + } + + private class CascadeDataSetObserver extends DataSetObserver { + @Override + public void onChanged() { + notifyDataSetChanged(); + } + + @Override + public void onInvalidated() { + notifyDataSetInvalidated(); + } + } +} \ No newline at end of file diff --git a/lite/src/com/commonsware/cwac/sacklist/SackOfViewsAdapter.java b/lite/src/com/commonsware/cwac/sacklist/SackOfViewsAdapter.java new file mode 100644 index 00000000..2d248e58 --- /dev/null +++ b/lite/src/com/commonsware/cwac/sacklist/SackOfViewsAdapter.java @@ -0,0 +1,177 @@ +/*** + Copyright (c) 2008-2009 CommonsWare, LLC + Portions (c) 2009 Google, Inc. + + 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 com.commonsware.cwac.sacklist; + +import java.util.ArrayList; +import java.util.List; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +/** + * Adapter that simply returns row views from a list. + * + * If you supply a size, you must implement newView(), to + * create a required view. The adapter will then cache these + * views. + * + * If you supply a list of views in the constructor, that + * list will be used directly. If any elements in the list + * are null, then newView() will be called just for those + * slots. + * + * Subclasses may also wish to override areAllItemsEnabled() + * (default: false) and isEnabled() (default: false), if some + * of their rows should be selectable. + * + * It is assumed each view is unique, and therefore will not + * get recycled. + * + * Note that this adapter is not designed for long lists. It + * is more for screens that should behave like a list. This + * is particularly useful if you combine this with other + * adapters (e.g., SectionedAdapter) that might have an + * arbitrary number of rows, so it all appears seamless. + */ +public class SackOfViewsAdapter extends BaseAdapter { + private List views=null; + + /** + * Constructor creating an empty list of views, but with + * a specified count. Subclasses must override newView(). + */ + public SackOfViewsAdapter(int count) { + super(); + + views=new ArrayList(count); + + for (int i=0;i views) { + super(); + + this.views=views; + } + + /** + * Get the data item associated with the specified + * position in the data set. + * @param position Position of the item whose data we want + */ + @Override + public Object getItem(int position) { + return(views.get(position)); + } + + /** + * How many items are in the data set represented by this + * Adapter. + */ + @Override + public int getCount() { + return(views.size()); + } + + /** + * Returns the number of types of Views that will be + * created by getView(). + */ + @Override + public int getViewTypeCount() { + return(getCount()); + } + + /** + * Get the type of View that will be created by getView() + * for the specified item. + * @param position Position of the item whose data we want + */ + @Override + public int getItemViewType(int position) { + return(position); + } + + /** + * Are all items in this ListAdapter enabled? If yes it + * means all items are selectable and clickable. + */ + @Override + public boolean areAllItemsEnabled() { + return(false); + } + + /** + * Returns true if the item at the specified position is + * not a separator. + * @param position Position of the item whose data we want + */ + @Override + public boolean isEnabled(int position) { + return(false); + } + + /** + * Get a View that displays the data at the specified + * position in the data set. + * @param position Position of the item whose data we want + * @param convertView View to recycle, if not null + * @param parent ViewGroup containing the returned View + */ + @Override + public View getView(int position, View convertView, + ViewGroup parent) { + View result=views.get(position); + + if (result==null) { + result=newView(position, parent); + views.set(position, result); + } + + return(result); + } + + /** + * Get the row id associated with the specified position + * in the list. + * @param position Position of the item whose data we want + */ + @Override + public long getItemId(int position) { + return(position); + } + + public boolean hasView(View v) { + return(views.contains(v)); + } + + /** + * Create a new View to go into the list at the specified + * position. + * @param position Position of the item whose data we want + * @param parent ViewGroup containing the returned View + */ + protected View newView(int position, ViewGroup parent) { + throw new RuntimeException("You must override newView()!"); + } +} \ No newline at end of file diff --git a/lite/src/fr/marvinlabs/widget/CheckableRelativeLayout.java b/lite/src/fr/marvinlabs/widget/CheckableRelativeLayout.java new file mode 100644 index 00000000..91932561 --- /dev/null +++ b/lite/src/fr/marvinlabs/widget/CheckableRelativeLayout.java @@ -0,0 +1,100 @@ +package fr.marvinlabs.widget; + +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Checkable; +import android.widget.RelativeLayout; + +/** + * Extension of a relative layout to provide a checkable behaviour + * + * @author marvinlabs + */ +public class CheckableRelativeLayout extends RelativeLayout implements Checkable { + + private boolean isChecked; + private List checkableViews; + + public CheckableRelativeLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialise(attrs); + } + + public CheckableRelativeLayout(Context context, AttributeSet attrs) { + super(context, attrs); + initialise(attrs); + } + + public CheckableRelativeLayout(Context context) { + super(context); + initialise(null); + } + + /* + * @see android.widget.Checkable#isChecked() + */ + public boolean isChecked() { + return isChecked; + } + + /* + * @see android.widget.Checkable#setChecked(boolean) + */ + public void setChecked(boolean isChecked) { + this.isChecked = isChecked; + for (Checkable c : checkableViews) { + c.setChecked(isChecked); + } + } + + /* + * @see android.widget.Checkable#toggle() + */ + public void toggle() { + this.isChecked = !this.isChecked; + for (Checkable c : checkableViews) { + c.toggle(); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + final int childCount = this.getChildCount(); + for (int i = 0; i < childCount; ++i) { + findCheckableChildren(this.getChildAt(i)); + } + } + + /** + * Read the custom XML attributes + */ + private void initialise(AttributeSet attrs) { + this.isChecked = false; + this.checkableViews = new ArrayList(5); + } + + /** + * Add to our checkable list all the children of the view that implement the + * interface Checkable + */ + private void findCheckableChildren(View v) { + if (v instanceof Checkable) { + this.checkableViews.add((Checkable) v); + } + + if (v instanceof ViewGroup) { + final ViewGroup vg = (ViewGroup) v; + final int childCount = vg.getChildCount(); + for (int i = 0; i < childCount; ++i) { + findCheckableChildren(vg.getChildAt(i)); + } + } + } +} diff --git a/lite/src/fr/marvinlabs/widget/InertCheckBox.java b/lite/src/fr/marvinlabs/widget/InertCheckBox.java new file mode 100644 index 00000000..5dd3080b --- /dev/null +++ b/lite/src/fr/marvinlabs/widget/InertCheckBox.java @@ -0,0 +1,70 @@ +package fr.marvinlabs.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.widget.CheckBox; + +/** + * CheckBox that does not react to any user event in order to let the container handle them. + */ +public class InertCheckBox extends CheckBox { + + // Provide the same constructors as the superclass + public InertCheckBox(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + // Provide the same constructors as the superclass + public InertCheckBox(Context context, AttributeSet attrs) { + super(context, attrs); + } + + // Provide the same constructors as the superclass + public InertCheckBox(Context context) { + super(context); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + // Make the checkbox not respond to any user event + return false; + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + // Make the checkbox not respond to any user event + return false; + } +} diff --git a/lite/src/org/transdroid/core/app/search/SearchHelper.java b/lite/src/org/transdroid/core/app/search/SearchHelper.java new file mode 100644 index 00000000..b72cb7b8 --- /dev/null +++ b/lite/src/org/transdroid/core/app/search/SearchHelper.java @@ -0,0 +1,109 @@ +package org.transdroid.core.app.search; + +import java.util.ArrayList; +import java.util.List; + +import org.androidannotations.annotations.EBean; +import org.androidannotations.annotations.EBean.Scope; +import org.androidannotations.annotations.RootContext; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; + +@EBean(scope = Scope.Singleton) +public class SearchHelper { + + static final int CURSOR_SEARCH_ID = 0; + static final int CURSOR_SEARCH_NAME = 1; + static final int CURSOR_SEARCH_TORRENTURL = 2; + static final int CURSOR_SEARCH_DETAILSURL = 3; + static final int CURSOR_SEARCH_SIZE = 4; + static final int CURSOR_SEARCH_ADDED = 5; + static final int CURSOR_SEARCH_SEEDERS = 6; + static final int CURSOR_SEARCH_LEECHERS = 7; + + static final int CURSOR_SITE_ID = 0; + static final int CURSOR_SITE_CODE = 1; + static final int CURSOR_SITE_NAME = 2; + static final int CURSOR_SITE_RSSURL = 3; + + @RootContext + protected Context context; + + public enum SearchSortOrder { + Combined, BySeeders + } + + /** + * Return whether the Torrent Search package is installed and available to query against + * @return True if the available sites can be retrieved from the content provider, false otherwise + */ + public boolean isTorrentSearchInstalled() { + return getAvailableSites() != null; + } + + /** + * Queries the Torrent Search package for all available in-app search sites. This method is synchronous. + * @return A list of available search sites as POJOs, or null if the Torrent Search package is not installed + */ + public List getAvailableSites() { + + // Try to access the TorrentSitesProvider to retrieve all available in-app torrent search sites + Uri uri = Uri.parse("content://org.transdroid.search.torrentsitesprovider/sites"); + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + if (cursor.moveToFirst()) { + List sites = new ArrayList(); + do { + // Read the cursor fields into the SearchSite object + sites.add(new SearchSite(cursor.getInt(CURSOR_SITE_ID), cursor.getString(CURSOR_SITE_CODE), cursor + .getString(CURSOR_SITE_NAME), cursor.getString(CURSOR_SITE_RSSURL))); + } while (cursor.moveToNext()); + cursor.close(); + return sites; + } + + // Torrent Search package is not yet installed + return null; + + } + + /** + * Queries the Torrent Search module to search for torrents on the web. This method is synchornous and should always + * be called in a background thread. + * @param query The search query to pass to the torrent site + * @param site The site to search, as retrieved from the TorrentSitesProvider, or null if the Torrent Search package + * @param sortBy.name() The sort order to request from the torrent site, if supported + * @return A list of torrent search results as POJOs, or null if the Torrent Search package is not installed + */ + public List search(String query, SearchSite site, SearchSortOrder sortBy) { + + // Try to query the TorrentSearchProvider to search for torrents on the web + Uri uri = Uri.parse("content://org.transdroid.search.torrentsearchprovider/search/" + query); + Cursor cursor; + if (site == null) { + // If no explicit site was supplied, rely on the Torrent Search package's default + cursor = context.getContentResolver().query(uri, null, null, null, sortBy.name()); + } else { + cursor = context.getContentResolver().query(uri, null, "SITE = ?", new String[] { site.getKey() }, + sortBy.name()); + } + if (cursor.moveToFirst()) { + List results = new ArrayList(); + do { + // Read the cursor fields into the SearchResult object + results.add(new SearchResult(cursor.getInt(CURSOR_SEARCH_ID), cursor.getString(CURSOR_SEARCH_NAME), + cursor.getString(CURSOR_SEARCH_TORRENTURL), cursor.getString(CURSOR_SEARCH_DETAILSURL), cursor + .getString(CURSOR_SEARCH_SIZE), cursor.getLong(CURSOR_SEARCH_ADDED), cursor + .getString(CURSOR_SEARCH_SEEDERS), cursor.getString(CURSOR_SEARCH_LEECHERS))); + } while (cursor.moveToNext()); + cursor.close(); + return results; + } + + // Torrent Search package is not yet installed + return null; + + } + +} diff --git a/lite/src/org/transdroid/core/app/search/SearchResult.java b/lite/src/org/transdroid/core/app/search/SearchResult.java new file mode 100644 index 00000000..6bbfaa76 --- /dev/null +++ b/lite/src/org/transdroid/core/app/search/SearchResult.java @@ -0,0 +1,64 @@ +package org.transdroid.core.app.search; + +import java.util.Date; + +/** + * Represents a search result as retrieved by querying the Torrent Search package. + * @author Eric Kok + */ +public class SearchResult { + + private final int id; + private final String name; + private final String torrentUrl; + private final String detailsUrl; + private final String size; + private final Date addedOn; + private final String seeders; + private final String leechers; + + public SearchResult(int id, String name, String torrentUrl, String detailsUrl, String size, long addedOnTime, + String seeders, String leechers) { + this.id = id; + this.name = name; + this.torrentUrl = torrentUrl; + this.detailsUrl = detailsUrl; + this.size = size; + this.addedOn = (addedOnTime == -1L) ? null : new Date(addedOnTime); + this.seeders = seeders; + this.leechers = leechers; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public String getTorrentUrl() { + return torrentUrl; + } + + public String getDetailsUrl() { + return detailsUrl; + } + + public String getSize() { + return size; + } + + public Date getAddedOn() { + return addedOn; + } + + public String getSeeders() { + return seeders; + } + + public String getLeechers() { + return leechers; + } + +} diff --git a/lite/src/org/transdroid/core/app/search/SearchSite.java b/lite/src/org/transdroid/core/app/search/SearchSite.java new file mode 100644 index 00000000..a30ad09e --- /dev/null +++ b/lite/src/org/transdroid/core/app/search/SearchSite.java @@ -0,0 +1,40 @@ +package org.transdroid.core.app.search; + +import org.transdroid.core.gui.lists.SimpleListItem; + +/** + * Represents an available torrent site that can be searched using the Torrent Search package. + * @author Eric Kok + */ +public class SearchSite implements SimpleListItem { + + private final int id; + private final String key; + private final String name; + private final String rssFeedUrl; + + public SearchSite(int id, String key, String name, String rssFeedUrl) { + this.id = id; + this.key = key; + this.name = name; + this.rssFeedUrl = rssFeedUrl; + } + + public int getId() { + return id; + } + + public String getKey() { + return key; + } + + @Override + public String getName() { + return name; + } + + public String getRssFeedUrl() { + return rssFeedUrl; + } + +} diff --git a/lite/src/org/transdroid/core/app/settings/ApplicationSettings.java b/lite/src/org/transdroid/core/app/settings/ApplicationSettings.java new file mode 100644 index 00000000..9bd6d723 --- /dev/null +++ b/lite/src/org/transdroid/core/app/settings/ApplicationSettings.java @@ -0,0 +1,189 @@ +package org.transdroid.core.app.settings; + +import java.util.ArrayList; +import java.util.List; + +import org.androidannotations.annotations.EBean; +import org.androidannotations.annotations.EBean.Scope; +import org.androidannotations.annotations.RootContext; +import org.transdroid.daemon.Daemon; +import org.transdroid.daemon.OS; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +/** + * Singleton object to access all application settings, including stored servers, web search sites and RSS feeds. + * @author Eric Kok + */ +@EBean(scope = Scope.Singleton) +public class ApplicationSettings { + + @RootContext + protected Context context; + private SharedPreferences prefs; + + protected ApplicationSettings(Context context) { + prefs = PreferenceManager.getDefaultSharedPreferences(context); + } + + /** + * Returns all available user-configured servers + * @return A list of all stored server settings objects + */ + public List getServerSettings() { + List servers = new ArrayList(); + for (int i = 0; i <= getMaxServer(); i++) { + servers.add(getServerSetting(i)); + } + return servers; + } + + /** + * Returns the order number/identifying key of the last server + * @return The zero-based order number (index) of the last stored server settings + */ + public int getMaxServer() { + for (int i = 0; true; i++) { + if (prefs.getString("server_type_" + i, null) == null) + return i - 1; + } + } + + /** + * Returns the user-specified server settings for a specific server. WARNING: This method does not check if the + * settings actually exist and may rely on empty defaults if called not a non-existing server. + * @param order The order number/identifying key of the settings to retrieve + * @return The server settings object, loaded from shared preferences + */ + public ServerSetting getServerSetting(int order) { + return new ServerSetting(order, prefs.getString("server_name_" + order, null), Daemon.fromCode(prefs.getString( + "server_type_" + order, null)), prefs.getString("server_address_" + order, null), prefs.getString( + "server_localaddress_" + order, null), prefs.getString("server_localnetwork_" + order, null), + Integer.parseInt(prefs.getString("server_port_" + order, "-1")), prefs.getBoolean("server_sslenabled_" + + order, false), prefs.getBoolean("server_ssltrustall_" + order, false), prefs.getString( + "server_ssltrustkey_" + order, null), prefs.getString("server_folder_" + order, null), + prefs.getBoolean("server_useauth_" + order, true), prefs.getString("server_user_" + order, null), + prefs.getString("server_pass_" + order, null), prefs.getString("server_extrapass_" + order, null), + OS.fromCode(prefs.getString("server_os_" + order, null)), prefs.getString( + "server_downloaddir_" + order, null), prefs.getString("server_ftpurl_" + order, null), + prefs.getString("server_ftppass_" + order, null), prefs.getInt("server_timeout_" + order, 8), + prefs.getBoolean("server_alarmfinished_" + order, true), prefs.getBoolean("server_alarmnew_" + order, + false), false); + } + + /** + * Returns the settings of the server that was last used by the user. As opposed to getLastUsedServerKey(int), this + * method checks whether a server was already registered as being last used and check whether the server still + * exists. It returns the first server if that fails. If no servers are configured, null is returned. + * @return A server settings object of the last used server (or, if not known, the first server), or null if no + * servers exist + */ + public ServerSetting getLastUsedServer() { + int max = getMaxServer(); // Zero-based index, so with max == 0 there is 1 server + if (max < 0) { + // No servers configured + return null; + } + int last = getLastUsedServerKey(); + if (last < 0 || last > max) { + // Last server was never set or no longer exists + return getServerSetting(0); + } + return getServerSetting(last); + } + + /** + * Returns the order number/unique key of the server that the used last used; use with getServerSettings(int) or + * call getLastUsedServer directly. WARNING: the returned integer may no longer refer to a known server settings + * object: check the bounds. + * @return An integer indicating the order number/key or the last used server, or -1 if it was not set + */ + public int getLastUsedServerKey() { + return prefs.getInt("system_lastusedserver", -1); + } + + /** + * Registers some server as being the last used by the user + * @param server The settings of the server that the user last used + */ + public void setLastUsedServer(ServerSetting server) { + setLastUsedServerKey(server.getOrder()); + } + + /** + * Registers the order number/unique key of some server as being last used by the user + * @param order The key identifying the specific server + */ + public void setLastUsedServerKey(int order) { + prefs.edit().putInt("system_lastusedserver", order).commit(); + } + + /** + * Returns all available user-configured web-based (as opped to in-app) search sites + * @return A list of all stored web search site settings objects + */ + public List getWebsearchSettings() { + List websearches = new ArrayList(); + for (int i = 0; i <= getMaxWebsearch(); i++) { + websearches.add(getWebsearchSetting(i)); + } + return websearches; + } + + /** + * Returns the order number/identifying key of the last web search site + * @return The zero-based order number (index) of the last stored web search site + */ + public int getMaxWebsearch() { + for (int i = 0; true; i++) { + if (prefs.getString("websearch_url_" + i, null) == null) + return i - 1; + } + } + + /** + * Returns the user-specified web-based search site setting for a specific site + * @param order The order number/identifying key of the settings to retrieve + * @return The web search site settings object, loaded from shared preferences + */ + public WebsearchSetting getWebsearchSetting(int order) { + return new WebsearchSetting(order, prefs.getString("websearch_name_" + order, null), prefs.getString( + "websearch_url_" + order, null)); + } + + /** + * Returns all available user-configured RSS feeds + * @return A list of all stored RSS feed settings objects + */ + public List getRssfeedSettings() { + List rssfeeds = new ArrayList(); + for (int i = 0; i <= getMaxRssfeed(); i++) { + rssfeeds.add(getRssfeedSetting(i)); + } + return rssfeeds; + } + + /** + * Returns the order number/identifying key of the last stored RSS feed + * @return The zero-based order number (index) of the last stored RSS feed + */ + public int getMaxRssfeed() { + for (int i = 0; true; i++) { + if (prefs.getString("rssfeed_feedurl_" + i, null) == null) + return i - 1; + } + } + + /** + * Returns the user-specified RSS feed setting for a specific feed + * @param order The order number/identifying key of the settings to retrieve + * @return The RSS feed settings object, loaded from shared preferences + */ + public RssfeedSetting getRssfeedSetting(int order) { + return new RssfeedSetting(order, prefs.getString("rssfeed_name_" + order, null), prefs.getString( + "rssfeed_feedurl_" + order, null), prefs.getBoolean("rssfeed_reqauth_" + order, false)); + } + +} diff --git a/lite/src/org/transdroid/core/app/settings/NotificationSettings.java b/lite/src/org/transdroid/core/app/settings/NotificationSettings.java new file mode 100644 index 00000000..58bd4003 --- /dev/null +++ b/lite/src/org/transdroid/core/app/settings/NotificationSettings.java @@ -0,0 +1,97 @@ +package org.transdroid.core.app.settings; + +import org.androidannotations.annotations.EBean; +import org.androidannotations.annotations.EBean.Scope; +import org.androidannotations.annotations.RootContext; +import org.transdroid.core.R; + +import android.content.Context; +import android.content.SharedPreferences; +import android.net.Uri; +import android.preference.PreferenceManager; +import android.provider.Settings; + +/** + * Allows instantiation of the settings specified in R.xml.pref_notifications. + * @author Eric Kok + */ +@EBean(scope = Scope.Singleton) +public class NotificationSettings { + + @RootContext + protected Context context; + private SharedPreferences prefs; + + protected NotificationSettings(Context context) { + prefs = PreferenceManager.getDefaultSharedPreferences(context); + } + + /** + * Whether the background service is enabled, i.e. whether the user want to receive notifications + * @return True if the server should be checked for torrent status updates + */ + public boolean isEnabled() { + return prefs.getBoolean("notifications_enabled", true); + } + + private String getRawInverval() { + return prefs.getString("notifications_interval", "10800"); + } + + /** + * Returns the interval between two server checks + * @return The interval, in milliseconds + */ + public Long getInvervalInMilliseconds() { + return Long.parseLong(getRawInverval()) * 1000L; + } + + private String getRawSound() { + return prefs.getString("notifications_sound", null); + } + + /** + * Returns the sound (ring tone) to play on a new notification, or null if it should not play any + * @return Either the user-specified sound, null if the user specified 'Silent' or the system default notification sound + */ + public Uri getSound() { + String raw = getRawSound(); + if (raw == null) + return null; + if (raw.equals("")) + return Settings.System.DEFAULT_NOTIFICATION_URI; + return Uri.parse(raw); + } + + /** + * Whether the device should vibrate on a new notification + * @return + */ + public boolean shouldVibrate() { + return prefs.getBoolean("notifications_vibrate", false); + } + + private int getRawLedColour() { + return prefs.getInt("notifications_ledcolour", -1); + } + + /** + * Returns the LED colour to use on a new notification + * @return The integer value of the user-specified or default colour + */ + public int getDesiredLedColour() { + int raw = getRawLedColour(); + if (raw <= 0) + return context.getResources().getColor(R.color.ledgreen); + return raw; + } + + /** + * Whether the background service should report to ADW Launcher + * @return True if the user want Transdroid to report to ADW Launcher + */ + public boolean shouldReportToAdwLauncher() { + return prefs.getBoolean("notifications_adwnotify", false); + } + +} diff --git a/lite/src/org/transdroid/core/app/settings/RssfeedSetting.java b/lite/src/org/transdroid/core/app/settings/RssfeedSetting.java new file mode 100644 index 00000000..b964cfb2 --- /dev/null +++ b/lite/src/org/transdroid/core/app/settings/RssfeedSetting.java @@ -0,0 +1,67 @@ +package org.transdroid.core.app.settings; + +import org.transdroid.core.gui.lists.SimpleListItem; + +import android.net.Uri; +import android.text.TextUtils; + +/** + * Represents a user-specified RSS feed. + * @author Eric Kok + */ +public class RssfeedSetting implements SimpleListItem { + + private static final String DEFAULT_NAME = "Default"; + + private final int order; + private final String name; + private final String url; + private final boolean requiresAuth; + private String lastNew; + + public RssfeedSetting(int order, String name, String baseUrl, boolean needsAuth) { + this.order = order; + this.name = name; + this.url = baseUrl; + this.requiresAuth = needsAuth; + this.lastNew = null; + } + + public int getOrder() { + return order; + } + + @Override + public String getName() { + if (!TextUtils.isEmpty(name)) + return name; + if (!TextUtils.isEmpty(url)) + return Uri.parse(url).getHost(); + return DEFAULT_NAME; + } + + public String getUrl() { + return url; + } + + public boolean requiresExternalAuthentication() { + return requiresAuth; + } + + /** + * Returns the URL of the item that was the newest last time we checked this feed + * @return The last new item's URL as URL-encoded string + */ + public String getLastNew() { + return this.lastNew; + } + + /** + * Record the URL of what is now the last item we retrieved + * @param lastNew The URL of the last new item as URL-encoded string + */ + public void setLastNew(String lastNew) { + this.lastNew = lastNew; + } + +} diff --git a/lite/src/org/transdroid/core/app/settings/ServerSetting.java b/lite/src/org/transdroid/core/app/settings/ServerSetting.java new file mode 100644 index 00000000..3070c730 --- /dev/null +++ b/lite/src/org/transdroid/core/app/settings/ServerSetting.java @@ -0,0 +1,222 @@ +package org.transdroid.core.app.settings; + +import org.transdroid.core.gui.lists.SimpleListItem; +import org.transdroid.daemon.Daemon; +import org.transdroid.daemon.DaemonSettings; +import org.transdroid.daemon.IDaemonAdapter; +import org.transdroid.daemon.OS; + +import android.text.TextUtils; + +/** + * Represents a user-configured remote server. + * @author Eric Kok + */ +public class ServerSetting implements SimpleListItem { + + private static final String DEFAULT_NAME = "Default"; + + private final int key; + private final String name; + private final Daemon type; + private final String address; + private final String localAddress; + private final String localNetwork; + private final int port; + private final String folder; + private final boolean useAuthentication; + private final String username; + private final String password; + private final String extraPass; + private final OS os; + private final String downloadDir; + private final String ftpUrl; + private final String ftpPassword; + private final int timeout; + private final boolean alarmOnFinishedDownload; + private final boolean alarmOnNewTorrent; + private final boolean ssl; + private final boolean sslTrustAll; + private final String sslTrustKey; + private final boolean isAutoGenerated; + + /** + * Creates a daemon settings instance, providing full connection details + * @param name A name used to identify this server to the user + * @param type The server daemon type + * @param address The server domain name or IP address + * @param localAddress The server domain or IP address when connected to the server's local network + * @param localNetwork The server's local network SSID + * @param port The port on which the server daemon is running + * @param sslTrustKey The specific key that will be accepted. + * @param folder The server folder (like a virtual sub-folder or an SCGI mount point) + * @param useAuthentication Whether to use basic authentication + * @param username The user name to provide during authentication + * @param password The password to provide during authentication + * @param extraPass The Deluge web interface password + * @param downloadDir The default download directory (which may also be used as base directory for file paths) + * @param ftpUrl The partial URL to connect to when requesting FTP-style transfers + * @param timeout The number of seconds to wait before timing out a connection attempt + * @param isAutoGenerated Whether this setting was generated rather than manually inputed by the user + */ + public ServerSetting(int key, String name, Daemon type, String address, String localAddress, String localNetwork, + int port, boolean ssl, boolean sslTrustAll, String sslTrustKey, String folder, boolean useAuthentication, + String username, String password, String extraPass, OS os, String downloadDir, String ftpUrl, + String ftpPassword, int timeout, boolean alarmOnFinishedDownload, boolean alarmOnNewTorrent, + boolean isAutoGenerated) { + this.key = key; + this.name = name; + this.type = type; + this.address = address; + this.localAddress = localAddress; + this.localNetwork = localNetwork; + this.port = port; + this.ssl = ssl; + this.sslTrustAll = sslTrustAll; + this.sslTrustKey = sslTrustKey; + this.folder = folder; + this.useAuthentication = useAuthentication; + this.username = username; + this.password = password; + this.extraPass = extraPass; + this.os = os; + this.downloadDir = downloadDir; + this.ftpUrl = ftpUrl; + this.ftpPassword = ftpPassword; + this.timeout = timeout; + this.alarmOnFinishedDownload = alarmOnFinishedDownload; + this.alarmOnNewTorrent = alarmOnNewTorrent; + this.isAutoGenerated = isAutoGenerated; + } + + @Override + public String getName() { + return (name == null || name.equals("") ? DEFAULT_NAME : name); + } + + public Daemon getType() { + return type; + } + + public String getAddress() { + return address; + } + + public String getLocalAddress() { + return localAddress; + } + + public String getLocalNetwork() { + return localNetwork; + } + + public int getPort() { + return port; + } + + public boolean getSsl() { + return ssl; + } + + public boolean getSslTrustAll() { + return sslTrustAll; + } + + public String getSslTrustKey() { + return sslTrustKey; + } + + public String getFolder() { + return folder; + } + + public boolean shouldUseAuthentication() { + return useAuthentication; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getExtraPassword() { + return extraPass; + } + + public OS getOS() { + return os; + } + + public String getDownloadDir() { + return downloadDir; + } + + public String getFtpUrl() { + return ftpUrl; + } + + public String getFtpPassword() { + return ftpPassword; + } + + public int getTimeoutInMilliseconds() { + return timeout * 1000; + } + + public boolean shouldAlarmOnFinishedDownload() { + return alarmOnFinishedDownload; + } + + public boolean shouldAlarmOnNewTorrent() { + return alarmOnNewTorrent; + } + + public boolean isAutoGenerated() { + return isAutoGenerated; + } + + public int getOrder() { + return this.key; + } + + public String getHumanReadableIdentifier() { + if (isAutoGenerated) { + // Hide the 'implementation details'; just give the username and server + return (this.shouldUseAuthentication() && !TextUtils.isEmpty(this.getUsername()) ? this.getUsername() + "@" + : "") + getAddress(); + } + return (this.ssl ? "https://" : "http://") + + (this.shouldUseAuthentication() && !TextUtils.isEmpty(this.getUsername()) ? this.getUsername() + "@" + : "") + getAddress() + ":" + getPort() + + (Daemon.supportsCustomFolder(getType()) && getFolder() != null ? getFolder() : ""); + } + + @Override + public boolean equals(Object o) { + if (o instanceof ServerSetting) { + // Directly compare order numbers/unique keys + return ((ServerSetting) o).getOrder() == this.key; + } else if (o instanceof DaemonSettings) { + // Old-style DaemonSettings objects can be equal if they were constructed from a ServerSettings object: + // idString should reflect the local key/order + return ((DaemonSettings) o).getIdString().equals(Integer.toString(this.key)); + } + // Other objects are never equal to this + return false; + } + + public IDaemonAdapter createServerAdapter() { + // Convert local server settings into an old-style DaemonSetting object + // The local integer key is converted to the idString string + // TODO: Add localaddress and localnetwork to DaemonSettings, or solve properly rework the Connect library + // handling of settings + DaemonSettings daemonSettings = new DaemonSettings(name, type, address, port, ssl, sslTrustAll, sslTrustKey, + folder, useAuthentication, username, password, extraPass, os, downloadDir, ftpUrl, ftpPassword, + timeout, alarmOnFinishedDownload, alarmOnNewTorrent, Integer.toString(key), isAutoGenerated); + return type.createAdapter(daemonSettings); + } + +} diff --git a/lite/src/org/transdroid/core/app/settings/SystemSettings.java b/lite/src/org/transdroid/core/app/settings/SystemSettings.java new file mode 100644 index 00000000..3030af70 --- /dev/null +++ b/lite/src/org/transdroid/core/app/settings/SystemSettings.java @@ -0,0 +1,30 @@ +package org.transdroid.core.app.settings; + +import org.androidannotations.annotations.EBean; +import org.androidannotations.annotations.RootContext; +import org.androidannotations.annotations.EBean.Scope; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +/** + * Allows instantiation of the settings specified in R.xml.pref_system. + * @author Eric Kok + */ +@EBean(scope = Scope.Singleton) +public class SystemSettings { + + @RootContext + protected Context context; + private SharedPreferences prefs; + + protected SystemSettings(Context context) { + prefs = PreferenceManager.getDefaultSharedPreferences(context); + } + + public boolean checkForUpdates() { + return prefs.getBoolean("system_checkupdates", true); + } + +} diff --git a/lite/src/org/transdroid/core/app/settings/WebsearchSetting.java b/lite/src/org/transdroid/core/app/settings/WebsearchSetting.java new file mode 100644 index 00000000..d278544a --- /dev/null +++ b/lite/src/org/transdroid/core/app/settings/WebsearchSetting.java @@ -0,0 +1,48 @@ +package org.transdroid.core.app.settings; + +import org.transdroid.core.gui.lists.SimpleListItem; + +import android.net.Uri; +import android.text.TextUtils; + +/** + * Represents a user-specified website that can be searched (by starting the browser, rather than in-app) + * @author Eric Kok + */ +public class WebsearchSetting implements SimpleListItem { + + private static final String DEFAULT_NAME = "Default"; + private static final String KEY_PREFIX = "websearch_"; + + private final int order; + private final String name; + private final String baseUrl; + + public WebsearchSetting(int order, String name, String baseUrl) { + this.order = order; + this.name = name; + this.baseUrl = baseUrl; + } + + public int getOrder() { + return order; + } + + @Override + public String getName() { + if (!TextUtils.isEmpty(name)) + return name; + if (!TextUtils.isEmpty(baseUrl)) + return Uri.parse(baseUrl).getHost(); + return DEFAULT_NAME; + } + + public String getBaseUrl() { + return baseUrl; + } + + public String getKey() { + return KEY_PREFIX + getOrder(); + } + +} diff --git a/lite/src/org/transdroid/core/gui/DetailsActivity.java b/lite/src/org/transdroid/core/gui/DetailsActivity.java new file mode 100644 index 00000000..0f597715 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/DetailsActivity.java @@ -0,0 +1,263 @@ +package org.transdroid.core.gui; + +import java.util.ArrayList; +import java.util.List; + +import org.androidannotations.annotations.AfterViews; +import org.androidannotations.annotations.Background; +import org.androidannotations.annotations.Bean; +import org.androidannotations.annotations.EActivity; +import org.androidannotations.annotations.Extra; +import org.androidannotations.annotations.FragmentById; +import org.androidannotations.annotations.InstanceState; +import org.androidannotations.annotations.OptionsItem; +import org.androidannotations.annotations.OptionsMenu; +import org.androidannotations.annotations.UiThread; +import org.transdroid.core.R; +import org.transdroid.core.app.settings.ApplicationSettings; +import org.transdroid.core.app.settings.ServerSetting; +import org.transdroid.core.gui.lists.LocalTorrent; +import org.transdroid.daemon.Daemon; +import org.transdroid.daemon.IDaemonAdapter; +import org.transdroid.daemon.Torrent; +import org.transdroid.daemon.TorrentDetails; +import org.transdroid.daemon.TorrentFile; +import org.transdroid.daemon.task.DaemonTaskFailureResult; +import org.transdroid.daemon.task.DaemonTaskResult; +import org.transdroid.daemon.task.DaemonTaskSuccessResult; +import org.transdroid.daemon.task.GetFileListTask; +import org.transdroid.daemon.task.GetFileListTaskSuccessResult; +import org.transdroid.daemon.task.GetTorrentDetailsTask; +import org.transdroid.daemon.task.GetTorrentDetailsTaskSuccessResult; +import org.transdroid.daemon.task.PauseTask; +import org.transdroid.daemon.task.RemoveTask; +import org.transdroid.daemon.task.ResumeTask; +import org.transdroid.daemon.task.RetrieveTask; +import org.transdroid.daemon.task.RetrieveTaskSuccessResult; +import org.transdroid.daemon.task.SetDownloadLocationTask; +import org.transdroid.daemon.task.SetLabelTask; +import org.transdroid.daemon.task.SetTrackersTask; +import org.transdroid.daemon.task.StartTask; +import org.transdroid.daemon.task.StopTask; + +import android.annotation.TargetApi; +import android.content.Intent; +import android.os.Build; +import android.widget.Toast; + +import com.actionbarsherlock.app.SherlockFragmentActivity; + +@EActivity(R.layout.activity_details) +@OptionsMenu(R.menu.activity_details) +public class DetailsActivity extends SherlockFragmentActivity implements TorrentTasksExecutor { + + @Extra + @InstanceState + protected Torrent torrent; + + // Settings + @Bean + protected ApplicationSettings applicationSettings; + private IDaemonAdapter currentConnection = null; + + // Details view components + @FragmentById(R.id.torrent_details) + protected DetailsFragment fragmentDetails; + + @AfterViews + protected void init() { + + // We require a torrent to be specified; otherwise close the activity + if (torrent == null) { + finish(); + return; + } + + // Simple action bar with up, torrent name as title and refresh button + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(torrent.getName()); + + // Connect to the last used server + ServerSetting lastUsed = applicationSettings.getLastUsedServer(); + currentConnection = lastUsed.createServerAdapter(); + + // Show details and load fine stats and torrent files + fragmentDetails.updateTorrent(torrent); + refreshTorrentDetails(); + refreshTorrentFiles(); + + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @OptionsItem(android.R.id.home) + protected void navigateUp() { + TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start(); + } + + @OptionsItem(R.id.action_refresh) + protected void refreshScreen() { + refreshTorrent(); + refreshTorrentDetails(); + refreshTorrentFiles(); + } + + @Background + protected void refreshTorrent() { + DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(); + if (result instanceof RetrieveTaskSuccessResult) { + onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), ((RetrieveTaskSuccessResult) result).getLabels()); + } else { + onCommunicationError((DaemonTaskFailureResult)result); + } + } + + @Background + protected void refreshTorrentDetails() { + if (!Daemon.supportsFineDetails(torrent.getDaemon())) + return; + DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(); + if (result instanceof GetTorrentDetailsTaskSuccessResult) { + onTorrentDetailsRetrieved(((GetTorrentDetailsTaskSuccessResult) result).getTorrentDetails()); + } else { + onCommunicationError((DaemonTaskFailureResult)result); + } + } + + @Background + protected void refreshTorrentFiles() { + if (!Daemon.supportsFileListing(torrent.getDaemon())) + return; + DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(); + if (result instanceof GetFileListTaskSuccessResult) { + onTorrentFilesRetrieved(((GetFileListTaskSuccessResult) result).getFiles()); + } else { + onCommunicationError((DaemonTaskFailureResult)result); + } + } + + @Background + @Override + public void resumeTorrent(Torrent torrent) { + torrent.mimicResume(); + DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_resumed); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void pauseTorrent(Torrent torrent) { + torrent.mimicPause(); + DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_paused); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void startTorrent(Torrent torrent, boolean forced) { + torrent.mimicStart(); + DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_started); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void stopTorrent(Torrent torrent) { + torrent.mimicStop(); + DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_stopped); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void removeTorrent(Torrent torrent, boolean withData) { + DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, withData ? R.string.result_removed_with_data + : R.string.result_removed); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void updateLabel(Torrent torrent, String newLabel) { + torrent.mimicNewLabel(newLabel); + DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_labelset, newLabel); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void updateTrackers(Torrent torrent, List newTrackers) { + DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_trackersupdated); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void updateLocation(Torrent torrent, String newLocation) { + DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_locationset, newLocation); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @UiThread + protected void onTaskSucceeded(DaemonTaskSuccessResult result, int successMessageId, String... messageParams) { + // TODO: Properly report this success + Toast.makeText(this, getString(successMessageId, (Object[]) messageParams), Toast.LENGTH_LONG).show(); + } + + @UiThread + protected void onCommunicationError(DaemonTaskFailureResult result) { + // TODO: Properly report this error + Toast.makeText(this, getString(LocalTorrent.getResourceForDaemonException(result.getException())), + Toast.LENGTH_LONG).show(); + } + + @UiThread + protected void onTorrentsRetrieved(List torrents, List labels) { + // Update the details fragment + fragmentDetails.perhapsUpdateTorrent(torrents); + } + + @UiThread + protected void onTorrentDetailsRetrieved(TorrentDetails torrentDetails) { + // Update the details fragment with the new fine details for the shown torrent + fragmentDetails.updateTorrentDetails(torrentDetails); + } + + @UiThread + protected void onTorrentFilesRetrieved(List torrentFiles) { + // Update the details fragment with the newly retrieved list of files + fragmentDetails.updateTorrentFiles(new ArrayList(torrentFiles)); + } + +} diff --git a/lite/src/org/transdroid/core/gui/DetailsFragment.java b/lite/src/org/transdroid/core/gui/DetailsFragment.java new file mode 100644 index 00000000..685a5527 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/DetailsFragment.java @@ -0,0 +1,199 @@ +package org.transdroid.core.gui; + +import java.util.ArrayList; +import java.util.List; + +import org.androidannotations.annotations.AfterViews; +import org.androidannotations.annotations.EFragment; +import org.androidannotations.annotations.InstanceState; +import org.androidannotations.annotations.OptionsItem; +import org.androidannotations.annotations.OptionsMenu; +import org.androidannotations.annotations.ViewById; +import org.transdroid.core.R; +import org.transdroid.core.gui.lists.DetailsAdapter; +import org.transdroid.core.gui.lists.SimpleListItemAdapter; +import org.transdroid.daemon.Daemon; +import org.transdroid.daemon.Torrent; +import org.transdroid.daemon.TorrentDetails; +import org.transdroid.daemon.TorrentFile; + +import android.view.View; +import android.widget.TextView; + +import com.actionbarsherlock.app.SherlockFragment; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.SherlockListView; + +/** + * Fragment that shown detailed statistics about some torrent. These come from some already fetched {@link Torrent} + * object, but it also retrieves further detailed statistics. + * @author Eric Kok + */ +@EFragment(R.layout.fragment_details) +@OptionsMenu(R.menu.fragment_details) +public class DetailsFragment extends SherlockFragment { + + // Local data + @InstanceState + protected Torrent torrent = null; + @InstanceState + protected TorrentDetails torrentDetails = null; + @InstanceState + protected ArrayList torrentFiles = null; + + // Views + @ViewById(R.id.details_list) + protected SherlockListView detailsList; + @ViewById + protected TextView emptyText; + + @AfterViews + protected void init() { + + detailsList.setAdapter(new DetailsAdapter(getActivity())); + if (torrent != null) + updateTorrent(torrent); + if (torrentDetails != null) + updateTorrentDetails(torrentDetails); + if (torrentFiles != null) + updateTorrentFiles(torrentFiles); + + } + + /** + * Updates the details adapter header to show the new torrent data + * @param newTorrent The new torrent object + */ + public void updateTorrent(Torrent newTorrent) { + clear(); + this.torrent = newTorrent; + ((DetailsAdapter) detailsList.getAdapter()).updateTorrent(newTorrent); + // Make the list (with detials header) visible + detailsList.setVisibility(View.VISIBLE); + emptyText.setVisibility(View.INVISIBLE); + // Also update the available actions in the action bar + getActivity().supportInvalidateOptionsMenu(); + } + + /** + * Updates the details adapter to show the list of trackers and tracker errors + * @param newTorrentDetails The new fine details object of some torrent + */ + public void updateTorrentDetails(TorrentDetails newTorrentDetails) { + this.torrentDetails = newTorrentDetails; + ((DetailsAdapter) detailsList.getAdapter()).updateTrackers(SimpleListItemAdapter.SimpleStringItem + .wrapStringsList(newTorrentDetails.getTrackers())); + ((DetailsAdapter) detailsList.getAdapter()).updateErrors(SimpleListItemAdapter.SimpleStringItem + .wrapStringsList(newTorrentDetails.getErrors())); + } + + /** + * Updates the list adapter to show a new list of torrent files, replacing the old files list + * @param newTorrents The new, updated list of torrent file objects + */ + public void updateTorrentFiles(ArrayList newTorrentFiles) { + this.torrentFiles = newTorrentFiles; + ((DetailsAdapter) detailsList.getAdapter()).updateTorrentFiles(newTorrentFiles); + } + + /** + * Can be called if some outside activity returned new torrents, so we can perhaps piggyback on this by update our + * data as well + * @param torrents The last of retrieved torrents + */ + public void perhapsUpdateTorrent(List torrents) { + for (Torrent newTorrent : torrents) { + if (newTorrent.getUniqueID().equals(this.torrent.getUniqueID())) { + // Found, so we can update our data as well + updateTorrent(newTorrent); + break; + } + } + } + + /** + * Clear the screen by fully clearing the internal merge list (with header and other lists) + */ + public void clear() { + detailsList.setAdapter(new DetailsAdapter(getActivity())); + detailsList.setVisibility(View.INVISIBLE); + emptyText.setVisibility(View.VISIBLE); + torrent = null; + torrentDetails = null; + torrentFiles = null; + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + // Update action availability + boolean startStop = Daemon.supportsStoppingStarting(torrent.getDaemon()); + menu.findItem(R.id.action_resume).setVisible(torrent.canResume()); + menu.findItem(R.id.action_pause).setVisible(torrent.canPause()); + menu.findItem(R.id.action_start).setVisible(startStop && torrent.canStart()); + menu.findItem(R.id.action_stop).setVisible(startStop && torrent.canStop()); + boolean removeWithData = Daemon.supportsRemoveWithData(torrent.getDaemon()); + menu.findItem(R.id.action_remove_withdata).setVisible(removeWithData); + boolean setLabel = Daemon.supportsSetLabel(torrent.getDaemon()); + menu.findItem(R.id.action_setlabel).setVisible(setLabel); + boolean setTrackers = Daemon.supportsSetTrackers(torrent.getDaemon()); + menu.findItem(R.id.action_updatetrackers).setVisible(setTrackers); + + } + + @OptionsItem(R.id.action_resume) + protected void resumeTorrent() { + getTasksExecutor().resumeTorrent(torrent); + } + + @OptionsItem(R.id.action_pause) + protected void pauseTorrent() { + getTasksExecutor().pauseTorrent(torrent); + } + + @OptionsItem(R.id.action_start_default) + protected void startTorrentDefault() { + getTasksExecutor().startTorrent(torrent, false); + } + + @OptionsItem(R.id.action_start_forced) + protected void startTorrentForced() { + getTasksExecutor().startTorrent(torrent, true); + } + + @OptionsItem(R.id.action_stop) + protected void stopTorrent() { + getTasksExecutor().stopTorrent(torrent); + } + + @OptionsItem(R.id.action_remove_default) + protected void removeTorrentDefault() { + getTasksExecutor().removeTorrent(torrent, false); + } + + @OptionsItem(R.id.action_remove_withdata) + protected void removeTorrentWithData() { + getTasksExecutor().removeTorrent(torrent, true); + } + + @OptionsItem(R.id.action_setlabel) + protected void setLabel() { + // TODO: Show label selection dialog + } + + @OptionsItem(R.id.action_updatetrackers) + protected void updateTrackers() { + // TODO: Show trackers edit dialog + } + + /** + * Returns the object responsible for executing torrent tasks against a connected server + * @return The executor for tasks on some torrent + */ + private TorrentTasksExecutor getTasksExecutor() { + // NOTE: Assumes the activity implements all the required torrent tasks + return (TorrentTasksExecutor) getActivity(); + } + +} diff --git a/lite/src/org/transdroid/core/gui/SearchHistoryProvider.java b/lite/src/org/transdroid/core/gui/SearchHistoryProvider.java new file mode 100644 index 00000000..da59bbf1 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/SearchHistoryProvider.java @@ -0,0 +1,22 @@ +package org.transdroid.core.gui; + +import android.content.Context; +import android.content.SearchRecentSuggestionsProvider; +import android.provider.SearchRecentSuggestions; + +public class SearchHistoryProvider extends SearchRecentSuggestionsProvider { + + public final static String AUTHORITY = "org.transdroid.core.gui.SearchHistoryProvider"; + public final static int MODE = DATABASE_MODE_QUERIES; + + public SearchHistoryProvider() { + super(); + setupSuggestions(AUTHORITY, MODE); + } + + public static void clearHistory(Context context) { + SearchRecentSuggestions suggestions = new SearchRecentSuggestions(context, + SearchHistoryProvider.AUTHORITY, SearchHistoryProvider.MODE); + suggestions.clearHistory(); + } +} diff --git a/lite/src/org/transdroid/core/gui/TorrentTasksExecutor.java b/lite/src/org/transdroid/core/gui/TorrentTasksExecutor.java new file mode 100644 index 00000000..c8bc7e55 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/TorrentTasksExecutor.java @@ -0,0 +1,16 @@ +package org.transdroid.core.gui; + +import java.util.List; + +import org.transdroid.daemon.Torrent; + +public interface TorrentTasksExecutor { + void resumeTorrent(Torrent torrent); + void pauseTorrent(Torrent torrent); + void startTorrent(Torrent torrent, boolean forced); + void stopTorrent(Torrent torrent); + void removeTorrent(Torrent torrent, boolean withData); + void updateLabel(Torrent torrent, String newLabel); + void updateTrackers(Torrent torrent, List newTrackers); + void updateLocation(Torrent torrent, String newLocation); +} \ No newline at end of file diff --git a/lite/src/org/transdroid/core/gui/TorrentsActivity.java b/lite/src/org/transdroid/core/gui/TorrentsActivity.java new file mode 100644 index 00000000..eee5e707 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/TorrentsActivity.java @@ -0,0 +1,474 @@ +package org.transdroid.core.gui; + +import java.util.ArrayList; +import java.util.List; + +import org.androidannotations.annotations.AfterViews; +import org.androidannotations.annotations.Background; +import org.androidannotations.annotations.Bean; +import org.androidannotations.annotations.EActivity; +import org.androidannotations.annotations.FragmentById; +import org.androidannotations.annotations.InstanceState; +import org.androidannotations.annotations.OptionsItem; +import org.androidannotations.annotations.OptionsMenu; +import org.androidannotations.annotations.SystemService; +import org.androidannotations.annotations.UiThread; +import org.androidannotations.annotations.ViewById; +import org.transdroid.core.R; +import org.transdroid.core.app.settings.ApplicationSettings; +import org.transdroid.core.app.settings.ServerSetting; +import org.transdroid.core.gui.lists.LocalTorrent; +import org.transdroid.core.gui.lists.SimpleListItem; +import org.transdroid.core.gui.navigation.FilterListAdapter; +import org.transdroid.core.gui.navigation.FilterListAdapter_; +import org.transdroid.core.gui.navigation.NavigationFilter; +import org.transdroid.core.gui.navigation.NavigationHelper; +import org.transdroid.core.gui.navigation.NavigationSelectionView.NavigationFilterManager; +import org.transdroid.core.gui.navigation.StatusType; +import org.transdroid.core.gui.settings.MainSettingsActivity_; +import org.transdroid.daemon.Daemon; +import org.transdroid.daemon.IDaemonAdapter; +import org.transdroid.daemon.Torrent; +import org.transdroid.daemon.task.DaemonTaskFailureResult; +import org.transdroid.daemon.task.DaemonTaskResult; +import org.transdroid.daemon.task.DaemonTaskSuccessResult; +import org.transdroid.daemon.task.GetStatsTask; +import org.transdroid.daemon.task.GetStatsTaskSuccessResult; +import org.transdroid.daemon.task.PauseTask; +import org.transdroid.daemon.task.RemoveTask; +import org.transdroid.daemon.task.ResumeTask; +import org.transdroid.daemon.task.RetrieveTask; +import org.transdroid.daemon.task.RetrieveTaskSuccessResult; +import org.transdroid.daemon.task.SetAlternativeModeTask; +import org.transdroid.daemon.task.SetDownloadLocationTask; +import org.transdroid.daemon.task.SetLabelTask; +import org.transdroid.daemon.task.SetTrackersTask; +import org.transdroid.daemon.task.StartTask; +import org.transdroid.daemon.task.StopTask; + +import android.annotation.TargetApi; +import android.app.SearchManager; +import android.os.Build; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.Toast; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.ActionBar.OnNavigationListener; +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.SherlockListView; +import com.actionbarsherlock.widget.SearchView; + +@EActivity(R.layout.activity_torrents) +@OptionsMenu(R.menu.activity_torrents) +public class TorrentsActivity extends SherlockFragmentActivity implements OnNavigationListener, TorrentTasksExecutor, NavigationFilterManager { + + // Navigation components + @Bean + protected NavigationHelper navigationHelper; + @ViewById + protected SherlockListView filtersList; + protected FilterListAdapter navigationListAdapter = null; + protected FilterListAdapter navigationSpinnerAdapter = null; + @SystemService + protected SearchManager searchManager; + + // Settings + @Bean + protected ApplicationSettings applicationSettings; + @InstanceState + boolean firstStart = true; + private IDaemonAdapter currentConnection = null; + @InstanceState + protected NavigationFilter currentFilter = null; + @InstanceState + protected boolean turleModeEnabled = false; + + // Torrents list components + @FragmentById(R.id.torrent_list) + protected TorrentsFragment fragmentTorrents; + + // Details view components + @FragmentById(R.id.torrent_details) + protected DetailsFragment fragmentDetails; + + @AfterViews + protected void init() { + + // Set up navigation, with an action bar spinner and possibly (if room) with a filter list + getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); + getSupportActionBar().setHomeButtonEnabled(false); + navigationSpinnerAdapter = FilterListAdapter_.getInstance_(this).setNavigationFilterManager(this); + // Servers are always added to the action bar spinner + navigationSpinnerAdapter.updateServers(applicationSettings.getServerSettings()); + getSupportActionBar().setListNavigationCallbacks(navigationSpinnerAdapter, this); + if (filtersList != null) { + // There was room for a dedicated filter list; add the status types + navigationListAdapter = FilterListAdapter_.getInstance_(this); + filtersList.setAdapter(navigationListAdapter); + navigationListAdapter.updateStatusTypes(StatusType.getAllStatusTypes(this)); + filtersList.setOnItemSelectedListener(onFilterListItemSelected); + } else { + // Add status types directly to the action bar spinner + navigationSpinnerAdapter.updateStatusTypes(StatusType.getAllStatusTypes(this)); + } + currentFilter = StatusType.getShowAllType(this); + + // Connect to the last used server + ServerSetting lastUsed = applicationSettings.getLastUsedServer(); + if (lastUsed == null) { + // No server settings yet; + return; + } + // Set this as selection in the action bar spinner; we can use the server setting key since we have stable ids + // TODO: Does this call the action bar item selection callback? And refreshes? + getSupportActionBar().setSelectedNavigationItem(lastUsed.getOrder()); + + // Handle any start up intents + if (firstStart) { + handleStartIntent(); + } + + } + + @Override + protected void onResume() { + super.onResume(); + + // Refresh server settings + navigationSpinnerAdapter.updateServers(applicationSettings.getServerSettings()); + ServerSetting lastUsed = applicationSettings.getLastUsedServer(); + if (lastUsed == null) { + // Still no settings + return; + } + // There is a server now: select it to establish a connection + filterSelected(lastUsed); + } + + @TargetApi(Build.VERSION_CODES.FROYO) + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + // For Android 2.1+, add an expandable SearchView to the action bar + MenuItem item = menu.findItem(R.id.action_search); + if (android.os.Build.VERSION.SDK_INT >= 8) { + final SearchView searchView = new SearchView(this); + searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); + searchView.setQueryRefinementEnabled(true); + item.setActionView(searchView); + } + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + // No connection yet; hide all menu options except settings + if (currentConnection == null) { + menu.findItem(R.id.action_add).setVisible(false); + menu.findItem(R.id.action_search).setVisible(false); + menu.findItem(R.id.action_rss).setVisible(false); + menu.findItem(R.id.action_enableturtle).setVisible(false); + menu.findItem(R.id.action_disableturtle).setVisible(false); + menu.findItem(R.id.action_refresh).setVisible(false); + menu.findItem(R.id.action_sort).setVisible(false); + menu.findItem(R.id.action_filter).setVisible(false); + menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + menu.findItem(R.id.action_help).setVisible(true); + fragmentTorrents.updateConnectionStatus(false); + getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + return true; + } + + // There is a connection (read: settings to some server known) + menu.findItem(R.id.action_add).setVisible(true); + menu.findItem(R.id.action_search).setVisible(true); + menu.findItem(R.id.action_rss).setVisible(true); + boolean hasAltMode = Daemon.supportsSetAlternativeMode(currentConnection.getType()); + menu.findItem(R.id.action_enableturtle).setVisible(hasAltMode && !turleModeEnabled); + menu.findItem(R.id.action_disableturtle).setVisible(hasAltMode && turleModeEnabled); + menu.findItem(R.id.action_refresh).setVisible(true); + menu.findItem(R.id.action_sort).setVisible(true); + menu.findItem(R.id.action_filter).setVisible(true); + menu.findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.findItem(R.id.action_help).setVisible(false); + fragmentTorrents.updateConnectionStatus(true); + getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); + + return true; + } + + /** + * Called when an item in the action bar navigation spinner was selected + */ + @Override + public boolean onNavigationItemSelected(int itemPosition, long itemId) { + Object item = navigationSpinnerAdapter.getItem(itemPosition); + if (item instanceof SimpleListItem) { + // A filter item was selected form the navigation spinner + filterSelected((SimpleListItem) item); + return true; + } + // A header was selected; no action + return false; + } + + // Handles clicks (selections) on the dedicated list of filter items (if it exists) + // NOTE: Unfortunately we cannot use the @ItemSelect(R.id.filters_list) annotation as it throws NPE exceptions when + // the list doesn't exist (read: on small screens) + protected OnItemSelectedListener onFilterListItemSelected = new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + filterSelected((SimpleListItem) filtersList.getAdapter().getItem(position)); + } + @Override + public void onNothingSelected(AdapterView parent) { + // TODO: Check if this happens + } + }; + + /** + * A new filter was selected; update the view over the current data + * @param selected True if the filter item was selected, false if it was deselected + * @param item The touched filter item + */ + protected void filterSelected(SimpleListItem item) { + + // Server selection + if (item instanceof ServerSetting) { + ServerSetting server = (ServerSetting) item; + + if (currentConnection != null && server.equals(currentConnection.getSettings())) { + // Already connected to this server; just ask for a refresh instead + refreshTorrents(); + return; + } + + // Update connection to the newly selected server and refresh + currentConnection = server.createServerAdapter(); + applicationSettings.setLastUsedServer(server); + clearScreens(); + refreshTorrents(); + return; + + } + + // Status type or label selection - both of which are navigation filters + if (item instanceof NavigationFilter) { + currentFilter = (NavigationFilter) item; + fragmentTorrents.applyFilter(currentFilter); + // Clear the details view + if (fragmentDetails != null) { + fragmentDetails.clear(); + } + } + + } + + @Override + public String getActiveFilterText() { + return currentFilter.getName(); + } + + @Override + public String getActiveServerText() { + return currentConnection.getSettings().getName(); + } + + /** + * If required, add torrents, switch to a specific server, etc. + */ + protected void handleStartIntent() { + // TODO: Handle start intent + } + + @OptionsItem(R.id.action_refresh) + protected void refreshScreen() { + refreshTorrents(); + getAdditionalStats(); + } + + @OptionsItem(R.id.action_enableturtle) + protected void enableTurtleMode() { + updateTurtleMode(true); + } + + @OptionsItem(R.id.action_disableturtle) + protected void disableTurtleMode() { + updateTurtleMode(false); + } + + @OptionsItem(R.id.action_settings) + protected void openSettings() { + MainSettingsActivity_.intent(this).start(); + } + + private void clearScreens() { + // Clear the currently shown list of torrent and perhaps the details + fragmentTorrents.clear(); + if (fragmentDetails != null) { + fragmentDetails.clear(); + } + } + + @Background + protected void refreshTorrents() { + DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(); + if (result instanceof RetrieveTaskSuccessResult) { + onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), ((RetrieveTaskSuccessResult) result).getLabels()); + } else { + onCommunicationError((DaemonTaskFailureResult)result); + } + } + + @Background + protected void getAdditionalStats() { + DaemonTaskResult result = GetStatsTask.create(currentConnection).execute(); + if (result instanceof GetStatsTaskSuccessResult) { + onTurtleModeRetrieved(((GetStatsTaskSuccessResult) result).isAlternativeModeEnabled()); + } else { + onCommunicationError((DaemonTaskFailureResult)result); + } + } + + @Background + protected void updateTurtleMode(boolean enable) { + DaemonTaskResult result = SetAlternativeModeTask.create(currentConnection, enable).execute(); + if (result instanceof GetStatsTaskSuccessResult) { + // Success; no need to retrieve it again - just update the visual indicator + onTurtleModeRetrieved(enable); + } else { + onCommunicationError((DaemonTaskFailureResult)result); + } + } + + @Background + @Override + public void resumeTorrent(Torrent torrent) { + torrent.mimicResume(); + DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_resumed); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void pauseTorrent(Torrent torrent) { + torrent.mimicPause(); + DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_paused); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void startTorrent(Torrent torrent, boolean forced) { + torrent.mimicStart(); + DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_started); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void stopTorrent(Torrent torrent) { + torrent.mimicStop(); + DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_stopped); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void removeTorrent(Torrent torrent, boolean withData) { + DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, withData ? R.string.result_removed_with_data + : R.string.result_removed); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void updateLabel(Torrent torrent, String newLabel) { + torrent.mimicNewLabel(newLabel); + DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_labelset, newLabel); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void updateTrackers(Torrent torrent, List newTrackers) { + DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_trackersupdated); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @Background + @Override + public void updateLocation(Torrent torrent, String newLocation) { + DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(); + if (result instanceof DaemonTaskResult) { + onTaskSucceeded((DaemonTaskSuccessResult) result, R.string.result_locationset, newLocation); + } else { + onCommunicationError((DaemonTaskFailureResult) result); + } + } + + @UiThread + protected void onTaskSucceeded(DaemonTaskSuccessResult result, int successMessageId, String... messageParams) { + // TODO: Properly report this success + Toast.makeText(this, getString(successMessageId, (Object[]) messageParams), Toast.LENGTH_LONG).show(); + } + + @UiThread + protected void onCommunicationError(DaemonTaskFailureResult result) { + // TODO: Properly report this error + Toast.makeText(this, getString(LocalTorrent.getResourceForDaemonException(result.getException())), + Toast.LENGTH_LONG).show(); + } + + @UiThread + protected void onTorrentsRetrieved(List torrents, List labels) { + // Report the newly retrieved list of torrents to the torrents fragment + fragmentTorrents.updateTorrents(new ArrayList(torrents)); + // Update the details fragment if the currently shown torrent is in the newly retrieved list + if (fragmentDetails != null) { + fragmentDetails.perhapsUpdateTorrent(torrents); + } + // TODO: Update local list of labels + } + + @UiThread + protected void onTurtleModeRetrieved(boolean turtleModeEnabled) { + turleModeEnabled = turtleModeEnabled; + supportInvalidateOptionsMenu(); + } + +} diff --git a/lite/src/org/transdroid/core/gui/TorrentsFragment.java b/lite/src/org/transdroid/core/gui/TorrentsFragment.java new file mode 100644 index 00000000..fc8d6b0d --- /dev/null +++ b/lite/src/org/transdroid/core/gui/TorrentsFragment.java @@ -0,0 +1,204 @@ +package org.transdroid.core.gui; + +import java.util.ArrayList; +import java.util.List; + +import org.androidannotations.annotations.AfterViews; +import org.androidannotations.annotations.EFragment; +import org.androidannotations.annotations.InstanceState; +import org.androidannotations.annotations.ItemClick; +import org.androidannotations.annotations.ViewById; +import org.transdroid.core.R; +import org.transdroid.core.gui.lists.TorrentsAdapter; +import org.transdroid.core.gui.lists.TorrentsAdapter_; +import org.transdroid.core.gui.navigation.NavigationFilter; +import org.transdroid.daemon.Torrent; + +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.actionbarsherlock.app.SherlockFragment; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.SherlockListView; +import com.actionbarsherlock.view.SherlockListView.MultiChoiceModeListenerCompat; + +@EFragment(R.layout.fragment_torrents) +public class TorrentsFragment extends SherlockFragment { + + // Local data + @InstanceState + protected ArrayList torrents = null; + @InstanceState + protected NavigationFilter currentFilter = null; + @InstanceState + protected boolean hasAConnection = false; + @InstanceState + protected boolean isLoading = false; + + // Views + @ViewById(R.id.torrent_list) + protected SherlockListView torrentsList; + @ViewById + protected TextView emptyText; + @ViewById + protected TextView nosettingsText; + @ViewById + protected ProgressBar loadingProgress; + + @AfterViews + protected void init() { + torrentsList.setAdapter(TorrentsAdapter_.getInstance_(getActivity())); + torrentsList.setMultiChoiceModeListener(onTorrentsSelected); + if (torrents != null) + updateTorrents(torrents); + } + + /** + * Updates the list adapter to show a new list of torrent objects, replacing the old torrents completely + * @param newTorrents The new, updated list of torrents + */ + public void updateTorrents(ArrayList newTorrents) { + torrents = newTorrents; + applyFilter(null); // Resets the filter and shown list of torrents + } + + /** + * Clear currently visible list of torrents + */ + public void clear() { + updateTorrents(null); + } + + /** + * Apply a filter on the current list of all torrents, showing the appropriate sublist of torrents only + * @param currentFilter + */ + public void applyFilter(NavigationFilter currentFilter) { + this.currentFilter = currentFilter; + if (torrents != null) { + // Build a local list of torrents that match the selected navigation filter + ArrayList filteredTorrents = new ArrayList(); + for (Torrent torrent : torrents) { + if (currentFilter.matches(torrent)) + filteredTorrents.add(torrent); + } + ((TorrentsAdapter) torrentsList.getAdapter()).update(filteredTorrents); + } + updateViewVisibility(); + } + + private MultiChoiceModeListenerCompat onTorrentsSelected = new MultiChoiceModeListenerCompat() { + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Show contextual action bar to start/stop/remove/etc. torrents in batch mode + mode.getMenuInflater().inflate(R.menu.fragment_torrents_cab, menu); + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + + // Get checked torrents + List checked = new ArrayList(); + for (int i = 0; i < torrentsList.getCheckedItemPositions().size(); i++) { + if (torrentsList.getCheckedItemPositions().get(i)) + checked.add((Torrent) torrentsList.getAdapter().getItem(i)); + } + + // Execute the requested action + switch (item.getItemId()) { + case R.id.action_resume: + for (Torrent torrent : checked) { + getTasksExecutor().resumeTorrent(torrent); + } + mode.finish(); + return true; + case R.id.action_pause: + for (Torrent torrent : checked) { + getTasksExecutor().pauseTorrent(torrent); + } + mode.finish(); + return true; + case R.id.action_remove_default: + for (Torrent torrent : checked) { + getTasksExecutor().removeTorrent(torrent, false); + } + mode.finish(); + return true; + case R.id.action_remove_withdata: + for (Torrent torrent : checked) { + getTasksExecutor().removeTorrent(torrent, true); + } + mode.finish(); + return true; + case R.id.action_setlabel: + // TODO: Open label selection dialogue + mode.finish(); + return true; + default: + return false; + } + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { + // TODO: Update title or otherwise show number of selected torrents? + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + } + + }; + + @ItemClick(R.id.torrent_list) + protected void torrentsListClicked(Torrent torrent) { + DetailsActivity_.intent(getActivity()).torrent(torrent).start(); + } + + /** + * Updates the shown screen depending on whether we have a connection (so torrents can be shown) or not (in case we + * need to show a message suggesting help) + * @param hasAConnection True if the user has servers configured and therefore has a connection that can be used + */ + public void updateConnectionStatus(boolean hasAConnection) { + this.hasAConnection = hasAConnection; + if (!hasAConnection) + clear(); + updateViewVisibility(); + } + + private void updateViewVisibility() { + if (!hasAConnection) { + torrentsList.setVisibility(View.GONE); + emptyText.setVisibility(View.GONE); + loadingProgress.setVisibility(View.GONE); + nosettingsText.setVisibility(View.VISIBLE); + return; + } + boolean isEmpty = torrents == null || torrentsList.getAdapter().isEmpty(); + nosettingsText.setVisibility(View.GONE); + torrentsList.setVisibility(!isLoading && !isEmpty? View.GONE: View.VISIBLE); + loadingProgress.setVisibility(isLoading? View.VISIBLE: View.GONE); + emptyText.setVisibility(!isLoading && isEmpty? View.VISIBLE: View.GONE); + } + + /** + * Returns the object responsible for executing torrent tasks against a connected server + * @return The executor for tasks on some torrent + */ + private TorrentTasksExecutor getTasksExecutor() { + // NOTE: Assumes the activity implements all the required torrent tasks + return (TorrentTasksExecutor) getActivity(); + } + +} diff --git a/lite/src/org/transdroid/core/gui/lists/DetailsAdapter.java b/lite/src/org/transdroid/core/gui/lists/DetailsAdapter.java new file mode 100644 index 00000000..77460a11 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/lists/DetailsAdapter.java @@ -0,0 +1,174 @@ +package org.transdroid.core.gui.lists; + +import java.util.ArrayList; +import java.util.List; + +import org.transdroid.core.R; +import org.transdroid.core.gui.navigation.FilterSeparatorView; +import org.transdroid.core.gui.navigation.FilterSeparatorView_; +import org.transdroid.daemon.Torrent; +import org.transdroid.daemon.TorrentFile; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +import com.commonsware.cwac.merge.MergeAdapter; + +/** + * List adapter that holds a header view showing torrent details and show the list list contained by the torrent. + * @author Eric Kok + */ +public class DetailsAdapter extends MergeAdapter { + + private TorrentDetailsView torrentDetailsView = null; + private FilterSeparatorView trackersSeparatorView = null; + private SimpleListItemAdapter trackersAdapter = null; + private FilterSeparatorView errorsSeparatorView = null; + private SimpleListItemAdapter errorsAdapter = null; + private FilterSeparatorView torrentFilesSeparatorView = null; + private TorrentFilesAdapter torrentFilesAdapter = null; + + public DetailsAdapter(Context context) { + // Immediately bind the adapters, or the MergeAdapter will not be able to determine the view types and instead + // display nothing at all + + // Torrent details header + torrentDetailsView = TorrentDetailsView_.build(context); + torrentDetailsView.setVisibility(View.GONE); + addView(torrentDetailsView, true); + + // Trackers + trackersSeparatorView = FilterSeparatorView_.build(context).setText(context.getString(R.string.status_trackers)); + trackersSeparatorView.setVisibility(View.GONE); + addView(trackersSeparatorView, true); + this.trackersAdapter = new SimpleListItemAdapter(context, new ArrayList()); + addAdapter(trackersAdapter); + + // Tracker errors + errorsSeparatorView = FilterSeparatorView_.build(context).setText(context.getString(R.string.status_errors)); + errorsSeparatorView.setVisibility(View.GONE); + addView(errorsSeparatorView, true); + this.errorsAdapter = new SimpleListItemAdapter(context, new ArrayList()); + addAdapter(errorsAdapter); + + // Torrent files + torrentFilesSeparatorView = FilterSeparatorView_.build(context).setText(context.getString(R.string.status_files)); + torrentFilesSeparatorView.setVisibility(View.GONE); + addView(torrentFilesSeparatorView, true); + this.torrentFilesAdapter = new TorrentFilesAdapter(context, new ArrayList()); + addAdapter(torrentFilesAdapter); + + } + + /** + * Update the torrent data in the details header of this merge adapter + * @param torrent The torrent for which detailed data is shown + */ + public void updateTorrent(Torrent torrent) { + torrentDetailsView.update(torrent); + torrentDetailsView.setVisibility(torrent == null? View.GONE: View.VISIBLE); + } + + /** + * Update the list of files contained in this torrent + * @param torrentFiles The new list of files, or null if the list and header should be hidden + */ + public void updateTorrentFiles(List torrentFiles) { + if (torrentFiles == null) { + torrentFilesAdapter.update(new ArrayList()); + torrentFilesSeparatorView.setVisibility(View.GONE); + } else { + torrentFilesAdapter.update(torrentFiles); + torrentFilesSeparatorView.setVisibility(View.VISIBLE); + } + } + + /** + * Update the list of trackers + * @param trackers The new list of trackers known for this torrent, or null if the list and header should be hidden + */ + public void updateTrackers(List trackers) { + if (trackers == null) { + trackersAdapter.update(new ArrayList()); + trackersSeparatorView.setVisibility(View.GONE); + } else { + trackersAdapter.update(trackers); + trackersSeparatorView.setVisibility(View.VISIBLE); + } + } + + /** + * Update the list of errors + * @param errors The new list of errors known for this torrent, or null if the list and header should be hidden + */ + public void updateErrors(List errors) { + if (errors == null) { + errorsAdapter.update(new ArrayList()); + errorsSeparatorView.setVisibility(View.GONE); + } else { + errorsAdapter.update(errors); + errorsSeparatorView.setVisibility(View.VISIBLE); + } + } + + /** + * Clear currently visible torrent, including header and shown lists + */ + public void clear() { + updateTorrent(null); + updateTorrentFiles(null); + updateErrors(null); + updateTrackers(null); + } + + protected static class TorrentFilesAdapter extends BaseAdapter { + + private final Context context; + private List items; + + public TorrentFilesAdapter(Context context, List items) { + this.context = context; + this.items = items; + } + + /** + * Allows updating of the full data list underlying this adapter, replacing all items + * @param newItems The new list of files to display + */ + public void update(List newItems) { + this.items = newItems; + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return items.size(); + } + + @Override + public TorrentFile getItem(int position) { + return items.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TorrentFileView torrentFileView; + if (convertView == null) { + torrentFileView = TorrentFileView_.build(context); + } else { + torrentFileView = (TorrentFileView) convertView; + } + torrentFileView.bind(getItem(position)); + return torrentFileView; + } + + } + +} diff --git a/lite/src/org/transdroid/core/gui/lists/LocalTorrent.java b/lite/src/org/transdroid/core/gui/lists/LocalTorrent.java new file mode 100644 index 00000000..76722530 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/lists/LocalTorrent.java @@ -0,0 +1,233 @@ +package org.transdroid.core.gui.lists; + +import java.util.Locale; + +import org.transdroid.core.R; +import org.transdroid.daemon.DaemonException; +import org.transdroid.daemon.Torrent; +import org.transdroid.daemon.TorrentStatus; +import org.transdroid.daemon.util.FileSizeConverter; +import org.transdroid.daemon.util.TimespanConverter; + +import android.content.res.Resources; + +/** + * Wrapper around Torrent to provide some addition getters that give translatable or otherwise formatted Strings of + * torrent statistics. + * @author Eric Kok + */ +public class LocalTorrent { + + /** + * Creates the LocalTorrent object so that the translatable/formattable version of a Torrent can be used. + * @param torrent The Torrent object + * @return The torrent wrapped as LocalTorrent object + */ + public static LocalTorrent fromTorrent(Torrent torrent) { + return new LocalTorrent(torrent); + } + + private final Torrent t; + + private LocalTorrent(Torrent torrent) { + this.t = torrent; + } + + private static final String DECIMAL_FORMATTER = "%.1f"; + + /** + * Builds a string showing the upload/download seed ratio. If not downloading, it will base the ratio on the total + * size; so if you created the torrent yourself you will have downloaded 0 bytes, but the ratio will pretend you + * have 100%. + * @return A nicely formatted string containing the upload/download seed ratio + */ + public String getRatioString() { + long baseSize = t.getTotalSize(); + if (t.getStatusCode() == TorrentStatus.Downloading) { + baseSize = t.getDownloadedEver(); + } + if (baseSize <= 0) { + return String.format(Locale.getDefault(), DECIMAL_FORMATTER, 0d); + } else if (t.getRatio() == Double.POSITIVE_INFINITY) { + return "\u221E"; + } else { + return String.format(Locale.getDefault(), DECIMAL_FORMATTER, t.getRatio()); + } + } + + /** + * Returns a formatted string indicating the current progress in terms of transferred bytes + * @param r The context resources, to access translations + * @param withAvailability Whether to show file availability in-line + * @return A nicely formatted string indicating torrent status and, if applicable, progress in bytes + */ + public String getProgressSizeText(Resources r, boolean withAvailability) { + + switch (t.getStatusCode()) { + case Waiting: + case Checking: + case Error: + // Not downloading yet + return r.getString(R.string.status_waitingtodl, FileSizeConverter.getSize(t.getTotalSize())); + case Downloading: + // Downloading + return r.getString( + R.string.status_size1, + FileSizeConverter.getSize(t.getDownloadedEver()), + FileSizeConverter.getSize(t.getTotalSize()), + String.format(DECIMAL_FORMATTER, t.getDownloadedPercentage() * 100) + + "%" + + (!withAvailability ? "" : "/" + + String.format(DECIMAL_FORMATTER, t.getAvailability() * 100) + "%")); + case Seeding: + case Paused: + case Queued: + // Seeding or paused + return r.getString(R.string.status_size2, FileSizeConverter.getSize(t.getTotalSize()), + FileSizeConverter.getSize(t.getUploadedEver())); + default: + return ""; + } + + } + + /** + * Returns a formatted string indicating either the expected time to download (ETA) or, when seeding, the ratio + * @param r The context resources, to access translations + * @return A string like '~ 34 seconds', or 'RATIO 8.2' or an empty string + */ + public String getProgressEtaRatioText(Resources r) { + switch (t.getStatusCode()) { + case Downloading: + // Downloading + return getRemainingTimeString(r, true, false); + case Seeding: + case Paused: + case Queued: + // Seeding or paused + return r.getString(R.string.status_ratio, getRatioString()); + case Waiting: + case Checking: + case Error: + default: + return ""; + } + } + + /** + * Returns a formatted string indicating the torrent status and connected peers + * @param r The context resources, to access translations + * @return A string like 'Queued' or, when seeding or leeching, '2 OF 28 PEERS' + */ + public String getProgressConnectionText(Resources r) { + + switch (t.getStatusCode()) { + case Waiting: + return r.getString(R.string.status_waiting); + case Checking: + return r.getString(R.string.status_checking); + case Downloading: + return r.getString(R.string.status_peers, t.getPeersSendingToUs(), t.getPeersConnected()); + case Seeding: + return r.getString(R.string.status_peers, t.getPeersGettingFromUs(), t.getPeersConnected()); + case Paused: + return r.getString(R.string.status_paused); + case Queued: + return r.getString(R.string.status_stopped); + case Error: + return r.getString(R.string.status_error); + default: + return r.getString(R.string.status_unknown); + } + + } + + /** + * Returns a formatted string indicating current transfer speeds for the torrent + * @param r The context resources, to access translations + * @return A string like '↓ 28KB/s ↑ 1.8MB/s', or an empty string when not transferrring + */ + public String getProgressSpeedText(Resources r) { + + switch (t.getStatusCode()) { + case Waiting: + case Checking: + case Paused: + case Queued: + return ""; + case Downloading: + return r.getString(R.string.status_speed_down, FileSizeConverter.getSize(t.getRateDownload()) + "/s") + " " + + r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s"); + case Seeding: + return r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s"); + default: + return ""; + } + + } + + public String getProgressStatusEta(Resources r) { + switch (t.getStatusCode()) { + case Waiting: + return r.getString(R.string.status_waiting).toUpperCase(Locale.getDefault()); + case Checking: + return r.getString(R.string.status_checking).toUpperCase(Locale.getDefault()); + case Error: + return r.getString(R.string.status_error).toUpperCase(Locale.getDefault()); + case Downloading: + // Downloading + return r.getString(R.string.status_downloading).toUpperCase(Locale.getDefault()) + " (" + + String.format(DECIMAL_FORMATTER, t.getDownloadedPercentage() * 100) + "%), " + + getRemainingTimeString(r, false, true); + case Seeding: + return r.getString(R.string.status_seeding).toUpperCase(Locale.getDefault()); + case Paused: + return r.getString(R.string.status_paused).toUpperCase(Locale.getDefault()); + case Queued: + return r.getString(R.string.status_queued).toUpperCase(Locale.getDefault()); + default: + return r.getString(R.string.status_unknown).toUpperCase(Locale.getDefault()); + } + } + + /** + * Returns a formatted string indicating the remaining download time + * @param r The context resources, to access translations + * @param inDays Whether to show days or use hours for > 24 hours left instead + * @return A string like '4d 8h 34m 5s' or '2m 3s' + */ + public String getRemainingTimeString(Resources r, boolean abbreviate, boolean inDays) { + if (t.getEta() == -1 || t.getEta() == -2) { + return r.getString(R.string.status_unknowneta); + } + return r.getString(abbreviate ? R.string.status_eta : R.string.status_etalong, + TimespanConverter.getTime(t.getEta(), inDays)); + } + + /** + * Convert a DaemonException to a translatable human-readable error message + * @param e The exception that was thrown by the server + * @return A string resource ID to show to the user + */ + public static int getResourceForDaemonException(DaemonException e) { + switch (e.getType()) { + case MethodUnsupported: + return R.string.error_jsonrequesterror; + case ConnectionError: + return R.string.error_httperror; + case UnexpectedResponse: + return R.string.error_jsonresponseerror; + case ParsingFailed: + return R.string.error_jsonrequesterror; + case NotConnected: + return R.string.error_daemonnotconnected; + case AuthenticationFailure: + return R.string.error_401; + case FileAccessError: + return R.string.error_torrentfile; + default: + return R.string.error_httperror; + } + } + +} diff --git a/lite/src/org/transdroid/core/gui/lists/SimpleListItem.java b/lite/src/org/transdroid/core/gui/lists/SimpleListItem.java new file mode 100644 index 00000000..0128a20d --- /dev/null +++ b/lite/src/org/transdroid/core/gui/lists/SimpleListItem.java @@ -0,0 +1,13 @@ +package org.transdroid.core.gui.lists; + + +/** + * Represents a filter item as shown in the navigation list or spinner. + * + * @author Eric Kok + */ +public interface SimpleListItem { + + public String getName(); + +} diff --git a/lite/src/org/transdroid/core/gui/lists/SimpleListItemAdapter.java b/lite/src/org/transdroid/core/gui/lists/SimpleListItemAdapter.java new file mode 100644 index 00000000..f402ab53 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/lists/SimpleListItemAdapter.java @@ -0,0 +1,93 @@ +package org.transdroid.core.gui.lists; + +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +public class SimpleListItemAdapter extends BaseAdapter { + + private final Context context; + private List items; + + public SimpleListItemAdapter(Context context, List items) { + this.context = context; + this.items = items; + } + + /** + * Allows updating of the full data list underlying this adapter, replacing all items + * @param newItems The new list of filter items to display + */ + public void update(List newItems) { + this.items = newItems; + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return items.size(); + } + + @Override + public SimpleListItem getItem(int position) { + return items.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + SimpleListItemView filterItemView; + if (convertView == null || !(convertView instanceof SimpleListItemView)) { + filterItemView = SimpleListItemView_.build(context); + } else { + filterItemView = (SimpleListItemView) convertView; + } + filterItemView.bind(getItem(position)); + return filterItemView; + } + + /** + * Represents a very simple list item that only contains a single string to show in the list. Use wrapStringsList to + * wrap an existing list of strings into a list of {@link SimpleListItem}s. + * @author Eric Kok + */ + public static class SimpleStringItem implements SimpleListItem { + + /** + * Wraps a simple string of strings into a list of SimpleStringItem to add as data to a + * {@link SimpleListItemAdapter} + * @param errorStrings A list of string + * @return A list of SimpleStringItem objects representing the input strings + */ + public static List wrapStringsList(List errorStrings) { + ArrayList errors = new ArrayList(); + if (errorStrings != null) { + for (String errorString : errorStrings) { + errors.add(new SimpleStringItem(errorString)); + } + } + return errors; + } + + private final String string; + + public SimpleStringItem(String string) { + this.string = string; + } + + @Override + public String getName() { + return this.string; + } + + } + +} \ No newline at end of file diff --git a/lite/src/org/transdroid/core/gui/lists/SimpleListItemView.java b/lite/src/org/transdroid/core/gui/lists/SimpleListItemView.java new file mode 100644 index 00000000..d428939f --- /dev/null +++ b/lite/src/org/transdroid/core/gui/lists/SimpleListItemView.java @@ -0,0 +1,29 @@ +package org.transdroid.core.gui.lists; + +import org.androidannotations.annotations.EViewGroup; +import org.androidannotations.annotations.ViewById; +import org.transdroid.core.R; + +import android.content.Context; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * View that represents some {@link SimpleListItem} object and simple prints out the text (in proper style) + * @author Eric Kok + */ +@EViewGroup(R.layout.list_item_filter) +public class SimpleListItemView extends LinearLayout { + + @ViewById + protected TextView itemText; + + public SimpleListItemView(Context context) { + super(context); + } + + public void bind(SimpleListItem filterItem) { + itemText.setText(filterItem.getName()); + } + +} diff --git a/lite/src/org/transdroid/core/gui/lists/TorrentDetailsView.java b/lite/src/org/transdroid/core/gui/lists/TorrentDetailsView.java new file mode 100644 index 00000000..db714017 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/lists/TorrentDetailsView.java @@ -0,0 +1,89 @@ +package org.transdroid.core.gui.lists; + +import org.androidannotations.annotations.EViewGroup; +import org.androidannotations.annotations.ViewById; +import org.transdroid.core.R; +import org.transdroid.daemon.Daemon; +import org.transdroid.daemon.Torrent; +import org.transdroid.daemon.util.FileSizeConverter; + +import android.content.Context; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.view.View; +import android.widget.RelativeLayout; +import android.widget.TextView; + +/** + * Represents a group of views that show torrent status, sizes, speeds and other details. + * @author Eric Kok + */ +@EViewGroup(R.layout.fragment_details_header) +public class TorrentDetailsView extends RelativeLayout { + + @ViewById + protected TextView labelText, dateaddedText, uploadedText, uploadedunitText, ratioText, upspeedText, seedersText, + downloadedunitText, downloadedText, totalsizeText, downspeedText, leechersText, statusText; + + public TorrentDetailsView(Context context) { + super(context); + } + + /** + * Update the text fields with new/updated torrent details + * @param torrent The torrent for which to show details + */ + public void update(Torrent torrent) { + + if (torrent == null) { + return; + } + + LocalTorrent local = LocalTorrent.fromTorrent(torrent); + + // Set label text + if (Daemon.supportsLabels(torrent.getDaemon())) { + if (TextUtils.isEmpty(torrent.getLabelName())) { + labelText.setText(getResources().getString(R.string.labels_unlabeled)); + } else { + labelText.setText(torrent.getLabelName()); + } + labelText.setVisibility(View.VISIBLE); + } else { + labelText.setVisibility(View.INVISIBLE); + } + + // Set status texts + if (torrent.getDateAdded() != null) { + dateaddedText.setText(getResources().getString( + R.string.status_sincedate, + DateUtils.getRelativeDateTimeString(getContext(), torrent.getDateAdded().getTime(), + DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_ABBREV_MONTH))); + dateaddedText.setVisibility(View.VISIBLE); + } else { + dateaddedText.setVisibility(View.INVISIBLE); + } + statusText.setText(getResources().getString(R.string.status_status, local.getProgressStatusEta(getResources()))); + ratioText.setText(getResources().getString(R.string.status_ratio, local.getRatioString())); + // TODO: Implement separate numbers of seeders and leechers + seedersText.setText(getResources().getString(R.string.status_peers, torrent.getPeersSendingToUs(), + torrent.getPeersConnected())); + leechersText.setText(getResources().getString(R.string.status_peers, torrent.getPeersSendingToUs(), + torrent.getPeersConnected())); + // TODO: Add field that displays torrent errors (as opposed to tracker errors) + // TODO: Add field that displays availability + + // Sizes and speeds texts + totalsizeText.setText(FileSizeConverter.getSize(torrent.getTotalSize())); + downloadedText.setText(FileSizeConverter.getSize(torrent.getDownloadedEver(), false)); + downloadedunitText.setText(FileSizeConverter.getSizeUnit(torrent.getDownloadedEver()).toString()); + uploadedText.setText(FileSizeConverter.getSize(torrent.getUploadedEver(), false)); + uploadedunitText.setText(FileSizeConverter.getSizeUnit(torrent.getUploadedEver()).toString()); + downspeedText.setText(getResources().getString(R.string.status_speed_down, + FileSizeConverter.getSize(torrent.getRateDownload()) + "/s")); + upspeedText.setText(getResources().getString(R.string.status_speed_up, + FileSizeConverter.getSize(torrent.getRateUpload()) + "/s")); + + } + +} diff --git a/lite/src/org/transdroid/core/gui/lists/TorrentFileView.java b/lite/src/org/transdroid/core/gui/lists/TorrentFileView.java new file mode 100644 index 00000000..0c9b3684 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/lists/TorrentFileView.java @@ -0,0 +1,53 @@ +package org.transdroid.core.gui.lists; + +import org.androidannotations.annotations.EViewGroup; +import org.androidannotations.annotations.ViewById; +import org.transdroid.core.R; +import org.transdroid.daemon.TorrentFile; + +import android.content.Context; +import android.widget.ImageView; +import android.widget.TextView; +import fr.marvinlabs.widget.CheckableRelativeLayout; + +/** + * View that represents some {@link TorrentFile} object and show the file's name, status and priority + * @author Eric Kok + */ +@EViewGroup(R.layout.list_item_torrentfile) +public class TorrentFileView extends CheckableRelativeLayout { + + @ViewById + protected TextView nameText, progressText, sizesText; + @ViewById + protected ImageView priorityImage; + + public TorrentFileView(Context context) { + super(context, null); + } + + public void bind(TorrentFile torrentFile) { + nameText.setText(torrentFile.getName()); + sizesText.setText(torrentFile.getDownloadedAndTotalSizeText()); + progressText.setText(torrentFile.getProgressText()); + switch (torrentFile.getPriority()) { + case Off: + priorityImage.setImageResource(R.drawable.ic_priority_off); + priorityImage.setContentDescription(getResources().getString(R.string.status_priority_low)); + break; + case Low: + priorityImage.setImageResource(R.drawable.ic_priority_low); + priorityImage.setContentDescription(getResources().getString(R.string.status_priority_normal)); + break; + case Normal: + priorityImage.setImageResource(R.drawable.ic_priority_normal); + priorityImage.setContentDescription(getResources().getString(R.string.status_priority_normal)); + break; + case High: + priorityImage.setImageResource(R.drawable.ic_priority_high); + priorityImage.setContentDescription(getResources().getString(R.string.status_priority_high)); + break; + } + } + +} diff --git a/lite/src/org/transdroid/core/gui/lists/TorrentProgressBar.java b/lite/src/org/transdroid/core/gui/lists/TorrentProgressBar.java new file mode 100644 index 00000000..c6347cee --- /dev/null +++ b/lite/src/org/transdroid/core/gui/lists/TorrentProgressBar.java @@ -0,0 +1,109 @@ +package org.transdroid.core.gui.lists; + +import org.transdroid.core.R; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; + +/** + * Draws a progress bar indicating the download progress as well as the torrent status. + * + * @author Eric Kok + */ +public class TorrentProgressBar extends View { + + private final float scale = getContext().getResources().getDisplayMetrics().density; + private final int MINIMUM_HEIGHT = (int) (3 * scale + 0.5f); + + private int progress; + private boolean isActive; + private boolean isError; + private final Paint notdonePaint = new Paint(); + private final Paint inactiveDonePaint = new Paint(); + private final Paint inactivePaint = new Paint(); + private final Paint progressPaint = new Paint(); + private final Paint donePaint = new Paint(); + private final Paint errorPaint = new Paint(); + private final RectF fullRect = new RectF(); + private final RectF progressRect = new RectF(); + + public void setProgress(int progress) { + this.progress = progress; + this.invalidate(); + } + + public void setActive(boolean isActive) { + this.isActive = isActive; + this.invalidate(); + } + + public void setError(boolean isError) { + this.isError = isError; + this.invalidate(); + } + + public TorrentProgressBar(Context context) { + super(context); + initPaints(); + } + + public TorrentProgressBar(Context context, AttributeSet attrs) { + super(context, attrs); + initPaints(); + + // Parse any set attributes from XML + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TorrentProgressBar); + if (a.hasValue(R.styleable.TorrentProgressBar_progress)) { + this.progress = a.getIndex(R.styleable.TorrentProgressBar_progress); + this.isActive = a.getBoolean(R.styleable.TorrentProgressBar_isActive, false); + } + a.recycle(); + } + + private void initPaints() { + notdonePaint.setColor(0xFFEEEEEE); + inactiveDonePaint.setColor(0xFFA759D4); + inactivePaint.setColor(0xFF9E9E9E); + progressPaint.setColor(0xFF42A8FA); + donePaint.setColor(0xFF8CCF29); + errorPaint.setColor(0xFFDE3939); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int ws = MeasureSpec.getSize(widthMeasureSpec); + int hs = Math.max(getHeight(), MINIMUM_HEIGHT); + setMeasuredDimension(ws, hs); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + int height = getHeight(); + int width = getWidth(); + fullRect.set(0, 0, width, height); + + // Error? + if (isError) { + canvas.drawRect(fullRect, errorPaint); + } else { + // Background rounded rectangle + canvas.drawRect(fullRect, notdonePaint); + + // Foreground progress indicator + if (progress > 0) { + progressRect.set(0, 0, width * ((float) progress / 100), height); + canvas.drawRect(progressRect, (isActive ? (progress == 100 ? donePaint : progressPaint) + : (progress == 100 ? inactiveDonePaint : inactivePaint))); + } + } + + } + +} diff --git a/lite/src/org/transdroid/core/gui/lists/TorrentView.java b/lite/src/org/transdroid/core/gui/lists/TorrentView.java new file mode 100644 index 00000000..897a6b0e --- /dev/null +++ b/lite/src/org/transdroid/core/gui/lists/TorrentView.java @@ -0,0 +1,47 @@ +package org.transdroid.core.gui.lists; + +import org.androidannotations.annotations.EViewGroup; +import org.androidannotations.annotations.ViewById; +import org.transdroid.core.R; +import org.transdroid.daemon.Torrent; +import org.transdroid.daemon.TorrentStatus; + +import android.content.Context; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import fr.marvinlabs.widget.CheckableRelativeLayout; + +/** + * View that represents some {@link Torrent} object and displays progress, status, speeds, etc. + * @author Eric Kok + */ +@EViewGroup(R.layout.list_item_torrent) +public class TorrentView extends CheckableRelativeLayout { + + @ViewById + protected ImageView priorityImage; + @ViewById + protected TextView nameText, ratioText, progressText, speedText, peersText; + @ViewById + protected TorrentProgressBar torrentProgressbar; + + public TorrentView(Context context) { + super(context); + } + + public void bind(Torrent torrent) { + LocalTorrent local = LocalTorrent.fromTorrent(torrent); + nameText.setText(torrent.getName()); + ratioText.setText(local.getProgressEtaRatioText(getResources())); + progressText.setText(local.getProgressSizeText(getResources(), false)); + speedText.setText(local.getProgressSpeedText(getResources())); + peersText.setText(local.getProgressConnectionText(getResources())); + torrentProgressbar.setProgress((int) (torrent.getDownloadedPercentage() * 100)); + torrentProgressbar.setActive(torrent.canPause());; + torrentProgressbar.setError(torrent.getStatusCode() == TorrentStatus.Error); + // TODO: Implement per-torrent priority and set priorityImage + priorityImage.setVisibility(View.INVISIBLE); + } + +} diff --git a/lite/src/org/transdroid/core/gui/lists/TorrentsAdapter.java b/lite/src/org/transdroid/core/gui/lists/TorrentsAdapter.java new file mode 100644 index 00000000..eeb17774 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/lists/TorrentsAdapter.java @@ -0,0 +1,72 @@ +package org.transdroid.core.gui.lists; + +import java.util.ArrayList; + +import org.androidannotations.annotations.EBean; +import org.androidannotations.annotations.RootContext; +import org.transdroid.core.gui.lists.TorrentView_; +import org.transdroid.daemon.Torrent; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +/** + * Adapter that contains a list of torrent objects to show. + * @author Eric Kok + */ +@EBean +public class TorrentsAdapter extends BaseAdapter { + + private ArrayList torrents = null; + + @RootContext + protected Context context; + + /** + * Allows updating the full internal list of torrents at once, replacing the old list + * @param newTorrents The new list of torrent objects + */ + public void update(ArrayList newTorrents) { + this.torrents = newTorrents; + notifyDataSetChanged(); + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public int getCount() { + if (torrents == null) + return 0; + return torrents.size(); + } + + @Override + public Torrent getItem(int position) { + if (torrents == null) + return null; + return torrents.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TorrentView torrentView; + if (convertView == null) { + torrentView = TorrentView_.build(context); + } else { + torrentView = (TorrentView) convertView; + } + torrentView.bind(getItem(position)); + return torrentView; + } + +} diff --git a/lite/src/org/transdroid/core/gui/navigation/FilterListAdapter.java b/lite/src/org/transdroid/core/gui/navigation/FilterListAdapter.java new file mode 100644 index 00000000..98a365a9 --- /dev/null +++ b/lite/src/org/transdroid/core/gui/navigation/FilterListAdapter.java @@ -0,0 +1,104 @@ +package org.transdroid.core.gui.navigation; + +import java.util.List; + +import org.androidannotations.annotations.EBean; +import org.androidannotations.annotations.RootContext; +import org.transdroid.core.R; +import org.transdroid.core.gui.lists.SimpleListItem; +import org.transdroid.core.gui.lists.SimpleListItemAdapter; +import org.transdroid.core.gui.navigation.NavigationSelectionView.NavigationFilterManager; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SpinnerAdapter; + +import com.commonsware.cwac.merge.MergeAdapter; + +/** + * List adapter that holds filter items, that is, servers, view types and labels. A header item is inserted where + * appropriate. + * @author Eric Kok + */ +@EBean +public class FilterListAdapter extends MergeAdapter implements SpinnerAdapter { + + @RootContext + protected Context context; + private SimpleListItemAdapter serverItems = null; + private SimpleListItemAdapter statusTypeItems = null; + private SimpleListItemAdapter labelItems = null; + private NavigationFilterManager navigationFilterManager; + + /** + * Stores which screen, or manager, handles navigation selection and display + * @param manager The navigation manager, which knows about the currently selected filter and server + * @return Itself, for method chaining + */ + public FilterListAdapter setNavigationFilterManager(NavigationFilterManager manager) { + this.navigationFilterManager = manager; + return this; + } + + /** + * Update the list of available servers + * @param servers The new list of available servers + */ + public void updateServers(List servers) { + if (this.serverItems == null && servers != null) { + addView(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_servers)), false); + this.serverItems = new SimpleListItemAdapter(context, servers); + addAdapter(serverItems); + } else if (this.serverItems != null && servers != null) { + this.serverItems.update(servers); + } else { + this.serverItems = null; + } + } + + /** + * Update the list of available status types + * @param statusTypes The new list of available status types + */ + public void updateStatusTypes(List statusTypes) { + if (this.statusTypeItems == null && statusTypes != null) { + addView(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_status)), false); + this.statusTypeItems = new SimpleListItemAdapter(context, statusTypes); + addAdapter(statusTypeItems); + } else if (this.statusTypeItems != null && statusTypes != null) { + this.statusTypeItems.update(statusTypes); + } else { + this.statusTypeItems = null; + } + } + + /** + * Update the list of available labels + * @param labels The new list of available labels + */ + public void updateLabels(List labels) { + if (this.labelItems == null && labels != null) { + addView(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_labels)), false); + this.labelItems = new SimpleListItemAdapter(context, labels); + addAdapter(labelItems); + } else if (this.serverItems != null && labels != null) { + this.labelItems.update(labels); + } else { + this.labelItems = null; + } + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + NavigationSelectionView filterItemView; + if (convertView == null || !(convertView instanceof NavigationSelectionView)) { + filterItemView = NavigationSelectionView_.build(context).setNavigationFilterManager(navigationFilterManager); + } else { + filterItemView = (NavigationSelectionView) convertView; + } + filterItemView.bind(); + return filterItemView; + } + +} diff --git a/lite/src/org/transdroid/core/gui/navigation/FilterSeparatorView.java b/lite/src/org/transdroid/core/gui/navigation/FilterSeparatorView.java new file mode 100644 index 00000000..a26516dc --- /dev/null +++ b/lite/src/org/transdroid/core/gui/navigation/FilterSeparatorView.java @@ -0,0 +1,38 @@ +package org.transdroid.core.gui.navigation; + +import org.androidannotations.annotations.EViewGroup; +import org.androidannotations.annotations.ViewById; +import org.transdroid.core.R; + +import android.content.Context; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * A list item that shows a sub header or separator (in underlined Holo style). + * + * @author Eric Kok + */ +@EViewGroup(R.layout.list_item_separator) +public class FilterSeparatorView extends LinearLayout { + + protected String text; + + @ViewById + protected TextView separatorText; + + public FilterSeparatorView(Context context) { + super(context); + } + + /** + * Sets the text that will be shown in this separator (sub header) + * @param text The new text to show + * @return Itself, for convenience of method chaining + */ + public FilterSeparatorView setText(String text) { + separatorText.setText(text); + return this; + } + +} diff --git a/lite/src/org/transdroid/core/gui/navigation/Label.java b/lite/src/org/transdroid/core/gui/navigation/Label.java new file mode 100644 index 00000000..748d3a7e --- /dev/null +++ b/lite/src/org/transdroid/core/gui/navigation/Label.java @@ -0,0 +1,55 @@ +package org.transdroid.core.gui.navigation; + +import org.transdroid.core.gui.lists.SimpleListItem; +import org.transdroid.daemon.Torrent; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents some label that is active or available on the server. + * @author Eric Kok + */ +public class Label implements SimpleListItem, NavigationFilter { + + private final String name; + + public Label(String name) { + this.name = name; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public boolean matches(Torrent torrent) { + return torrent.getLabelName() != null && torrent.getLabelName().equals(name); + } + + private Label(Parcel in) { + this.name = in.readString(); + } + + public static final Parcelable.Creator