diff --git a/Habitica/assets/migrations/Habitica/29.sql b/Habitica/assets/migrations/Habitica/29.sql new file mode 100644 index 000000000..1f9c5522b --- /dev/null +++ b/Habitica/assets/migrations/Habitica/29.sql @@ -0,0 +1 @@ +ALTER TABLE Preferences ADD COLUMN 'sound' varchar(50); \ No newline at end of file diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 724a8567a..b8851355b 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -433,4 +433,7 @@ To start, which parts of your life do you want to improve? Open Market Your Dailies will next reset the first time you use Habitica after %1$s. Make sure you have completed your Dailies before this time! + + Audio Theme + Change Habitica\'s Audio Theme diff --git a/Habitica/res/values/values.xml b/Habitica/res/values/values.xml index f4a5b03ef..296e9e3b4 100644 --- a/Habitica/res/values/values.xml +++ b/Habitica/res/values/values.xml @@ -86,4 +86,24 @@ zh_TW + + + Off + Daniel the Bard + Watts\' Theme + Gokul Theme + LuneFox\'s Theme + Rosstavo\'s Theme + Dewin\'s Theme + + + + off + danielTheBard + wattsTheme + gokulTheme + luneFoxTheme + rosstavoTheme + dewinTheme + diff --git a/Habitica/res/xml/preferences_fragment.xml b/Habitica/res/xml/preferences_fragment.xml index ed3c5b961..78210bb17 100644 --- a/Habitica/res/xml/preferences_fragment.xml +++ b/Habitica/res/xml/preferences_fragment.xml @@ -67,14 +67,19 @@ android:entries="@array/weekdays" android:entryValues="@array/weekdayValues" android:summary="@string/pref_first_day_of_the_week_summary" - android:order="4"/> + android:order="5"/> + - > download(List files) { + return Observable.from(files) + .flatMap(audioFile -> { + File file = new File(getFullAudioFilePath(audioFile)); + if (file.exists()) { + // Important, or else the MediaPlayer can't access this file + file.setReadable(true, false); + audioFile.setFile(file); + return Observable.just(audioFile); + } + + final Observable fileObservable = Observable.create(sub -> { + Request request = new Request.Builder().url(audioFile.getWebUrl()).build(); + + Response response; + try { + response = client.newCall(request).execute(); + if (!response.isSuccessful()) { throw new IOException(); } + } catch (IOException io) { + throw OnErrorThrowable.from(OnErrorThrowable.addValueAsLastCause(io, audioFile)); + } + + if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + try { + BufferedSink sink = Okio.buffer(Okio.sink(file)); + sink.writeAll(response.body().source()); + sink.flush(); + sink.close(); + } catch (IOException io) { + throw OnErrorThrowable.from(OnErrorThrowable.addValueAsLastCause(io, audioFile)); + } + + file.setReadable(true, false); + audioFile.setFile(file); + sub.onNext(audioFile); + sub.onCompleted(); + } + }); + return fileObservable.subscribeOn(Schedulers.io()); + }, 5) + .toList() + .map(ArrayList::new); + } + + private String getExternalCacheDir() { + return HabiticaApplication.getInstance(HabiticaApplication.currentActivity).getExternalCacheDir().getPath(); + } + + public String getFullAudioFilePath(SoundFile soundFile) { + return getExternalCacheDir() + File.separator + soundFile.getFilePath(); + } +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.java b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.java new file mode 100644 index 000000000..1e14120ca --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/SoundManager.java @@ -0,0 +1,95 @@ +package com.habitrpg.android.habitica.helpers; + +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.util.Log; + +import com.habitrpg.android.habitica.HabiticaApplication; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import javax.inject.Inject; + +import rx.Observable; +import rx.schedulers.Schedulers; + +public class SoundManager { + public static String SoundAchievementUnlocked = "Achievement_Unlocked"; + public static String SoundChat = "Chat"; + public static String SoundDaily = "Daily"; + public static String SoundDeath = "Death"; + public static String SoundItemDrop = "Item_Drop"; + public static String SoundLevelUp = "Level_Up"; + public static String SoundMinusHabit = "Minus_Habit"; + public static String SoundPlusHabit = "Plus_Habit"; + public static String SoundReward = "Reward"; + public static String SoundTodo = "ToDo"; + + @Inject + SoundFileLoader soundFileLoader; + private String soundTheme; + + private MediaPlayer mp = new MediaPlayer(); + + private HashMap loadedSoundFiles; + + public SoundManager(){ + HabiticaApplication.getInstance(HabiticaApplication.currentActivity).getComponent().inject(this); + + loadedSoundFiles = new HashMap<>(); + } + + public void setSoundTheme(String soundTheme){ + this.soundTheme = soundTheme; + } + + public Observable> preloadAllFiles() { + if(soundTheme == "off") { + return Observable.empty(); + } + + ArrayList soundFiles = new ArrayList<>(); + + soundFiles.add(new SoundFile(soundTheme, SoundAchievementUnlocked)); + soundFiles.add(new SoundFile(soundTheme, SoundChat)); + soundFiles.add(new SoundFile(soundTheme, SoundDaily)); + soundFiles.add(new SoundFile(soundTheme, SoundDeath)); + soundFiles.add(new SoundFile(soundTheme, SoundItemDrop)); + soundFiles.add(new SoundFile(soundTheme, SoundLevelUp)); + soundFiles.add(new SoundFile(soundTheme, SoundMinusHabit)); + soundFiles.add(new SoundFile(soundTheme, SoundPlusHabit)); + soundFiles.add(new SoundFile(soundTheme, SoundReward)); + soundFiles.add(new SoundFile(soundTheme, SoundTodo)); + return soundFileLoader.download(soundFiles); + } + + public void clearLoadedFiles(){ + loadedSoundFiles.clear(); + } + + public void loadAndPlayAudio(String type){ + if(soundTheme == "off") + { + return; + } + + if(loadedSoundFiles.containsKey(type)){ + loadedSoundFiles.get(type).play(); + } else { + ArrayList soundFiles = new ArrayList<>(); + + soundFiles.add(new SoundFile(soundTheme, type)); + soundFileLoader.download(soundFiles).observeOn(Schedulers.newThread()).subscribe(audioFiles1 -> { + SoundFile file = soundFiles.get(0); + + loadedSoundFiles.put(type, file); + file.play(); + + }, throwable -> throwable.printStackTrace()); + } + } + +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.java b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.java index ce5bda5e2..8fb00425b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.java @@ -1,15 +1,16 @@ package com.habitrpg.android.habitica.modules; -import com.habitrpg.android.habitica.APIHelper; -import com.habitrpg.android.habitica.HabiticaApplication; -import com.habitrpg.android.habitica.R; -import com.habitrpg.android.habitica.helpers.TagsHelper; - import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.support.v7.preference.PreferenceManager; +import com.habitrpg.android.habitica.HabiticaApplication; +import com.habitrpg.android.habitica.R; +import com.habitrpg.android.habitica.helpers.SoundFileLoader; +import com.habitrpg.android.habitica.helpers.SoundManager; +import com.habitrpg.android.habitica.helpers.TagsHelper; + import javax.inject.Named; import javax.inject.Singleton; @@ -53,4 +54,15 @@ public class AppModule { public Resources providesResources(Context context) { return context.getResources(); } + + @Provides + public SoundFileLoader providesSoundFileLoader(){ + return new SoundFileLoader(); + } + + @Provides + @Singleton + public SoundManager providesSoundManager() { + return new SoundManager(); + } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.java b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.java index b8d9f7f34..7f0a8d294 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.java @@ -13,6 +13,7 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.NotificationCompat; +import android.util.Log; /** * Created by keithholliday on 5/29/16. diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java index 575050a90..90cdd6577 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java @@ -4,14 +4,12 @@ import android.app.AlarmManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.content.ComponentName; - import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; - import android.content.res.Configuration; import android.database.sqlite.SQLiteDoneException; import android.databinding.DataBindingUtil; @@ -21,6 +19,10 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.SoundPool; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -41,12 +43,13 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; -import com.amplitude.api.Amplitude; import com.habitrpg.android.habitica.APIHelper; +import com.habitrpg.android.habitica.helpers.SoundFile; import com.habitrpg.android.habitica.HabiticaApplication; import com.habitrpg.android.habitica.HostConfig; import com.habitrpg.android.habitica.NotificationPublisher; import com.habitrpg.android.habitica.R; +import com.habitrpg.android.habitica.helpers.SoundFileLoader; import com.habitrpg.android.habitica.callbacks.HabitRPGUserCallback; import com.habitrpg.android.habitica.callbacks.ItemsCallback; import com.habitrpg.android.habitica.callbacks.MergeUserCallback; @@ -84,6 +87,7 @@ import com.habitrpg.android.habitica.events.commands.UnlockPathCommand; import com.habitrpg.android.habitica.events.commands.UpdateUserCommand; import com.habitrpg.android.habitica.helpers.AmplitudeManager; import com.habitrpg.android.habitica.helpers.LanguageHelper; +import com.habitrpg.android.habitica.helpers.SoundManager; import com.habitrpg.android.habitica.helpers.TaskAlarmManager; import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager; import com.habitrpg.android.habitica.ui.AvatarView; @@ -143,8 +147,6 @@ import com.raizlabs.android.dbflow.structure.BaseModel; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; -import org.json.JSONException; -import org.json.JSONObject; import org.solovyev.android.checkout.ActivityCheckout; import org.solovyev.android.checkout.Checkout; @@ -157,7 +159,6 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.TimeZone; import java.util.concurrent.TimeUnit; @@ -167,6 +168,7 @@ import butterknife.BindView; import retrofit2.adapter.rxjava.HttpException; import rx.Observable; import rx.functions.Action1; +import rx.schedulers.Schedulers; import static com.habitrpg.android.habitica.ui.helpers.UiUtils.SnackbarDisplayType; import static com.habitrpg.android.habitica.ui.helpers.UiUtils.showSnackbar; @@ -179,6 +181,9 @@ public class MainActivity extends BaseActivity implements Action1, Ha public static final int MIN_LEVEL_FOR_SKILLS = 11; @Inject public APIHelper apiHelper; + + @Inject + public SoundManager soundManager; @Inject public MaintenanceApiService maintenanceService; public HabitRPGUser user; @@ -255,6 +260,8 @@ public class MainActivity extends BaseActivity implements Action1, Ha getResources().updateConfiguration(configuration, getResources().getDisplayMetrics()); + + if (!HabiticaApplication.checkUserAuthentication(this, hostConfig)) { return; } @@ -382,6 +389,8 @@ public class MainActivity extends BaseActivity implements Action1, Ha apiHelper.languageCode = preferences.getLanguage(); } + soundManager.setSoundTheme(preferences.getSound()); + Calendar calendar = new GregorianCalendar(); TimeZone timeZone = calendar.getTimeZone(); long offset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.getTimeInMillis()), TimeUnit.MILLISECONDS); @@ -1002,6 +1011,7 @@ public class MainActivity extends BaseActivity implements Action1, Ha } else { snackbarMessage = getApplicationContext().getString(R.string.armoireExp); } + soundManager.loadAndPlayAudio(SoundManager.SoundItemDrop); } else if (!event.Reward.getId().equals("potion")) { EventBus.getDefault().post(new TaskRemovedEvent(event.Reward.getId())); } @@ -1031,6 +1041,7 @@ public class MainActivity extends BaseActivity implements Action1, Ha }, throwable -> { }); } else { + soundManager.loadAndPlayAudio(SoundManager.SoundReward); // user created Rewards apiHelper.apiService.postTaskDirection(rewardKey, TaskDirection.down.toString()) .compose(apiHelper.configureApiCallObserver()) @@ -1205,7 +1216,10 @@ public class MainActivity extends BaseActivity implements Action1, Ha private void showSnackBarForDataReceived(final TaskDirectionData data) { if (data.get_tmp() != null) { if (data.get_tmp().getDrop() != null) { - new Handler().postDelayed(() -> showSnackbar(MainActivity.this, floatingMenuWrapper, data.get_tmp().getDrop().getDialog(), SnackbarDisplayType.DROP), 3000L); + new Handler().postDelayed(() -> { + showSnackbar(MainActivity.this, floatingMenuWrapper, data.get_tmp().getDrop().getDialog(), SnackbarDisplayType.DROP); + soundManager.loadAndPlayAudio(SoundManager.SoundItemDrop); + }, 3000L); } } } @@ -1284,12 +1298,14 @@ public class MainActivity extends BaseActivity implements Action1, Ha }) .create(); - + soundManager.loadAndPlayAudio(SoundManager.SoundDeath); this.faintDialog.show(); } } private void displayLevelUpDialog(int level) { + soundManager.loadAndPlayAudio(SoundManager.SoundLevelUp); + SuppressedModals suppressedModals = user.getPreferences().getSuppressModals(); if (suppressedModals != null) { if (suppressedModals.getLevelUp()) { @@ -1447,6 +1463,15 @@ public class MainActivity extends BaseActivity implements Action1, Ha .compose(apiHelper.configureApiCallObserver()) .subscribe(new TaskScoringCallback(this, event.Task.getId()), throwable -> { }); + + switch(event.Task.type){ + case Task.TYPE_DAILY: { + soundManager.loadAndPlayAudio(SoundManager.SoundDaily); + } break; + case Task.TYPE_TODO: { + soundManager.loadAndPlayAudio(SoundManager.SoundTodo); + } break; + } } @Subscribe @@ -1463,6 +1488,8 @@ public class MainActivity extends BaseActivity implements Action1, Ha .compose(apiHelper.configureApiCallObserver()) .subscribe(new TaskScoringCallback(this, event.habit.getId()), throwable -> { }); + + soundManager.loadAndPlayAudio(event.Up ? SoundManager.SoundPlusHabit : SoundManager.SoundMinusHabit); } @Subscribe diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.java index 7b7aefac9..250d53ed7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.java @@ -1,6 +1,7 @@ package com.habitrpg.android.habitica.ui.fragments; import com.habitrpg.android.habitica.APIHelper; +import com.habitrpg.android.habitica.helpers.SoundManager; import com.habitrpg.android.habitica.ui.activities.MainActivity; import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser; import com.raizlabs.android.dbflow.sql.builder.Condition; @@ -21,6 +22,9 @@ public abstract class BaseMainFragment extends BaseFragment { @Inject public APIHelper apiHelper; + @Inject + protected SoundManager soundManager; + public MainActivity activity; public TabLayout tabLayout; public FrameLayout floatingMenuWrapper; diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.java index f0cc564e6..59980b303 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.java @@ -1,7 +1,5 @@ package com.habitrpg.android.habitica.ui.fragments.preferences; -import android.app.AlarmManager; -import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -14,10 +12,10 @@ import android.support.v7.preference.PreferenceScreen; import com.habitrpg.android.habitica.APIHelper; import com.habitrpg.android.habitica.HabiticaApplication; -import com.habitrpg.android.habitica.NotificationPublisher; import com.habitrpg.android.habitica.R; import com.habitrpg.android.habitica.callbacks.MergeUserCallback; import com.habitrpg.android.habitica.helpers.LanguageHelper; +import com.habitrpg.android.habitica.helpers.SoundManager; import com.habitrpg.android.habitica.helpers.TaskAlarmManager; import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager; import com.habitrpg.android.habitica.prefs.TimePreference; @@ -30,7 +28,6 @@ import com.raizlabs.android.dbflow.runtime.transaction.TransactionListener; import com.raizlabs.android.dbflow.sql.builder.Condition; import com.raizlabs.android.dbflow.sql.language.Select; -import java.util.Calendar; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -42,6 +39,8 @@ public class PreferencesFragment extends BasePreferencesFragment implements @Inject public APIHelper apiHelper; + @Inject + public SoundManager soundManager; private Context context; private TimePreference timePreference; private PreferenceScreen pushNotificationsPreference; @@ -224,6 +223,23 @@ public class PreferencesFragment extends BasePreferencesFragment implements this.startActivity(intent); getActivity().finishAffinity(); } + } else if (key.equals("audioTheme")) { + String newAudioTheme = sharedPreferences.getString(key, "off"); + + Map updateData = new HashMap<>(); + updateData.put("preferences.sound", newAudioTheme); + MergeUserCallback mergeUserCallback = new MergeUserCallback(activity, user); + apiHelper.apiService.updateUser(updateData) + .compose(apiHelper.configureApiCallObserver()) + .subscribe(mergeUserCallback, throwable -> { + }); + + Preferences preferences = user.getPreferences(); + preferences.setSound(newAudioTheme); + + soundManager.setSoundTheme(newAudioTheme); + + soundManager.preloadAllFiles(); } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java index 841b737cd..f35ef6d76 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java @@ -16,6 +16,7 @@ import com.habitrpg.android.habitica.events.commands.EditTagCommand; import com.habitrpg.android.habitica.events.commands.FilterTasksByTagsCommand; import com.habitrpg.android.habitica.events.commands.RefreshUserCommand; import com.habitrpg.android.habitica.events.commands.UpdateTagCommand; +import com.habitrpg.android.habitica.helpers.SoundManager; import com.habitrpg.android.habitica.helpers.TagsHelper; import com.habitrpg.android.habitica.ui.activities.MainActivity; import com.habitrpg.android.habitica.ui.activities.TaskFormActivity; diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Preferences.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Preferences.java index 4bd6fc991..3918dc849 100644 --- a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Preferences.java +++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Preferences.java @@ -33,7 +33,7 @@ public class Preferences extends BaseModel { @Column private boolean costume, toolbarCollapsed, advancedCollapsed, tagsCollapsed, newTaskEdit, disableClasses, stickyHeader, sleep, hideHeader; @Column - private String allocationMode, shirt, skin, size, background, chair, language; + private String allocationMode, shirt, skin, size, background, chair, language, sound; @Column private int dayStart, timezoneOffset; @@ -152,6 +152,14 @@ public class Preferences extends BaseModel { this.size = size; } + public String getSound() { + return sound; + } + + public void setSound(String sound) { + this.sound = sound; + } + public int getTimezoneOffset() { return timezoneOffset; }