commit 172eb3b76f302644141af6ddabfd3736fce082c2 Author: vanitasvitae Date: Sun May 10 13:38:24 2015 +0200 Initial commit diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..92b6161 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/de/vanitasvitae/sticktoalbum/MainActivity.java b/app/src/main/java/de/vanitasvitae/sticktoalbum/MainActivity.java new file mode 100644 index 0000000..5f45f70 --- /dev/null +++ b/app/src/main/java/de/vanitasvitae/sticktoalbum/MainActivity.java @@ -0,0 +1,430 @@ +package de.vanitasvitae.sticktoalbum; + +import android.app.AlertDialog; +import android.content.ContentUris; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.support.v7.app.ActionBarActivity; +import android.text.InputType; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.MimeTypeMap; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.Toast; + +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.util.ArrayList; + +/** + * "Stick" your photos and videos into albums by moving them into new folders and updating + * Androids MediaStore. + * @author vanitas + */ +public class MainActivity extends ActionBarActivity +{ + private ArrayList sharedMedia; + private static String Tag = "stickToAlbum"; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + String appDirectoryPath = sharedPref.getString(SettingsActivity.KEY_PREF_APP_PATH, ""); + + if(appDirectoryPath.isEmpty()) + { + appDirectoryPath = resetAppDirectoryPath(); + } + sharedMedia = new ArrayList(); + + Intent intent = getIntent(); + String action = intent.getAction(); + String type = intent.getType(); + + Log.d(Tag, "Intent: " + action + ", " + type); + + if (Intent.ACTION_SEND.equals(action) && type != null) + { + if (type.startsWith("image/") || type.startsWith("video/")) + { + handleSendSingleFile(intent); + } + } + else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) + { + handleSendMultipleFiles(intent); + } + else + { + handleStartFromHomeScreen(); + } + } + + /** + * Handle one single file being shared with the application. + * Add the files Uri to the list of files to move and open the saveDialog. + * @param intent + */ + void handleSendSingleFile(Intent intent) { + Uri imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + if (imageUri != null) { + sharedMedia.add(imageUri); + } + openSaveDialog(); + } + + /** + * Handle multiple files being shared with the application. + * Add the files Uris to the list of files to move and open the saveDialog. + * @param intent + */ + void handleSendMultipleFiles(Intent intent) { + ArrayList imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + if (imageUris != null) { + sharedMedia = imageUris; + } + openSaveDialog(); + } + + /** + * Show Dialog to inform the user about the fact, that the feature he/she wants to use is not + * implemented + */ + void handleUnimplementedFeature() + { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.dialog_title_unimplemented_feature); + builder.setMessage(R.string.dialog_content_unimplemented_feature); + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.cancel(); + finish(); + } + }); + builder.show(); + } + + /** + * Open a Dialog that prompts the user to enter an album name. + * Reopens dialog, if album name is empty. + * Closes app after moving files or when canceled. + */ + private void openSaveDialog() + { + final Context context = this; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getResources().getText(R.string.dialog_title_album_name)); + + LayoutInflater inflater = this.getLayoutInflater(); + View dialogView = inflater.inflate(R.layout.save_dialog, null); + builder.setView(dialogView); + + final EditText input = (EditText) dialogView.findViewById(R.id.album_name); + input.setInputType(InputType.TYPE_CLASS_TEXT); + ListView existingAlbums = (ListView) dialogView.findViewById(R.id.album_list); + final ArrayList directoryList = getFoldersOnDirectory(getAppDirectoryPath()); + ArrayAdapter adapter = new ArrayAdapter(context, android.R.layout.simple_list_item_1, directoryList); + existingAlbums.setAdapter(adapter); + existingAlbums.setOnItemClickListener(new AdapterView.OnItemClickListener() + { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + input.setText(directoryList.get(position)); + } + }); + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + String aName = input.getText().toString(); + if(!aName.isEmpty()) + { + moveFiles(aName); + finish(); + } + else + { + Toast.makeText(context, getResources().getText(R.string.toast_invalid_album_name), Toast.LENGTH_SHORT).show(); + openSaveDialog(); + } + } + }); + builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.cancel(); + finish(); + } + }); + builder.show(); + } + + /** + * Show dialog that informs the user about how to use the app. + * Close app when closed. + */ + private void handleStartFromHomeScreen() + { + final Context context = this; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getResources().getText(R.string.dialog_title_on_start_from_home_screen)); + builder.setMessage(getResources().getText(R.string.dialog_content_on_start_from_home_screen)); + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.cancel(); + finish(); + } + }); + builder.setNegativeButton(R.string.action_settings, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + Intent intent = new Intent(context, SettingsActivity.class); + startActivity(intent); + //handleUnimplementedFeature(); + } + }); + builder.show(); + } + + /** + * Move selected files to a new album. The album is located at appDirectoryPath/albumName + * @param albumName + */ + private void moveFiles(String albumName) + { + for(Uri u: sharedMedia) + { + String srcDest = getRealPathFromURI(this, u); + if(!moveImageFromTo(srcDest, calcTargetPath(albumName, srcDest))) + { + System.out.println("Fail: " +srcDest); + } + } + } + + /** + * Move file src to target. + * @param src + * @param target + * @return + */ + private boolean moveImageFromTo(String src, String target) + { + if(!src.equals(target)) + new LoadInitialImageData().doInBackground(src,target); + return true; + } + + private void deleteFileFromMediaStore(String path) + { + if(getMimeType(path).startsWith("image/")) + { + String[] retCol = {MediaStore.Images.Media._ID}; + Cursor cur = this.getContentResolver().query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + retCol, + MediaStore.MediaColumns.DATA+"='"+path+"'", null, null); + if (cur.getCount() == 0) + { + Log.e(Tag, "Could not find image "+path+". Therefore can't delete file from MediaStore"); + return; + } + cur.moveToFirst(); + int id = cur.getInt(cur.getColumnIndex(MediaStore.MediaColumns._ID)); + cur.close(); + Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id); + this.getContentResolver().delete(uri, null, null); + Log.v(Tag, "Deleted image "+path+" from MediaStore as "+uri); + } + else if(getMimeType(path).startsWith("video/")) + { + String[] retCol = {MediaStore.Video.Media._ID}; + Cursor cur = this.getContentResolver().query( + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + retCol, + MediaStore.MediaColumns.DATA+"='"+path+"'",null,null); + if(cur.getCount() == 0) + { + Log.e(Tag, "Could not find video "+path+". Therefore can't delete file from MediaStore"); + return; + } + cur.moveToFirst(); + int id = cur.getInt(cur.getColumnIndex(MediaStore.MediaColumns._ID)); + cur.close(); + Uri uri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id); + this.getContentResolver().delete(uri, null, null); + Log.v(Tag, "Deleted video "+path+" from MediaStore as "+uri); + } + } + + private void addFileToMediaStore(String path) + { + MediaScannerConnection.scanFile( + getApplicationContext(), + new String[]{path}, + null, + new MediaScannerConnection.OnScanCompletedListener() + { + @Override + public void onScanCompleted(String path, Uri uri) + { + Log.v(Tag, "File "+path+" was scanned successfully: "+uri); + } + }); + } + + private String calcTargetPath(String albumName, String srcDest) + { + String file = getAppDirectoryPath()+albumName+srcDest.substring(srcDest.lastIndexOf("/")); + Log.v(Tag, "Path for "+srcDest+" in "+albumName+" will be "+file); + return file; + } + + public String resetAppDirectoryPath() + { + String path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pictures/Albums/"; + Log.d(Tag, "AppDirectoryPath = "+path); + SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(this).edit(); + prefs.putString(SettingsActivity.KEY_PREF_APP_PATH, path); + prefs.apply(); + return path; + } + + public String getAppDirectoryPath() + { + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + String appDirectoryPath = sharedPref.getString(SettingsActivity.KEY_PREF_APP_PATH, ""); + if(!appDirectoryPath.isEmpty()) return appDirectoryPath; + else + { + return resetAppDirectoryPath(); + } + } + + public ArrayList getFoldersOnDirectory(String dirPath) + { + File parent = new File(dirPath); + File[] cont = parent.listFiles(); + ArrayList subDirs = new ArrayList<>(); + if(cont != null) + { + for (File f : cont) + { + if (f.isDirectory()) subDirs.add(f.getName()); + } + } + return subDirs; + } + + public String getRealPathFromURI(Context context, Uri contentUri) + { + String[] projection = {MediaStore.Images.Media.DATA}; + Cursor cursor = context.getContentResolver().query(contentUri, projection, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + String realPath = cursor.getString(column_index); + cursor.close(); + Log.v(Tag, "Real path for file "+contentUri+" is "+realPath); + return realPath; + } + + public static String getMimeType(String url) + { + String type = null; + String extension = MimeTypeMap.getFileExtensionFromUrl(url); + if (extension != null) { + MimeTypeMap mime = MimeTypeMap.getSingleton(); + type = mime.getMimeTypeFromExtension(extension); + } + return type; + } + + /** + * String[] = {src, target} + */ + private class LoadInitialImageData extends AsyncTask + { + @Override + protected Void doInBackground(String... params) + { + InputStream in = null; + OutputStream out = null; + String src = params[0]; + String target = params[1]; + try + { + File dir = new File(src.substring(0, src.lastIndexOf("/"))); + if (!dir.exists()) + dir.mkdirs(); + in = new FileInputStream(src); + File outFile = new File(target.substring(0, target.lastIndexOf("/"))); + if (!outFile.exists()) + outFile.mkdirs(); + out = new FileOutputStream(target); + + byte[] buffer = new byte[1024]; + int read; + while ((read = in.read(buffer)) != -1) + { + out.write(buffer, 0, read); + } + in.close(); + out.flush(); + out.close(); + + addFileToMediaStore(target); + deleteFileFromMediaStore(src); + + } catch (FileNotFoundException e) + { + e.printStackTrace(); + return null; + } catch (IOException e) + { + e.printStackTrace(); + return null; + } + return null; + } + + @Override + protected void onPostExecute(Void v) + { + } + } +} diff --git a/app/src/main/java/de/vanitasvitae/sticktoalbum/SettingsActivity.java b/app/src/main/java/de/vanitasvitae/sticktoalbum/SettingsActivity.java new file mode 100644 index 0000000..0dc6db4 --- /dev/null +++ b/app/src/main/java/de/vanitasvitae/sticktoalbum/SettingsActivity.java @@ -0,0 +1,38 @@ +package de.vanitasvitae.sticktoalbum; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Environment; +import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; + +/** + * Created by vanitas on 10.05.15. + */ +public class SettingsActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener +{ + public final static String KEY_PREF_APP_PATH = "pref_app_path"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.layout.settings); + getPreferenceScreen().getSharedPreferences() + .registerOnSharedPreferenceChangeListener(this); + } + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + if (key.equals(KEY_PREF_APP_PATH)) { + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); + String appDirectoryPath = sharedPref.getString(SettingsActivity.KEY_PREF_APP_PATH, ""); + if(appDirectoryPath.isEmpty()) + { + String path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pictures/Albums/"; + SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(this).edit(); + prefs.putString(SettingsActivity.KEY_PREF_APP_PATH, path); + prefs.apply(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/sticktoalbum.png b/app/src/main/res/drawable/sticktoalbum.png new file mode 100644 index 0000000..a04f8a0 Binary files /dev/null and b/app/src/main/res/drawable/sticktoalbum.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..932b1d9 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,17 @@ + + + + diff --git a/app/src/main/res/layout/save_dialog.xml b/app/src/main/res/layout/save_dialog.xml new file mode 100644 index 0000000..248af40 --- /dev/null +++ b/app/src/main/res/layout/save_dialog.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/settings.xml b/app/src/main/res/layout/settings.xml new file mode 100644 index 0000000..fe0ad10 --- /dev/null +++ b/app/src/main/res/layout/settings.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file 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..08940cc --- /dev/null +++ b/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/mipmap-hdpi/sticktoalbum.png b/app/src/main/res/mipmap-hdpi/sticktoalbum.png new file mode 100644 index 0000000..cf5e170 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/sticktoalbum.png differ diff --git a/app/src/main/res/mipmap-mdpi/sticktoalbum.png b/app/src/main/res/mipmap-mdpi/sticktoalbum.png new file mode 100644 index 0000000..05ad991 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/sticktoalbum.png differ diff --git a/app/src/main/res/mipmap-xhdpi/sticktoalbum.png b/app/src/main/res/mipmap-xhdpi/sticktoalbum.png new file mode 100644 index 0000000..6cc1020 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/sticktoalbum.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/sticktoalbum.png b/app/src/main/res/mipmap-xxhdpi/sticktoalbum.png new file mode 100644 index 0000000..3741cee Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/sticktoalbum.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/sticktoalbum.png b/app/src/main/res/mipmap-xxxhdpi/sticktoalbum.png new file mode 100644 index 0000000..a04f8a0 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/sticktoalbum.png differ diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..aca813a --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,17 @@ + + StickToAlbum + + Albumnamen eingeben + Teile Fotos mit der App! + Teile Fotos (oder Videos) mit StickToAlbum, um diese in ein Album zu verschieben. + Funktion nicht verfügbar + Diese Funktion ist noch nicht fertig implementiert.\nSchau mal nach Updates :) + Bitte gebe einen Albumnamen ein! + Einstellungen + OK + Abbrechen + Neues Album + + Speicherort + Wo sollen neue Alben angelegt werden? Um zurückzusetzen, lösche diesen Wert und starte die App neu. + \ No newline at end of file diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..7e8d605 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..793b286 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,17 @@ + + StickToAlbum + + Enter Album Name + Share Images with the app! + In order to stick photos in albums, you have to share those photos (or videos) with this app. + Feature unavailable + This feature is not implemented yet.\nCheck out updates :) + Please enter valid album name! + Settings + OK + Cancel + New Album + + Album Directory + In which directory are Albums located? Delete value and restart app to reset. + \ No newline at end of file diff --git a/app/src/main/res/values/strings_activity_settings.xml b/app/src/main/res/values/strings_activity_settings.xml new file mode 100644 index 0000000..96455b2 --- /dev/null +++ b/app/src/main/res/values/strings_activity_settings.xml @@ -0,0 +1,61 @@ + + Settings + + + + + General + + Enable social recommendations + Recommendations for people to contact + based on your message history + + + Display name + John Smith + + Add friends to messages + + Always + When possible + Never + + + 1 + 0 + -1 + + + + Data & sync + + Sync frequency + + 15 minutes + 30 minutes + 1 hour + 3 hours + 6 hours + Never + + + 15 + 30 + 60 + 180 + 360 + -1 + + + System sync settings + + + Notifications + + New message notifications + + Ringtone + Silent + + Vibrate + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..173e12e --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/sticktoalbum-web.png b/app/src/main/sticktoalbum-web.png new file mode 100644 index 0000000..c7f3921 Binary files /dev/null and b/app/src/main/sticktoalbum-web.png differ