├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── swiftpot │ │ └── wsdownloader │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── swiftpot │ │ │ └── wsdownloader │ │ │ ├── HomeActivity.java │ │ │ ├── adapter │ │ │ └── RecyclerViewMediaAdapter.java │ │ │ └── base │ │ │ └── BaseActivity.java │ └── res │ │ ├── drawable │ │ ├── ic_check_black_24dp.xml │ │ ├── ic_clear_black_24dp.xml │ │ ├── ic_dashboard_black_24dp.xml │ │ ├── ic_empty_24dp.xml │ │ ├── ic_error_outline_black_24dp.xml │ │ ├── ic_home_black_24dp.xml │ │ ├── ic_info_outline_black_24dp.xml │ │ └── ic_notifications_black_24dp.xml │ │ ├── layout │ │ ├── activity_home.xml │ │ └── recyclerview_media_row_item.xml │ │ ├── menu │ │ └── navigation.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── swiftpot │ └── wsdownloader │ └── ExampleUnitTest.java ├── build.gradle ├── descriptionalImages └── wsdownloader_demo.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | 11 | 12 | 13 | 14 | 15 | # Windows image file caches 16 | Thumbs.db 17 | ehthumbs.db 18 | 19 | # Folder config file 20 | Desktop.ini 21 | 22 | # Recycle Bin used on file shares 23 | $RECYCLE.BIN/ 24 | 25 | # Windows Installer files 26 | *.cab 27 | *.msi 28 | *.msm 29 | *.msp 30 | 31 | # ========================= 32 | # Operating System Files 33 | # ========================= 34 | 35 | # OSX 36 | # ========================= 37 | 38 | .DS_Store 39 | .AppleDouble 40 | .LSOverride 41 | 42 | # Icon must ends with two \r. 43 | Icon 44 | 45 | 46 | # Thumbnails 47 | ._* 48 | 49 | # Files that might appear on external disk 50 | .Spotlight-V100 51 | .Trashes 52 | 53 | # Android Studio 54 | .idea/* 55 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs. 56 | .gradle/ 57 | build/ 58 | .settings 59 | bin/ 60 | gen/ 61 | gradle/ 62 | .classpath 63 | local.properties 64 | gradlew 65 | gradlew.bat 66 | .project 67 | WSDownloader.iml 68 | libs/libs.iml 69 | .idea/ 70 | gradle.properties 71 | projectFilesBackup 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WSDownloader(Whatsapp Status Downloader) 2 | 3 | 4 | Overview 5 | --- 6 | As the name suggests,the app downloads viewed Whatsapp status updates posted by your friends and family to your Gallery. 7 | I wrote the app in 47minutes exactly out of boredom. =D 8 | 9 | Demonstration 10 | --- 11 | ![](https://github.com/RbkGh/WSDownloader/raw/master/descriptionalImages/wsdownloader_demo.gif) 12 | 13 | Quick Run 14 | --- 15 | Click "Run" in android studio 16 | 17 | OR 18 | 19 | from command line : 20 | 21 | gradle installDebug 22 | 23 | This will install the app on your connected android device.Open app on your device afterwards. 24 | 25 | OR 26 | 27 | [Download Latest Version Of App](https://github.com/RbkGh/WSDownloader/releases/download/1.0.2/WSDownloader_1.0.2.apk) 28 | 29 | Warning 30 | --- 31 | 32 | You are using an open source project! 33 | 34 | We are not responsible for stolen passwords, dead SD cards, 35 | thermonuclear war, or you getting fired because the files got wiped. Please 36 | do some research if you have any concerns about features included in this app 37 | before installing it! YOU are choosing to install this application, and if 38 | you point the finger at us for messing up your files, i will troll you. 39 | 40 | A lot. 41 | 42 | 43 | Contributions 44 | ----- 45 | To contribute quickly, head over to "issues" section and fix any issue, then submit a PR. 46 | 47 | Make sure to write your name and email id in format `Name` in the license declaration above every file you make change to. 48 | 49 | Also, take a look at [this guide](https://source.android.com/source/code-style.html) before making changes in code. 50 | 51 | 52 | Author(s) 53 | --------- 54 | - Rodney Boachie 55 | 56 | 57 | ### License: 58 | 59 | Copyright (C) 2017 Rodney Boachie 60 | This file is part of WSDownloader 61 | WSDownloader is free software: you can redistribute it and/or modify 62 | it under the terms of the GNU General Public License as published by 63 | the Free Software Foundation, either version 3 of the License, or 64 | (at your option) any later version. 65 | This program is distributed in the hope that it will be useful, 66 | but WITHOUT ANY WARRANTY; without even the implied warranty of 67 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 68 | GNU General Public License for more details. 69 | You should have received a copy of the GNU General Public License 70 | along with this program. If not, see . 71 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "com.swiftpot.wsdownloader" 8 | minSdkVersion 16 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | vectorDrawables.useSupportLibrary = true 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | def supportLibVersion = '25.2.0' 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 26 | exclude group: 'com.android.support', module: 'support-annotations' 27 | }) 28 | compile "com.android.support:appcompat-v7:${supportLibVersion}" 29 | compile "com.android.support:design:${supportLibVersion}" 30 | compile "com.android.support:support-vector-drawable:${supportLibVersion}" 31 | compile "com.android.support:cardview-v7:${supportLibVersion}" 32 | 33 | compile 'com.karumi:dexter:4.1.0' 34 | compile 'com.github.matecode:Snacky:1.0.2' 35 | compile 'com.karumi:dexter:4.1.0' 36 | testCompile 'junit:junit:4.12' 37 | } 38 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/swiftpot/wsdownloader/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.swiftpot.wsdownloader; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.swiftpot.wsdownloader", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RbkGh/WSDownloader/e3e217ecc66e21160fc92ec265e0e633d48027aa/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/swiftpot/wsdownloader/HomeActivity.java: -------------------------------------------------------------------------------- 1 | package com.swiftpot.wsdownloader; 2 | 3 | import android.os.Bundle; 4 | import android.os.Environment; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | 9 | import com.swiftpot.wsdownloader.adapter.RecyclerViewMediaAdapter; 10 | import com.swiftpot.wsdownloader.base.BaseActivity; 11 | 12 | import java.io.File; 13 | import java.util.ArrayList; 14 | 15 | public class HomeActivity extends BaseActivity { 16 | 17 | private static final String WHATSAPP_STATUSES_LOCATION = "/Android/media/com.whatsapp/WhatsApp/Media/.Statuses"; 18 | private RecyclerView mRecyclerViewMediaList; 19 | private LinearLayoutManager mLinearLayoutManager; 20 | public static final String TAG = "Home"; 21 | 22 | @Override 23 | public void onCreate(@Nullable Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | 26 | 27 | setContentView(R.layout.activity_home); 28 | getSupportActionBar().setIcon(R.mipmap.ic_launcher); 29 | getSupportActionBar().setHomeButtonEnabled(true); 30 | getSupportActionBar().setDisplayShowHomeEnabled(true); 31 | 32 | mRecyclerViewMediaList = (RecyclerView) findViewById(R.id.recyclerViewMedia); 33 | mLinearLayoutManager = new LinearLayoutManager(this); 34 | mRecyclerViewMediaList.setLayoutManager(mLinearLayoutManager); 35 | RecyclerViewMediaAdapter recyclerViewMediaAdapter = new RecyclerViewMediaAdapter(this.getListFiles(new File(Environment.getExternalStorageDirectory().toString()+WHATSAPP_STATUSES_LOCATION)), HomeActivity.this); 36 | mRecyclerViewMediaList.setAdapter(recyclerViewMediaAdapter); 37 | } 38 | 39 | /** 40 | * get all the files in specified directory 41 | * 42 | * @param parentDir 43 | * @return 44 | */ 45 | private ArrayList getListFiles(File parentDir) { 46 | ArrayList inFiles = new ArrayList(); 47 | File[] files; 48 | files = parentDir.listFiles(); 49 | if (files != null) { 50 | for (File file : files) { 51 | 52 | if (file.getName().endsWith(".jpg") || 53 | file.getName().endsWith(".gif") || 54 | file.getName().endsWith(".mp4")) { 55 | if (!inFiles.contains(file)) 56 | inFiles.add(file); 57 | } 58 | } 59 | } 60 | return inFiles; 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/swiftpot/wsdownloader/adapter/RecyclerViewMediaAdapter.java: -------------------------------------------------------------------------------- 1 | package com.swiftpot.wsdownloader.adapter; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.media.MediaPlayer; 7 | import android.net.Uri; 8 | import android.os.Environment; 9 | import android.support.v7.widget.CardView; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.util.Log; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.Button; 16 | import android.widget.ImageView; 17 | import android.widget.VideoView; 18 | 19 | import com.swiftpot.wsdownloader.R; 20 | 21 | import java.io.File; 22 | import java.io.FileInputStream; 23 | import java.io.FileOutputStream; 24 | import java.io.IOException; 25 | import java.nio.channels.FileChannel; 26 | import java.util.ArrayList; 27 | 28 | import de.mateware.snacky.Snacky; 29 | 30 | /** 31 | * Created by Ace Programmer Rbk on 06-May-17 32 | * 8:24 AM 33 | */ 34 | public class RecyclerViewMediaAdapter extends RecyclerView.Adapter { 35 | 36 | private static String DIRECTORY_TO_SAVE_MEDIA_NOW = "/WSDownloader/"; 37 | private ArrayList filesList; 38 | private Activity activity; 39 | 40 | public RecyclerViewMediaAdapter(ArrayList filesList, Activity activity) { 41 | this.filesList = filesList; 42 | this.activity = activity; 43 | setHasStableIds(true); 44 | } 45 | 46 | @Override 47 | public RecyclerViewMediaAdapter.FileHolder onCreateViewHolder(ViewGroup parent, int viewType) { 48 | View inflatedView = LayoutInflater.from(parent.getContext()) 49 | .inflate(R.layout.recyclerview_media_row_item, parent, false); 50 | return new FileHolder(inflatedView); 51 | } 52 | 53 | @Override 54 | public void onBindViewHolder(final RecyclerViewMediaAdapter.FileHolder holder, int position) { 55 | File currentFile = filesList.get(position); 56 | 57 | holder.buttonImageDownload.setOnClickListener(this.downloadMediaItem(currentFile)); 58 | holder.buttonVideoDownload.setOnClickListener(this.downloadMediaItem(currentFile)); 59 | 60 | if (currentFile.getAbsolutePath().endsWith(".mp4")) { 61 | holder.cardViewImageMedia.setVisibility(View.GONE); 62 | holder.cardViewVideoMedia.setVisibility(View.VISIBLE); 63 | Uri video = Uri.parse(currentFile.getAbsolutePath()); 64 | holder.videoViewVideoMedia.setVideoURI(video); 65 | holder.videoViewVideoMedia.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 66 | @Override 67 | public void onPrepared(MediaPlayer mp) { 68 | mp.setLooping(true); 69 | holder.videoViewVideoMedia.start(); 70 | } 71 | }); 72 | } else { 73 | Bitmap myBitmap = BitmapFactory.decodeFile(currentFile.getAbsolutePath()); 74 | holder.imageViewImageMedia.setImageBitmap(myBitmap); 75 | } 76 | 77 | } 78 | 79 | @Override 80 | public int getItemCount() { 81 | return filesList.size(); 82 | } 83 | 84 | public View.OnClickListener downloadMediaItem(final File sourceFile) { 85 | 86 | return new View.OnClickListener() { 87 | 88 | @Override 89 | public void onClick(View v) { 90 | new Runnable(){ 91 | 92 | @Override 93 | public void run() { 94 | try { 95 | copyFile(sourceFile, new File(Environment.getExternalStorageDirectory().toString() + DIRECTORY_TO_SAVE_MEDIA_NOW + sourceFile.getName())); 96 | Snacky.builder(). 97 | setActivty(activity). 98 | setText(R.string.save_successful_message). 99 | success(). 100 | show(); 101 | } catch (Exception e) { 102 | e.printStackTrace(); 103 | Log.e("RecyclerV", "onClick: Error:"+e.getMessage() ); 104 | 105 | Snacky.builder(). 106 | setActivty(activity). 107 | setText(R.string.save_error_message). 108 | error(). 109 | show(); 110 | } 111 | } 112 | }.run(); 113 | } 114 | }; 115 | } 116 | 117 | /** 118 | * copy file to destination. 119 | * 120 | * @param sourceFile 121 | * @param destFile 122 | * @throws IOException 123 | */ 124 | public static void copyFile(File sourceFile, File destFile) throws IOException { 125 | if (!destFile.getParentFile().exists()) 126 | destFile.getParentFile().mkdirs(); 127 | 128 | if (!destFile.exists()) { 129 | destFile.createNewFile(); 130 | } 131 | 132 | FileChannel source = null; 133 | FileChannel destination = null; 134 | 135 | try { 136 | source = new FileInputStream(sourceFile).getChannel(); 137 | destination = new FileOutputStream(destFile).getChannel(); 138 | destination.transferFrom(source, 0, source.size()); 139 | } finally { 140 | if (source != null) { 141 | source.close(); 142 | } 143 | if (destination != null) { 144 | destination.close(); 145 | } 146 | } 147 | } 148 | 149 | 150 | public static class FileHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 151 | 152 | ImageView imageViewImageMedia; 153 | VideoView videoViewVideoMedia; 154 | CardView cardViewVideoMedia; 155 | CardView cardViewImageMedia; 156 | Button buttonVideoDownload; 157 | Button buttonImageDownload; 158 | 159 | public FileHolder(View itemView) { 160 | super(itemView); 161 | imageViewImageMedia = (ImageView) itemView.findViewById(R.id.imageViewImageMedia); 162 | videoViewVideoMedia = (VideoView) itemView.findViewById(R.id.videoViewVideoMedia); 163 | cardViewVideoMedia = (CardView) itemView.findViewById(R.id.cardViewVideoMedia); 164 | cardViewImageMedia = (CardView) itemView.findViewById(R.id.cardViewImageMedia); 165 | buttonImageDownload = (Button) itemView.findViewById(R.id.buttonImageDownload); 166 | buttonVideoDownload = (Button) itemView.findViewById(R.id.buttonVideoDownload); 167 | 168 | itemView.setOnClickListener(this); 169 | } 170 | 171 | @Override 172 | public void onClick(View v) { 173 | 174 | } 175 | 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /app/src/main/java/com/swiftpot/wsdownloader/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.swiftpot.wsdownloader.base; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.util.Log; 8 | 9 | import com.karumi.dexter.Dexter; 10 | import com.karumi.dexter.MultiplePermissionsReport; 11 | import com.karumi.dexter.PermissionToken; 12 | import com.karumi.dexter.listener.DexterError; 13 | import com.karumi.dexter.listener.PermissionRequest; 14 | import com.karumi.dexter.listener.PermissionRequestErrorListener; 15 | import com.karumi.dexter.listener.multi.MultiplePermissionsListener; 16 | 17 | import java.util.List; 18 | 19 | /** 20 | * Created by Ace Programmer Rbk on 10-May-17 21 | * 5:33 PM 22 | */ 23 | public class BaseActivity extends AppCompatActivity { 24 | @Override 25 | public void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | getPermissionsForAndroid23AndBeyond(this); 28 | } 29 | 30 | public void getPermissionsForAndroid23AndBeyond(Activity activity){ 31 | Dexter.withActivity(activity) 32 | .withPermissions( 33 | Manifest.permission.READ_EXTERNAL_STORAGE, 34 | Manifest.permission.WRITE_EXTERNAL_STORAGE 35 | ).withListener(new MultiplePermissionsListener() { 36 | @Override public void onPermissionsChecked(MultiplePermissionsReport report) {/* ... */} 37 | @Override public void onPermissionRationaleShouldBeShown(List permissions, PermissionToken token) {/* ... */} 38 | }).withErrorListener(new PermissionRequestErrorListener() { 39 | @Override public void onError(DexterError error) { 40 | Log.e("Dexter", "There was an error: " + error.toString()); 41 | } 42 | }).onSameThread().check(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clear_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dashboard_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_empty_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error_outline_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info_outline_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 26 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recyclerview_media_row_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 17 | 18 | 23 |