commit add996cbafb63056c105b3fcba1185306c57e1dc Author: Idoideas Date: Mon Nov 12 02:55:24 2018 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b75303 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..30aa626 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..821cf50 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac69df9 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# StickerMaker for WhatsApp + +[![Get it in the Play Store](https://i.imgur.com/GcvRPKp.png)](https://play.google.com/store/apps/details?id=com.idoideas.stickermaker) + + + +You probably heard, or even using right now, the worldwide social messaging app. And altough it is owned by the same company, I'm not talking about Facebook or FB Messenger. I'm talking about the popular mobile app, WhatsApp. + +Many people around the world are using this app on a daily basis to send simple, short messages to their friends, colleagues and family. And as of such, the WhatsApp developer team has to make the experience of using their app the best possible, and the most fun for sure. + +We all know there are alternatives with richer messeges. Telegram, Messenger, Kik and stuff. However, I don't think no one can beat the user base WhatsApp has, and for that, we are tied to their services. + +Recently, they were graceful enough to finally bring us Stickers support for the app, something many apps had before. It was a surprise to be sure, but a welcomed one. However, when I tested this feature, I realized the only way to make my own sticker pack, I need to develop a whole new Android app and have people install it from the store (or through APK, but ordinary people won't do that). So I realized a solution to that problem must be made, so any person could create and share any kind of sticker pack they want, easily and without any hassle, directly from their mobile device. + +Introducing ***StickerMaker for WhatsApp*** app by Idoideas, a simple way to enrich your stickers experience on WhatsApp, without the need of any programming knowledge. + +## So what can you do in this app? + +* Create and organize your very own sticker packs by pack name and creator. +* Share your newly created packs to your friends as easily as clicking a button. +* Load your friends' original packs to your WhatsApp client by sharing the files recieved to the app. +* Modify packs that were created using the app to your liking! + +## Notice! + +***Sticker packs created using the app and loaded using the app require the app to stay installed on the device and loaded on it if you want to use them. If you happen to remove the app from your device, the sticker packs will be removed from your WhatsApp client and your packs will be lost if weren't shared.*** + +## Download + +You can [download the latest APK right here](), or [download the app directly from the Play Store](https://play.google.com/store/apps/details?id=com.idoideas.stickermaker). + +**Supports all Android devices, all the way back to KitKat 4.4!** + + +## Screenshots + +Tested on OnePlus 3T, 2018. + + + + + + +## Contact + +* Twitter: [@idoideas](http://twitter.com/idoideas) +* Reddit: [/u/Idoideas](https://www.reddit.com/user/idoideas) +* Email: Idoideasmail@gmail.com diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..bc9eda7 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,63 @@ +apply plugin: 'com.android.application' + +android { + aaptOptions { + noCompress "webp" + } + compileSdkVersion 28 + buildToolsVersion "28.0.3" + defaultConfig { + applicationId "com.idoideas.stickermaker" + minSdkVersion 19 + targetSdkVersion 28 + versionCode 3 + versionName "1.0.2" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + def contentProviderAuthority = applicationId + ".WhatsAppLicensedCode.StickerContentProvider" + // Creates a placeholder property to use in the manifest. + manifestPlaceholders = + [contentProviderAuthority: contentProviderAuthority] + // Adds a new field for the authority to the BuildConfig class. + buildConfigField("String", + "CONTENT_PROVIDER_AUTHORITY", + "\"${contentProviderAuthority}\"") + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + dataBinding { + enabled = true + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha07' + implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha07' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:design:28.0.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'com.facebook.fresco:fresco:1.10.0' + implementation 'com.facebook.fresco:webpsupport:1.10.0' + implementation 'com.facebook.fresco:animated-webp:1.10.0' + implementation 'com.facebook.fresco:webpsupport:1.10.0' + implementation 'com.google.code.gson:gson:2.8.2' + implementation 'com.google.android.gms:play-services-ads:17.1.0' + implementation 'com.heinrichreimersoftware:material-intro:1.6' + implementation 'com.android.billingclient:billing:1.0' + implementation 'com.github.iammert:MaterialIntroView:1.6.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# 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 *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/release/app-release.apk b/app/release/app-release.apk new file mode 100644 index 0000000..18746d1 Binary files /dev/null and b/app/release/app-release.apk differ diff --git a/app/release/output.json b/app/release/output.json new file mode 100644 index 0000000..1caf134 --- /dev/null +++ b/app/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":3,"versionName":"1.0.2","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/androidTest/java/com/idoideas/stickermaker/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/idoideas/stickermaker/ExampleInstrumentedTest.java new file mode 100644 index 0000000..8a10d88 --- /dev/null +++ b/app/src/androidTest/java/com/idoideas/stickermaker/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.idoideas.stickermaker; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.idoideas.stickermaker", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5520639 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/idoideas/stickermaker/DataArchiver.java b/app/src/main/java/com/idoideas/stickermaker/DataArchiver.java new file mode 100644 index 0000000..8f9ed85 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/DataArchiver.java @@ -0,0 +1,370 @@ +package com.idoideas.stickermaker; + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.Log; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.reflect.TypeToken; +import com.idoideas.stickermaker.WhatsAppBasedCode.Sticker; +import com.idoideas.stickermaker.WhatsAppBasedCode.StickerPack; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +public class DataArchiver { + + private static int BUFFER = 8192; + + public static boolean writeStickerBookJSON(List sb, Context context) + { + try { + SharedPreferences mSettings = context.getSharedPreferences("StickerMaker", Context.MODE_PRIVATE); + + String writeValue = new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriSerializer()) + .create() + .toJson( + sb, + new TypeToken>() {}.getType()); + SharedPreferences.Editor mEditor = mSettings.edit(); + mEditor.putString("stickerbook", writeValue); + mEditor.apply(); + return true; + } + catch(Exception e) + { + return false; + } + } + + public static ArrayList readStickerPackJSON(Context context) + { + SharedPreferences mSettings = context.getSharedPreferences("StickerMaker", Context.MODE_PRIVATE); + + String loadValue = mSettings.getString("stickerbook", ""); + Type listType = new TypeToken>(){}.getType(); + return new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriDeserializer()) + .create() + .fromJson(loadValue, listType); + } + + public static String createZipFileFromStickerPack(StickerPack sp, Context context){ + String id = sp.getIdentifier(); + String path = context.getFilesDir()+"/"+id; + + createZip cz = new createZip(context, sp); + cz.execute(); + + return path + "/" + sp.getIdentifier() + ".zip"; + } + + public static void importZipFileToStickerPack(Uri uri, Context context){ + String zipPath = ""; + try { + zipPath = FilesUtils.inputStreamToSavedFile(context.getContentResolver().openInputStream(uri), context, UUID.randomUUID().toString()+".zip"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + parseZip pz = new parseZip(context, zipPath); + pz.execute(); + + } + + public static void zip(ArrayList _files, String zipFileName) { + try { + BufferedInputStream origin = null; + FileOutputStream dest = new FileOutputStream(zipFileName); + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream( + dest)); + byte data[] = new byte[BUFFER]; + + for (int i = 0; i < _files.size(); i++) { + Log.v("Compress", "Adding: " + _files.get(i)); + FileInputStream fi = new FileInputStream(_files.get(i)); + origin = new BufferedInputStream(fi, BUFFER); + + ZipEntry entry = new ZipEntry(_files.get(i).substring(_files.get(i).lastIndexOf("/") + 1)); + out.putNextEntry(entry); + int count; + + while ((count = origin.read(data, 0, BUFFER)) != -1) { + out.write(data, 0, count); + } + origin.close(); + } + + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void unzip(String _zipFile, String _targetLocation) { + + //create target location folder if not exist + dirChecker(_targetLocation); + + try { + FileInputStream fin = new FileInputStream(_zipFile); + ZipInputStream zin = new ZipInputStream(fin); + ZipEntry ze = null; + while ((ze = zin.getNextEntry()) != null) { + Log.w("DECOMPRESSING FILE", ze.getName()); + //create dir if required while unzipping + if (ze.isDirectory()) { + dirChecker(ze.getName()); + } else { + FileOutputStream fout = new FileOutputStream(_targetLocation + ze.getName()); + for (int c = zin.read(); c != -1; c = zin.read()) { + fout.write(c); + } + + zin.closeEntry(); + fout.close(); + } + + } + zin.close(); + Log.w("ENDED DECOMPRESSING", "DONEEEEEE"); + } catch (Exception e) { + System.out.println(e); + } + } + + private static void dirChecker(String dir) { + File f = new File(dir); + if (!f.isDirectory()) { + f.mkdirs(); + } + } + + private static void stickerPackToJSONFile(StickerPack sp, String path, Context context) { + try { + String writeValue = new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriSerializer()) + .create() + .toJson( + sp, + StickerPack.class); + + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(new File(path, sp.getIdentifier()+".json"))); + outputStreamWriter.write(writeValue); + outputStreamWriter.close(); + } + catch (IOException e) { + Log.e("Exception", "File write failed: " + e.toString()); + } + } + + private static StickerPack JSONFileToStickerPack(String id, String path, Context context) { + + String ret = ""; + + try { + + InputStream inputStream = new FileInputStream(new File(path, id+".json")); + + + if ( inputStream != null ) { + InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader); + String receiveString = ""; + StringBuilder stringBuilder = new StringBuilder(); + + while ( (receiveString = bufferedReader.readLine()) != null ) { + stringBuilder.append(receiveString); + } + + inputStream.close(); + ret = stringBuilder.toString(); + } + } + catch (FileNotFoundException e) { + Log.e("login activity", "File not found: " + e.toString()); + } catch (IOException e) { + Log.e("login activity", "Can not read file: " + e.toString()); + } + + return new GsonBuilder() + .registerTypeAdapter(Uri.class, new UriDeserializer()) + .create() + .fromJson(ret, StickerPack.class); + } + + + public static class UriSerializer implements JsonSerializer { + public JsonElement serialize(Uri src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.toString()); + } + } + + public static class UriDeserializer implements JsonDeserializer { + @Override + public Uri deserialize(final JsonElement src, final Type srcType, + final JsonDeserializationContext context) throws JsonParseException { + return Uri.parse(src.toString().replace("\"", "")); + } + } + + private static class createZip extends AsyncTask { + private ProgressDialog dialog; + private StickerPack sp; + + public createZip(Context context, StickerPack sp) { + dialog = new ProgressDialog(context); + this.sp = sp; + } + + @Override + protected void onPreExecute() { + dialog.setMessage("Creating Sticker Pack Zip, Please wait..."); + dialog.show(); + } + + @Override + protected void onPostExecute(Void d) { + if (dialog.isShowing()) { + dialog.dismiss(); + } + String id = sp.getIdentifier(); + String path = dialog.getContext().getFilesDir()+"/"+id; + ExternalPacksManager.sendStickerPackZipThroughWhatsApp(dialog.getContext(), id); + } + + @Override + protected Void doInBackground(Void... params) { + String id = sp.getIdentifier(); + ArrayList namesOfFiles = new ArrayList<>(); + String path = dialog.getContext().getFilesDir()+"/"+id; + + stickerPackToJSONFile(sp, path+"/", dialog.getContext()); + + for(Sticker s : sp.getStickers()){ + namesOfFiles.add(path+"/"+sp.getIdentifier()+"-"+s.getImageFileName()+".webp"); + } + namesOfFiles.add(path+"/"+sp.getIdentifier()+"-trayImage.webp"); + namesOfFiles.add(path+"/"+sp.getIdentifier()+".json"); + zip(namesOfFiles, path + "/" + sp.getIdentifier() + ".zip"); + return null; + } + + } + + private static class parseZip extends AsyncTask { + private ProgressDialog dialog; + private String zipPath; + private boolean isAlreadyLoaded = false; + private boolean isFailed = false; + + + public parseZip(Context context, String zipPath) { + dialog = new ProgressDialog(context); + dialog.setCancelable(false); + this.zipPath = zipPath; + } + + @Override + protected void onPreExecute() { + dialog.setMessage("Parsing Sticker Pack Zip, Please Wait..."); + dialog.setCancelable(false); + dialog.show(); + } + + @Override + protected void onPostExecute(Void d) { + if (dialog.isShowing()) { + dialog.dismiss(); + } + if (isAlreadyLoaded){ + AlertDialog.Builder builder = new AlertDialog.Builder(dialog.getContext()); + builder.setMessage("Sticker Pack is already loaded.") + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + if (isFailed){ + AlertDialog.Builder builder = new AlertDialog.Builder(dialog.getContext()); + builder.setMessage("The zip file that was imported is not a proper sticker pack file.") + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + } + + @Override + protected Void doInBackground(Void... params) { + String packId = zipPath.split("/")[zipPath.split("/").length-1].replace(".zip", ""); + unzip(zipPath, dialog.getContext().getFilesDir()+"/"+packId+"/"); + + String path = dialog.getContext().getFilesDir()+"/"+packId; + + String originalID = FilesUtils.getActualIDOfPack(path+"/"); + + File oldFolder = new File(dialog.getContext().getFilesDir(),packId); + + if(StickerBook.getStickerPackById(originalID)==null){ + File newFolder = null; + try{ + newFolder = new File(dialog.getContext().getFilesDir(),originalID); + oldFolder.renameTo(newFolder); + + StickerPack sp = JSONFileToStickerPack(originalID, newFolder.getAbsolutePath(), dialog.getContext()); + + StickerBook.addStickerPackExisting(sp); + } catch (Exception e){ + isFailed = true; + if(newFolder!=null){ + newFolder.delete(); + } else { + oldFolder.delete(); + } + + } + + } else { + oldFolder.delete(); + isAlreadyLoaded = true; + } + return null; + } + + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/ExternalPacksManager.java b/app/src/main/java/com/idoideas/stickermaker/ExternalPacksManager.java new file mode 100644 index 0000000..fc7bb3d --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/ExternalPacksManager.java @@ -0,0 +1,51 @@ +package com.idoideas.stickermaker; + +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Environment; +import android.os.StrictMode; +import android.provider.MediaStore; + +import com.facebook.common.file.FileUtils; + +import java.io.File; +import java.util.ArrayList; + +public class ExternalPacksManager { + public static void sendStickerPackZipThroughWhatsApp(Context context, String id){ + StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); + StrictMode.setVmPolicy(builder.build()); + + FilesUtils.handleCopyFilesToSdCard(context, id); + + Uri uri = Uri.fromFile(new File( + Environment.getExternalStorageDirectory().getAbsolutePath() + + "/" + context.getString(R.string.app_name) + +"/"+id+".zip") + ); + + Uri noticeImage = Uri.parse(MediaStore.Images.Media.insertImage(context.getContentResolver(), + BitmapFactory.decodeResource(context.getResources(), R.drawable.stickerpacknotice), null, null)); + + Intent share = new Intent(); + share.setAction(Intent.ACTION_SEND_MULTIPLE); + share.setType("*/*"); + share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + ArrayList files = new ArrayList(); + files.add(noticeImage); + files.add(uri); + + //share.putExtra(Intent.EXTRA_STREAM, uri); + share.putParcelableArrayListExtra(Intent.EXTRA_STREAM, files); + + share.setPackage("com.whatsapp"); + + context.startActivity(share); + } + + + +} diff --git a/app/src/main/java/com/idoideas/stickermaker/FilesUtils.java b/app/src/main/java/com/idoideas/stickermaker/FilesUtils.java new file mode 100644 index 0000000..a215473 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/FilesUtils.java @@ -0,0 +1,146 @@ +package com.idoideas.stickermaker; + +import android.content.Context; +import android.net.Uri; +import android.os.Environment; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.FileChannel; + +public class FilesUtils { + public static void handleCopyFilesToSdCard(final Context context, String id) { + File targetFolder = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + context.getString(R.string.app_name) + ); + deleteAllFiles(targetFolder); + if (!targetFolder.exists()) { + targetFolder.mkdirs(); + } + try { + exportFile(new File(context.getFilesDir()+"/"+id+"/"+id+".zip"),targetFolder); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void deleteAllFiles(File targetFolder) { + File[] filesList = targetFolder.listFiles(); + if (filesList == null) { + return; + } + for (File file : filesList) { + if (file.isDirectory()) { + deleteAllFiles(file); + file.delete(); + } else { + file.delete(); + } + } + + } + + + + private static File exportFile(File src, File dst) throws IOException { + + //if folder does not exist + if (!dst.exists()) { + if (!dst.mkdir()) { + return null; + } + } + + File expFile = new File(dst.getPath() + File.separator + src.getPath().split("/")[src.getPath().split("/").length-1]); + FileChannel inChannel = null; + FileChannel outChannel = null; + + try { + inChannel = new FileInputStream(src).getChannel(); + outChannel = new FileOutputStream(expFile).getChannel(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + try { + inChannel.transferTo(0, inChannel.size(), outChannel); + } finally { + if (inChannel != null) + inChannel.close(); + if (outChannel != null) + outChannel.close(); + } + + return expFile; + } + + public static String inputStreamToSavedFile(InputStream input, Context context, String filename){ + File targetFolder = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + context.getString(R.string.app_name) + ); + deleteAllFiles(targetFolder); + File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + context.getString(R.string.app_name) + , filename); + try { + file.createNewFile(); + OutputStream output = new FileOutputStream(file); + try { + byte[] buffer = new byte[4 * 1024]; // or other buffer size + int read; + + while ((read = input.read(buffer)) != -1) { + output.write(buffer, 0, read); + } + + output.flush(); + return file.getAbsolutePath(); + } finally { + output.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + input.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return file.getAbsolutePath(); + } + } + + public static String getActualIDOfPack(String path){ + File directory = new File(path); + File[] files = directory.listFiles(); + for (int i = 0; i < files.length; i++) + { + if(files[i].getName().contains(".json")){ + return files[i].getName().replace(".json", ""); + } + } + return null; + } + + + public static int getUriSize(Uri uri, Context context) throws IOException { + try (final InputStream inputStream = context.getContentResolver().openInputStream(uri); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + if (inputStream == null) { + throw new IOException("cannot get URI SIZE"); + } + int read; + byte[] data = new byte[16384]; + + while ((read = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, read); + } + return buffer.toByteArray().length; + } + } + + +} diff --git a/app/src/main/java/com/idoideas/stickermaker/ImageManipulation.java b/app/src/main/java/com/idoideas/stickermaker/ImageManipulation.java new file mode 100644 index 0000000..865a1ff --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/ImageManipulation.java @@ -0,0 +1,117 @@ +package com.idoideas.stickermaker; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +public class ImageManipulation { + + public static Uri convertImageToWebP(Uri uri, String StickerBookId, String StickerId, Context context){ + try { + Bitmap bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(),uri); + + dirChecker(context.getFilesDir()+"/"+StickerBookId); + + String path = context.getFilesDir()+"/"+StickerBookId+"/"+StickerBookId+"-"+StickerId+".webp"; + + Log.w("Conversion Data: ", "path: " + path); + + /*FileOutputStream out = new FileOutputStream(path); + bitmap = Bitmap.createScaledBitmap(bitmap, 512, 512, true); + + Log.w("IMAGE SIZE before comperssion", ""+FilesUtils.getUriSize(Uri.fromFile(new File(path)), context)); + + bitmap.compress(Bitmap.CompressFormat.WEBP, 100, out); //100-best quality + + Log.w("IMAGE SIZE first", ""+FilesUtils.getUriSize(Uri.fromFile(new File(path)), context)); + */ + makeSmallestBitmapCompatible(path, bitmap); + + return Uri.fromFile(new File(path)); + } catch (Exception e) { + e.printStackTrace(); + } + return uri; + } + + public static Uri convertIconTrayToWebP(Uri uri, String StickerBookId, String StickerId, Context context){ + try { + Bitmap bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(),uri); + + dirChecker(context.getFilesDir()+"/"+StickerBookId); + + String path = context.getFilesDir()+"/"+StickerBookId+"/"+StickerBookId+"-"+StickerId+".webp"; + + Log.w("Conversion Data: ", "path: " + path); + + FileOutputStream out = new FileOutputStream(path); + bitmap = Bitmap.createScaledBitmap(bitmap, 256, 256, true); + bitmap.compress(Bitmap.CompressFormat.WEBP, 100, out); //100-best quality + out.close(); + + return Uri.fromFile(new File(path)); + } catch (Exception e) { + e.printStackTrace(); + } + return uri; + } + + private static void dirChecker(String dir) { + File f = new File(dir); + if (!f.isDirectory()) { + f.mkdirs(); + } + } + + private static byte[] getByteArray(Bitmap bitmap, int quality) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + bitmap.compress(Bitmap.CompressFormat.WEBP, + quality, + bos); + + return bos.toByteArray(); + } + + private static void makeSmallestBitmapCompatible(String path, Bitmap bitmap){ + int quality = 100; + FileOutputStream outs = null; + try { + outs = new FileOutputStream(path); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + bitmap = Bitmap.createScaledBitmap(bitmap, 512, 512, true); + + int byteArrayLength = 100000; + ByteArrayOutputStream bos = null; + + while((byteArrayLength/1000)>=100){ + bos = new ByteArrayOutputStream(); + + bitmap.compress(Bitmap.CompressFormat.WEBP, + quality, + bos); + + byteArrayLength = bos.toByteArray().length; + quality-=10; + + Log.w("IMAGE SIZE IS NOW", byteArrayLength+""); + } + try { + outs.write(bos.toByteArray()); + outs.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/NewUserIntroActivity.java b/app/src/main/java/com/idoideas/stickermaker/NewUserIntroActivity.java new file mode 100644 index 0000000..3eeb6f6 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/NewUserIntroActivity.java @@ -0,0 +1,194 @@ +package com.idoideas.stickermaker; + +import android.Manifest; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.view.ViewPager; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.widget.Toast; + +import com.heinrichreimersoftware.materialintro.app.IntroActivity; +import com.heinrichreimersoftware.materialintro.app.NavigationPolicy; +import com.heinrichreimersoftware.materialintro.app.OnNavigationBlockedListener; +import com.heinrichreimersoftware.materialintro.slide.SimpleSlide; +import com.idoideas.stickermaker.WhatsAppBasedCode.StickerPackListActivity; + +public class NewUserIntroActivity extends IntroActivity { + + @Override protected void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + + setButtonBackVisible(false); + addSlide(new SimpleSlide.Builder() + .title("Welcome to StickerMaker for WhatsApp!") + .description("The perfect solution for making and sharing your own WhatsApp sticker packs!") + .image(R.drawable.stickermakerlogo) + .background(R.color.colorAccent) + .scrollable(false) + .build()); + + if(!checkIfBatteryOptimizationIgnored() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ + addSlide(new SimpleSlide.Builder() + .title("We've recognized our app is optimized by Android's Doze system.") + .description("In order for the app to work correctly, please disable the optimization for \"StickerMaker\" after clicking the button.") + .background(R.color.colorAccent) + .scrollable(false) + .buttonCtaLabel("Let's Go") + .buttonCtaClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS); + startActivityForResult(intent, 4113); + } + }) + .build()); + } + + int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); + + if (permission != PackageManager.PERMISSION_GRANTED) { + addSlide(new SimpleSlide.Builder() + .title("We need to save and fetch your stickers!") + .description("In order for the app to work correctly, please allow the write and read storage permissions.") + .background(R.color.colorAccent) + .scrollable(false) + .buttonCtaLabel("Grant Permissions") + .buttonCtaClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + verifyStoragePermissions(NewUserIntroActivity.this); + } + }) + .build()); + } + + addSlide(new SimpleSlide.Builder() + .title("Everything is set!") + .description("You can start creating your sticker packs!\n\n" + + "Please notice! Sticker packs created or loaded with the app require the app being installed on device.\n\n" + + "Uninstalling the app will cause of removing the sticker packs from your WhatsApp client " + + "and will be lost if they weren't shared.") + .background(R.color.colorAccent) + .scrollable(false) + .build()); + + addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int i, float v, int i1) { + + } + + @Override + public void onPageSelected(int i) { + if(i>0 && i!=getSlides().size()-1){ + setNavigation(false, true); + } else { + setNavigation(true, false); + } + + + } + + @Override + public void onPageScrollStateChanged(int i) { + + } + }); + + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if(requestCode == 4113){ + if(checkIfBatteryOptimizationIgnored()){ + setNavigation(true, false); + nextSlide(); + } + } + } + + private boolean checkIfBatteryOptimizationIgnored(){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + String packageName = getPackageName(); + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + return pm.isIgnoringBatteryOptimizations(packageName); + } else { + return true; + } + } + + + private void setNavigation(boolean forward, boolean backward){ + setNavigationPolicy(new NavigationPolicy() { + @Override + public boolean canGoForward(int i) { + return forward; + } + + @Override + public boolean canGoBackward(int i) { + return backward; + } + }); + } + + public static void verifyStoragePermissions(Activity activity) { + // Check if we have write permission + int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); + + if (permission != PackageManager.PERMISSION_GRANTED) { + // We don't have permission so prompt the user + ActivityCompat.requestPermissions( + activity, + new String[]{ + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }, + 1 + ); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + case 1: + if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { + AlertDialog alertDialog = new AlertDialog.Builder(this) + .setPositiveButton("Let's Go", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + verifyStoragePermissions(NewUserIntroActivity.this); + } + }) + .create(); + alertDialog.setTitle("Notice!"); + alertDialog.setMessage("Allowing storage permissions is crucial for the app to work. Please grant the permissions."); + alertDialog.show(); + } else { + setNavigation(true, false); + nextSlide(); + } + break; + } + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/StickerBook.java b/app/src/main/java/com/idoideas/stickermaker/StickerBook.java new file mode 100644 index 0000000..5435dd4 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/StickerBook.java @@ -0,0 +1,100 @@ +package com.idoideas.stickermaker; + + +import android.content.Context; +import android.util.Log; + +import com.idoideas.stickermaker.WhatsAppBasedCode.StickerPack; + +import java.io.File; +import java.util.ArrayList; + +public class StickerBook { + + static Context myContext; + public static ArrayList allStickerPacks = checkIfPacksAreNull(); + + public static void init(Context context){ + myContext = context; + ArrayList lsp = DataArchiver.readStickerPackJSON(context); + if(lsp!=null && lsp.size()!=0){ + allStickerPacks = lsp; + } + } + + private static ArrayList checkIfPacksAreNull(){ + if(allStickerPacks==null){ + Log.w("IS PACKS NULL?", "YES"); + return new ArrayList<>(); + } + Log.w("IS PACKS NULL?", "NO"); + return allStickerPacks; + } + + /*public static String addNewStickerPack(String name, String publisher, String trayImage){ + String newId = UUID.randomUUID().toString(); + StickerPack sp = new StickerPack(newId, + name, + publisher, + trayImage, + "", + "", + "", + ""); + allStickerPacks.add(sp); + return newId; + }*/ + + public static void addStickerPackExisting(StickerPack sp){ + allStickerPacks.add(sp); + } + + public static ArrayList getAllStickerPacks(){ + return allStickerPacks; + } + + public static StickerPack getStickerPackByName(String stickerPackName){ + for (StickerPack sp : allStickerPacks){ + if(sp.getName().equals(stickerPackName)){ + return sp; + } + } + return null; + } + + public static StickerPack getStickerPackById(String stickerPackId){ + if(allStickerPacks.isEmpty()){ + init(myContext); + } + Log.w("THIS IS THE ALL STICKER", allStickerPacks.toString()); + for (StickerPack sp : allStickerPacks){ + if(sp.getIdentifier().equals(stickerPackId)){ + return sp; + } + } + return null; + } + + public static StickerPack getStickerPackByIdWithContext(String stickerPackId, Context context){ + if(allStickerPacks.isEmpty()){ + init(context); + } + Log.w("THIS IS THE ALL STICKER", allStickerPacks.toString()); + for (StickerPack sp : allStickerPacks){ + if(sp.getIdentifier().equals(stickerPackId)){ + return sp; + } + } + return null; + } + + public static void deleteStickerPackById(String stickerPackId){ + StickerPack myStickerPack = getStickerPackById(stickerPackId); + new File(myStickerPack.getTrayImageUri().getPath()).getParentFile().delete(); + allStickerPacks.remove(myStickerPack); + } + + public static StickerPack getStickerPackByIndex(int index){ + return allStickerPacks.get(index); + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/BaseActivity.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/BaseActivity.java new file mode 100755 index 0000000..713fbc7 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/BaseActivity.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; + +public abstract class BaseActivity extends AppCompatActivity { + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + public static final class MessageDialogFragment extends DialogFragment { + private static final String ARG_TITLE_ID = "title_id"; + private static final String ARG_MESSAGE = "message"; + + public static DialogFragment newInstance(@StringRes int titleId, String message) { + DialogFragment fragment = new MessageDialogFragment(); + Bundle arguments = new Bundle(); + arguments.putInt(ARG_TITLE_ID, titleId); + arguments.putString(ARG_MESSAGE, message); + fragment.setArguments(arguments); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + @StringRes final int title = getArguments().getInt(ARG_TITLE_ID); + String message = getArguments().getString(ARG_MESSAGE); + + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()) + .setMessage(message) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + MessageDialogFragment.this.dismiss(); + } + }); + + if (title != 0) { + dialogBuilder.setTitle(title); + } + return dialogBuilder.create(); + } + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/BottomFadingRecyclerView.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/BottomFadingRecyclerView.java new file mode 100755 index 0000000..8ceb8c1 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/BottomFadingRecyclerView.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; + +public class BottomFadingRecyclerView extends RecyclerView { + public BottomFadingRecyclerView(Context context) { + super(context); + } + + public BottomFadingRecyclerView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public BottomFadingRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected float getTopFadingEdgeStrength() { + return 0.0f; + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/Sticker.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/Sticker.java new file mode 100755 index 0000000..fca5f38 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/Sticker.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +public class Sticker implements Parcelable { + String imageFileName; + List emojis; + Uri uri; + long size; + + public Sticker(String imageFileName, List emojis) { + this.imageFileName = imageFileName; + this.emojis = emojis; + } + + public Sticker(String imageFileName, Uri uri, List emojis) { + this.imageFileName = imageFileName; + this.emojis = emojis; + this.uri = uri; + } + + public Sticker(Parcel in) { + imageFileName = in.readString(); + emojis = in.createStringArrayList(); + size = in.readLong(); + } + + + public static final Creator CREATOR = new Creator() { + @Override + public Sticker createFromParcel(Parcel in) { + return new Sticker(in); + } + + @Override + public Sticker[] newArray(int size) { + return new Sticker[size]; + } + }; + + public void setSize(long size) { + this.size = size; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(imageFileName); + dest.writeStringList(emojis); + dest.writeLong(size); + } + + public Uri getUri() { + return uri; + } + + public String getImageFileName() { + return imageFileName; + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerContentProvider.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerContentProvider.java new file mode 100755 index 0000000..615c2e3 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerContentProvider.java @@ -0,0 +1,318 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import com.idoideas.stickermaker.BuildConfig; +import com.idoideas.stickermaker.StickerBook; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class StickerContentProvider extends ContentProvider { + + /** + * Do not change the strings listed below, as these are used by WhatsApp. And changing these will break the interface between sticker app and WhatsApp. + */ + public static final String STICKER_PACK_IDENTIFIER_IN_QUERY = "sticker_pack_identifier"; + public static final String STICKER_PACK_NAME_IN_QUERY = "sticker_pack_name"; + public static final String STICKER_PACK_PUBLISHER_IN_QUERY = "sticker_pack_publisher"; + public static final String STICKER_PACK_ICON_IN_QUERY = "sticker_pack_icon"; + public static final String ANDROID_APP_DOWNLOAD_LINK_IN_QUERY = "android_play_store_link"; + public static final String IOS_APP_DOWNLOAD_LINK_IN_QUERY = "ios_app_download_link"; + public static final String PUBLISHER_EMAIL = "sticker_pack_publisher_email"; + public static final String PUBLISHER_WEBSITE = "sticker_pack_publisher_website"; + public static final String PRIVACY_POLICY_WEBSITE = "sticker_pack_privacy_policy_website"; + public static final String LICENSE_AGREENMENT_WEBSITE = "sticker_pack_license_agreement_website"; + + public static final String STICKER_FILE_NAME_IN_QUERY = "sticker_file_name"; + public static final String STICKER_FILE_EMOJI_IN_QUERY = "sticker_emoji"; + + + public static final String CONTENT_SCHEME = "content"; + public static Uri AUTHORITY_URI = new Uri.Builder().scheme(StickerContentProvider.CONTENT_SCHEME).authority(BuildConfig.CONTENT_PROVIDER_AUTHORITY).appendPath(StickerContentProvider.METADATA).build(); + + /** + * Do not change the values in the UriMatcher because otherwise, WhatsApp will not be able to fetch the stickers from the ContentProvider. + */ + private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + static final String METADATA = "metadata"; + private static final int METADATA_CODE = 1; + + private static final int METADATA_CODE_FOR_SINGLE_PACK = 2; + + static final String STICKERS = "stickers"; + private static final int STICKERS_CODE = 3; + + static final String STICKERS_ASSET = "stickers_asset"; + private static final int STICKERS_ASSET_CODE = 4; + + private static final int STICKER_PACK_TRAY_ICON_CODE = 5; + + private List stickerPackList; + + @Override + public boolean onCreate() { + final String authority = BuildConfig.CONTENT_PROVIDER_AUTHORITY; + if (!authority.startsWith(Objects.requireNonNull(getContext()).getPackageName())) { + throw new IllegalStateException("your authority (" + authority + ") for the content provider should start with your package name: " + getContext().getPackageName()); + } + + //the call to get the metadata for the sticker packs. + MATCHER.addURI(authority, METADATA, METADATA_CODE); + + //the call to get the metadata for single sticker pack. * represent the identifier + MATCHER.addURI(authority, METADATA + "/*", METADATA_CODE_FOR_SINGLE_PACK); + + //gets the list of stickers for a sticker pack, * respresent the identifier. + MATCHER.addURI(authority, STICKERS + "/*", STICKERS_CODE); + + for (StickerPack stickerPack : getStickerPackList()) { + MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + stickerPack.trayImageFile, STICKER_PACK_TRAY_ICON_CODE); + for (Sticker sticker : stickerPack.getStickers()) { + MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + sticker.imageFileName, STICKERS_ASSET_CODE); + } + } + + return true; + } + + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + final int code = MATCHER.match(uri); + Log.w("BASIC Query URI", String.valueOf(uri)); + if(StickerBook.getAllStickerPacks().isEmpty()){ + StickerBook.init(getContext()); + } + if (code == METADATA_CODE) { + return getPackForAllStickerPacks(uri); + } else if (code == METADATA_CODE_FOR_SINGLE_PACK) { + return getCursorForSingleStickerPack(uri); + } else if (code == STICKERS_CODE) { + return getStickersForAStickerPack(uri); + } else { + throw new IllegalArgumentException("Unknown URI: " + uri); + } + } + + @Nullable + @Override + public AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode) { + final int matchCode = MATCHER.match(uri); + Log.w("ASSETFILE QUERY URI", String.valueOf(uri) + ""); + Log.w("ASSETFILE QUERY PATH", uri.getPath() + ""); + final List pathSegments = uri.getPathSegments(); + StickerPack csp = StickerBook.getStickerPackByIdWithContext(pathSegments.get(pathSegments.size() - 2), getContext()); + if(csp!=null){ + String filename = pathSegments.get(pathSegments.size() - 1); + + + ParcelFileDescriptor pfd = null; + try { + if(filename.equals("trayimage") && csp.getTrayImageUri()!=null){ + pfd = Objects.requireNonNull(getContext()).getContentResolver().openFileDescriptor( + csp.getTrayImageUri(), "r"); + Log.w("ASSETFILE ACTUAL URI", String.valueOf(csp.getTrayImageUri()) + ""); + + } else { + try { + pfd = Objects.requireNonNull(getContext()).getContentResolver().openFileDescriptor( + csp.getStickerById(Integer.valueOf(filename)).getUri(), "r"); + Log.w("ASSETFILE ACTUAL URI", String.valueOf(csp.getStickerById(Integer.valueOf(filename)).getUri()) + ""); + } catch (NullPointerException e){ + Log.e("StickerMaker", "WhatsApp tried to access a non existent sticker, id: " + filename); + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + + return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); + } + return getImageAsset(uri); + /*if (matchCode == STICKERS_ASSET_CODE || matchCode == STICKER_PACK_TRAY_ICON_CODE) { + return getImageAsset(uri); + } + return null;*/ + } + + @Nullable + @Override + public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { + Log.w("TESTING THIS", "IS IT OPENING FILE REGULARLY?"); + return super.openFile(uri, mode); + } + + @Override + public String getType(@NonNull Uri uri) { + final int matchCode = MATCHER.match(uri); + switch (matchCode) { + case METADATA_CODE: + return "vnd.android.cursor.dir/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + METADATA; + case METADATA_CODE_FOR_SINGLE_PACK: + return "vnd.android.cursor.item/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + METADATA; + case STICKERS_CODE: + return "vnd.android.cursor.dir/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + STICKERS; + case STICKERS_ASSET_CODE: + return "image/webp"; + case STICKER_PACK_TRAY_ICON_CODE: + return "image/png"; + default: + throw new IllegalArgumentException("Unknown URI: " + uri); + } + } + + public List getStickerPackList() { + /*if (stickerPackList == null) { + readContentFile(); + }*/ + stickerPackList = StickerBook.getAllStickerPacks(); + return stickerPackList; + } + + + private Cursor getPackForAllStickerPacks(@NonNull Uri uri) { + return getStickerPackInfo(uri, getStickerPackList()); + } + + private Cursor getCursorForSingleStickerPack(@NonNull Uri uri) { + final String identifier = uri.getLastPathSegment(); + for (StickerPack stickerPack : getStickerPackList()) { + if (identifier.equals(stickerPack.identifier)) { + return getStickerPackInfo(uri, Collections.singletonList(stickerPack)); + } + } + + return getStickerPackInfo(uri, new ArrayList()); + } + + @NonNull + private Cursor getStickerPackInfo(@NonNull Uri uri, @NonNull List stickerPackList) { + MatrixCursor cursor = new MatrixCursor( + new String[]{ + STICKER_PACK_IDENTIFIER_IN_QUERY, + STICKER_PACK_NAME_IN_QUERY, + STICKER_PACK_PUBLISHER_IN_QUERY, + STICKER_PACK_ICON_IN_QUERY, + ANDROID_APP_DOWNLOAD_LINK_IN_QUERY, + IOS_APP_DOWNLOAD_LINK_IN_QUERY, + PUBLISHER_EMAIL, + PUBLISHER_WEBSITE, + PRIVACY_POLICY_WEBSITE, + LICENSE_AGREENMENT_WEBSITE + }); + for (StickerPack stickerPack : stickerPackList) { + MatrixCursor.RowBuilder builder = cursor.newRow(); + builder.add(stickerPack.identifier); + builder.add(stickerPack.name); + builder.add(stickerPack.publisher); + builder.add(stickerPack.trayImageFile); + builder.add(stickerPack.androidPlayStoreLink); + builder.add(stickerPack.iosAppStoreLink); + builder.add(stickerPack.publisherEmail); + builder.add(stickerPack.publisherWebsite); + builder.add(stickerPack.privacyPolicyWebsite); + builder.add(stickerPack.licenseAgreementWebsite); + } + cursor.setNotificationUri(Objects.requireNonNull(getContext()).getContentResolver(), uri); + return cursor; + } + + @NonNull + private Cursor getStickersForAStickerPack(@NonNull Uri uri) { + final String identifier = uri.getLastPathSegment(); + MatrixCursor cursor = new MatrixCursor(new String[]{STICKER_FILE_NAME_IN_QUERY, STICKER_FILE_EMOJI_IN_QUERY}); + for (StickerPack stickerPack : getStickerPackList()) { + if (identifier.equals(stickerPack.identifier)) { + for (Sticker sticker : stickerPack.getStickers()) { + cursor.addRow(new Object[]{sticker.imageFileName, TextUtils.join(",", sticker.emojis)}); + } + } + } + cursor.setNotificationUri(Objects.requireNonNull(getContext()).getContentResolver(), uri); + return cursor; + } + + private AssetFileDescriptor getImageAsset(Uri uri) throws IllegalArgumentException { + Log.w("IMAGE ASSET", "ASKING FOR ASSET?"); + AssetManager am = Objects.requireNonNull(getContext()).getAssets(); + final List pathSegments = uri.getPathSegments(); + if (pathSegments.size() != 3) { + throw new IllegalArgumentException("path segments should be 3, uri is: " + uri); + } + String fileName = pathSegments.get(pathSegments.size() - 1); + final String identifier = pathSegments.get(pathSegments.size() - 2); + if (TextUtils.isEmpty(identifier)) { + throw new IllegalArgumentException("identifier is empty, uri: " + uri); + } + if (TextUtils.isEmpty(fileName)) { + throw new IllegalArgumentException("file name is empty, uri: " + uri); + } + //making sure the file that is trying to be fetched is in the list of stickers. + for (StickerPack stickerPack : getStickerPackList()) { + if (identifier.equals(stickerPack.identifier)) { + if (fileName.equals(stickerPack.trayImageFile)) { + return fetchFile(uri, am, fileName, identifier); + } else { + for (Sticker sticker : stickerPack.getStickers()) { + if (fileName.equals(sticker.imageFileName)) { + return fetchFile(uri, am, fileName, identifier); + } + } + } + } + } + return null; + } + + private AssetFileDescriptor fetchFile(@NonNull Uri uri, @NonNull AssetManager am, @NonNull String fileName, @NonNull String identifier) { + try { + return am.openFd(identifier + "/" + fileName); + } catch (IOException e) { + Log.e(Objects.requireNonNull(getContext()).getPackageName(), "IOException when getting asset file, uri:" + uri, e); + return null; + } + } + + + @Override + public int delete(@NonNull Uri uri, @Nullable String selection, String[] selectionArgs) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public Uri insert(@NonNull Uri uri, ContentValues values) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public int update(@NonNull Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + throw new UnsupportedOperationException("Not supported"); + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPack.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPack.java new file mode 100755 index 0000000..da05885 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPack.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.content.Context; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import com.idoideas.stickermaker.ImageManipulation; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class StickerPack implements Parcelable { + Uri trayImageUri; + String identifier; + String name; + String publisher; + String trayImageFile; + final String publisherEmail; + final String publisherWebsite; + final String privacyPolicyWebsite; + final String licenseAgreementWebsite; + + String iosAppStoreLink; + private List stickers; + private long totalSize; + String androidPlayStoreLink; + private boolean isWhitelisted; + private int stickersAddedIndex = 0; + + /*public StickerPack(String identifier, String name, String publisher, String trayImageFile, String publisherEmail, String publisherWebsite, String privacyPolicyWebsite, String licenseAgreementWebsite) { + this.identifier = identifier; + this.name = name; + this.publisher = publisher; + this.trayImageFile = trayImageFile; + this.trayImageUri = Uri.parse(""); + this.publisherEmail = publisherEmail; + this.publisherWebsite = publisherWebsite; + this.privacyPolicyWebsite = privacyPolicyWebsite; + this.licenseAgreementWebsite = licenseAgreementWebsite; + this.stickers = new ArrayList<>(); + }*/ + + public StickerPack(String identifier, String name, String publisher, Uri trayImageUri, String publisherEmail, String publisherWebsite, String privacyPolicyWebsite, String licenseAgreementWebsite, Context context) { + this.identifier = identifier; + this.name = name; + this.publisher = publisher; + this.trayImageFile = "trayimage"; + this.trayImageUri = ImageManipulation.convertIconTrayToWebP(trayImageUri, this.identifier, "trayImage", context); + this.publisherEmail = publisherEmail; + this.publisherWebsite = publisherWebsite; + this.privacyPolicyWebsite = privacyPolicyWebsite; + this.licenseAgreementWebsite = licenseAgreementWebsite; + this.stickers = new ArrayList<>(); + } + + void setIsWhitelisted(boolean isWhitelisted) { + this.isWhitelisted = isWhitelisted; + } + + boolean getIsWhitelisted() { + return isWhitelisted; + } + + protected StickerPack(Parcel in) { + identifier = in.readString(); + name = in.readString(); + publisher = in.readString(); + trayImageFile = in.readString(); + publisherEmail = in.readString(); + publisherWebsite = in.readString(); + privacyPolicyWebsite = in.readString(); + licenseAgreementWebsite = in.readString(); + iosAppStoreLink = in.readString(); + stickers = in.createTypedArrayList(Sticker.CREATOR); + totalSize = in.readLong(); + androidPlayStoreLink = in.readString(); + isWhitelisted = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public StickerPack createFromParcel(Parcel in) { + return new StickerPack(in); + } + + @Override + public StickerPack[] newArray(int size) { + return new StickerPack[size]; + } + }; + + public void addSticker(Uri uri, Context context){ + String index = String.valueOf(stickersAddedIndex); + this.stickers.add(new Sticker( + index, + ImageManipulation.convertImageToWebP(uri, this.identifier, index, context), + new ArrayList())); + stickersAddedIndex++; + } + + public void deleteSticker(Sticker sticker){ + new File(sticker.getUri().getPath()).delete(); + this.stickers.remove(sticker); + } + + public Sticker getSticker(int index){ + return this.stickers.get(index); + } + + public Sticker getStickerById(int index){ + for(Sticker s : this.stickers){ + if(s.getImageFileName().equals(String.valueOf(index))){ + return s; + } + } + return null; + } + + public void setAndroidPlayStoreLink(String androidPlayStoreLink) { + this.androidPlayStoreLink = androidPlayStoreLink; + } + + public void setIosAppStoreLink(String iosAppStoreLink) { + this.iosAppStoreLink = iosAppStoreLink; + } + + public List getStickers() { + return stickers; + } + + public long getTotalSize() { + return totalSize; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(identifier); + dest.writeString(name); + dest.writeString(publisher); + dest.writeString(trayImageFile); + dest.writeString(publisherEmail); + dest.writeString(publisherWebsite); + dest.writeString(privacyPolicyWebsite); + dest.writeString(licenseAgreementWebsite); + dest.writeString(iosAppStoreLink); + dest.writeTypedList(stickers); + dest.writeLong(totalSize); + dest.writeString(androidPlayStoreLink); + dest.writeByte((byte) (isWhitelisted ? 1 : 0)); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getIdentifier() { + return this.identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public Uri getTrayImageUri() { + return trayImageUri; + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackDetailsActivity.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackDetailsActivity.java new file mode 100755 index 0000000..b510809 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackDetailsActivity.java @@ -0,0 +1,371 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.ClipData; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.AdView; +import com.google.android.gms.ads.InterstitialAd; +import com.google.android.gms.ads.MobileAds; +import com.idoideas.stickermaker.BuildConfig; +import com.idoideas.stickermaker.DataArchiver; +import com.idoideas.stickermaker.R; +import com.idoideas.stickermaker.StickerBook; + +import java.lang.ref.WeakReference; +import java.util.Objects; + +public class StickerPackDetailsActivity extends BaseActivity { + + /** + * Do not change below values of below 3 lines as this is also used by WhatsApp + */ + public static final String EXTRA_STICKER_PACK_ID = "sticker_pack_id"; + public static final String EXTRA_STICKER_PACK_AUTHORITY = "sticker_pack_authority"; + public static final String EXTRA_STICKER_PACK_NAME = "sticker_pack_name"; + + public static final int ADD_PACK = 200; + public static final String EXTRA_STICKER_PACK_WEBSITE = "sticker_pack_website"; + public static final String EXTRA_STICKER_PACK_EMAIL = "sticker_pack_email"; + public static final String EXTRA_STICKER_PACK_PRIVACY_POLICY = "sticker_pack_privacy_policy"; + public static final String EXTRA_STICKER_PACK_TRAY_ICON = "sticker_pack_tray_icon"; + public static final String EXTRA_SHOW_UP_BUTTON = "show_up_button"; + public static final String EXTRA_STICKER_PACK_DATA = "sticker_pack"; + private static final String TAG = "StickerPackDetails"; + + private RecyclerView recyclerView; + private GridLayoutManager layoutManager; + private StickerPreviewAdapter stickerPreviewAdapter; + private int numColumns; + private View addButton; + private View alreadyAddedText; + private StickerPack stickerPack; + private View divider; + private WhiteListCheckAsyncTask whiteListCheckAsyncTask; + private FrameLayout shareButton; + private FrameLayout deleteButton; + private AdView mAdView; + private InterstitialAd mInterstitialAd; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.sticker_pack_details); + + MobileAds.initialize(this, getString(R.string.admob_ad_id)); + mAdView = findViewById(R.id.adView); + AdRequest adRequest = new AdRequest + .Builder() + .addTestDevice(getString(R.string.test_device)) + .build(); + mAdView.loadAd(adRequest); + + mInterstitialAd = new InterstitialAd(this); + mInterstitialAd.setAdUnitId(getString(R.string.admob_fullscreen_adding_pack_unit_id)); + mInterstitialAd.loadAd(new AdRequest.Builder().addTestDevice(getString(R.string.test_device)).build()); + + boolean showUpButton = getIntent().getBooleanExtra(EXTRA_SHOW_UP_BUTTON, false); + stickerPack = StickerBook.getStickerPackById(getIntent().getStringExtra(EXTRA_STICKER_PACK_DATA)); + TextView packNameTextView = findViewById(R.id.pack_name); + TextView packPublisherTextView = findViewById(R.id.author); + ImageView packTrayIcon = findViewById(R.id.tray_image); + + addButton = findViewById(R.id.add_to_whatsapp_button); + shareButton = findViewById(R.id.share_pack_button); + deleteButton = findViewById(R.id.delete_pack_button); + alreadyAddedText = findViewById(R.id.already_added_text); + layoutManager = new GridLayoutManager(this, 1); + recyclerView = findViewById(R.id.sticker_list); + recyclerView.setLayoutManager(layoutManager); + recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(pageLayoutListener); + recyclerView.addOnScrollListener(dividerScrollListener); + divider = findViewById(R.id.divider); + if (stickerPreviewAdapter == null) { + stickerPreviewAdapter = new StickerPreviewAdapter(getLayoutInflater(), R.drawable.sticker_error, getResources().getDimensionPixelSize(R.dimen.sticker_pack_details_image_size), getResources().getDimensionPixelSize(R.dimen.sticker_pack_details_image_padding), stickerPack); + recyclerView.setAdapter(stickerPreviewAdapter); + } + packNameTextView.setText(stickerPack.name); + packPublisherTextView.setText(stickerPack.publisher); + packTrayIcon.setImageURI(stickerPack.getTrayImageUri()); + findViewById(R.id.add_sticker_to_pack).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openFile(); + } + }); + + addButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if(stickerPack.getStickers().size()>=3) { + StickerPackDetailsActivity.this.addStickerPackToWhatsApp(stickerPack); + } else { + AlertDialog alertDialog = new AlertDialog.Builder(StickerPackDetailsActivity.this) + .setNegativeButton("Ok", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }).create(); + alertDialog.setTitle("Invalid Action"); + alertDialog.setMessage("In order to be applied to WhatsApp, the sticker pack must have at least 3 stickers. Please add more stickers first."); + alertDialog.show(); + } + } + }); + + shareButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + DataArchiver.createZipFileFromStickerPack(stickerPack, StickerPackDetailsActivity.this); + } + }); + + deleteButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + AlertDialog alertDialog = new AlertDialog.Builder(StickerPackDetailsActivity.this) + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }) + .setPositiveButton("Confirm", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + StickerBook.deleteStickerPackById(stickerPack.getIdentifier()); + finish(); + Intent intent = new Intent(StickerPackDetailsActivity.this, StickerPackListActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + Toast.makeText(StickerPackDetailsActivity.this, "Sticker Pack deleted", Toast.LENGTH_SHORT).show(); + } + }).create(); + alertDialog.setTitle("Are you sure?"); + alertDialog.setMessage("Deleting this package will also remove it from your WhatsApp app."); + alertDialog.show(); + } + }); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(showUpButton); + getSupportActionBar().setTitle(showUpButton ? R.string.title_activity_sticker_pack_details_multiple_pack : R.string.title_activity_sticker_pack_details_single_pack); + } + } + + private void launchInfoActivity(String publisherWebsite, String publisherEmail, String privacyPolicyWebsite, String trayIconUriString) { + Intent intent = new Intent(StickerPackDetailsActivity.this, StickerPackInfoActivity.class); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_ID, stickerPack.identifier); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_WEBSITE, publisherWebsite); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_EMAIL, publisherEmail); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_PRIVACY_POLICY, privacyPolicyWebsite); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_TRAY_ICON, stickerPack.getTrayImageUri().toString()); + startActivity(intent); + } + + private void openFile() { + Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT); + i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + i.setType("image/*"); + startActivityForResult(i, 3000); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.toolbar, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_info && stickerPack != null) { + final String publisherWebsite = stickerPack.publisherWebsite; + final String publisherEmail = stickerPack.publisherEmail; + final String privacyPolicyWebsite = stickerPack.privacyPolicyWebsite; + Uri trayIconUri = stickerPack.getTrayImageUri(); + launchInfoActivity(publisherWebsite, publisherEmail, privacyPolicyWebsite, trayIconUri.toString()); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void addStickerPackToWhatsApp(StickerPack sp) { + Intent intent = new Intent(); + intent.setAction("com.whatsapp.intent.action.ENABLE_STICKER_PACK"); + Log.w("IS IT A NEW IDENTIFIER?", sp.getIdentifier()); + intent.putExtra(EXTRA_STICKER_PACK_ID, sp.getIdentifier()); + intent.putExtra(EXTRA_STICKER_PACK_AUTHORITY, BuildConfig.CONTENT_PROVIDER_AUTHORITY); + intent.putExtra(EXTRA_STICKER_PACK_NAME, sp.getName()); + try { + startActivityForResult(intent, 200); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.error_adding_sticker_pack, Toast.LENGTH_LONG).show(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == ADD_PACK) { + if (resultCode == Activity.RESULT_CANCELED && data != null) { + final String validationError = data.getStringExtra("validation_error"); + if (validationError != null) { + if (BuildConfig.DEBUG) { + //validation error should be shown to developer only, not users. + MessageDialogFragment.newInstance(R.string.title_validation_error, validationError).show(getSupportFragmentManager(), "validation error"); + } + Log.e(TAG, "Validation failed:" + validationError); + } + } else { + if (mInterstitialAd.isLoaded()) { + mInterstitialAd.show(); + mInterstitialAd = new InterstitialAd(this); + mInterstitialAd.setAdUnitId(getString(R.string.admob_fullscreen_adding_pack_unit_id)); + mInterstitialAd.loadAd(new AdRequest.Builder().addTestDevice(getString(R.string.test_device)).build()); + } + } + } else if(requestCode == 3000){ + if(data!=null){ + if(data.getClipData()!=null){ + ClipData clipData = data.getClipData(); + for(int i = 0; i < clipData.getItemCount(); i++) + { + ClipData.Item path = clipData.getItemAt(i); + Uri uri = path.getUri(); + getContentResolver().takePersistableUriPermission(Objects.requireNonNull(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION); + stickerPack.addSticker(uri, this); + } + } else { + Uri uri = data.getData(); + getContentResolver().takePersistableUriPermission(Objects.requireNonNull(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION); + stickerPack.addSticker(uri, this); + } + finish(); + startActivity(getIntent()); + } + } + } + + private final ViewTreeObserver.OnGlobalLayoutListener pageLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + setNumColumns(recyclerView.getWidth() / recyclerView.getContext().getResources().getDimensionPixelSize(R.dimen.sticker_pack_details_image_size)); + } + }; + + private void setNumColumns(int numColumns) { + if (this.numColumns != numColumns) { + layoutManager.setSpanCount(numColumns); + this.numColumns = numColumns; + if (stickerPreviewAdapter != null) { + stickerPreviewAdapter.notifyDataSetChanged(); + } + } + } + + private final RecyclerView.OnScrollListener dividerScrollListener = new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) { + super.onScrollStateChanged(recyclerView, newState); + updateDivider(recyclerView); + } + + @Override + public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { + super.onScrolled(recyclerView, dx, dy); + updateDivider(recyclerView); + } + + private void updateDivider(RecyclerView recyclerView) { + boolean showDivider = recyclerView.computeVerticalScrollOffset() > 0; + if (divider != null) { + divider.setVisibility(showDivider ? View.VISIBLE : View.INVISIBLE); + } + } + }; + + @Override + protected void onResume() { + super.onResume(); + whiteListCheckAsyncTask = new WhiteListCheckAsyncTask(this); + whiteListCheckAsyncTask.execute(stickerPack); + } + + @Override + protected void onPause() { + super.onPause(); + if (whiteListCheckAsyncTask != null && !whiteListCheckAsyncTask.isCancelled()) { + whiteListCheckAsyncTask.cancel(true); + } + } + + private void updateAddUI(Boolean isWhitelisted) { + if (isWhitelisted) { + addButton.setVisibility(View.GONE); + alreadyAddedText.setVisibility(View.VISIBLE); + } else { + addButton.setVisibility(View.VISIBLE); + alreadyAddedText.setVisibility(View.GONE); + } + } + + static class WhiteListCheckAsyncTask extends AsyncTask { + private final WeakReference stickerPackDetailsActivityWeakReference; + + WhiteListCheckAsyncTask(StickerPackDetailsActivity stickerPackListActivity) { + this.stickerPackDetailsActivityWeakReference = new WeakReference<>(stickerPackListActivity); + } + + @Override + protected final Boolean doInBackground(StickerPack... stickerPacks) { + StickerPack stickerPack = stickerPacks[0]; + final StickerPackDetailsActivity stickerPackDetailsActivity = stickerPackDetailsActivityWeakReference.get(); + //noinspection SimplifiableIfStatement + if (stickerPackDetailsActivity == null) { + return false; + } + return WhitelistCheck.isWhitelisted(stickerPackDetailsActivity, stickerPack.identifier); + } + + @Override + protected void onPostExecute(Boolean isWhitelisted) { + final StickerPackDetailsActivity stickerPackDetailsActivity = stickerPackDetailsActivityWeakReference.get(); + if (stickerPackDetailsActivity != null) { + stickerPackDetailsActivity.updateAddUI(isWhitelisted); + } + } + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackInfoActivity.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackInfoActivity.java new file mode 100755 index 0000000..9f3f4b6 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackInfoActivity.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.content.Intent; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import com.idoideas.stickermaker.R; + +import java.io.FileNotFoundException; +import java.io.InputStream; + +public class StickerPackInfoActivity extends BaseActivity { + + private static final String TAG = "StickerPackInfoActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sticker_pack_info); + + final String trayIconUriString = getIntent().getStringExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_TRAY_ICON); + final String website = getIntent().getStringExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_WEBSITE); + final String email = getIntent().getStringExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_EMAIL); + final String privacyPolicy = getIntent().getStringExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_PRIVACY_POLICY); + + final TextView trayIcon = findViewById(R.id.tray_icon); + try { + final InputStream inputStream = getContentResolver().openInputStream(Uri.parse(trayIconUriString)); + final BitmapDrawable trayDrawable = new BitmapDrawable(inputStream); + final Drawable emailDrawable = getResources().getDrawable(R.drawable.sticker_3rdparty_email); + trayDrawable.setBounds(new Rect(0, 0, emailDrawable.getIntrinsicWidth(), emailDrawable.getIntrinsicHeight())); + trayIcon.setCompoundDrawables(trayDrawable, null, null, null); + } catch (FileNotFoundException e) { + Log.e(TAG, "could not find the uri for the tray image:" + trayIconUriString); + } + + final TextView viewWebpage = findViewById(R.id.view_webpage); + if (TextUtils.isEmpty(website)) { + viewWebpage.setVisibility(View.GONE); + } else { + viewWebpage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + StickerPackInfoActivity.this.launchWebpage(website); + } + }); + } + + final TextView sendEmail = findViewById(R.id.send_email); + if (TextUtils.isEmpty(email)) { + sendEmail.setVisibility(View.GONE); + } else { + sendEmail.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + StickerPackInfoActivity.this.launchEmailClient(email); + } + }); + } + + final TextView viewPrivacyPolicy = findViewById(R.id.privacy_policy); + if (TextUtils.isEmpty(privacyPolicy)) { + viewPrivacyPolicy.setVisibility(View.GONE); + } else { + viewPrivacyPolicy.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + StickerPackInfoActivity.this.launchWebpage(privacyPolicy); + } + }); + } + } + + private void launchEmailClient(String email) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("plain/text"); + intent.putExtra(Intent.EXTRA_EMAIL, new String[]{email}); + startActivity(Intent.createChooser(intent, "Send email with")); + } + + private void launchWebpage(String website) { + Uri uri = Uri.parse(website); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + } + +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackListActivity.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackListActivity.java new file mode 100755 index 0000000..9b96f12 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackListActivity.java @@ -0,0 +1,586 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.InputType; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingClientStateListener; +import com.android.billingclient.api.BillingFlowParams; +import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.PurchasesUpdatedListener; +import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.soloader.SoLoader; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.AdView; +import com.google.android.gms.ads.InterstitialAd; +import com.google.android.gms.ads.MobileAds; +import com.idoideas.stickermaker.BuildConfig; +import com.idoideas.stickermaker.DataArchiver; +import com.idoideas.stickermaker.NewUserIntroActivity; +import com.idoideas.stickermaker.R; +import com.idoideas.stickermaker.StickerBook; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import co.mobiwise.materialintro.shape.Focus; +import co.mobiwise.materialintro.shape.FocusGravity; +import co.mobiwise.materialintro.shape.ShapeType; +import co.mobiwise.materialintro.view.MaterialIntroView; + +import static com.idoideas.stickermaker.NewUserIntroActivity.verifyStoragePermissions; + + +public class StickerPackListActivity extends BaseActivity { + public static final String EXTRA_STICKER_PACK_LIST_DATA = "sticker_pack_list"; + private static final int STICKER_PREVIEW_DISPLAY_LIMIT = 5; + private static final String TAG = "StickerPackList"; + private LinearLayoutManager packLayoutManager; + private static RecyclerView packRecyclerView; + private static StickerPackListAdapter allStickerPacksListAdapter; + WhiteListCheckAsyncTask whiteListCheckAsyncTask; + ArrayList stickerPackList; + public static Context context; + public static String newName, newCreator; + private AdView mAdView; + private InterstitialAd mInterstitialAd; + private BillingClient mBillingClient; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sticker_pack_list); + + MobileAds.initialize(this, getString(R.string.admob_ad_id)); + mAdView = findViewById(R.id.adView); + AdRequest adRequest = new AdRequest + .Builder() + .addTestDevice(getString(R.string.test_device)) + .build(); + mAdView.loadAd(adRequest); + + mInterstitialAd = new InterstitialAd(this); + mInterstitialAd.setAdUnitId(getString(R.string.admob_fullscreen_adding_pack_unit_id)); + mInterstitialAd.loadAd(new AdRequest.Builder().addTestDevice(getString(R.string.test_device)).build()); + + StickerBook.init(this); + + Fresco.initialize(this); + + context = getApplicationContext(); + + SoLoader.init(this, /* native exopackage */ false); + + mBillingClient = BillingClient.newBuilder(this).setListener(new PurchasesUpdatedListener() { + @Override + public void onPurchasesUpdated(int responseCode, @Nullable List purchases) { + + } + }).build(); + mBillingClient.startConnection(new BillingClientStateListener() { + @Override + public void onBillingSetupFinished(@BillingClient.BillingResponse int billingResponseCode) { + if (billingResponseCode == BillingClient.BillingResponse.OK) { + // The billing client is ready. You can query purchases here. + } + } + @Override + public void onBillingServiceDisconnected() { + + } + }); + + packRecyclerView = findViewById(R.id.sticker_pack_list); + stickerPackList = StickerBook.getAllStickerPacks();//getIntent().getParcelableArrayListExtra( EXTRA_STICKER_PACK_LIST_DATA); + showStickerPackList(stickerPackList); + + if (Intent.ACTION_SEND.equals(getIntent().getAction())) { + Bundle extras = getIntent().getExtras(); + if (extras.containsKey(Intent.EXTRA_STREAM)) { + Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM); + if(uri!=null){ + DataArchiver.importZipFileToStickerPack(uri, StickerPackListActivity.this); + } + } + } + + if(toShowIntro()){ + startActivityForResult(new Intent(this, NewUserIntroActivity.class), 1114); + } + + } + + @Override + protected void onResume() { + super.onResume(); + + String action = getIntent().getAction(); + if(action == null) { + Log.v("Example", "Force restart"); + Intent intent = new Intent(this, StickerPackListActivity.class); + intent.setAction("Already created"); + startActivity(intent); + finish(); + } + + whiteListCheckAsyncTask = new WhiteListCheckAsyncTask(this); + //noinspection unchecked + whiteListCheckAsyncTask.execute(stickerPackList); + } + + + @Override + protected void onPause() { + super.onPause(); + DataArchiver.writeStickerBookJSON(StickerBook.getAllStickerPacks(), this); + if (whiteListCheckAsyncTask != null && !whiteListCheckAsyncTask.isCancelled()) { + whiteListCheckAsyncTask.cancel(true); + } + } + + @Override + protected void onDestroy() { + DataArchiver.writeStickerBookJSON(StickerBook.getAllStickerPacks(), this); + super.onDestroy(); + } + + + public void showStickerPackList(List stickerPackList) { + allStickerPacksListAdapter = new StickerPackListAdapter(stickerPackList, onAddButtonClickedListener); + packRecyclerView.setAdapter(allStickerPacksListAdapter); + packLayoutManager = new LinearLayoutManager(this); + packLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration( + packRecyclerView.getContext(), + packLayoutManager.getOrientation() + ); + packRecyclerView.addItemDecoration(dividerItemDecoration); + packRecyclerView.setLayoutManager(packLayoutManager); + packRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(this::recalculateColumnCount); + } + + + + private StickerPackListAdapter.OnAddButtonClickedListener onAddButtonClickedListener = new StickerPackListAdapter.OnAddButtonClickedListener() { + @Override + public void onAddButtonClicked(StickerPack pack) { + if(pack.getStickers().size()>=3) { + Intent intent = new Intent(); + intent.setAction("com.whatsapp.intent.action.ENABLE_STICKER_PACK"); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_ID, pack.identifier); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_AUTHORITY, BuildConfig.CONTENT_PROVIDER_AUTHORITY); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_NAME, pack.name); + try { + StickerPackListActivity.this.startActivityForResult(intent, StickerPackDetailsActivity.ADD_PACK); + } catch (ActivityNotFoundException e) { + Toast.makeText(StickerPackListActivity.this, R.string.error_adding_sticker_pack, Toast.LENGTH_LONG).show(); + } + } else { + AlertDialog alertDialog = new AlertDialog.Builder(StickerPackListActivity.this) + .setNegativeButton("Ok", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }).create(); + alertDialog.setTitle("Invalid Action"); + alertDialog.setMessage("In order to be applied to WhatsApp, the sticker pack must have at least 3 stickers. Please add more stickers first."); + alertDialog.show(); + } + } + }; + + private void recalculateColumnCount() { + final int previewSize = getResources().getDimensionPixelSize(R.dimen.sticker_pack_list_item_preview_image_size); + int firstVisibleItemPosition = packLayoutManager.findFirstVisibleItemPosition(); + StickerPackListItemViewHolder viewHolder = (StickerPackListItemViewHolder) packRecyclerView.findViewHolderForAdapterPosition(firstVisibleItemPosition); + if (viewHolder != null) { + final int max = Math.max(viewHolder.imageRowView.getMeasuredWidth() / previewSize, 1); + int numColumns = Math.min(STICKER_PREVIEW_DISPLAY_LIMIT, max); + allStickerPacksListAdapter.setMaxNumberOfStickersInARow(numColumns); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == StickerPackDetailsActivity.ADD_PACK) { + if (resultCode == Activity.RESULT_CANCELED && data != null) { + final String validationError = data.getStringExtra("validation_error"); + if (validationError != null) { + if (BuildConfig.DEBUG) { + //validation error should be shown to developer only, not users. + MessageDialogFragment.newInstance(R.string.title_validation_error, validationError).show(getSupportFragmentManager(), "validation error"); + } + Log.e(TAG, "Validation failed:" + validationError); + } + } else { + if (mInterstitialAd.isLoaded()) { + mInterstitialAd.show(); + mInterstitialAd = new InterstitialAd(this); + mInterstitialAd.setAdUnitId(getString(R.string.admob_fullscreen_adding_pack_unit_id)); + mInterstitialAd.loadAd(new AdRequest.Builder().addTestDevice(getString(R.string.test_device)).build()); + } + } + } else if (data!=null && requestCode==2319){ + Uri uri = data.getData(); + getContentResolver().takePersistableUriPermission(Objects.requireNonNull(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION); + createNewStickerPackAndOpenIt(newName, newCreator, uri); + } else if(requestCode == 1114){ + makeIntroNotRunAgain(); + + new MaterialIntroView.Builder(this) + .enableIcon(false) + .setFocusGravity(FocusGravity.CENTER) + .setFocusType(Focus.MINIMUM) + .setDelayMillis(500) + .enableFadeAnimation(true) + .performClick(true) + .setInfoText("To add new sticker packs, click here.") + .setShape(ShapeType.CIRCLE) + .setTarget(findViewById(R.id.action_add)) + .setUsageId("intro_card") //THIS SHOULD BE UNIQUE ID + .show(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.toolbar_main, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_add) { + addNewStickerPackInInterface(); + return true; + } else if(item.getItemId() == R.id.action_info){ + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + LayoutInflater inflater = this.getLayoutInflater(); + View dialogView = inflater.inflate(R.layout.about_layout, null); + + dialogView.findViewById(R.id.redditlogo).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.reddit.com/u/idoideas")); + startActivity(browserIntent); + } + }); + + dialogView.findViewById(R.id.twitterlogo).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.twitter.com/idoideas")); + startActivity(browserIntent); + } + }); + + dialogView.findViewById(R.id.githublogo).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.github.com/idoideas")); + startActivity(browserIntent); + } + }); + + dialogBuilder.setView(dialogView); + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.show(); + } else if (item.getItemId() == R.id.action_donate){ + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Thank you for donation!"); + builder.setMessage("We appreciate your support in great apps and open-source projects.\n\nHow much would you like to donate?"); + builder.setPositiveButton("A Sandwich - 5$", + new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int id) + { + dialog.cancel(); + startInAppPurchase("5_dollar_donation"); + } + }); + + builder.setNeutralButton("A Piece of Gum - 1$", + new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int id) + { + dialog.cancel(); + startInAppPurchase("1_dollar_donation"); + } + }); + + builder.setNegativeButton("A Coffee - 3$", + new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int id) + { + dialog.cancel(); + startInAppPurchase("3_dollar_donation"); + } + }); + builder.create().show(); + } + return super.onOptionsItemSelected(item); + } + + + static class WhiteListCheckAsyncTask extends AsyncTask, Void, List> { + private final WeakReference stickerPackListActivityWeakReference; + + WhiteListCheckAsyncTask(StickerPackListActivity stickerPackListActivity) { + this.stickerPackListActivityWeakReference = new WeakReference<>(stickerPackListActivity); + } + + @SafeVarargs + @Override + protected final List doInBackground(List... lists) { + List stickerPackList = lists[0]; + final StickerPackListActivity stickerPackListActivity = stickerPackListActivityWeakReference.get(); + if (stickerPackListActivity == null) { + return stickerPackList; + } + for (StickerPack stickerPack : stickerPackList) { + stickerPack.setIsWhitelisted(WhitelistCheck.isWhitelisted(stickerPackListActivity, stickerPack.identifier)); + } + return stickerPackList; + } + + @Override + protected void onPostExecute(List stickerPackList) { + final StickerPackListActivity stickerPackListActivity = stickerPackListActivityWeakReference.get(); + if (stickerPackListActivity != null) { + stickerPackListActivity.allStickerPacksListAdapter.notifyDataSetChanged(); + } + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + case 1: + if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { + AlertDialog alertDialog = new AlertDialog.Builder(this) + .setPositiveButton("Let's Go", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + verifyStoragePermissions(StickerPackListActivity.this); + } + }) + .create(); + alertDialog.setTitle("Notice!"); + alertDialog.setMessage("We've recognized you denied the storage access permission for this app." + + "\n\nIn order for this app to work, storage access is required."); + alertDialog.show(); + } + break; + } + } + + private void addNewStickerPackInInterface(){ + + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle("Create New Sticker Pack"); + dialog.setMessage("Please specify title and creator for the pack."); + + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + + final EditText nameBox = new EditText(this); + nameBox.setLines(1); + LinearLayout.LayoutParams buttonLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + buttonLayoutParams.setMargins(50, 0, 50, 10); + nameBox.setLayoutParams(buttonLayoutParams); + nameBox.setHint("Pack Name"); + nameBox.setInputType(InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); + layout.addView(nameBox); + + final EditText creatorBox = new EditText(this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + creatorBox.setAutofillHints("name"); + } + creatorBox.setLines(1); + creatorBox.setLayoutParams(buttonLayoutParams); + creatorBox.setInputType(InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); + creatorBox.setHint("Creator"); + layout.addView(creatorBox); + + dialog.setView(layout); + + dialog.setPositiveButton("OK", null); + + dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + final AlertDialog ad = dialog.create(); + + ad.show(); + + Button b = ad.getButton(AlertDialog.BUTTON_POSITIVE); + b.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View view) { + if(TextUtils.isEmpty(nameBox.getText())){ + nameBox.setError("Package name is required!"); + } + + if(TextUtils.isEmpty(creatorBox.getText())){ + creatorBox.setError("Creator is required!"); + } + + if(!TextUtils.isEmpty(nameBox.getText()) && !TextUtils.isEmpty(creatorBox.getText())) { + ad.dismiss(); + createDialogForPickingIconImage(nameBox, creatorBox); + } + } + }); + + creatorBox.setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { + b.performClick(); + } + return false; + } + }); + } + + private void createDialogForPickingIconImage(EditText nameBox, EditText creatorBox){ + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Pick your pack's icon image"); + builder.setMessage("Now you will pick the new sticker pack's icon image.") + .setCancelable(false) + .setPositiveButton("Let's go", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + openFileTray(nameBox.getText().toString(), creatorBox.getText().toString()); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + + private void createNewStickerPackAndOpenIt(String name, String creator, Uri trayImage){ + String newId = UUID.randomUUID().toString(); + StickerPack sp = new StickerPack( + newId, + name, + creator, + trayImage, + "", + "", + "", + "", + this); + StickerBook.addStickerPackExisting(sp); + + Intent intent = new Intent(this, StickerPackDetailsActivity.class); + intent.putExtra(StickerPackDetailsActivity.EXTRA_SHOW_UP_BUTTON, true); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_DATA, newId); + intent.putExtra("isNewlyCreated", true); + this.startActivity(intent); + } + + private void openFileTray(String name, String creator) { + Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT); + i.setType("image/*"); + newName = name; + newCreator = creator; + startActivityForResult(i, 2319); + } + + private void makeIntroNotRunAgain(){ + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); + boolean previouslyStarted = prefs.getBoolean("isAlreadyShown", false); + if(!previouslyStarted) { + SharedPreferences.Editor edit = prefs.edit(); + edit.putBoolean("isAlreadyShown", false); + edit.commit(); + } + } + + private boolean toShowIntro(){ + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); + return prefs.getBoolean("isAlreadyShown", true); + } + + private void startInAppPurchase(String sku){ + mBillingClient.consumeAsync("", new ConsumeResponseListener() { + @Override + public void onConsumeResponse(int responseCode, String purchaseToken) { + + } + }); + BillingFlowParams flowParams = BillingFlowParams.newBuilder() + .setSku(sku) + .setType(BillingClient.SkuType.INAPP) + .build(); + mBillingClient.launchBillingFlow(StickerPackListActivity.this, flowParams); + Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP); + ConsumeResponseListener listener = new ConsumeResponseListener() { + @Override + public void onConsumeResponse(@BillingClient.BillingResponse int responseCode, String outToken) { + if (responseCode == BillingClient.BillingResponse.OK) { + Toast.makeText(getApplicationContext(), "Thank you so much for your donation!", Toast.LENGTH_LONG).show(); + } + }}; + if (purchasesResult!=null){ + if(purchasesResult.getPurchasesList()!=null){ + if(purchasesResult.getPurchasesList().size()>0){ + for (int i = 0; i { + @NonNull + private List stickerPacks; + @NonNull + private final OnAddButtonClickedListener onAddButtonClickedListener; + private int maxNumberOfStickersInARow; + + StickerPackListAdapter(@NonNull List stickerPacks, @NonNull OnAddButtonClickedListener onAddButtonClickedListener) { + this.stickerPacks = stickerPacks; + this.onAddButtonClickedListener = onAddButtonClickedListener; + } + + @NonNull + @Override + public StickerPackListItemViewHolder onCreateViewHolder(@NonNull final ViewGroup viewGroup, final int i) { + final Context context = viewGroup.getContext(); + final LayoutInflater layoutInflater = LayoutInflater.from(context); + final View stickerPackRow = layoutInflater.inflate(R.layout.sticker_packs_list_item, viewGroup, false); + return new StickerPackListItemViewHolder(stickerPackRow); + } + + @Override + public void onBindViewHolder(@NonNull final StickerPackListItemViewHolder viewHolder, final int index) { + StickerPack pack = stickerPacks.get(index); + final Context context = viewHolder.publisherView.getContext(); + viewHolder.publisherView.setText("by " + pack.publisher); + //viewHolder.filesizeView.setText(Formatter.formatShortFileSize(context, pack.getTotalSize())); + + viewHolder.titleView.setText(pack.name); + viewHolder.container.setOnClickListener(view -> { + Intent intent = new Intent(view.getContext(), StickerPackDetailsActivity.class); + intent.putExtra(StickerPackDetailsActivity.EXTRA_SHOW_UP_BUTTON, true); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_DATA, pack.identifier); + view.getContext().startActivity(intent); + }); + viewHolder.imageRowView.removeAllViews(); + //if this sticker pack contains less stickers than the max, then take the smaller size. + int actualNumberOfStickersToShow = Math.min(maxNumberOfStickersInARow, pack.getStickers().size()); + for (int i = 0; i < actualNumberOfStickersToShow; i++) { + final SimpleDraweeView rowImage = (SimpleDraweeView) LayoutInflater.from(context).inflate(R.layout.sticker_pack_list_item_image, viewHolder.imageRowView, false); + rowImage.setImageURI(pack.getSticker(i).getUri()); + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) rowImage.getLayoutParams(); + final int marginBetweenImages = (viewHolder.imageRowView.getMeasuredWidth() - maxNumberOfStickersInARow * viewHolder.imageRowView.getContext().getResources().getDimensionPixelSize(R.dimen.sticker_pack_list_item_preview_image_size)) / (maxNumberOfStickersInARow - 1) - lp.leftMargin - lp.rightMargin; + if (i != actualNumberOfStickersToShow - 1 && marginBetweenImages > 0) { //do not set the margin for the last image + lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin + marginBetweenImages, lp.bottomMargin); + rowImage.setLayoutParams(lp); + } + viewHolder.imageRowView.addView(rowImage); + } + setAddButtonAppearance(viewHolder.addButton, pack); + + viewHolder.shareButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + DataArchiver.createZipFileFromStickerPack(pack, context); + } + }); + } + + private void setAddButtonAppearance(ImageView addButton, StickerPack pack) { + if (pack.getIsWhitelisted()) { + addButton.setImageResource(R.drawable.sticker_3rdparty_added); + addButton.setClickable(false); + addButton.setOnClickListener(null); + addButton.setBackgroundDrawable(null); + } else { + addButton.setImageResource(android.R.drawable.stat_sys_download); + addButton.setOnClickListener(v -> onAddButtonClickedListener.onAddButtonClicked(pack)); + TypedValue outValue = new TypedValue(); + addButton.getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); + addButton.setBackgroundResource(outValue.resourceId); + } + } + + @Override + public int getItemCount() { + return stickerPacks.size(); + } + + void setMaxNumberOfStickersInARow(int maxNumberOfStickersInARow) { + if (this.maxNumberOfStickersInARow != maxNumberOfStickersInARow) { + this.maxNumberOfStickersInARow = maxNumberOfStickersInARow; + notifyDataSetChanged(); + } + } + + public interface OnAddButtonClickedListener { + void onAddButtonClicked(StickerPack stickerPack); + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackListItemViewHolder.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackListItemViewHolder.java new file mode 100755 index 0000000..e5ae5be --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackListItemViewHolder.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.idoideas.stickermaker.R; + +public class StickerPackListItemViewHolder extends RecyclerView.ViewHolder { + + View container; + TextView titleView; + TextView publisherView; + //TextView filesizeView; + ImageView addButton; + ImageView shareButton; + LinearLayout imageRowView; + + StickerPackListItemViewHolder(final View itemView) { + super(itemView); + container = itemView; + titleView = itemView.findViewById(R.id.sticker_pack_title); + publisherView = itemView.findViewById(R.id.sticker_pack_publisher); + //filesizeView = itemView.findViewById(R.id.sticker_pack_filesize); + addButton = itemView.findViewById(R.id.add_button_on_list); + shareButton = itemView.findViewById(R.id.export_button_on_list); + imageRowView = itemView.findViewById(R.id.sticker_packs_list_item_image_list); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackValidator.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackValidator.java new file mode 100755 index 0000000..42024a6 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPackValidator.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.text.TextUtils; + +import com.facebook.animated.webp.WebPImage; +import com.idoideas.stickermaker.BuildConfig; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +public class StickerPackValidator { + private static final int STICKER_FILE_SIZE_LIMIT_KB = 100; + private static final int EMOJI_LIMIT = 3; + private static final int IMAGE_HEIGHT = 512; + private static final int IMAGE_WIDTH = 512; + private static final int STICKER_SIZE_MIN = 3; + private static final int STICKER_SIZE_MAX = 30; + private static final int CHAR_COUNT_MAX = 128; + private static final long ONE_KIBIBYTE = 8 * 1024; + private static final int TRAY_IMAGE_FILE_SIZE_MAX_KB = 50; + private static final int TRAY_IMAGE_DIMENSION_MIN = 24; + private static final int TRAY_IMAGE_DIMENSION_MAX = 512; + + /** + * Checks whether a sticker pack contains valid data + */ + public static void verifyStickerPackValidity(@NonNull Context context, @NonNull StickerPack stickerPack) throws IllegalStateException { + if (TextUtils.isEmpty(stickerPack.identifier)) { + throw new IllegalStateException("sticker pack identifier is empty"); + } + if (stickerPack.identifier.length() > CHAR_COUNT_MAX) { + throw new IllegalStateException("sticker pack identifier cannot exceed " + CHAR_COUNT_MAX + " characters"); + } + checkStringValidity(stickerPack.identifier); + if (TextUtils.isEmpty(stickerPack.publisher)) { + throw new IllegalStateException("sticker pack publisher is empty, sticker pack identifier:" + stickerPack.identifier); + } + if (stickerPack.publisher.length() > CHAR_COUNT_MAX) { + throw new IllegalStateException("sticker pack publisher cannot exceed " + CHAR_COUNT_MAX + " characters, sticker pack identifier:" + stickerPack.identifier); + } + if (TextUtils.isEmpty(stickerPack.name)) { + throw new IllegalStateException("sticker pack name is empty, sticker pack identifier:" + stickerPack.identifier); + } + if (stickerPack.name.length() > CHAR_COUNT_MAX) { + throw new IllegalStateException("sticker pack name cannot exceed " + CHAR_COUNT_MAX + " characters, sticker pack identifier:" + stickerPack.identifier); + } + if (TextUtils.isEmpty(stickerPack.trayImageFile)) { + throw new IllegalStateException("sticker pack tray id is empty, sticker pack identifier:" + stickerPack.identifier); + } + try { + final byte[] bytes = fetchStickerAsset(stickerPack.identifier, stickerPack.trayImageFile, context.getContentResolver()); + if (bytes.length > TRAY_IMAGE_FILE_SIZE_MAX_KB * ONE_KIBIBYTE) { + throw new IllegalStateException("tray image should be less than " + TRAY_IMAGE_FILE_SIZE_MAX_KB + " KB, tray image file: " + stickerPack.trayImageFile); + } + Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + if (bitmap.getHeight() > TRAY_IMAGE_DIMENSION_MAX || bitmap.getHeight() < TRAY_IMAGE_DIMENSION_MIN) { + throw new IllegalStateException("tray image height should between " + TRAY_IMAGE_DIMENSION_MIN + " and " + TRAY_IMAGE_DIMENSION_MAX + " pixels, current tray image height is " + bitmap.getHeight() + ", tray image file: " + stickerPack.trayImageFile); + } + if (bitmap.getWidth() > TRAY_IMAGE_DIMENSION_MAX || bitmap.getWidth() < TRAY_IMAGE_DIMENSION_MIN) { + throw new IllegalStateException("tray image width should be between " + TRAY_IMAGE_DIMENSION_MIN + " and " + TRAY_IMAGE_DIMENSION_MAX + " pixels, current tray image width is " + bitmap.getWidth() + ", tray image file: " + stickerPack.trayImageFile); + } + } catch (IOException e) { + throw new IllegalStateException("Cannot open tray image, " + stickerPack.trayImageFile, e); + } + final List stickers = stickerPack.getStickers(); + if (stickers.size() < STICKER_SIZE_MIN || stickers.size() > STICKER_SIZE_MAX) { + throw new IllegalStateException("sticker pack sticker count should be between 3 to 30 inclusive, it currently has " + stickers.size() + ", sticker pack identifier:" + stickerPack.identifier); + } + for (final Sticker sticker : stickers) { + validateSticker(context, stickerPack.identifier, sticker); + } + } + + private static void validateSticker(@NonNull Context context, @NonNull final String identifier, @NonNull final Sticker sticker) throws IllegalStateException { + if (sticker.emojis.size() > EMOJI_LIMIT) { + throw new IllegalStateException("emoji count exceed limit, sticker pack identifier:" + identifier + ", filename:" + sticker.imageFileName); + } + if (TextUtils.isEmpty(sticker.imageFileName)) { + throw new IllegalStateException("no file path for sticker, sticker pack identifier:" + identifier); + } + validateStickerFile(context, identifier, sticker.imageFileName); + } + + private static void validateStickerFile(@NonNull Context context, @NonNull String identifier, @NonNull final String fileName) throws IllegalStateException { + try { + final byte[] bytes = fetchStickerAsset(identifier, fileName, context.getContentResolver()); + if (bytes.length > STICKER_FILE_SIZE_LIMIT_KB * ONE_KIBIBYTE) { + throw new IllegalStateException("sticker should be less than " + STICKER_FILE_SIZE_LIMIT_KB + "KB, sticker pack identifier:" + identifier + ", filename:" + fileName); + } + try { + final WebPImage webPImage = WebPImage.create(bytes); + if (webPImage.getHeight() != IMAGE_HEIGHT) { + throw new IllegalStateException("sticker height should be " + IMAGE_HEIGHT + ", sticker pack identifier:" + identifier + ", filename:" + fileName); + } + if (webPImage.getWidth() != IMAGE_WIDTH) { + throw new IllegalStateException("sticker width should be " + IMAGE_WIDTH + ", sticker pack identifier:" + identifier + ", filename:" + fileName); + } + if (webPImage.getFrameCount() > 1) { + throw new IllegalStateException("sticker shoud be a static image, no animated sticker support at the moment, sticker pack identifier:" + identifier + ", filename:" + fileName); + } + } catch (IllegalArgumentException e) { + throw new IllegalStateException("Error parsing webp image, sticker pack identifier:" + identifier + ", filename:" + fileName, e); + } + } catch (IOException e) { + throw new IllegalStateException("cannot open sticker file: sticker pack identifier:" + identifier + ", filename:" + fileName, e); + } + } + + private static void checkStringValidity(@NonNull String string) { + String pattern = "[\\w-.,'\\s]+"; // [a-zA-Z0-9_-.' ] + if (!string.matches(pattern)) { + throw new IllegalStateException(string + " contains invalid characters, allowed characters are a to z, A to Z, _ , ' - . and space character"); + } + if (string.contains("..")) { + throw new IllegalStateException(string + " cannot contain .."); + } + } + + private static byte[] fetchStickerAsset(@NonNull final String identifier, @NonNull final String name, ContentResolver contentResolver) throws IOException { + try (final InputStream inputStream = contentResolver.openInputStream(getStickerAssetUri(identifier, name)); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + if (inputStream == null) { + throw new IOException("cannot read sticker asset:" + identifier + "/" + name); + } + int read; + byte[] data = new byte[16384]; + + while ((read = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, read); + } + return buffer.toByteArray(); + } + } + + private static Uri getStickerAssetUri(String identifier, String stickerName) { + return new Uri.Builder().scheme(StickerContentProvider.CONTENT_SCHEME).authority(BuildConfig.CONTENT_PROVIDER_AUTHORITY).appendPath(StickerContentProvider.STICKERS_ASSET).appendPath("2").appendPath(stickerName).build(); + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPreviewAdapter.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPreviewAdapter.java new file mode 100755 index 0000000..0f76ba6 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPreviewAdapter.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.Toast; + +import com.idoideas.stickermaker.R; + +public class StickerPreviewAdapter extends RecyclerView.Adapter { + + @NonNull + private StickerPack stickerPack; + + private final int cellSize; + private int cellLimit; + private int cellPadding; + private final int errorResource; + + private final LayoutInflater layoutInflater; + + StickerPreviewAdapter( + @NonNull final LayoutInflater layoutInflater, + final int errorResource, + final int cellSize, + final int cellPadding, + @NonNull final StickerPack stickerPack) { + this.cellSize = cellSize; + this.cellPadding = cellPadding; + this.cellLimit = 0; + this.layoutInflater = layoutInflater; + this.errorResource = errorResource; + this.stickerPack = stickerPack; + } + + @NonNull + @Override + public StickerPreviewViewHolder onCreateViewHolder(@NonNull final ViewGroup viewGroup, final int i) { + View itemView = layoutInflater.inflate(R.layout.sticker_image, viewGroup, false); + StickerPreviewViewHolder vh = new StickerPreviewViewHolder(itemView); + + ViewGroup.LayoutParams layoutParams = vh.stickerPreviewView.getLayoutParams(); + layoutParams.height = cellSize; + layoutParams.width = cellSize; + vh.stickerPreviewView.setLayoutParams(layoutParams); + vh.stickerPreviewView.setPadding(cellPadding, cellPadding, cellPadding, cellPadding); + + return vh; + } + + @Override + public void onBindViewHolder(@NonNull final StickerPreviewViewHolder stickerPreviewViewHolder, final int i) { + Sticker thisSticker = stickerPack.getSticker(i); + Context thisContext = stickerPreviewViewHolder.stickerPreviewView.getContext(); + stickerPreviewViewHolder.stickerPreviewView.setImageResource(errorResource); + stickerPreviewViewHolder.stickerPreviewView.setImageURI(thisSticker.getUri()); + stickerPreviewViewHolder.stickerPreviewView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ImageView image = new ImageView(thisContext); + image.setImageURI(thisSticker.getUri()); + AlertDialog alertDialog = new AlertDialog.Builder(thisContext) + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }) + .setPositiveButton("Confirm", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if(stickerPack.getStickers().size()>3 || !WhitelistCheck.isWhitelisted(thisContext, stickerPack.getIdentifier())){ + dialogInterface.dismiss(); + stickerPack.deleteSticker(thisSticker); + Activity thisActivity = ((Activity)thisContext); + thisActivity.finish(); + thisActivity.startActivity(thisActivity.getIntent()); + Toast.makeText(thisContext, "Sticker Pack deleted", Toast.LENGTH_SHORT).show(); + } else { + AlertDialog alertDialog = new AlertDialog.Builder(thisContext) + .setNegativeButton("Ok", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }).create(); + alertDialog.setTitle("Invalid Action"); + alertDialog.setMessage("A sticker pack that is already applied to WhatsApp cannot have less than 3 stickers. " + + "In order to remove additional stickers, please add more to the pack first or remove the pack from the WhatsApp app."); + alertDialog.show(); + } + } + }) + .setView(image) + .create(); + alertDialog.setTitle("Do you want to delete this sticker?"); + alertDialog.setMessage("Deleting this sticker will also remove it from your WhatsApp app."); + alertDialog.show(); + } + }); + } + + @Override + public int getItemCount() { + int numberOfPreviewImagesInPack; + numberOfPreviewImagesInPack = stickerPack.getStickers().size(); + if (cellLimit > 0) { + return Math.min(numberOfPreviewImagesInPack, cellLimit); + } + return numberOfPreviewImagesInPack; + } +} diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPreviewViewHolder.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPreviewViewHolder.java new file mode 100755 index 0000000..ab28639 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/StickerPreviewViewHolder.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.facebook.drawee.view.SimpleDraweeView; +import com.idoideas.stickermaker.R; + +public class StickerPreviewViewHolder extends RecyclerView.ViewHolder { + + public SimpleDraweeView stickerPreviewView; + + StickerPreviewViewHolder(final View itemView) { + super(itemView); + stickerPreviewView = itemView.findViewById(R.id.sticker_preview); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/WhitelistCheck.java b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/WhitelistCheck.java new file mode 100755 index 0000000..d31cb42 --- /dev/null +++ b/app/src/main/java/com/idoideas/stickermaker/WhatsAppBasedCode/WhitelistCheck.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.idoideas.stickermaker.WhatsAppBasedCode; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; + +import com.idoideas.stickermaker.BuildConfig; + +@SuppressWarnings("FieldCanBeLocal") +public class WhitelistCheck { + private static final String AUTHORITY_QUERY_PARAM = "authority"; + private static final String IDENTIFIER_QUERY_PARAM = "identifier"; + private static String STICKER_APP_AUTHORITY = BuildConfig.CONTENT_PROVIDER_AUTHORITY; + private static String CONSUMER_WHATSAPP_PACKAGE_NAME = "com.whatsapp"; + private static String SMB_WHATSAPP_PACKAGE_NAME = "com.whatsapp.w4b"; + private static String CONTENT_PROVIDER = ".provider.sticker_whitelist_check"; + private static String QUERY_PATH = "is_whitelisted"; + private static String QUERY_RESULT_COLUMN_NAME = "result"; + + static boolean isWhitelisted(@NonNull Context context, @NonNull String identifier) { + try { + boolean consumerResult = isWhitelistedFromProvider(context, identifier, CONSUMER_WHATSAPP_PACKAGE_NAME); + boolean smbResult = isWhitelistedFromProvider(context, identifier, SMB_WHATSAPP_PACKAGE_NAME); + return consumerResult && smbResult; + } catch (Exception e) { + return false; + } + } + + private static boolean isWhitelistedFromProvider(@NonNull Context context, @NonNull String identifier, String whatsappPackageName) { + final PackageManager packageManager = context.getPackageManager(); + if (isPackageInstalled(whatsappPackageName, packageManager)) { + final String whatsappProviderAuthority = whatsappPackageName + CONTENT_PROVIDER; + final ProviderInfo providerInfo = packageManager.resolveContentProvider(whatsappProviderAuthority, PackageManager.GET_META_DATA); + // provider is not there. + if (providerInfo == null) { + return false; + } + final Uri queryUri = new Uri.Builder().scheme(StickerContentProvider.CONTENT_SCHEME).authority(whatsappProviderAuthority).appendPath(QUERY_PATH).appendQueryParameter(AUTHORITY_QUERY_PARAM, STICKER_APP_AUTHORITY).appendQueryParameter(IDENTIFIER_QUERY_PARAM, identifier).build(); + try (final Cursor cursor = context.getContentResolver().query(queryUri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + final int whiteListResult = cursor.getInt(cursor.getColumnIndexOrThrow(QUERY_RESULT_COLUMN_NAME)); + return whiteListResult == 1; + } + } + } else { + //if app is not installed, then don't need to take into its whitelist info into account. + return true; + } + return false; + } + + private static boolean isPackageInstalled(String packageName, PackageManager packageManager) { + try { + final ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0); + //noinspection SimplifiableIfStatement + if (applicationInfo != null) { + return applicationInfo.enabled; + } else { + return false; + } + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } +} diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_add.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_add.png new file mode 100755 index 0000000..13214f7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_add.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_added.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_added.png new file mode 100755 index 0000000..c30bdc2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_added.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_email.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_email.png new file mode 100755 index 0000000..373765a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_email.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_info.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_info.png new file mode 100755 index 0000000..2ee2db6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_info.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_privacy.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_privacy.png new file mode 100755 index 0000000..644821f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_privacy.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_wa.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_wa.png new file mode 100755 index 0000000..0d856f4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_wa.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_web.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_web.png new file mode 100755 index 0000000..86ef3fa Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_web.png differ diff --git a/app/src/main/res/drawable-v24/btn_blue.xml b/app/src/main/res/drawable-v24/btn_blue.xml new file mode 100755 index 0000000..1b9c1b1 --- /dev/null +++ b/app/src/main/res/drawable-v24/btn_blue.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable-v24/btn_green.xml b/app/src/main/res/drawable-v24/btn_green.xml new file mode 100755 index 0000000..e59ed52 --- /dev/null +++ b/app/src/main/res/drawable-v24/btn_green.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable-v24/btn_green_normal.9.png b/app/src/main/res/drawable-v24/btn_green_normal.9.png new file mode 100755 index 0000000..2132794 Binary files /dev/null and b/app/src/main/res/drawable-v24/btn_green_normal.9.png differ diff --git a/app/src/main/res/drawable-v24/btn_green_pressed.9.png b/app/src/main/res/drawable-v24/btn_green_pressed.9.png new file mode 100755 index 0000000..00ff160 Binary files /dev/null and b/app/src/main/res/drawable-v24/btn_green_pressed.9.png differ diff --git a/app/src/main/res/drawable-v24/btn_red.xml b/app/src/main/res/drawable-v24/btn_red.xml new file mode 100755 index 0000000..46fcce7 --- /dev/null +++ b/app/src/main/res/drawable-v24/btn_red.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_add.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_add.png new file mode 100755 index 0000000..04e633e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_add.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_added.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_added.png new file mode 100755 index 0000000..0b64b96 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_added.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_email.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_email.png new file mode 100755 index 0000000..856e2c1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_email.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_info.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_info.png new file mode 100755 index 0000000..9eea757 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_info.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_privacy.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_privacy.png new file mode 100755 index 0000000..5c3ab69 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_privacy.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_wa.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_wa.png new file mode 100755 index 0000000..d8f369b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_wa.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_web.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_web.png new file mode 100755 index 0000000..abe17d1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_web.png differ diff --git a/app/src/main/res/drawable-xxhdpi/btn_green_normal.9.png b/app/src/main/res/drawable-xxhdpi/btn_green_normal.9.png new file mode 100755 index 0000000..2132794 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/btn_green_normal.9.png differ diff --git a/app/src/main/res/drawable-xxhdpi/btn_green_pressed.9.png b/app/src/main/res/drawable-xxhdpi/btn_green_pressed.9.png new file mode 100755 index 0000000..00ff160 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/btn_green_pressed.9.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_add.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_add.png new file mode 100755 index 0000000..03c4381 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_add.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_added.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_added.png new file mode 100755 index 0000000..71c9532 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_added.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_email.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_email.png new file mode 100755 index 0000000..7a7e537 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_email.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_info.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_info.png new file mode 100755 index 0000000..ad780e6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_info.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_privacy.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_privacy.png new file mode 100755 index 0000000..e278a39 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_privacy.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_wa.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_wa.png new file mode 100755 index 0000000..9f2902f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_wa.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_web.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_web.png new file mode 100755 index 0000000..14e7dd2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_web.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_error.png b/app/src/main/res/drawable-xxhdpi/sticker_error.png new file mode 100755 index 0000000..a9a3e43 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_error.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_add.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_add.png new file mode 100755 index 0000000..c2f27cd Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_add.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_added.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_added.png new file mode 100755 index 0000000..e5cb169 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_added.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_email.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_email.png new file mode 100755 index 0000000..d021790 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_email.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_info.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_info.png new file mode 100755 index 0000000..eebdb32 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_info.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_privacy.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_privacy.png new file mode 100755 index 0000000..004edcb Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_privacy.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_wa.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_wa.png new file mode 100755 index 0000000..c92fb83 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_wa.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_web.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_web.png new file mode 100755 index 0000000..955d021 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_web.png differ diff --git a/app/src/main/res/drawable/githublogo.png b/app/src/main/res/drawable/githublogo.png new file mode 100644 index 0000000..6fdd067 Binary files /dev/null and b/app/src/main/res/drawable/githublogo.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/redditlogo.png b/app/src/main/res/drawable/redditlogo.png new file mode 100644 index 0000000..6f1e9d6 Binary files /dev/null and b/app/src/main/res/drawable/redditlogo.png differ diff --git a/app/src/main/res/drawable/sticker_error.png b/app/src/main/res/drawable/sticker_error.png new file mode 100755 index 0000000..a9a3e43 Binary files /dev/null and b/app/src/main/res/drawable/sticker_error.png differ diff --git a/app/src/main/res/drawable/stickerbanner.png b/app/src/main/res/drawable/stickerbanner.png new file mode 100644 index 0000000..98bf31e Binary files /dev/null and b/app/src/main/res/drawable/stickerbanner.png differ diff --git a/app/src/main/res/drawable/stickermakerlogo.png b/app/src/main/res/drawable/stickermakerlogo.png new file mode 100644 index 0000000..2a1c56a Binary files /dev/null and b/app/src/main/res/drawable/stickermakerlogo.png differ diff --git a/app/src/main/res/drawable/stickerpacknotice.png b/app/src/main/res/drawable/stickerpacknotice.png new file mode 100644 index 0000000..0b1de5c Binary files /dev/null and b/app/src/main/res/drawable/stickerpacknotice.png differ diff --git a/app/src/main/res/drawable/twitterlogo.png b/app/src/main/res/drawable/twitterlogo.png new file mode 100644 index 0000000..48e9641 Binary files /dev/null and b/app/src/main/res/drawable/twitterlogo.png differ diff --git a/app/src/main/res/layout/about_layout.xml b/app/src/main/res/layout/about_layout.xml new file mode 100644 index 0000000..18c754e --- /dev/null +++ b/app/src/main/res/layout/about_layout.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_sticker_pack_info.xml b/app/src/main/res/layout/activity_sticker_pack_info.xml new file mode 100755 index 0000000..741756a --- /dev/null +++ b/app/src/main/res/layout/activity_sticker_pack_info.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_sticker_pack_list.xml b/app/src/main/res/layout/activity_sticker_pack_list.xml new file mode 100755 index 0000000..fb41a30 --- /dev/null +++ b/app/src/main/res/layout/activity_sticker_pack_list.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/sticker_image.xml b/app/src/main/res/layout/sticker_image.xml new file mode 100755 index 0000000..742c232 --- /dev/null +++ b/app/src/main/res/layout/sticker_image.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/sticker_pack_details.xml b/app/src/main/res/layout/sticker_pack_details.xml new file mode 100755 index 0000000..c1ba016 --- /dev/null +++ b/app/src/main/res/layout/sticker_pack_details.xml @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/sticker_pack_list_item_image.xml b/app/src/main/res/layout/sticker_pack_list_item_image.xml new file mode 100755 index 0000000..6fc4822 --- /dev/null +++ b/app/src/main/res/layout/sticker_pack_list_item_image.xml @@ -0,0 +1,8 @@ + + diff --git a/app/src/main/res/layout/sticker_packs_list_item.xml b/app/src/main/res/layout/sticker_packs_list_item.xml new file mode 100755 index 0000000..592fbf6 --- /dev/null +++ b/app/src/main/res/layout/sticker_packs_list_item.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml new file mode 100644 index 0000000..b7c8fc7 --- /dev/null +++ b/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/menu/toolbar.xml b/app/src/main/res/menu/toolbar.xml new file mode 100755 index 0000000..ae5b319 --- /dev/null +++ b/app/src/main/res/menu/toolbar.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/toolbar_main.xml b/app/src/main/res/menu/toolbar_main.xml new file mode 100755 index 0000000..97d5032 --- /dev/null +++ b/app/src/main/res/menu/toolbar_main.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml new file mode 100644 index 0000000..fecac13 --- /dev/null +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..b25ef28 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,13 @@ + + + @android:color/transparent + #BABABA + #128C7E + #B3B3B3 + #EBEBEB + @color/colorAccent + #4286f4 + #3065ba + #f44141 + #cc3939 + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..fcb9e54 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,11 @@ + + 16dp + 16dp + 16dp + 80dp + 8dp + 8dp + 50dp + 4dp + 4dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..ff3121c --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,42 @@ + + StickerMaker + Settings + Sticker Pack Tray Image + More info about sticker pack + Add to WhatsApp + Share Pack + Delete Pack + Add sticker pack to WhatsApp + Share sticker pack to other users + StickerMaker + + Sticker Pack + Sticker details + Info + View webpage + Send email + Privacy policy + "Could not add this sticker pack. Please install the latest version of WhatsApp before adding sticker pack" + Error: %s \nSee logcat for stack trace. + Error with the pack + Tray icon + Sticker pack added to WhatsApp + 00C4003037A3F623DA3CC00DE9B803BB + + + + + + + ca-app-pub-3940256099942544~3347511713 + ca-app-pub-3940256099942544/6300978111 + ca-app-pub-3940256099942544/6300978111 + ca-app-pub-3940256099942544/1033173712 + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..a3acf65 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/test/java/com/idoideas/stickermaker/ExampleUnitTest.java b/app/src/test/java/com/idoideas/stickermaker/ExampleUnitTest.java new file mode 100644 index 0000000..70b9575 --- /dev/null +++ b/app/src/test/java/com/idoideas/stickermaker/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.idoideas.stickermaker; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..1cea1bf --- /dev/null +++ b/build.gradle @@ -0,0 +1,29 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + maven { + url "https://maven.google.com" + } + maven { url 'https://jitpack.io' } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..3c61dea --- /dev/null +++ b/gradle.properties @@ -0,0 +1,15 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +android.enableBuildCache=true + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..80ed5ae --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Nov 03 02:30:34 IST 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'