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