diff --git a/Emoji Switcher/build.gradle b/Emoji Switcher/build.gradle index a570081..7545063 100644 --- a/Emoji Switcher/build.gradle +++ b/Emoji Switcher/build.gradle @@ -2,13 +2,13 @@ apply plugin: 'com.android.application' android { compileSdkVersion 23 - buildToolsVersion '22.0.1' + buildToolsVersion '23.0.2' defaultConfig { minSdkVersion 19 targetSdkVersion 23 - versionCode 14 - versionName "2.0.1" + versionCode 16 + versionName "2.0.2" } compileOptions { @@ -18,16 +18,16 @@ android { } dependencies { - compile 'com.google.android.gms:play-services-ads:7.8.0' + compile 'com.google.android.gms:play-services-ads:8.4.0' compile 'commons-io:commons-io:2.4' - compile 'com.google.guava:guava:18.0' - compile 'io.reactivex:rxandroid:1.0.1' - compile 'com.trello:rxlifecycle:0.2.0' - compile 'com.trello:rxlifecycle-components:0.2.0' + compile 'com.google.guava:guava:19.0' + compile 'io.reactivex:rxandroid:1.1.0' + compile 'com.trello:rxlifecycle:0.4.0' + compile 'com.trello:rxlifecycle-components:0.4.0' compile files('libs/RootTools-4.2.jar') - compile 'com.android.support:appcompat-v7:23.0.0' - compile 'com.android.support:recyclerview-v7:23.0.0' - compile 'com.yqritc:recyclerview-flexibledivider:1.2.5' + compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:recyclerview-v7:23.1.1' + compile 'com.yqritc:recyclerview-flexibledivider:1.2.6' compile 'org.solovyev.android.views:linear-layout-manager:0.5@aar' compile 'com.squareup.retrofit:retrofit:1.9.0' compile 'com.mani:thindownloadmanager:1.0.0' diff --git a/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/EmojiSwitcherUtils.java b/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/EmojiSwitcherUtils.java index af8cf4d..32fa43f 100644 --- a/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/EmojiSwitcherUtils.java +++ b/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/EmojiSwitcherUtils.java @@ -138,7 +138,7 @@ public void onNext(Boolean ready) { }); if (InstallProgress.hasDownloadStage(emojiSet)) { - final InstallProgress progress = new InstallProgress(); + InstallProgress progress = new InstallProgress(); progress.currentStage = InstallProgress.Stage.Download; subscriber.onNext(progress); @@ -155,6 +155,8 @@ public void onNext(Boolean ready) { .subscribe(new Action1() { @Override public void call(Integer percent) { + InstallProgress progress = new InstallProgress(); + progress.currentStage = InstallProgress.Stage.Download; progress.currentStageProgress = percent; subscriber.onNext(progress); } @@ -198,29 +200,50 @@ public enum Stage { public String getTitle() { return "HTC override"; } + @Override + public boolean hasPercentProgress() { + return false; + } }, Download { @Override public String getTitle() { return "Download new emoji"; } + @Override + public boolean hasPercentProgress() { + return true; + } }, Backup { @Override public String getTitle() { return "Backup old emoji"; } + @Override + public boolean hasPercentProgress() { + return false; + } }, Install { @Override public String getTitle() { return "Install new emoji"; } + @Override + public boolean hasPercentProgress() { + return false; + } }, Done { @Override public String getTitle() { return "Done!"; } + @Override + public boolean hasPercentProgress() { + return false; + } }; public abstract String getTitle(); + public abstract boolean hasPercentProgress(); } static boolean hasHtcStage() { diff --git a/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/InstallEmojiFragment.java b/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/InstallEmojiFragment.java index 22b5e5f..566647e 100644 --- a/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/InstallEmojiFragment.java +++ b/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/InstallEmojiFragment.java @@ -156,6 +156,7 @@ public InstallStageAdapter(boolean hasHtcStage, boolean hasDownloadStage, boolea if (!hasHtcStage) stages.remove(EmojiSwitcherUtils.InstallProgress.Stage.HtcFix); if (!hasDownloadStage) stages.remove(EmojiSwitcherUtils.InstallProgress.Stage.Download); if (!hasBackupStage) stages.remove(EmojiSwitcherUtils.InstallProgress.Stage.Backup); + setHasStableIds(true); } public void updateProgress(EmojiSwitcherUtils.InstallProgress installProgress) { @@ -164,7 +165,7 @@ public void updateProgress(EmojiSwitcherUtils.InstallProgress installProgress) { if (oldProgress == null || oldProgress.currentStage != installProgress.currentStage || oldProgress.currentStageProgress != installProgress.currentStageProgress) { - notifyDataSetChanged(); + notifyDataSetChanged(); } } @@ -176,25 +177,36 @@ public StageHolder onCreateViewHolder(ViewGroup parent, int viewType) { @Override public void onBindViewHolder(StageHolder holder, int position) { - holder.title.setText((position + 1) + ". " + stages.get(position).getTitle()); - if (position == getStagePosition(installProgress.currentStage)) { + EmojiSwitcherUtils.InstallProgress.Stage stage = stages.get(position); + + holder.title.setText(String.format("%d. %s", (position + 1), stage.getTitle())); + if (stage == installProgress.currentStage) { holder.title.setEnabled(true); - if (installProgress.currentStage == EmojiSwitcherUtils.InstallProgress.Stage.Done) { + if (stage == EmojiSwitcherUtils.InstallProgress.Stage.Done) { holder.loading.setVisibility(View.INVISIBLE); } else { - holder.loading.setVisibility(View.VISIBLE); + if (installProgress.currentStage.hasPercentProgress()) { + holder.loading.setVisibility(View.GONE); + holder.percent.setVisibility(View.VISIBLE); + holder.percent.setText(String.format("%d%%", installProgress.currentStageProgress)); + } else { + holder.percent.setVisibility(View.INVISIBLE); + holder.loading.setVisibility(View.VISIBLE); + } } } else { holder.title.setEnabled(false); holder.loading.setVisibility(View.INVISIBLE); + holder.percent.setVisibility(View.INVISIBLE); } } - private int getStagePosition(EmojiSwitcherUtils.InstallProgress.Stage stage) { - return stages.indexOf(stage); - } + @Override + public long getItemId(int position) { + return stages.get(position).ordinal(); + } - @Override + @Override public int getItemCount() { if (installProgress == null) { return 0; @@ -207,12 +219,14 @@ class StageHolder extends RecyclerView.ViewHolder { private View root; private TextView title; private ProgressBar loading; + private TextView percent; public StageHolder(View itemView) { super(itemView); root = itemView; title = (TextView) itemView.findViewById(R.id.install_emoji_stages_listitem_title); loading = (ProgressBar) itemView.findViewById(R.id.install_emoji_stages_listitem_loading); + percent = (TextView) itemView.findViewById(R.id.install_emoji_stages_listitem_percent); } } } diff --git a/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/SetListingUtils.java b/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/SetListingUtils.java index b812723..298a0e6 100644 --- a/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/SetListingUtils.java +++ b/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/SetListingUtils.java @@ -6,16 +6,15 @@ import com.stevenschoen.emojiswitcher.billing.IabHelper; import com.stevenschoen.emojiswitcher.network.EmojiSetListing; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class SetListingUtils { public static boolean userOwnsSet(EmojiSetListing listing, IabHelper billingHelper) { - if (listing.googlePlaySku == null) { + if (listing.googlePlaySku == null || listing.free) { return true; } else { - List skuList = new ArrayList<>(); - skuList.add(listing.googlePlaySku); + List skuList = Collections.singletonList(listing.googlePlaySku); try { return billingHelper.queryInventory(true, skuList, null).hasPurchase(listing.googlePlaySku); } catch (IabException e) { diff --git a/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/SwitcherActivity.java b/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/SwitcherActivity.java index 56e0f55..d43f94a 100644 --- a/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/SwitcherActivity.java +++ b/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/SwitcherActivity.java @@ -1,10 +1,14 @@ package com.stevenschoen.emojiswitcher; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.net.ConnectivityManager; +import android.net.Uri; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -21,6 +25,7 @@ import com.google.android.gms.ads.AdRequest; import com.google.android.gms.ads.AdSize; import com.google.android.gms.ads.AdView; +import com.stevenschoen.emojiswitcher.billing.IabException; import com.stevenschoen.emojiswitcher.billing.IabHelper; import com.stevenschoen.emojiswitcher.billing.IabResult; import com.stevenschoen.emojiswitcher.billing.Inventory; @@ -30,10 +35,15 @@ import com.trello.rxlifecycle.components.support.RxAppCompatActivity; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.concurrent.Callable; +import rx.Observable; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; +import rx.functions.Func2; import rx.subjects.BehaviorSubject; public class SwitcherActivity extends RxAppCompatActivity implements InstallEmojiFragment.Callbacks { @@ -75,27 +85,27 @@ private void init() { .observeOn(AndroidSchedulers.mainThread()) .compose(this.bindToLifecycle()) .subscribe(new Action1() { - @Override - public void call(EmojiSetsResponse emojiSetsResponse) { - emojiSetListings.clear(); - emojiSetListings.addAll(emojiSetsResponse.emojiSets); - emojiSetsAdapter.notifyDataSetChanged(); - if (spinnerInstallEmojis.getSelectedItemPosition() == AdapterView.INVALID_POSITION) { - for (int i = 0; i < emojiSetListings.size(); i++) { - if (emojiSetListings.get(i).selectByDefault) { - spinnerInstallEmojis.setSelection(i); - break; - } - } - } - emojiSetsResponseObservable.onNext(emojiSetsResponse); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - throwable.printStackTrace(); - } - }); + @Override + public void call(EmojiSetsResponse emojiSetsResponse) { + emojiSetListings.clear(); + emojiSetListings.addAll(emojiSetsResponse.emojiSets); + emojiSetsAdapter.notifyDataSetChanged(); + if (spinnerInstallEmojis.getSelectedItemPosition() == AdapterView.INVALID_POSITION) { + for (int i = 0; i < emojiSetListings.size(); i++) { + if (emojiSetListings.get(i).selectByDefault) { + spinnerInstallEmojis.setSelection(i); + break; + } + } + } + emojiSetsResponseObservable.onNext(emojiSetsResponse); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + throwable.printStackTrace(); + } + }); textCurrentEmojiSet = (TextView) findViewById(R.id.text_currentemojisetdetected_is); @@ -163,7 +173,7 @@ public void onIabPurchaseFinished(IabResult result, Purchase info) { setupBilling(); } - private void installSet(EmojiSetListing listing) { + private void installSet(EmojiSetListing listing) { Bundle options = new Bundle(); options.putParcelable("listing", listing); InstallEmojiFragment installFragment = (InstallEmojiFragment) Fragment.instantiate(SwitcherActivity.this, InstallEmojiFragment.class.getName(), options); @@ -178,35 +188,35 @@ private void refreshCurrentSystemEmojiSet() { emojiSetsResponseObservable .compose(this.bindToLifecycle()) .subscribe(new Action1() { - @Override - public void call(EmojiSetsResponse emojiSetsResponse) { - EmojiSwitcherUtils.currentEmojiSet(SwitcherActivity.this, emojiSetListings) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Action1() { - @Override - public void call(EmojiSetListing emojiSetListing) { - if (emojiSetListing != null) { - textCurrentEmojiSet.setTextColor(getResources().getColor(R.color.current_emojis_good)); - textCurrentEmojiSet.setText(emojiSetListing.name); - } else { - textCurrentEmojiSet.setTextColor(getResources().getColor(R.color.current_emojis_bad)); - textCurrentEmojiSet.setText(R.string.unknown); - } - buttonRefreshEmojiState.setEnabled(true); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - throwable.printStackTrace(); - } - }); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - throwable.printStackTrace(); - } - }); + @Override + public void call(EmojiSetsResponse emojiSetsResponse) { + EmojiSwitcherUtils.currentEmojiSet(SwitcherActivity.this, emojiSetListings) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(EmojiSetListing emojiSetListing) { + if (emojiSetListing != null) { + textCurrentEmojiSet.setTextColor(getResources().getColor(R.color.current_emojis_good)); + textCurrentEmojiSet.setText(emojiSetListing.name); + } else { + textCurrentEmojiSet.setTextColor(getResources().getColor(R.color.current_emojis_bad)); + textCurrentEmojiSet.setText(R.string.unknown); + } + buttonRefreshEmojiState.setEnabled(true); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + throwable.printStackTrace(); + } + }); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + throwable.printStackTrace(); + } + }); } private void verifyRoot() { @@ -224,6 +234,7 @@ private void setupBilling() { public void onIabSetupFinished(IabResult result) { if (result.isSuccess()) { checkAds(); + notifyEmojiOnePurchasers(); } else { Log.d("asdf", "Problem setting up in-app billing: " + result); } @@ -235,50 +246,106 @@ private void checkAds() { List additionalSkuList = new ArrayList<>(); additionalSkuList.add(EmojiSwitcherUtils.SKU_REMOVEADS); billingHelper.queryInventoryAsync(true, additionalSkuList, - new IabHelper.QueryInventoryFinishedListener() { - @Override - public void onQueryInventoryFinished(IabResult result, Inventory inv) { - if (result.isSuccess()) { - purchasedRemoveAds = inv.hasPurchase(EmojiSwitcherUtils.SKU_REMOVEADS); - final View removeAdsButton = findViewById(R.id.button_removeads); - if (!purchasedRemoveAds && !BuildConfig.DEBUG) { - AdRequest adRequest = new AdRequest.Builder().build(); - - adView = new AdView(SwitcherActivity.this); - adView.setAdSize(AdSize.SMART_BANNER); - adView.setAdUnitId(EmojiSwitcherUtils.GOOGLE_ADS_UNITID); - FrameLayout adHolder = (FrameLayout) findViewById(R.id.holder_ad); - adHolder.addView(adView); - adView.loadAd(adRequest); - - removeAdsButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - billingHelper.launchPurchaseFlow(SwitcherActivity.this, - EmojiSwitcherUtils.SKU_REMOVEADS, 0, new IabHelper.OnIabPurchaseFinishedListener() { - @Override - public void onIabPurchaseFinished(IabResult result, Purchase info) { - checkAds(); - } - }); - } - }); - removeAdsButton.setAlpha(0); - removeAdsButton.setVisibility(View.VISIBLE); - removeAdsButton.animate().alpha(0.75f); - } else { - removeAdsButton.animate().alpha(0).withEndAction(new Runnable() { - @Override - public void run() { - removeAdsButton.setVisibility(View.GONE); - } - }); - } - } - } - }); + new IabHelper.QueryInventoryFinishedListener() { + @Override + public void onQueryInventoryFinished(IabResult result, Inventory inv) { + if (result.isSuccess()) { + purchasedRemoveAds = inv.hasPurchase(EmojiSwitcherUtils.SKU_REMOVEADS); + final View removeAdsButton = findViewById(R.id.button_removeads); + if (!purchasedRemoveAds && !BuildConfig.DEBUG) { + AdRequest adRequest = new AdRequest.Builder().build(); + + adView = new AdView(SwitcherActivity.this); + adView.setAdSize(AdSize.SMART_BANNER); + adView.setAdUnitId(EmojiSwitcherUtils.GOOGLE_ADS_UNITID); + FrameLayout adHolder = (FrameLayout) findViewById(R.id.holder_ad); + adHolder.addView(adView); + adView.loadAd(adRequest); + + removeAdsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + billingHelper.launchPurchaseFlow(SwitcherActivity.this, + EmojiSwitcherUtils.SKU_REMOVEADS, 0, new IabHelper.OnIabPurchaseFinishedListener() { + @Override + public void onIabPurchaseFinished(IabResult result, Purchase info) { + checkAds(); + } + }); + } + }); + removeAdsButton.setAlpha(0); + removeAdsButton.setVisibility(View.VISIBLE); + removeAdsButton.animate().alpha(0.75f); + } else { + removeAdsButton.animate().alpha(0).withEndAction(new Runnable() { + @Override + public void run() { + removeAdsButton.setVisibility(View.GONE); + } + }); + } + } + } + }); } + private void notifyEmojiOnePurchasers() { + Observable.combineLatest( + Observable.fromCallable(new Callable() { + @Override + public Boolean call() throws Exception { + return PreferenceManager.getDefaultSharedPreferences(SwitcherActivity.this).getBoolean("notifiedEmojiOne", false); + } + }), + emojiSetsResponseObservable, + new Func2() { + @Override + public Boolean call(Boolean alreadyNotified, EmojiSetsResponse emojiSetsResponse) { + if (alreadyNotified) return false; + for (EmojiSetListing listing : emojiSetsResponse.emojiSets) { + if (Objects.equals(listing.googlePlaySku, "emojiswitcher_set_emojione")) { + try { + return billingHelper.queryInventory(true, Collections.singletonList(listing.googlePlaySku), null) + .hasPurchase(listing.googlePlaySku); + } catch (IabException e) { + e.printStackTrace(); + return false; + } + } + } + return false; + } + }).observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(Boolean notify) { + if (notify) { + new AlertDialog.Builder(SwitcherActivity.this) + .setMessage("Emoji One is now available to everyone for free! You're entitled to a full refund for purchasing it.") + .setPositiveButton("Request refund", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent sendEmailIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:steven@stevenschoen.com")); + sendEmailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"steven@stevenschoen.com"}); + sendEmailIntent.putExtra(Intent.EXTRA_SUBJECT, "Emoji One refund request"); + sendEmailIntent.putExtra(Intent.EXTRA_TEXT, "My Google Play email is: \n(Enter your Google Play email above.)"); + startActivity(Intent.createChooser(sendEmailIntent, "Request refund by email")); + } + }) + .setNegativeButton("Cancel", null) + .show(); + PreferenceManager.getDefaultSharedPreferences(SwitcherActivity.this).edit().putBoolean("notifiedEmojiOne", true).apply(); + } + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + throwable.printStackTrace(); + } + }); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); diff --git a/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/network/EmojiSetListing.java b/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/network/EmojiSetListing.java index 8544e54..d2ffb81 100644 --- a/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/network/EmojiSetListing.java +++ b/Emoji Switcher/src/main/java/com/stevenschoen/emojiswitcher/network/EmojiSetListing.java @@ -13,6 +13,7 @@ public class EmojiSetListing implements Parcelable { public String googlePlaySku; @SerializedName("default") public boolean selectByDefault; + public boolean free; @Override public String toString() { @@ -31,6 +32,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.md5); dest.writeString(this.googlePlaySku); dest.writeByte(selectByDefault ? (byte) 1 : (byte) 0); + dest.writeByte(free ? (byte) 1 : (byte) 0); } public EmojiSetListing() { } @@ -41,6 +43,7 @@ protected EmojiSetListing(Parcel in) { this.md5 = in.readString(); this.googlePlaySku = in.readString(); this.selectByDefault = in.readByte() != 0; + this.free = in.readByte() != 0; } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { diff --git a/Emoji Switcher/src/main/res/layout/install_emoji_stages_listitem.xml b/Emoji Switcher/src/main/res/layout/install_emoji_stages_listitem.xml index 3986535..d5d9503 100644 --- a/Emoji Switcher/src/main/res/layout/install_emoji_stages_listitem.xml +++ b/Emoji Switcher/src/main/res/layout/install_emoji_stages_listitem.xml @@ -11,7 +11,7 @@ android:layout_marginStart="16dp" android:gravity="center_vertical" android:textAppearance="@style/TextAppearance.AppCompat.Small" - tools:text="Stage name" /> + tools:text="Stage name"/> + android:indeterminate="true"/> + \ No newline at end of file diff --git a/build.gradle b/build.gradle index d8f5f60..4411d8c 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.0' + classpath 'com.android.tools.build:gradle:1.5.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.11.3' } }