diff --git a/.gitignore b/.gitignore
index ccf2efe..afdf45b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,9 @@ proguard/
# Log Files
*.log
+
+.gradle
+.idea
+.DS_Store
+/captures
+*.iml
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..49e1625
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.library'
+apply plugin: 'me.tatarka.retrolambda'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.2"
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ defaultConfig {
+ minSdkVersion 9
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:23.1.0'
+ compile 'com.facebook.fresco:fbcore:0.7.0'
+ compile 'io.reactivex:rxandroid:1.0.1'
+}
+
+apply from: "${rootDir}/libraries/BuildScripts/gradle/staticAnalysis.gradle"
diff --git a/proguard-rules.pro b/proguard-rules.pro
new file mode 100644
index 0000000..181828a
--- /dev/null
+++ b/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\ZuZuK\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# 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/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f0ab3f7
--- /dev/null
+++ b/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/src/main/java/org/roboswag/components/audio/HeadsetStateObserver.java b/src/main/java/org/roboswag/components/audio/HeadsetStateObserver.java
new file mode 100644
index 0000000..134f048
--- /dev/null
+++ b/src/main/java/org/roboswag/components/audio/HeadsetStateObserver.java
@@ -0,0 +1,80 @@
+package org.roboswag.components.audio;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import rx.Observable;
+import rx.subjects.BehaviorSubject;
+
+/**
+ * Created by Gavriil Sitnikov on 02/11/2015.
+ * TODO: fill description
+ */
+public class HeadsetStateObserver {
+
+ @Nullable
+ private static HeadsetStateObserver instance;
+
+ @NonNull
+ public synchronized static HeadsetStateObserver getInstance(@NonNull Context context) {
+ if (instance == null) {
+ instance = new HeadsetStateObserver(context);
+ }
+ return instance;
+ }
+
+ private final AudioManager audioManager;
+ private final BehaviorSubject isPluggedInSubject;
+ private final Observable isPluggedInObservable;
+
+ @Nullable
+ private IsPluggedInReceiver isPluggedInReceiver;
+
+ private HeadsetStateObserver(@NonNull Context context) {
+ audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ isPluggedInSubject = BehaviorSubject.create();
+ isPluggedInObservable = isPluggedInSubject
+ .distinctUntilChanged()
+ .doOnSubscribe(() -> {
+ isPluggedInReceiver = new IsPluggedInReceiver();
+ context.registerReceiver(isPluggedInReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
+ isPluggedInSubject.onNext(isPluggedIn());
+ })
+ .doOnUnsubscribe(() -> {
+ if (isPluggedInReceiver == null) {
+ throw new IllegalStateException("IsPluggedInReceiver is null on unsubscribe");
+ }
+
+ context.unregisterReceiver(isPluggedInReceiver);
+ isPluggedInReceiver = null;
+ })
+ .replay(1)
+ .refCount();
+ }
+
+ @SuppressWarnings("deprecation")
+ public boolean isPluggedIn() {
+ return audioManager.isWiredHeadsetOn();
+ }
+
+ public Observable observeIsPluggedIn() {
+ return isPluggedInObservable;
+ }
+
+ private class IsPluggedInReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_HEADSET_PLUG.equals(intent.getAction())) {
+ isPluggedInSubject.onNext(intent.getExtras().getInt("state") != 0);
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/roboswag/components/audio/VolumeController.java b/src/main/java/org/roboswag/components/audio/VolumeController.java
new file mode 100644
index 0000000..e09e7c0
--- /dev/null
+++ b/src/main/java/org/roboswag/components/audio/VolumeController.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
+ *
+ * This file is part of RoboSwag library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.roboswag.components.audio;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+
+import rx.Observable;
+import rx.Subscription;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.subjects.BehaviorSubject;
+
+/**
+ * Created by Gavriil Sitnikov on 02/11/2015.
+ * TODO: fill description
+ */
+public class VolumeController {
+
+ @Nullable
+ private static VolumeController instance;
+
+ @NonNull
+ public synchronized static VolumeController getInstance(@NonNull Context context) {
+ if (instance == null) {
+ instance = new VolumeController(context);
+ }
+ return instance;
+ }
+
+ private final AudioManager audioManager;
+ private final int maxVolume;
+ private final BehaviorSubject volumeSubject;
+ private final Observable volumeObservable;
+
+ @Nullable
+ private VolumeObserver volumeObserver;
+ @Nullable
+ private SeekBar seekBar;
+ @Nullable
+ private ImageView volumeDown;
+ @Nullable
+ private ImageView volumeUp;
+ @Nullable
+ private Subscription seekBarSubscription;
+
+ private VolumeController(@NonNull Context context) {
+ audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ volumeSubject = BehaviorSubject.create();
+ volumeObservable = volumeSubject
+ .distinctUntilChanged()
+ .doOnSubscribe(() -> {
+ volumeObserver = new VolumeObserver();
+ context.getContentResolver()
+ .registerContentObserver(Settings.System.CONTENT_URI, true, volumeObserver);
+ updateVolume();
+ })
+ .doOnUnsubscribe(() -> {
+ if (volumeObserver == null) {
+ throw new IllegalStateException("VolumeObserver is null on unsubscribe");
+ }
+ context.getContentResolver()
+ .unregisterContentObserver(volumeObserver);
+ volumeObserver = null;
+ })
+ .replay(1)
+ .refCount();
+ }
+
+ private void updateVolume() {
+ volumeSubject.onNext(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+ }
+
+ public void setVolume(int value) {
+ if (value < 0 || value > maxVolume) {
+ throw new IllegalStateException("Volume: " + value + " out of bounds [0," + maxVolume + "]");
+ }
+ if (getVolume() != value) {
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, value, 0);
+ }
+ }
+
+ public int getVolume() {
+ return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ }
+
+ @NonNull
+ public Observable observeVolume() {
+ return volumeObservable;
+ }
+
+ public void attachSeekBar(@NonNull SeekBar seekBar) {
+ if (this.seekBar != null) {
+ throw new IllegalArgumentException("Attached SeekBar is not null");
+ }
+ this.seekBar = seekBar;
+ this.seekBar.setMax(maxVolume);
+ this.seekBar.setProgress(getVolume());
+ this.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ setVolume(progress);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+
+ });
+
+ seekBarSubscription = observeVolume()
+ .subscribeOn(AndroidSchedulers.mainThread())
+ .subscribe(seekBar::setProgress);
+ }
+
+ public void attachVolumeButtons(@NonNull ImageView volumeDown, @NonNull ImageView volumeUp) {
+ if (this.volumeDown != null && this.volumeUp != null) {
+ throw new IllegalArgumentException("Attached volume buttons is not null");
+ }
+ this.volumeDown = volumeDown;
+ this.volumeUp = volumeUp;
+
+ volumeUp.setOnClickListener(v -> {
+ if (getVolume() != maxVolume) {
+ setVolume(getVolume() + 1);
+ }
+ });
+
+ volumeDown.setOnClickListener(v -> {
+ if (getVolume() != 0) {
+ setVolume(getVolume() - 1);
+ }
+ });
+ seekBarSubscription = observeVolume()
+ .subscribeOn(AndroidSchedulers.mainThread())
+ .subscribe(seekBar::setProgress);
+ }
+
+ public void detachSeekBar(@NonNull SeekBar seekBar) {
+ if (this.seekBar != seekBar) {
+ throw new IllegalArgumentException("Wrong SeekBar: " + seekBar + " != " + this.seekBar);
+ }
+ if (seekBarSubscription == null) {
+ throw new IllegalStateException("SeekBarSubscription is null on detach of SeekBar");
+ }
+
+ this.seekBar.setOnSeekBarChangeListener(null);
+ seekBarSubscription.unsubscribe();
+ seekBarSubscription = null;
+ this.seekBar = null;
+ }
+
+ public void detachVolumeButtons(@NonNull ImageView volumeDownImageView, @NonNull ImageView volumeUpImageView) {
+ if (this.volumeDown != volumeDownImageView && this.volumeUp != volumeUpImageView) {
+ throw new IllegalArgumentException("Wrong SeekBar: " + seekBar + " != " + this.seekBar);
+ }
+
+ this.volumeDown = null;
+ this.volumeUp = null;
+ }
+
+ private class VolumeObserver extends ContentObserver {
+
+ public VolumeObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ updateVolume();
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/roboswag/components/navigation/BaseActivity.java b/src/main/java/org/roboswag/components/navigation/BaseActivity.java
new file mode 100644
index 0000000..61770db
--- /dev/null
+++ b/src/main/java/org/roboswag/components/navigation/BaseActivity.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
+ *
+ * This file is part of RoboSwag library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.roboswag.components.navigation;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+/**
+ * Created by Gavriil Sitnikov on 21/10/2015.
+ * TODO: fill description
+ */
+public abstract class BaseActivity extends AppCompatActivity
+ implements FragmentManager.OnBackStackChangedListener,
+ OnFragmentStartedListener {
+
+ private static final String TOP_FRAGMENT_TAG_MARK = "TOP_FRAGMENT";
+
+ private boolean isPaused = false;
+
+ /* Returns id of main fragments container where navigation-node fragments should be */
+ protected int getFragmentContainerId() {
+ throw new UnsupportedOperationException("Implement getFragmentContainerId method to use fragment managing");
+ }
+
+ /* Returns if last fragment in stack is top (added by setFragment) like fragment from sidebar menu */
+ public boolean isCurrentFragmentTop() {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ if (fragmentManager.getBackStackEntryCount() == 0) {
+ return true;
+ }
+
+ String topFragmentTag = fragmentManager
+ .getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1)
+ .getName();
+ return topFragmentTag != null && topFragmentTag.contains(TOP_FRAGMENT_TAG_MARK);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getSupportFragmentManager().addOnBackStackChangedListener(this);
+ }
+
+ @Override
+ protected void onResume() {
+ isPaused = false;
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ isPaused = true;
+ super.onPause();
+ }
+
+ @Override
+ public void onFragmentStarted(BaseFragment fragment) {
+ }
+
+ /* Raises when back stack changes */
+ @Override
+ public void onBackStackChanged() {
+ }
+
+ /* Setting fragment of special class as first in stack */
+ public Fragment setFirstFragment(Class> fragmentClass) {
+ return setFirstFragment(fragmentClass, null);
+ }
+
+ /* Setting fragment of special class as first in stack with args */
+ public Fragment setFirstFragment(Class> fragmentClass, Bundle args) {
+ if (isPaused) {
+ //TODO: log
+ return null;
+ }
+
+ FragmentManager fragmentManager = getSupportFragmentManager();
+
+ if (fragmentManager.getBackStackEntryCount() > 0) {
+ fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ }
+
+ Fragment fragment = Fragment.instantiate(this, fragmentClass.getName(), args);
+ fragmentManager.beginTransaction()
+ .replace(getFragmentContainerId(), fragment, null)
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
+ .commit();
+ return fragment;
+ }
+
+ private Fragment addFragmentToStack(Class> fragmentClass, Bundle args, String backStackTag) {
+ if (isPaused) {
+ //TODO: log
+ return null;
+ }
+
+ Fragment fragment = Fragment.instantiate(this, fragmentClass.getName(), args);
+ getSupportFragmentManager().beginTransaction()
+ .replace(getFragmentContainerId(), fragment, backStackTag)
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
+ .addToBackStack(backStackTag)
+ .commit();
+ return fragment;
+ }
+
+ /* Setting fragment of special class as top */
+ public Fragment setFragment(Class fragmentClass) {
+ return setFragment(fragmentClass, null);
+ }
+
+ /* Setting fragment of special class as top with args */
+ public Fragment setFragment(Class fragmentClass, Bundle args) {
+ return addFragmentToStack(fragmentClass, args, fragmentClass.getName() + ' ' + TOP_FRAGMENT_TAG_MARK);
+ }
+
+ /* Pushing fragment of special class to fragments stack */
+ public Fragment pushFragment(Class fragmentClass) {
+ return pushFragment(fragmentClass, null);
+ }
+
+ /* Pushing fragment of special class with args to fragments stack */
+ public Fragment pushFragment(Class fragmentClass, Bundle args) {
+ return addFragmentToStack(fragmentClass, args, fragmentClass.getName());
+ }
+
+ /* Raises when device back button pressed */
+ @Override
+ public void onBackPressed() {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+
+ boolean backPressResult = false;
+ if (fragmentManager.getFragments() != null) {
+ for (Fragment fragment : fragmentManager.getFragments()) {
+ if (fragment != null && fragment.isResumed() && fragment instanceof BaseFragment) {
+ backPressResult = backPressResult || ((BaseFragment) fragment).onBackPressed();
+ }
+ }
+ }
+
+ if (!backPressResult) {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ FragmentManager fragmentManager = getSupportFragmentManager();
+
+ boolean homePressResult = false;
+ if (fragmentManager.getFragments() != null) {
+ for (Fragment fragment : fragmentManager.getFragments()) {
+ if (fragment != null
+ && fragment.isResumed()
+ && fragment instanceof BaseFragment) {
+ homePressResult = homePressResult || ((BaseFragment) fragment).onHomePressed();
+ }
+ }
+ }
+
+ if (homePressResult) {
+ return true;
+ }
+
+ int stackSize = fragmentManager.getBackStackEntryCount();
+
+ switch (stackSize) {
+ case 0:
+ return false;
+ case 1:
+ fragmentManager.popBackStack();
+ return true;
+ default:
+ String lastFragmentName = fragmentManager.getBackStackEntryAt(stackSize - 1).getName();
+ for (int i = stackSize - 2; i >= 0; i--) {
+ String currentFragmentName = fragmentManager.getBackStackEntryAt(i).getName();
+ if (currentFragmentName == null || !currentFragmentName.equals(lastFragmentName)) {
+ fragmentManager.popBackStackImmediate(currentFragmentName, 0);
+ break;
+ } else if (i == 0) {
+ fragmentManager.popBackStackImmediate(currentFragmentName, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ } else {
+ lastFragmentName = currentFragmentName;
+ }
+ }
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /* Hides device keyboard */
+ public void hideSoftInput() {
+ if (getCurrentFocus() != null) {
+ InputMethodManager inputManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
+ View mainFragmentContainer = findViewById(getFragmentContainerId());
+ if (mainFragmentContainer != null) {
+ mainFragmentContainer.requestFocus();
+ }
+ }
+ }
+
+ /* Shows device keyboard */
+ public void showSoftInput(View view) {
+ InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
+ }
+
+}
diff --git a/src/main/java/org/roboswag/components/navigation/BaseFragment.java b/src/main/java/org/roboswag/components/navigation/BaseFragment.java
new file mode 100644
index 0000000..2c98b4a
--- /dev/null
+++ b/src/main/java/org/roboswag/components/navigation/BaseFragment.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
+ *
+ * This file is part of RoboSwag library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.roboswag.components.navigation;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.view.View;
+
+/**
+ * Created by Gavriil Sitnikov on 21/10/2015.
+ * TODO: fill description
+ */
+public abstract class BaseFragment extends Fragment
+ implements OnFragmentStartedListener {
+
+ @Nullable
+ private TViewHolder viewHolder;
+
+ /* Returns base activity */
+ @Nullable
+ protected BaseActivity getBaseActivity() {
+ return (BaseActivity) getActivity();
+ }
+
+ @Nullable
+ protected TViewHolder getViewHolder() {
+ return viewHolder;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (view == null) {
+ throw new IllegalStateException("Background fragments are deprecated - view shouldn't be null");
+ }
+ viewHolder = createViewHolder(view, savedInstanceState);
+ }
+
+ @NonNull
+ protected abstract TViewHolder createViewHolder(@NonNull View view, @Nullable Bundle savedInstanceState);
+
+ @Override
+ public void onFragmentStarted(BaseFragment fragment) {
+ }
+
+ @Deprecated
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (viewHolder == null || getBaseActivity() == null) {
+ throw new IllegalStateException("ViewHolder or BaseActivity is null at onStart point");
+ }
+ onStart(viewHolder, getBaseActivity());
+ }
+
+ protected void onStart(@NonNull TViewHolder viewHolder, @NonNull BaseActivity baseActivity) {
+ Fragment parentFragment = getParentFragment();
+ if (parentFragment != null) {
+ if (parentFragment instanceof OnFragmentStartedListener) {
+ ((OnFragmentStartedListener) parentFragment).onFragmentStarted(this);
+ }
+ } else {
+ baseActivity.onFragmentStarted(this);
+ }
+ }
+
+ @Deprecated
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (viewHolder == null || getBaseActivity() == null) {
+ throw new IllegalStateException("ViewHolder or BaseActivity is null at onResume point");
+ }
+ onResume(viewHolder, getBaseActivity());
+ }
+
+ protected void onResume(@NonNull TViewHolder viewHolder, @NonNull BaseActivity baseActivity) {
+ }
+
+ /* Raises when device back button pressed */
+ public boolean onBackPressed() {
+ FragmentManager fragmentManager = getChildFragmentManager();
+ boolean result = false;
+
+ if (fragmentManager.getFragments() == null) {
+ return false;
+ }
+
+ for (Fragment fragment : fragmentManager.getFragments()) {
+ if (fragment != null
+ && fragment.isResumed()
+ && fragment instanceof BaseFragment) {
+ result = result || ((BaseFragment) fragment).onBackPressed();
+ }
+ }
+ return result;
+ }
+
+ /* Raises when ActionBar home button pressed */
+ public boolean onHomePressed() {
+ FragmentManager fragmentManager = getChildFragmentManager();
+ boolean result = false;
+
+ if (fragmentManager.getFragments() == null) {
+ return false;
+ }
+
+ for (Fragment fragment : fragmentManager.getFragments()) {
+ if (fragment != null
+ && fragment.isResumed()
+ && fragment instanceof BaseFragment) {
+ result = result || ((BaseFragment) fragment).onHomePressed();
+ }
+ }
+ return result;
+ }
+
+ @Deprecated
+ @Override
+ public void onPause() {
+ if (viewHolder == null || getBaseActivity() == null) {
+ throw new IllegalStateException("ViewHolder or BaseActivity is null at onPause point");
+ }
+ onPause(viewHolder, getBaseActivity());
+ super.onPause();
+ }
+
+ protected void onPause(@NonNull TViewHolder viewHolder, @NonNull BaseActivity baseActivity) {
+ }
+
+ @Deprecated
+ @Override
+ public void onStop() {
+ if (viewHolder == null || getBaseActivity() == null) {
+ throw new IllegalStateException("ViewHolder or BaseActivity is null at onStop point");
+ }
+ onStop(viewHolder, getBaseActivity());
+ super.onStop();
+ }
+
+ protected void onStop(@NonNull TViewHolder viewHolder, @NonNull BaseActivity baseActivity) {
+ }
+
+ @Deprecated
+ @Override
+ public void onDestroyView() {
+ if (viewHolder == null) {
+ throw new IllegalStateException("ViewHolder is null at onStop point");
+ }
+ onDestroyView(viewHolder);
+ super.onDestroyView();
+ this.viewHolder = null;
+ }
+
+ protected void onDestroyView(@NonNull TViewHolder viewHolder) {
+ viewHolder.onDestroy();
+ }
+
+ public class ViewHolder {
+
+ private final Context context;
+ private final Handler postHandler = new Handler();
+
+ /* Returns post handler to executes code on UI thread */
+ @NonNull
+ protected Handler getPostHandler() {
+ return postHandler;
+ }
+
+ @NonNull
+ protected Context getContext() {
+ return context;
+ }
+
+ public ViewHolder(@NonNull View view){
+ context = view.getContext();
+ }
+
+ protected void onDestroy() {
+ postHandler.removeCallbacksAndMessages(null);
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/roboswag/components/navigation/OnFragmentStartedListener.java b/src/main/java/org/roboswag/components/navigation/OnFragmentStartedListener.java
new file mode 100644
index 0000000..a3f4dc0
--- /dev/null
+++ b/src/main/java/org/roboswag/components/navigation/OnFragmentStartedListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
+ *
+ * This file is part of RoboSwag library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.roboswag.components.navigation;
+
+/**
+ * Created by Gavriil Sitnikov on 08/10/2014.
+ * Base interface to listen fragment changing
+ */
+public interface OnFragmentStartedListener {
+
+ /* Raises by fragment to notify that it is started */
+ void onFragmentStarted(BaseFragment fragment);
+}
\ No newline at end of file
diff --git a/src/main/java/org/roboswag/components/services/ServiceBinder.java b/src/main/java/org/roboswag/components/services/ServiceBinder.java
new file mode 100644
index 0000000..7887f07
--- /dev/null
+++ b/src/main/java/org/roboswag/components/services/ServiceBinder.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
+ *
+ * This file is part of RoboSwag library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.roboswag.components.services;
+
+import android.app.Service;
+import android.os.Binder;
+import android.support.annotation.NonNull;
+
+/**
+ * Created by Gavriil Sitnikov on 03/10/2015.
+ * Basic binding to service
+ */
+public class ServiceBinder extends Binder {
+
+ @NonNull
+ private final TService service;
+
+ public ServiceBinder(@NonNull TService service) {
+ this.service = service;
+ }
+
+ /* Returns service that binder holds */
+ @NonNull
+ public TService getService() {
+ return service;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/roboswag/components/telephony/IsCallingObserver.java b/src/main/java/org/roboswag/components/telephony/IsCallingObserver.java
new file mode 100644
index 0000000..6ed084a
--- /dev/null
+++ b/src/main/java/org/roboswag/components/telephony/IsCallingObserver.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
+ *
+ * This file is part of RoboSwag library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.roboswag.components.telephony;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+import rx.Observable;
+import rx.subjects.BehaviorSubject;
+
+/**
+ * Created by Gavriil Sitnikov on 02/11/2015.
+ * TODO: fill description
+ */
+public class IsCallingObserver {
+
+ @Nullable
+ private static IsCallingObserver instance;
+
+ @NonNull
+ public synchronized static IsCallingObserver getInstance(@NonNull Context context) {
+ if (instance == null) {
+ instance = new IsCallingObserver(context);
+ }
+ return instance;
+ }
+
+ private final BehaviorSubject isCallingSubject = BehaviorSubject.create();
+ private final Observable isCallingObservable;
+
+ private IsCallingObserver(@NonNull Context context) {
+ TelephonyManager phoneStateManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ phoneStateManager.listen(new PhoneStateListener() {
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ super.onCallStateChanged(state, incomingNumber);
+ isCallingSubject.onNext(isCallingState(state));
+ }
+ }, PhoneStateListener.LISTEN_CALL_STATE);
+ isCallingSubject.onNext(isCallingState(phoneStateManager.getCallState()));
+ isCallingObservable = isCallingSubject
+ .distinctUntilChanged()
+ .replay(1)
+ .refCount();
+ }
+
+ private boolean isCallingState(int state) {
+ return state != TelephonyManager.CALL_STATE_IDLE;
+ }
+
+ @NonNull
+ public Observable observeIsCalling() {
+ return isCallingObservable;
+ }
+
+}
diff --git a/src/main/java/org/roboswag/components/utils/FrescoUtils.java b/src/main/java/org/roboswag/components/utils/FrescoUtils.java
new file mode 100644
index 0000000..3702705
--- /dev/null
+++ b/src/main/java/org/roboswag/components/utils/FrescoUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
+ *
+ * This file is part of RoboSwag library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.roboswag.components.utils;
+
+import android.net.Uri;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+
+import com.facebook.common.util.UriUtil;
+
+/**
+ * Created by Gavriil Sitnikov on 20/10/2015.
+ * TODO: fill description
+ */
+public final class FrescoUtils {
+
+ @NonNull
+ public static Uri getResourceUri(@DrawableRes int resourceId) {
+ return new Uri.Builder().scheme(UriUtil.LOCAL_RESOURCE_SCHEME).path(String.valueOf(resourceId)).build();
+ }
+
+ private FrescoUtils() {
+ }
+
+}
diff --git a/src/main/java/org/roboswag/components/utils/Typefaces.java b/src/main/java/org/roboswag/components/utils/Typefaces.java
new file mode 100644
index 0000000..6904b8a
--- /dev/null
+++ b/src/main/java/org/roboswag/components/utils/Typefaces.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
+ *
+ * This file is part of RoboSwag library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.roboswag.components.utils;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Created by Gavriil Sitnikov on 18/07/2014.
+ * Typefaces manager
+ */
+public class Typefaces {
+
+ private static final HashMap TYPEFACES = new HashMap<>();
+
+ /**
+ * Returns typeface by name from assets 'fonts' folder
+ */
+ @NonNull
+ public synchronized static Typeface getByName(@NonNull Context context, @NonNull String name) {
+ Typeface result = TYPEFACES.get(name);
+ if (result == null) {
+ AssetManager assetManager = context.getAssets();
+ try {
+ List fonts = Arrays.asList(assetManager.list("fonts"));
+ if (fonts.contains(name + ".ttf")) {
+ result = Typeface.createFromAsset(assetManager, "fonts/" + name + ".ttf");
+ } else if (fonts.contains(name + ".otf")) {
+ result = Typeface.createFromAsset(assetManager, "fonts/" + name + ".otf");
+ } else
+ throw new IllegalStateException("Can't find .otf or .ttf file in folder 'fonts' with name: " + name);
+ } catch (IOException e) {
+ throw new IllegalStateException("Typefaces files should be in folder named 'fonts'");
+ }
+ TYPEFACES.put(name, result);
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/roboswag/components/views/TypefacedTextView.java b/src/main/java/org/roboswag/components/views/TypefacedTextView.java
new file mode 100644
index 0000000..43b6587
--- /dev/null
+++ b/src/main/java/org/roboswag/components/views/TypefacedTextView.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
+ *
+ * This file is part of RoboSwag library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.roboswag.components.views;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import org.roboswag.components.R;
+
+import org.roboswag.components.utils.Typefaces;
+
+/**
+ * Created by Gavriil Sitnikov on 18/07/2014.
+ * TextView that supports fonts from Typefaces class
+ */
+public class TypefacedTextView extends TextView {
+
+ public TypefacedTextView(Context context) {
+ super(context);
+ initialize(context, null);
+ }
+
+ public TypefacedTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(context, attrs);
+ }
+
+ public TypefacedTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initialize(context, attrs);
+ }
+
+ public void setTypeface(String name, int style) {
+ setTypeface(Typefaces.getByName(getContext(), name), style);
+ }
+
+ public void setTypeface(String name) {
+ setTypeface(name, Typeface.NORMAL);
+ }
+
+ private void initialize(Context context, AttributeSet attrs) {
+ String customTypeface = null;
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TypefacedTextView);
+ customTypeface = a.getString(R.styleable.TypefacedTextView_customTypeface);
+ a.recycle();
+ }
+
+ if (customTypeface != null && !isInEditMode()) {
+ Typeface typeface = getTypeface();
+ setTypeface(customTypeface, typeface != null ? typeface.getStyle() : Typeface.NORMAL);
+ }
+ }
+
+}
diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..1ff4753
--- /dev/null
+++ b/src/main/res/values/attrs.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file