├── .gitignore
├── COPYING
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── woefe
│ │ └── shoppinglist
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── java
│ └── com
│ │ └── woefe
│ │ └── shoppinglist
│ │ ├── activity
│ │ ├── AboutActivity.java
│ │ ├── BinderActivity.java
│ │ ├── EditBar.java
│ │ ├── InvalidFragment.java
│ │ ├── MainActivity.java
│ │ ├── RecyclerListAdapter.java
│ │ ├── SettingsActivity.java
│ │ ├── SettingsFragment.java
│ │ └── ShoppingListFragment.java
│ │ ├── dialog
│ │ ├── ConfirmationDialog.java
│ │ ├── DirectoryChooser.java
│ │ └── TextInputDialog.java
│ │ └── shoppinglist
│ │ ├── DirectoryStatus.java
│ │ ├── ListItem.java
│ │ ├── ListsChangeListener.java
│ │ ├── ShoppingList.java
│ │ ├── ShoppingListException.java
│ │ ├── ShoppingListMarshaller.java
│ │ ├── ShoppingListService.java
│ │ ├── ShoppingListUnmarshaller.java
│ │ ├── ShoppingListsManager.java
│ │ └── UnmarshallException.java
│ └── res
│ ├── drawable-anydpi
│ └── ic_done_white_24dp.xml
│ ├── drawable-hdpi
│ ├── ic_add_black_24dp.png
│ ├── ic_delete_forever_white_24.png
│ ├── ic_delete_sweep_white_24dp.png
│ ├── ic_done_white_24dp.png
│ ├── ic_menu_white_24dp.png
│ ├── ic_playlist_add_white_24dp.png
│ ├── ic_sd_storage_black_24dp.png
│ ├── ic_sort_white_24dp.png
│ └── ic_swap_vert_black_24dp.png
│ ├── drawable-mdpi
│ ├── ic_add_black_24dp.png
│ ├── ic_delete_forever_white_24.png
│ ├── ic_delete_sweep_white_24dp.png
│ ├── ic_done_white_24dp.png
│ ├── ic_menu_white_24dp.png
│ ├── ic_playlist_add_white_24dp.png
│ ├── ic_sd_storage_black_24dp.png
│ ├── ic_sort_white_24dp.png
│ └── ic_swap_vert_black_24dp.png
│ ├── drawable-xhdpi
│ ├── ic_add_black_24dp.png
│ ├── ic_delete_forever_white_24.png
│ ├── ic_delete_sweep_white_24dp.png
│ ├── ic_done_white_24dp.png
│ ├── ic_menu_white_24dp.png
│ ├── ic_playlist_add_white_24dp.png
│ ├── ic_sd_storage_black_24dp.png
│ ├── ic_sort_white_24dp.png
│ └── ic_swap_vert_black_24dp.png
│ ├── drawable-xxhdpi
│ ├── ic_add_black_24dp.png
│ ├── ic_delete_forever_white_24.png
│ ├── ic_delete_sweep_white_24dp.png
│ ├── ic_done_white_24dp.png
│ ├── ic_menu_white_24dp.png
│ ├── ic_playlist_add_white_24dp.png
│ ├── ic_sd_storage_black_24dp.png
│ ├── ic_sort_white_24dp.png
│ └── ic_swap_vert_black_24dp.png
│ ├── drawable-xxxhdpi
│ ├── ic_add_black_24dp.png
│ ├── ic_delete_forever_white_24.png
│ ├── ic_delete_sweep_white_24dp.png
│ ├── ic_done_black_36dp.png
│ ├── ic_menu_white_24dp.png
│ ├── ic_playlist_add_white_24dp.png
│ ├── ic_sd_storage_black_24dp.png
│ ├── ic_sort_white_24dp.png
│ └── ic_swap_vert_black_24dp.png
│ ├── drawable
│ ├── button_add_selector.xml
│ ├── ic_launcher_foreground.xml
│ └── list_activated_background.xml
│ ├── layout
│ ├── activity_about.xml
│ ├── activity_main.xml
│ ├── dialog_directory_chooser.xml
│ ├── dialog_text_input.xml
│ ├── drawer_list_item.xml
│ ├── fragment_invalid.xml
│ ├── fragment_shoppinglist.xml
│ ├── list_item.xml
│ └── toolbar_main.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.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-de
│ └── strings.xml
│ ├── values-fr
│ └── strings.xml
│ ├── values-it
│ └── strings.xml
│ ├── values-nl
│ └── strings.xml
│ ├── values-ru
│ └── strings.xml
│ ├── values-v21
│ └── styles.xml
│ ├── values-w820dp
│ └── dimens.xml
│ ├── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── preferences.xml
├── build.gradle
├── fastlane
└── metadata
│ └── android
│ ├── de-DE
│ ├── full_description.txt
│ └── short_description.txt
│ └── en-US
│ ├── full_description.txt
│ ├── images
│ └── phoneScreenshots
│ │ ├── 00_lists.png
│ │ └── 01_list_content.png
│ └── short_description.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mobile Tools for Java (J2ME)
2 | .mtj.tmp/
3 |
4 | # Package Files #
5 | *.jar
6 | *.war
7 | *.ear
8 |
9 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
10 | hs_err_pid*
11 |
12 | ## Android
13 |
14 | # Built application files
15 | *.apk
16 | *.ap_
17 |
18 | # Files for the Dalvik VM
19 | *.dex
20 |
21 | # Java class files
22 | *.class
23 |
24 | # Generated files
25 | bin/
26 | gen/
27 |
28 | # Gradle files
29 | .gradle/
30 | build/
31 | */build/
32 |
33 | # Local configuration file (sdk path, etc)
34 | local.properties
35 |
36 | # Proguard folder generated by Eclipse
37 | proguard/
38 |
39 | # Log Files
40 | *.log
41 |
42 | # Android Studio Navigation editor temp files
43 | .navigation/
44 |
45 | # Android Studio captures folder
46 | captures/
47 |
48 | ## Android Studio
49 | *.iml
50 |
51 | # Directory-based project format:
52 | .idea/
53 |
54 | # File-based project format:
55 | *.ipr
56 | *.iws
57 |
58 | # Plugin-specific files:
59 |
60 | # IntelliJ
61 | /out/
62 |
63 | # mpeltonen/sbt-idea plugin
64 | .idea_modules/
65 |
66 | # JIRA plugin
67 | atlassian-ide-plugin.xml
68 |
69 | # Crashlytics plugin (for Android Studio and IntelliJ)
70 | com_crashlytics_export_strings.xml
71 | crashlytics.properties
72 | crashlytics-build.properties
73 | fabric.properties
74 |
75 | # Ignore Gradle GUI config
76 | gradle-app.setting
77 |
78 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
79 | !gradle-wrapper.jar
80 |
81 | # Cache of project
82 | .gradletasknamecache
83 |
84 | ## C Build Files
85 | **/obj/local/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

ShoppingList
2 | A simple shopping list for Android
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## ShoppingList text file
14 | ShoppingList saves your shopping lists as a plain text files.
15 | The syntax of a ShoppingList file is quite simple and easy to read and edit.
16 |
17 | ### Syntax
18 | * The very first line of the file is the name of the list in square brackets
19 | * Empty lines or lines with only white spaces are ignored
20 | * Every item of the list corresponds to a single line in the file
21 | * Checked items start with `//`
22 | * Specifying the amount of an item is optional
23 | * The amount of an item and its name are separated by the #-Sign
24 |
25 | ### Example
26 | ```
27 | [ ShoppingList ]
28 |
29 | Milk
30 | Bananas #
31 | Juice #2 Liters
32 | // Eggs #12
33 | ```
34 |
35 | ## Synchronization
36 | To synchronize the lists select a folder in the ShoppingList settings which gets synchronized by whichever sync-software you use.
37 | The ShoppingList app will automatically detect lists (and changes to the lists) that show up in the folder.
38 | Note that unfortunately most synchronisation solutions on Android cannot continuously watch and synchronize the folder.
39 |
40 | ### Nextcloud Example
41 | 1. Create a `shoppinglists` folder in your Nextcloud
42 | 2. Sync this folder to your Nextcloud files app.
43 | 3. Go to ShoppingList settings and find this folder on your Android file system (e.g. `/storage/emulated/0/Android/media/com.nextcloud.client/nextcloud/user@cloud.kom/shoppinglists`)
44 | 4. If you want to sync the lists, open the Nextcloud files app and synchronize the shopping list folder
45 |
46 | Better Integration with Nextcloud is [planned](https://github.com/woefe/ShoppingList/issues/17).
47 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | apply plugin: 'com.android.application'
21 |
22 | android {
23 | compileSdkVersion 28
24 | buildToolsVersion '28.0.3'
25 |
26 | defaultConfig {
27 | applicationId "com.woefe.shoppinglist"
28 | minSdkVersion 19
29 | targetSdkVersion 28
30 | versionCode 12
31 | versionName "0.11.0"
32 | }
33 | buildTypes {
34 | release {
35 | minifyEnabled false
36 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
37 | }
38 | }
39 | }
40 |
41 | dependencies {
42 | implementation fileTree(dir: 'libs', include: ['*.jar'])
43 | implementation 'com.android.support:appcompat-v7:28.0.0'
44 | implementation 'com.android.support:design:28.0.0'
45 | implementation 'com.android.support:preference-v7:28.0.0'
46 | implementation 'com.android.support:recyclerview-v7:28.0.0'
47 | }
48 |
--------------------------------------------------------------------------------
/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 /home/popeye/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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/woefe/shoppinglist/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist;
21 |
22 | import android.app.Application;
23 | import android.test.ApplicationTestCase;
24 |
25 | /**
26 | * Testing Fundamentals
27 | */
28 | public class ApplicationTest extends ApplicationTestCase {
29 | public ApplicationTest() {
30 | super(Application.class);
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
23 |
24 |
25 |
26 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/activity/AboutActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.activity;
21 |
22 | import android.os.Bundle;
23 | import android.support.annotation.Nullable;
24 | import android.support.v7.app.AppCompatActivity;
25 | import android.text.Html;
26 | import android.text.method.LinkMovementMethod;
27 | import android.widget.TextView;
28 |
29 | import com.woefe.shoppinglist.BuildConfig;
30 | import com.woefe.shoppinglist.R;
31 |
32 | /**
33 | * @author Wolfgang Popp
34 | */
35 |
36 | public class AboutActivity extends AppCompatActivity {
37 |
38 | @Override
39 | protected void onCreate(@Nullable Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_about);
42 | TextView textView = findViewById(R.id.about_text);
43 | textView.setMovementMethod(LinkMovementMethod.getInstance());
44 | textView.setText(Html.fromHtml(getString(R.string.about_title)));
45 | textView.append(Html.fromHtml(getString(R.string.about_version, BuildConfig.VERSION_NAME)));
46 | textView.append("\n");
47 | textView.append("\n");
48 | textView.append("\n");
49 | textView.append(Html.fromHtml(getString(R.string.about_github)));
50 | textView.append("\n");
51 | textView.append("\n");
52 | textView.append("\n");
53 | textView.append(Html.fromHtml(getString(R.string.about_license)));
54 | textView.append("\n");
55 | textView.append("\n");
56 | textView.append("\n");
57 | textView.append(Html.fromHtml(getString(R.string.about_author)));
58 | textView.append("\n");
59 | textView.append("\n");
60 | textView.append("\n");
61 | textView.append(Html.fromHtml(getString(R.string.about_contributors)));
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/activity/BinderActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.activity;
21 |
22 | import android.content.ComponentName;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.content.ServiceConnection;
26 | import android.os.IBinder;
27 | import android.support.v7.app.AppCompatActivity;
28 |
29 | import com.woefe.shoppinglist.shoppinglist.ShoppingListService;
30 |
31 | /**
32 | * @author Wolfgang Popp
33 | */
34 | public abstract class BinderActivity extends AppCompatActivity {
35 |
36 | private final ShoppingListServiceConnection serviceConnection = new ShoppingListServiceConnection();
37 | private ShoppingListService.ShoppingListBinder binder = null;
38 |
39 | @Override
40 | protected void onStart() {
41 | super.onStart();
42 | Intent intent = new Intent(this, ShoppingListService.class);
43 | bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
44 | }
45 |
46 | protected void onStop() {
47 | unbindService(serviceConnection);
48 | super.onStop();
49 | }
50 |
51 | public boolean isServiceConnected() {
52 | return binder != null;
53 | }
54 |
55 | protected ShoppingListService.ShoppingListBinder getBinder() {
56 | return binder;
57 | }
58 |
59 | protected abstract void onServiceConnected(ShoppingListService.ShoppingListBinder binder);
60 |
61 | protected abstract void onServiceDisconnected(ShoppingListService.ShoppingListBinder binder);
62 |
63 | private class ShoppingListServiceConnection implements ServiceConnection {
64 |
65 | @Override
66 | public void onServiceConnected(ComponentName name, IBinder iBinder) {
67 | binder = ((ShoppingListService.ShoppingListBinder) iBinder);
68 | BinderActivity.this.onServiceConnected(binder);
69 | }
70 |
71 | @Override
72 | public void onServiceDisconnected(ComponentName name) {
73 | BinderActivity.this.onServiceDisconnected(binder);
74 | binder = null;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/activity/EditBar.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.activity;
21 |
22 | import android.content.Context;
23 | import android.os.Bundle;
24 | import android.support.design.widget.FloatingActionButton;
25 | import android.support.v7.widget.RecyclerView;
26 | import android.text.Editable;
27 | import android.text.TextWatcher;
28 | import android.view.GestureDetector;
29 | import android.view.KeyEvent;
30 | import android.view.MotionEvent;
31 | import android.view.View;
32 | import android.view.ViewConfiguration;
33 | import android.view.inputmethod.InputMethodManager;
34 | import android.widget.EditText;
35 | import android.widget.ImageButton;
36 | import android.widget.RelativeLayout;
37 | import android.widget.TextView;
38 | import android.widget.Toast;
39 |
40 | import com.woefe.shoppinglist.R;
41 | import com.woefe.shoppinglist.shoppinglist.ShoppingList;
42 |
43 | import java.util.HashSet;
44 | import java.util.Set;
45 |
46 | public class EditBar implements ShoppingList.ShoppingListListener {
47 | private static final String KEY_SAVED_DESCRIPTION = "SAVED_DESCRIPTION";
48 | private static final String KEY_SAVED_QUANTITY = "SAVED_QUANTITY";
49 | private static final String KEY_SAVED_MODE = "SAVED_MODE";
50 | private static final String KEY_SAVE_IS_VISIBLE = "SAVE_IS_VISIBLE";
51 | private final Context ctx;
52 | private final RelativeLayout layout;
53 | private final EditText descriptionText;
54 | private final EditText quantityText;
55 | private final TextView duplicateWarnText;
56 | private Mode mode;
57 | private EditBarListener listener;
58 | private final FloatingActionButton fab;
59 | private int position;
60 | private final Set descriptionIndex = new HashSet<>();
61 | private ShoppingList shoppingList;
62 |
63 | public EditBar(View boundView, final Context ctx) {
64 | this.ctx = ctx;
65 | this.layout = boundView.findViewById(R.id.layout_add_item);
66 | final ImageButton button = boundView.findViewById(R.id.button_add_new_item);
67 | this.descriptionText = boundView.findViewById(R.id.new_item_description);
68 | this.quantityText = boundView.findViewById(R.id.new_item_quantity);
69 | this.duplicateWarnText = boundView.findViewById(R.id.text_warn);
70 | this.mode = Mode.ADD;
71 |
72 | layout.setVisibility(View.GONE);
73 |
74 | quantityText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
75 | @Override
76 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
77 | onConfirm();
78 | return true;
79 | }
80 | });
81 |
82 | button.setOnClickListener(new View.OnClickListener() {
83 | @Override
84 | public void onClick(View v) {
85 | onConfirm();
86 | }
87 | });
88 |
89 | setButtonEnabled(button, false);
90 | descriptionText.addTextChangedListener(new TextWatcher() {
91 | @Override
92 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
93 |
94 | }
95 |
96 | @Override
97 | public void onTextChanged(CharSequence s, int start, int before, int count) {
98 | String str = s.toString();
99 | if (str.equals("")) {
100 | setButtonEnabled(button, false);
101 | } else {
102 | setButtonEnabled(button, true);
103 | }
104 | checkDuplicate(str);
105 | }
106 |
107 | @Override
108 | public void afterTextChanged(Editable s) {
109 |
110 | }
111 | });
112 |
113 | fab = boundView.findViewById(R.id.fab_add);
114 | fab.setOnClickListener(new View.OnClickListener() {
115 | @Override
116 | public void onClick(View v) {
117 | fab.hide();
118 | showAdd();
119 | }
120 | });
121 | }
122 |
123 | private void checkDuplicate(String str) {
124 | if (mode != Mode.ADD || !isVisible()) {
125 | return;
126 | }
127 | if (descriptionIndex.contains(str.toLowerCase())) {
128 | duplicateWarnText.setText(ctx.getString(R.string.duplicate_warning, str));
129 | duplicateWarnText.setVisibility(View.VISIBLE);
130 | } else {
131 | duplicateWarnText.setVisibility(View.GONE);
132 | }
133 | }
134 |
135 | private void setButtonEnabled(ImageButton button, boolean enabled) {
136 | button.setEnabled(enabled);
137 | button.setClickable(enabled);
138 | button.setImageAlpha(enabled ? 255 : 100);
139 | }
140 |
141 | private void onConfirm() {
142 | String desc = descriptionText.getText().toString();
143 | String qty = quantityText.getText().toString();
144 |
145 | if (desc.equals("")) {
146 | Toast.makeText(ctx, R.string.error_description_empty, Toast.LENGTH_SHORT).show();
147 | return;
148 | }
149 |
150 | if (mode == Mode.ADD) {
151 | listener.onNewItem(desc, qty);
152 | descriptionText.requestFocus();
153 | } else if (mode == Mode.EDIT) {
154 | listener.onEditSave(position, desc, qty);
155 | }
156 |
157 | descriptionText.setText("");
158 | quantityText.setText("");
159 | }
160 |
161 | public void showEdit(int position, String description, String quantity) {
162 | this.position = position;
163 | prepare(Mode.EDIT, description, quantity);
164 | show();
165 | }
166 |
167 | public void showAdd() {
168 | prepare(Mode.ADD, "", "");
169 | show();
170 | }
171 |
172 | private void prepare(Mode mode, String description, String quantity) {
173 | this.mode = mode;
174 | quantityText.setText(quantity);
175 | descriptionText.setText("");
176 | descriptionText.append(description);
177 | }
178 |
179 | public void enableAutoHideFAB(RecyclerView view) {
180 | final GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
181 | private final int slop = ViewConfiguration.get(ctx).getScaledPagingTouchSlop();
182 | private float start = -1;
183 | private float triggerPosition = -1;
184 |
185 | @Override
186 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
187 | if (isNewEvent(e1)) {
188 | start = e1.getY();
189 | }
190 | final float end = e2.getY();
191 |
192 | if (end - start > slop) {
193 | showFAB();
194 | start = end;
195 | } else if (end - start < -slop) {
196 | hideFAB();
197 | start = end;
198 | }
199 | return super.onScroll(e1, e2, distanceX, distanceY);
200 | }
201 |
202 | private boolean isNewEvent(MotionEvent e1) {
203 | boolean isNewEvent = e1 != null && !(e1.getY() == triggerPosition);
204 | if (isNewEvent) {
205 | triggerPosition = e1.getY();
206 | }
207 | return isNewEvent;
208 | }
209 | };
210 |
211 | final GestureDetector detector = new GestureDetector(ctx, gestureListener);
212 |
213 | view.setOnTouchListener(new View.OnTouchListener() {
214 | @Override
215 | public boolean onTouch(View v, MotionEvent event) {
216 | detector.onTouchEvent(event);
217 | return false;
218 | }
219 | });
220 | }
221 |
222 | private void showFAB() {
223 | if (!isVisible()) {
224 | fab.show();
225 | }
226 | }
227 |
228 | private void hideFAB() {
229 | fab.hide();
230 | }
231 |
232 | private void show() {
233 | layout.setVisibility(View.VISIBLE);
234 | descriptionText.requestFocus();
235 | InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
236 | if (imm != null) {
237 | imm.showSoftInput(descriptionText, InputMethodManager.SHOW_IMPLICIT);
238 | }
239 | }
240 |
241 | public void hide() {
242 | descriptionText.clearFocus();
243 | quantityText.clearFocus();
244 | layout.setVisibility(View.GONE);
245 | duplicateWarnText.setText("");
246 | duplicateWarnText.setVisibility(View.GONE);
247 | InputMethodManager imm = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
248 | if (imm != null) {
249 | imm.hideSoftInputFromWindow(layout.getWindowToken(), 0);
250 | }
251 | fab.show();
252 | fab.requestFocus();
253 | }
254 |
255 | public boolean isVisible() {
256 | return layout.getVisibility() != View.GONE;
257 | }
258 |
259 | public void addEditBarListener(EditBarListener l) {
260 | listener = l;
261 | }
262 |
263 | public void removeEditBarListener(EditBarListener l) {
264 | if (l == listener) {
265 | listener = null;
266 | }
267 | }
268 |
269 | public void saveState(Bundle state) {
270 | state.putString(KEY_SAVED_DESCRIPTION, descriptionText.getText().toString());
271 | state.putString(KEY_SAVED_QUANTITY, quantityText.getText().toString());
272 | state.putBoolean(KEY_SAVE_IS_VISIBLE, isVisible());
273 | state.putSerializable(KEY_SAVED_MODE, mode);
274 | }
275 |
276 | public void restoreState(Bundle state) {
277 | String description = state.getString(KEY_SAVED_DESCRIPTION);
278 | String quantity = state.getString(KEY_SAVED_QUANTITY);
279 | Mode mode = (Mode) state.getSerializable(KEY_SAVED_MODE);
280 | if (state.getBoolean(KEY_SAVE_IS_VISIBLE)) {
281 | prepare(mode, description, quantity);
282 | layout.setVisibility(View.VISIBLE);
283 | fab.hide();
284 | }
285 | }
286 |
287 | public void connectShoppingList(ShoppingList shoppingList) {
288 | this.shoppingList = shoppingList;
289 | this.shoppingList.addListener(this);
290 | onShoppingListUpdate(shoppingList, null);
291 | }
292 |
293 | public void disconnectShoppingList() {
294 | if (shoppingList != null) {
295 | shoppingList.removeListener(this);
296 | shoppingList = null;
297 | }
298 | }
299 |
300 | @Override
301 | public void onShoppingListUpdate(ShoppingList list, ShoppingList.Event e) {
302 | if (mode == Mode.EDIT) {
303 | hide();
304 | return;
305 | }
306 | descriptionIndex.clear();
307 | descriptionIndex.addAll(list.createDescriptionIndex());
308 | checkDuplicate(descriptionText.getText().toString());
309 | }
310 |
311 | public interface EditBarListener {
312 | void onEditSave(int position, String description, String quantity);
313 |
314 | void onNewItem(String description, String quantity);
315 | }
316 |
317 | private enum Mode {
318 | EDIT, ADD
319 | }
320 |
321 |
322 | }
323 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/activity/InvalidFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.activity;
21 |
22 | import android.app.Fragment;
23 | import android.os.Bundle;
24 | import android.support.annotation.Nullable;
25 | import android.view.LayoutInflater;
26 | import android.view.View;
27 | import android.view.ViewGroup;
28 |
29 | import com.woefe.shoppinglist.R;
30 |
31 | public class InvalidFragment extends Fragment {
32 | @Nullable
33 | @Override
34 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
35 | return inflater.inflate(R.layout.fragment_invalid, container, false);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/activity/RecyclerListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.woefe.shoppinglist.activity;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.drawable.ColorDrawable;
6 | import android.graphics.drawable.Drawable;
7 | import android.support.annotation.NonNull;
8 | import android.support.v4.content.ContextCompat;
9 | import android.support.v7.widget.RecyclerView;
10 | import android.support.v7.widget.helper.ItemTouchHelper;
11 | import android.view.LayoutInflater;
12 | import android.view.MotionEvent;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.widget.ImageView;
16 | import android.widget.TextView;
17 |
18 | import com.woefe.shoppinglist.R;
19 | import com.woefe.shoppinglist.shoppinglist.ListItem;
20 | import com.woefe.shoppinglist.shoppinglist.ShoppingList;
21 |
22 | /**
23 | * @author Wolfgang Popp
24 | */
25 | public class RecyclerListAdapter extends RecyclerView.Adapter {
26 | private final int colorChecked;
27 | private final int colorDefault;
28 | private final int colorBackground;
29 | private ShoppingList shoppingList;
30 | private ItemTouchHelper touchHelper;
31 | private ItemLongClickListener longClickListener;
32 |
33 | private final ShoppingList.ShoppingListListener listener = new ShoppingList.ShoppingListListener() {
34 | @Override
35 | public void onShoppingListUpdate(ShoppingList list, ShoppingList.Event e) {
36 | switch (e.getState()) {
37 | case ShoppingList.Event.ITEM_CHANGED:
38 | notifyItemChanged(e.getIndex());
39 | break;
40 | case ShoppingList.Event.ITEM_INSERTED:
41 | notifyItemInserted(e.getIndex());
42 | break;
43 | case ShoppingList.Event.ITEM_MOVED:
44 | notifyItemMoved(e.getOldIndex(), e.getNewIndex());
45 | break;
46 | case ShoppingList.Event.ITEM_REMOVED:
47 | notifyItemRemoved(e.getIndex());
48 | break;
49 | default:
50 | notifyDataSetChanged();
51 | }
52 | }
53 | };
54 |
55 | public RecyclerListAdapter(Context ctx) {
56 | colorChecked = ContextCompat.getColor(ctx, R.color.textColorChecked);
57 | colorDefault = ContextCompat.getColor(ctx, R.color.textColorDefault);
58 | colorBackground = ContextCompat.getColor(ctx, R.color.colorListItemBackground);
59 | touchHelper = new ItemTouchHelper(new RecyclerListCallback(ctx));
60 | }
61 |
62 | public void connectShoppingList(ShoppingList shoppingList) {
63 | this.shoppingList = shoppingList;
64 | shoppingList.addListener(listener);
65 | notifyDataSetChanged();
66 | }
67 |
68 | public void disconnectShoppingList() {
69 | if (shoppingList != null) {
70 | shoppingList.removeListener(listener);
71 | shoppingList = null;
72 | }
73 | }
74 |
75 | public void move(int fromPos, int toPos) {
76 | shoppingList.move(fromPos, toPos);
77 | }
78 |
79 | public void remove(int pos) {
80 | shoppingList.remove(pos);
81 | }
82 |
83 | public void registerRecyclerView(RecyclerView view) {
84 | touchHelper.attachToRecyclerView(view);
85 | }
86 |
87 | public void setOnItemLongClickListener(ItemLongClickListener listener) {
88 | this.longClickListener = listener;
89 | }
90 |
91 | @NonNull
92 | @Override
93 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
94 | View v = LayoutInflater.from(parent.getContext())
95 | .inflate(R.layout.list_item, parent, false);
96 | return new ViewHolder(v);
97 | }
98 |
99 | @Override
100 | public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
101 | ListItem listItem = shoppingList.get(position);
102 | holder.description.setText(listItem.getDescription());
103 | holder.quantity.setText(listItem.getQuantity());
104 |
105 | if (listItem.isChecked()) {
106 | holder.description.setTextColor(colorChecked);
107 | holder.quantity.setTextColor(colorChecked);
108 | } else {
109 | holder.description.setTextColor(colorDefault);
110 | holder.quantity.setTextColor(colorDefault);
111 | }
112 |
113 | holder.itemView.setBackgroundColor(colorBackground);
114 |
115 | holder.view.setOnClickListener(new View.OnClickListener() {
116 | @Override
117 | public void onClick(View v) {
118 | shoppingList.toggleChecked(holder.getAdapterPosition());
119 | }
120 | });
121 |
122 |
123 | holder.view.setOnLongClickListener(new View.OnLongClickListener() {
124 | @Override
125 | public boolean onLongClick(View v) {
126 | return longClickListener != null
127 | && longClickListener.onLongClick(holder.getAdapterPosition());
128 | }
129 | });
130 |
131 | holder.dragHandler.setOnTouchListener(new View.OnTouchListener() {
132 | @Override
133 | public boolean onTouch(View v, MotionEvent event) {
134 | if (event.getAction() == MotionEvent.ACTION_DOWN) {
135 | touchHelper.startDrag(holder);
136 | return true;
137 | }
138 | return false;
139 | }
140 | });
141 |
142 | }
143 |
144 | @Override
145 | public int getItemCount() {
146 | if (shoppingList != null) {
147 | return shoppingList.size();
148 | }
149 | return 0;
150 | }
151 |
152 | public interface ItemLongClickListener {
153 | boolean onLongClick(int position);
154 | }
155 |
156 | public static class ViewHolder extends RecyclerView.ViewHolder {
157 | TextView description;
158 | TextView quantity;
159 | ImageView dragHandler;
160 | View view;
161 |
162 | public ViewHolder(View itemView) {
163 | super(itemView);
164 | view = itemView;
165 | description = itemView.findViewById(R.id.text_description);
166 | quantity = itemView.findViewById(R.id.text_quantity);
167 | dragHandler = itemView.findViewById(R.id.drag_n_drop_handler);
168 | }
169 | }
170 |
171 | public class RecyclerListCallback extends ItemTouchHelper.Callback {
172 | private ColorDrawable background;
173 | private Drawable deleteIcon;
174 | private int backgroundColor;
175 |
176 | public RecyclerListCallback(Context ctx) {
177 | this.background = new ColorDrawable();
178 | this.deleteIcon = ContextCompat.getDrawable(ctx, R.drawable.ic_delete_forever_white_24);
179 | this.backgroundColor = ContextCompat.getColor(ctx, R.color.colorCritical);
180 | }
181 |
182 | @Override
183 | public boolean isItemViewSwipeEnabled() {
184 | return true;
185 | }
186 |
187 | @Override
188 | public boolean isLongPressDragEnabled() {
189 | return false;
190 | }
191 |
192 | @Override
193 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
194 | final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
195 | final int swipeFlags = ItemTouchHelper.START;
196 | return makeMovementFlags(dragFlags, swipeFlags);
197 | }
198 |
199 | @Override
200 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
201 | if (viewHolder.getItemViewType() != target.getItemViewType()) {
202 | return false;
203 | }
204 |
205 | RecyclerListAdapter.this.move(viewHolder.getAdapterPosition(), target.getAdapterPosition());
206 | return true;
207 | }
208 |
209 | @Override
210 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
211 | RecyclerListAdapter.this.remove(viewHolder.getAdapterPosition());
212 | }
213 |
214 | @Override
215 | public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
216 |
217 | if (actionState != ItemTouchHelper.ACTION_STATE_SWIPE) {
218 | super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
219 | return;
220 | }
221 |
222 | View itemView = viewHolder.itemView;
223 |
224 | int backgroundLeft = itemView.getRight() + (int) dX;
225 | background.setBounds(backgroundLeft, itemView.getTop(), itemView.getRight(), itemView.getBottom());
226 | background.setColor(backgroundColor);
227 | background.draw(c);
228 |
229 | int itemHeight = itemView.getBottom() - itemView.getTop();
230 | int intrinsicHeight = deleteIcon.getIntrinsicHeight();
231 | int iconTop = itemView.getTop() + (itemHeight - intrinsicHeight) / 2;
232 | int iconMargin = (itemHeight - intrinsicHeight) / 2;
233 | int iconLeft = itemView.getRight() - iconMargin - deleteIcon.getIntrinsicWidth();
234 | int iconRight = itemView.getRight() - iconMargin;
235 | int iconBottom = iconTop + intrinsicHeight;
236 | deleteIcon.setBounds(iconLeft, iconTop, iconRight, iconBottom);
237 | deleteIcon.draw(c);
238 |
239 | // Fade out the view as it is swiped out of the parent's bounds
240 | final float alpha = 1.0f - Math.abs(dX) / (float) itemView.getWidth();
241 | itemView.setAlpha(alpha);
242 | itemView.setTranslationX(dX);
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/activity/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.activity;
21 |
22 | import android.os.Bundle;
23 | import android.support.v7.app.AppCompatActivity;
24 |
25 | /**
26 | * @author Wolfgang Popp.
27 | */
28 | public class SettingsActivity extends AppCompatActivity {
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | getSupportFragmentManager().beginTransaction()
34 | .replace(android.R.id.content, new SettingsFragment())
35 | .commit();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/activity/SettingsFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.activity;
21 |
22 | import android.app.Activity;
23 | import android.content.Intent;
24 | import android.content.SharedPreferences;
25 | import android.os.Bundle;
26 | import android.support.annotation.Nullable;
27 | import android.support.v4.app.FragmentActivity;
28 | import android.support.v7.preference.EditTextPreference;
29 | import android.support.v7.preference.Preference;
30 | import android.support.v7.preference.PreferenceCategory;
31 | import android.support.v7.preference.PreferenceFragmentCompat;
32 | import android.support.v7.preference.PreferenceManager;
33 |
34 | import com.woefe.shoppinglist.R;
35 | import com.woefe.shoppinglist.dialog.DirectoryChooser;
36 |
37 | /**
38 | * @author Wolfgang Popp.
39 | */
40 | public class SettingsFragment extends PreferenceFragmentCompat implements
41 | SharedPreferences.OnSharedPreferenceChangeListener {
42 |
43 | public static final String KEY_DIRECTORY_LOCATION = "FILE_LOCATION";
44 | private static final int REQUEST_CODE_CHOOSE_DIR = 123;
45 |
46 | @Override
47 | public void onCreate(Bundle savedInstanceState) {
48 | super.onCreate(savedInstanceState);
49 | addPreferencesFromResource(R.xml.preferences);
50 | getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
51 |
52 | // show the current value in the settings screen
53 | for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
54 | initSummary(getPreferenceScreen().getPreference(i));
55 | }
56 | }
57 |
58 | @Override
59 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
60 |
61 | }
62 |
63 | @Override
64 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
65 | super.onActivityCreated(savedInstanceState);
66 |
67 | final Preference fileLocationPref = findPreference("FILE_LOCATION");
68 |
69 | fileLocationPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
70 | @Override
71 | public boolean onPreferenceClick(Preference preference) {
72 | Intent intent = new Intent(getContext(), DirectoryChooser.class);
73 | startActivityForResult(intent, REQUEST_CODE_CHOOSE_DIR);
74 | return true;
75 | }
76 | });
77 | }
78 |
79 | @Override
80 | public void onDestroy() {
81 | super.onDestroy();
82 | getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
83 | }
84 |
85 | private void initSummary(Preference p) {
86 | if (p instanceof PreferenceCategory) {
87 | PreferenceCategory cat = (PreferenceCategory) p;
88 | for (int i = 0; i < cat.getPreferenceCount(); i++) {
89 | initSummary(cat.getPreference(i));
90 | }
91 | } else {
92 | updatePreferences(p);
93 | }
94 | }
95 |
96 | private void updatePreferences(Preference p) {
97 | if (KEY_DIRECTORY_LOCATION.equals(p.getKey())) {
98 | String path = getSharedPreferences().getString(KEY_DIRECTORY_LOCATION, "");
99 | p.setSummary(path);
100 | }
101 | if (p instanceof EditTextPreference) {
102 | EditTextPreference editTextPref = (EditTextPreference) p;
103 | p.setSummary(editTextPref.getText());
104 | }
105 | }
106 |
107 | private SharedPreferences getSharedPreferences() {
108 | FragmentActivity activity = getActivity();
109 | assert activity != null;
110 | return PreferenceManager.getDefaultSharedPreferences(activity);
111 | }
112 |
113 | @Override
114 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
115 | if (key.equals(KEY_DIRECTORY_LOCATION)) {
116 | Preference p = findPreference(key);
117 | updatePreferences(p);
118 | }
119 | }
120 |
121 | @Override
122 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
123 | super.onActivityResult(requestCode, resultCode, data);
124 | switch (requestCode) {
125 | case (REQUEST_CODE_CHOOSE_DIR): {
126 | if (resultCode == Activity.RESULT_OK) {
127 | String path = data.getStringExtra(DirectoryChooser.SELECTED_PATH);
128 | SharedPreferences.Editor editor = getSharedPreferences().edit();
129 | editor.putString(KEY_DIRECTORY_LOCATION, path).apply();
130 | }
131 | break;
132 | }
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/activity/ShoppingListFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.activity;
21 |
22 | import android.app.Fragment;
23 | import android.os.Bundle;
24 | import android.support.annotation.Nullable;
25 | import android.support.v7.widget.DividerItemDecoration;
26 | import android.support.v7.widget.LinearLayoutManager;
27 | import android.support.v7.widget.RecyclerView;
28 | import android.view.LayoutInflater;
29 | import android.view.View;
30 | import android.view.ViewGroup;
31 |
32 | import com.woefe.shoppinglist.R;
33 | import com.woefe.shoppinglist.shoppinglist.ListItem;
34 | import com.woefe.shoppinglist.shoppinglist.ShoppingList;
35 |
36 | public class ShoppingListFragment extends Fragment implements EditBar.EditBarListener {
37 |
38 | private EditBar editBar;
39 | private RecyclerView recyclerView;
40 | private RecyclerView.LayoutManager layoutManager;
41 | private RecyclerListAdapter adapter;
42 | private View rootView;
43 | private ShoppingList shoppingList;
44 |
45 | public static ShoppingListFragment newInstance(ShoppingList shoppingList) {
46 | ShoppingListFragment fragment = new ShoppingListFragment();
47 | fragment.setShoppingList(shoppingList);
48 | return fragment;
49 | }
50 |
51 | public void setShoppingList(ShoppingList shoppingList) {
52 | this.shoppingList = shoppingList;
53 | connectList();
54 | }
55 |
56 | @Override
57 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
58 | rootView = inflater.inflate(R.layout.fragment_shoppinglist, container, false);
59 |
60 | recyclerView = rootView.findViewById(R.id.shoppingListView);
61 | registerForContextMenu(recyclerView);
62 |
63 | recyclerView.setHasFixedSize(true);
64 |
65 | layoutManager = new LinearLayoutManager(getActivity());
66 | recyclerView.setLayoutManager(layoutManager);
67 |
68 | DividerItemDecoration divider = new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL);
69 | recyclerView.addItemDecoration(divider);
70 |
71 | editBar = new EditBar(rootView, getActivity());
72 | editBar.addEditBarListener(this);
73 | editBar.enableAutoHideFAB(recyclerView);
74 |
75 | if (savedInstanceState != null) {
76 | editBar.restoreState(savedInstanceState);
77 | }
78 |
79 | return rootView;
80 | }
81 |
82 | @Override
83 | public void onDestroyView() {
84 | editBar.removeEditBarListener(this);
85 | editBar.hide();
86 | super.onDestroyView();
87 | }
88 |
89 | private void connectList() {
90 | if (shoppingList != null && adapter != null) {
91 | adapter.connectShoppingList(shoppingList);
92 | }
93 | if (shoppingList != null && editBar != null) {
94 | editBar.connectShoppingList(shoppingList);
95 | }
96 | }
97 |
98 | @Override
99 | public void onStop() {
100 | adapter.disconnectShoppingList();
101 | editBar.disconnectShoppingList();
102 | super.onStop();
103 | }
104 |
105 | @Override
106 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
107 | super.onActivityCreated(savedInstanceState);
108 | adapter = new RecyclerListAdapter(getActivity());
109 | connectList();
110 | adapter.registerRecyclerView(recyclerView);
111 | adapter.setOnItemLongClickListener(new RecyclerListAdapter.ItemLongClickListener() {
112 | @Override
113 | public boolean onLongClick(int position) {
114 | ListItem listItem = shoppingList.get(position);
115 | editBar.showEdit(position, listItem.getDescription(), listItem.getQuantity());
116 | return true;
117 | }
118 | });
119 | recyclerView.setAdapter(adapter);
120 | }
121 |
122 | public boolean onBackPressed() {
123 | if (editBar.isVisible()) {
124 | editBar.hide();
125 | return true;
126 | }
127 | return false;
128 | }
129 |
130 | @Override
131 | public void onSaveInstanceState(Bundle outState) {
132 | super.onSaveInstanceState(outState);
133 | editBar.saveState(outState);
134 | }
135 |
136 | @Override
137 | public void onEditSave(int position, String description, String quantity) {
138 | shoppingList.editItem(position, description, quantity);
139 | editBar.hide();
140 | recyclerView.smoothScrollToPosition(position);
141 | }
142 |
143 | @Override
144 | public void onNewItem(String description, String quantity) {
145 | shoppingList.add(description, quantity);
146 | recyclerView.smoothScrollToPosition(recyclerView.getAdapter().getItemCount() - 1);
147 | }
148 |
149 | public void removeAllCheckedItems() {
150 | shoppingList.removeAllCheckedItems();
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/dialog/ConfirmationDialog.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.dialog;
21 |
22 | import android.app.AlertDialog;
23 | import android.app.Dialog;
24 | import android.content.Context;
25 | import android.content.DialogInterface;
26 | import android.os.Bundle;
27 | import android.support.annotation.NonNull;
28 | import android.support.v4.app.DialogFragment;
29 | import android.support.v7.app.AppCompatActivity;
30 | import android.text.Html;
31 |
32 | import com.woefe.shoppinglist.R;
33 |
34 | /**
35 | * @author Wolfgang Popp.
36 | */
37 | public class ConfirmationDialog extends DialogFragment {
38 | private static final String TAG = ConfirmationDialog.class.getSimpleName();
39 | private static final String KEY_MESSAGE = "MESSAGE";
40 | private ConfirmationDialogListener listener;
41 | private String message;
42 | private int action;
43 |
44 |
45 | public interface ConfirmationDialogListener {
46 | void onPositiveButtonClicked(int action);
47 |
48 | void onNegativeButtonClicked(int action);
49 | }
50 |
51 | public static void show(AppCompatActivity activity, String message, int action) {
52 | ConfirmationDialog dialog = new ConfirmationDialog();
53 | dialog.message = message;
54 | dialog.action = action;
55 | dialog.show(activity.getSupportFragmentManager(), TAG);
56 | }
57 |
58 | @Override
59 | public void onAttach(Context ctx) {
60 | super.onAttach(ctx);
61 | listener = (ConfirmationDialogListener) ctx;
62 | }
63 |
64 | @NonNull
65 | @Override
66 | public Dialog onCreateDialog(Bundle savedInstanceState) {
67 | super.onCreateDialog(savedInstanceState);
68 |
69 | if (savedInstanceState != null) {
70 | message = savedInstanceState.getString(KEY_MESSAGE);
71 | }
72 |
73 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
74 | builder.setMessage(Html.fromHtml(message))
75 | .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
76 | @Override
77 | public void onClick(DialogInterface dialog, int which) {
78 | listener.onPositiveButtonClicked(action);
79 | }
80 | })
81 | .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
82 | @Override
83 | public void onClick(DialogInterface dialog, int which) {
84 | listener.onNegativeButtonClicked(action);
85 | }
86 | });
87 |
88 | return builder.create();
89 | }
90 |
91 | @Override
92 | public void onSaveInstanceState(Bundle outState) {
93 | super.onSaveInstanceState(outState);
94 | outState.putString(KEY_MESSAGE, message);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/dialog/DirectoryChooser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.dialog;
21 |
22 | import android.Manifest;
23 | import android.app.AlertDialog;
24 | import android.content.DialogInterface;
25 | import android.content.Intent;
26 | import android.content.pm.PackageManager;
27 | import android.os.Bundle;
28 | import android.os.Environment;
29 | import android.support.annotation.Nullable;
30 | import android.support.design.widget.FloatingActionButton;
31 | import android.support.v4.app.ActivityCompat;
32 | import android.support.v4.content.ContextCompat;
33 | import android.support.v7.app.AppCompatActivity;
34 | import android.view.View;
35 | import android.widget.AdapterView;
36 | import android.widget.ArrayAdapter;
37 | import android.widget.Button;
38 | import android.widget.ImageButton;
39 | import android.widget.ListView;
40 | import android.widget.TextView;
41 | import android.widget.Toast;
42 |
43 | import com.woefe.shoppinglist.R;
44 |
45 | import java.io.File;
46 | import java.util.ArrayList;
47 | import java.util.Arrays;
48 | import java.util.Collections;
49 | import java.util.Comparator;
50 | import java.util.List;
51 | import java.util.ListIterator;
52 |
53 | /**
54 | * @author Wolfgang Popp
55 | */
56 |
57 | public class DirectoryChooser extends AppCompatActivity implements TextInputDialog.TextInputDialogListener {
58 | public static final String SELECTED_PATH = "SELECTED_PATH";
59 | private static final String KEY_CURRENT_DIR = "CURRENT_DIR";
60 | private static final int ACTION_READ_INPUT = 1;
61 | private static final String PARENT_DIR = "..";
62 | private static final int REQUEST_CODE_EXT_STORAGE = 2;
63 |
64 | private ArrayAdapter directoryViewAdapter;
65 | private File currentDirectory;
66 | private TextView title;
67 |
68 | @Override
69 | protected void onCreate(@Nullable Bundle savedInstanceState) {
70 | super.onCreate(savedInstanceState);
71 | setContentView(R.layout.dialog_directory_chooser);
72 |
73 | directoryViewAdapter = new ArrayAdapter<>(this, R.layout.drawer_list_item);
74 | ListView directoryView = findViewById(R.id.directoryListView);
75 | directoryView.setOnItemClickListener(new ListView.OnItemClickListener() {
76 | @Override
77 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
78 | String selectedDir = directoryViewAdapter.getItem(position);
79 | if (selectedDir != null) {
80 | if (selectedDir.equals(PARENT_DIR)) {
81 | changeDirectory(currentDirectory.getParentFile());
82 | } else {
83 | changeDirectory(new File(currentDirectory, selectedDir));
84 | }
85 | }
86 | }
87 | });
88 | directoryView.setAdapter(directoryViewAdapter);
89 |
90 |
91 | Button okButton = findViewById(R.id.button_dialog_ok);
92 | okButton.setOnClickListener(new View.OnClickListener() {
93 | @Override
94 | public void onClick(View v) {
95 | accept();
96 | }
97 | });
98 |
99 | Button cancelButton = findViewById(R.id.button_dialog_cancel);
100 | cancelButton.setOnClickListener(new View.OnClickListener() {
101 | @Override
102 | public void onClick(View v) {
103 | cancel();
104 | }
105 | });
106 |
107 | ImageButton storageButton = findViewById(R.id.button_choose_storage);
108 | storageButton.setOnClickListener(new View.OnClickListener() {
109 | @Override
110 | public void onClick(View v) {
111 | chooseStorageLocation();
112 | }
113 | });
114 |
115 | FloatingActionButton fab = findViewById(R.id.fab_add);
116 | fab.setOnClickListener(new View.OnClickListener() {
117 | @Override
118 | public void onClick(View v) {
119 | createNewDir();
120 | }
121 | });
122 |
123 | title = findViewById(R.id.title);
124 |
125 | File directory;
126 | String savedDir;
127 | File[] storageLocations = listStorageLocations();
128 |
129 | if (savedInstanceState != null
130 | && (savedDir = savedInstanceState.getString(KEY_CURRENT_DIR)) != null) {
131 |
132 | directory = new File(savedDir);
133 | } else if (storageLocations.length > 0) {
134 | directory = storageLocations[0];
135 | } else {
136 | directory = new File(""); // Jeez you phone is broken?!?!
137 | }
138 |
139 | changeDirectory(directory);
140 | }
141 |
142 | @Override
143 | protected void onStart() {
144 | super.onStart();
145 |
146 | int result = ContextCompat.checkSelfPermission(this,
147 | Manifest.permission.WRITE_EXTERNAL_STORAGE);
148 |
149 | if (result == PackageManager.PERMISSION_DENIED) {
150 | ActivityCompat.requestPermissions(this,
151 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
152 | REQUEST_CODE_EXT_STORAGE);
153 | }
154 | }
155 |
156 | @Override
157 | public void onInputComplete(String input, int action) {
158 | if (action == ACTION_READ_INPUT) {
159 | File newDir = new File(currentDirectory, input);
160 | boolean success = newDir.mkdir();
161 | if (success) {
162 | changeDirectory(newDir);
163 | } else {
164 | Toast.makeText(this, getString(R.string.err_create_dir, input), Toast.LENGTH_LONG).show();
165 | }
166 | }
167 | }
168 |
169 | @Override
170 | public void onSaveInstanceState(Bundle outState) {
171 | outState.putString(KEY_CURRENT_DIR, currentDirectory.getAbsolutePath());
172 | super.onSaveInstanceState(outState);
173 | }
174 |
175 | private void accept() {
176 | if (!currentDirectory.canWrite()) {
177 | Toast.makeText(this, "Cannot write to directory", Toast.LENGTH_LONG).show();
178 | return;
179 | }
180 |
181 | Intent intent = new Intent();
182 | intent.putExtra(SELECTED_PATH, currentDirectory.getAbsolutePath());
183 | setResult(RESULT_OK, intent);
184 | finish();
185 | }
186 |
187 | private void cancel() {
188 | setResult(RESULT_CANCELED);
189 | finish();
190 | }
191 |
192 | private void createNewDir() {
193 | NewDirectoryDialog.Builder builder =
194 | new TextInputDialog.Builder(this, NewDirectoryDialog.class);
195 |
196 | builder.setAction(ACTION_READ_INPUT)
197 | .setMessage(R.string.create_new_dir)
198 | .setHint(R.string.name_of_new_dir)
199 | .show();
200 | }
201 |
202 | private void chooseStorageLocation() {
203 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
204 | final File[] locations = listStorageLocations();
205 | final String[] locationNames = new String[locations.length];
206 | for (int i = 0; i < locations.length; i++) {
207 | locationNames[i] = locations[i].getAbsolutePath();
208 | }
209 |
210 | builder.setTitle(R.string.select_storage_location)
211 | .setItems(locationNames, new DialogInterface.OnClickListener() {
212 | @Override
213 | public void onClick(DialogInterface dialog, int which) {
214 | changeDirectory(locations[which]);
215 | }
216 | }).create().show();
217 | }
218 |
219 | private File[] listStorageLocations() {
220 | final List locations = new ArrayList<>();
221 |
222 | locations.add(Environment.getExternalStorageDirectory());
223 | locations.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
224 | locations.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS));
225 | locations.addAll(Arrays.asList(getExternalFilesDirs(Environment.DIRECTORY_DOCUMENTS)));
226 |
227 | ListIterator it = locations.listIterator();
228 | while (it.hasNext()) {
229 | File directory = it.next();
230 | if (directory == null
231 | || !Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(directory))
232 | || !directory.canExecute()
233 | || !directory.canRead()) {
234 |
235 | it.remove();
236 | }
237 | }
238 |
239 | locations.add(getFilesDir());
240 |
241 | return locations.toArray(new File[locations.size()]);
242 | }
243 |
244 | private void changeDirectory(File directory) {
245 | if (directory == null
246 | || !directory.canRead()
247 | || !directory.canExecute()) {
248 |
249 | Toast.makeText(this, R.string.warn_no_dir_access, Toast.LENGTH_LONG).show();
250 | return;
251 | }
252 |
253 | List directories = new ArrayList<>();
254 | for (File file : directory.listFiles()) {
255 | if (file.isDirectory()) {
256 | directories.add(file.getName());
257 | }
258 | }
259 |
260 | Collections.sort(directories, new Comparator() {
261 | @Override
262 | public int compare(String o1, String o2) {
263 | return o1.compareToIgnoreCase(o2);
264 | }
265 | });
266 |
267 | title.setText(directory.getAbsolutePath());
268 | directoryViewAdapter.clear();
269 | directoryViewAdapter.add(PARENT_DIR);
270 | directoryViewAdapter.addAll(directories);
271 | currentDirectory = directory;
272 | }
273 |
274 | public static class NewDirectoryDialog extends TextInputDialog {
275 | @Override
276 | public boolean onValidateInput(String input) {
277 | boolean isValid = !input.contains("/");
278 | if (!isValid) {
279 | Toast.makeText(getContext(), R.string.err_illegal_char, Toast.LENGTH_LONG).show();
280 | }
281 | return isValid;
282 | }
283 | }
284 | }
285 |
286 |
287 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/dialog/TextInputDialog.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.dialog;
21 |
22 | import android.content.Context;
23 | import android.os.Bundle;
24 | import android.support.annotation.NonNull;
25 | import android.support.annotation.Nullable;
26 | import android.support.annotation.StringRes;
27 | import android.support.v4.app.DialogFragment;
28 | import android.support.v4.app.Fragment;
29 | import android.support.v4.app.FragmentActivity;
30 | import android.support.v4.app.FragmentManager;
31 | import android.util.Log;
32 | import android.view.KeyEvent;
33 | import android.view.LayoutInflater;
34 | import android.view.View;
35 | import android.view.ViewGroup;
36 | import android.widget.Button;
37 | import android.widget.EditText;
38 | import android.widget.TextView;
39 |
40 | import com.woefe.shoppinglist.R;
41 |
42 | public class TextInputDialog extends DialogFragment {
43 | private static final String TAG = DialogFragment.class.getSimpleName();
44 | private static final String KEY_MESSAGE = "MESSAGE";
45 | private static final String KEY_INPUT = "INPUT";
46 | private static final String KEY_HINT = "INPUT";
47 | private TextInputDialogListener listener;
48 | private String message;
49 | private String hint;
50 | private int action;
51 | private EditText inputField;
52 |
53 |
54 | public interface TextInputDialogListener {
55 | void onInputComplete(String input, int action);
56 | }
57 |
58 | @Override
59 | public void onAttach(Context ctx) {
60 | super.onAttach(ctx);
61 | Fragment owner = getParentFragment();
62 | if (ctx instanceof TextInputDialogListener) {
63 | listener = (TextInputDialogListener) ctx;
64 | } else if (owner instanceof TextInputDialogListener) {
65 | listener = (TextInputDialogListener) owner;
66 | } else {
67 | Log.e(TAG, "Dialog not attached");
68 | }
69 | }
70 |
71 | @Nullable
72 | @Override
73 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
74 | super.onCreateView(inflater, container, savedInstanceState);
75 |
76 | String inputText = "";
77 | if (savedInstanceState != null) {
78 | message = savedInstanceState.getString(KEY_MESSAGE);
79 | hint = savedInstanceState.getString(KEY_HINT);
80 | inputText = savedInstanceState.getString(KEY_INPUT);
81 | }
82 |
83 | View dialogRoot = inflater.inflate(R.layout.dialog_text_input, container, false);
84 | TextView label = dialogRoot.findViewById(R.id.dialog_label);
85 | Button cancelButton = dialogRoot.findViewById(R.id.button_dialog_cancel);
86 | Button okButton = dialogRoot.findViewById(R.id.button_dialog_ok);
87 | label.setText(message);
88 |
89 | inputField = dialogRoot.findViewById(R.id.dialog_text_field);
90 | inputField.setHint(hint);
91 | inputField.setText(inputText);
92 | inputField.requestFocus();
93 |
94 | inputField.setOnEditorActionListener(new TextView.OnEditorActionListener() {
95 | @Override
96 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
97 | onInputComplete();
98 | return true;
99 | }
100 | });
101 |
102 | cancelButton.setOnClickListener(new View.OnClickListener() {
103 | @Override
104 | public void onClick(View v) {
105 | dismiss();
106 | }
107 | });
108 |
109 | okButton.setOnClickListener(new View.OnClickListener() {
110 | @Override
111 | public void onClick(View v) {
112 | onInputComplete();
113 | }
114 | });
115 |
116 | return dialogRoot;
117 | }
118 |
119 | private void onInputComplete() {
120 | String input = inputField.getText().toString();
121 | if (onValidateInput(input)) {
122 | listener.onInputComplete(input, action);
123 | dismiss();
124 | }
125 | }
126 |
127 | public boolean onValidateInput(String input) {
128 | return true;
129 | }
130 |
131 | @Override
132 | public void onSaveInstanceState(Bundle outState) {
133 | super.onSaveInstanceState(outState);
134 | outState.putString(KEY_MESSAGE, message);
135 | outState.putString(KEY_HINT, hint);
136 | outState.putString(KEY_INPUT, inputField.getText().toString());
137 | }
138 |
139 | public static class Builder {
140 | private final FragmentActivity activity;
141 | private TextInputDialog dialog;
142 | private FragmentManager fragmentManager;
143 |
144 | public Builder(FragmentActivity activity, Class extends TextInputDialog> clazz) {
145 | this.activity = activity;
146 | try {
147 | this.dialog = clazz.newInstance();
148 | } catch (java.lang.InstantiationException | IllegalAccessException e) {
149 | throw new IllegalStateException("Cannot start dialog" + clazz.getSimpleName());
150 | }
151 | }
152 |
153 | public Builder setMessage(String message) {
154 | dialog.message = message;
155 | return this;
156 | }
157 |
158 | public Builder setMessage(@StringRes int messageID) {
159 | return setMessage(activity.getString(messageID));
160 | }
161 |
162 | public Builder setHint(String hint) {
163 | dialog.hint = hint;
164 | return this;
165 | }
166 |
167 | public Builder setHint(@StringRes int hintID) {
168 | return setHint(activity.getString(hintID));
169 | }
170 |
171 | public Builder setAction(int action) {
172 | dialog.action = action;
173 | return this;
174 | }
175 |
176 | public Builder setFragmentManager(FragmentManager manager) {
177 | fragmentManager = manager;
178 | return this;
179 | }
180 |
181 | public Builder setTargetFragment(Fragment fragment, int requestCode) {
182 | dialog.setTargetFragment(fragment, requestCode);
183 | return this;
184 | }
185 |
186 | public void show() {
187 | if (fragmentManager == null) {
188 | fragmentManager = activity.getSupportFragmentManager();
189 | }
190 | dialog.show(fragmentManager, TAG);
191 | }
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/shoppinglist/DirectoryStatus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.shoppinglist;
21 |
22 | import android.content.Context;
23 | import android.content.SharedPreferences;
24 | import android.preference.PreferenceManager;
25 |
26 | import com.woefe.shoppinglist.activity.SettingsFragment;
27 |
28 | import java.io.File;
29 |
30 | /**
31 | * @author Wolfgang Popp
32 | */
33 | class DirectoryStatus {
34 | public enum Status {IS_OK, NOT_A_DIRECTORY, CANNOT_WRITE}
35 |
36 | private static final String DEFAULT_DIRECTORY = "ShoppingLists";
37 | private Status reason;
38 | private String directory;
39 |
40 | public DirectoryStatus(Context ctx) {
41 | //ctx = ctx.getApplicationContext();
42 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctx);
43 | String directory = sharedPreferences.getString(SettingsFragment.KEY_DIRECTORY_LOCATION, "").trim();
44 | String defaultDir = ctx.getFileStreamPath(DEFAULT_DIRECTORY).getAbsolutePath();
45 | File file = new File(directory);
46 |
47 | if (directory.equals("")) {
48 | init(Status.IS_OK, defaultDir);
49 | } else if (!file.isDirectory()) {
50 | init(Status.NOT_A_DIRECTORY, defaultDir);
51 | } else if (!file.canWrite()) {
52 | init(Status.CANNOT_WRITE, defaultDir);
53 | } else {
54 | init(Status.IS_OK, directory);
55 | }
56 | }
57 |
58 | private void init(Status reason, String directory) {
59 | this.reason = reason;
60 | this.directory = directory;
61 | new File(directory).mkdirs();
62 | }
63 |
64 | public boolean isFallback() {
65 | return reason != Status.IS_OK;
66 | }
67 |
68 | public String getDirectory() {
69 | return directory;
70 | }
71 |
72 | public Status getReason() {
73 | return reason;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/shoppinglist/ListItem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.shoppinglist;
21 |
22 | /**
23 | * @author Wolfgang Popp.
24 | */
25 | public class ListItem {
26 | private boolean isChecked;
27 | private String description;
28 | private String quantity;
29 |
30 | public ListItem(boolean isChecked, String description, String quantity) {
31 | this.isChecked = isChecked;
32 | this.description = description;
33 | this.quantity = quantity;
34 | }
35 |
36 | public boolean isChecked() {
37 | return isChecked;
38 | }
39 |
40 | public void setChecked(boolean isChecked) {
41 | this.isChecked = isChecked;
42 | }
43 |
44 | public String getDescription() {
45 | return description;
46 | }
47 |
48 | public void setDescription(String description) {
49 | this.description = description;
50 | }
51 |
52 | public String getQuantity() {
53 | return quantity;
54 | }
55 |
56 | public void setQuantity(String quantity) {
57 | this.quantity = quantity;
58 | }
59 |
60 | static class ListItemWithID extends ListItem {
61 | private final int id;
62 |
63 | public ListItemWithID(int id, ListItem item) {
64 | super(item.isChecked, item.description, item.quantity);
65 | this.id = id;
66 | }
67 |
68 | public int getId() {
69 | return id;
70 | }
71 |
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/shoppinglist/ListsChangeListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.shoppinglist;
21 |
22 | /**
23 | * @author Wolfgang Popp
24 | */
25 | public interface ListsChangeListener {
26 | void onListsChanged();
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/shoppinglist/ShoppingList.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.shoppinglist;
21 |
22 | import android.os.Build;
23 | import android.support.annotation.NonNull;
24 |
25 | import java.util.ArrayList;
26 | import java.util.Arrays;
27 | import java.util.Collection;
28 | import java.util.Comparator;
29 | import java.util.HashSet;
30 | import java.util.Iterator;
31 | import java.util.LinkedList;
32 | import java.util.List;
33 | import java.util.ListIterator;
34 | import java.util.Set;
35 | import java.util.function.Predicate;
36 | import java.util.function.UnaryOperator;
37 |
38 | public class ShoppingList extends ArrayList {
39 |
40 | private String name;
41 | private static int currentID;
42 | private final List listeners = new LinkedList<>();
43 |
44 | public ShoppingList(String name) {
45 | super();
46 | this.name = name;
47 | }
48 |
49 | public ShoppingList(String name, Collection collection) {
50 | super(collection);
51 | this.name = name;
52 | }
53 |
54 | public String getName() {
55 | return name;
56 | }
57 |
58 | public void setName(String name) {
59 | this.name = name;
60 | notifyListChanged(Event.newOther());
61 | }
62 |
63 | public int getId(int index) {
64 | return ((ListItem.ListItemWithID) get(index)).getId();
65 | }
66 |
67 | @Override
68 | public boolean add(ListItem item) {
69 | boolean res = super.add(new ListItem.ListItemWithID(generateID(), item));
70 | notifyListChanged(Event.newItemInserted(size() - 1));
71 | return res;
72 | }
73 |
74 | public boolean add(String description, String quantity) {
75 | return add(new ListItem(false, description, quantity));
76 | }
77 |
78 | @Override
79 | public void add(int index, ListItem element) {
80 | super.add(index, element);
81 | notifyListChanged(Event.newItemInserted(index));
82 | }
83 |
84 | @Override
85 | public boolean addAll(Collection extends ListItem> c) {
86 | boolean b = super.addAll(c);
87 | notifyListChanged(Event.newOther());
88 | return b;
89 | }
90 |
91 | @Override
92 | public boolean addAll(int index, Collection extends ListItem> c) {
93 | boolean b = super.addAll(index, c);
94 | notifyListChanged(Event.newOther());
95 | return b;
96 | }
97 |
98 | @Override
99 | public ListItem set(int index, ListItem element) {
100 | ListItem old = super.set(index, element);
101 | notifyListChanged(Event.newItemChanged(index));
102 | return old;
103 | }
104 |
105 | @Override
106 | public ListItem remove(int index) {
107 | ListItem res = super.remove(index);
108 | notifyListChanged(Event.newItemRemoved(index));
109 | return res;
110 | }
111 |
112 | @Override
113 | public boolean remove(Object o) {
114 | boolean b = super.remove(o);
115 | if (b) {
116 | notifyListChanged(Event.newOther());
117 | }
118 | return b;
119 | }
120 |
121 | @Override
122 | protected void removeRange(int fromIndex, int toIndex) {
123 | super.removeRange(fromIndex, toIndex);
124 | notifyListChanged(Event.newOther());
125 | }
126 |
127 | @Override
128 | public boolean removeAll(Collection> c) {
129 | boolean b = super.removeAll(c);
130 | if (b) {
131 | notifyListChanged(Event.newOther());
132 | }
133 | return b;
134 | }
135 |
136 | @Override
137 | public boolean removeIf(Predicate super ListItem> filter) {
138 | boolean b = false;
139 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
140 | b = super.removeIf(filter);
141 | }
142 | if (b) {
143 | notifyListChanged(Event.newOther());
144 | }
145 | return b;
146 | }
147 |
148 | @Override
149 | public void replaceAll(UnaryOperator operator) {
150 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
151 | super.replaceAll(operator);
152 | notifyListChanged(Event.newOther());
153 | }
154 | }
155 |
156 | @Override
157 | public boolean retainAll(Collection> c) {
158 | boolean b = super.retainAll(c);
159 | if (b) {
160 | notifyListChanged(Event.newOther());
161 | }
162 | return b;
163 | }
164 |
165 | @Override
166 | public void clear() {
167 | super.clear();
168 | notifyListChanged(Event.newOther());
169 | }
170 |
171 | @NonNull
172 | @Override
173 | public Iterator iterator() {
174 | return new Itr(super.iterator());
175 | }
176 |
177 | @NonNull
178 | @Override
179 | public ListIterator listIterator(int index) {
180 | return new ListItr(super.listIterator(index));
181 | }
182 |
183 | @NonNull
184 | @Override
185 | public ListIterator listIterator() {
186 | return new ListItr(super.listIterator());
187 | }
188 |
189 | @Override
190 | public void sort(Comparator super ListItem> c) {
191 | ListItem[] items = toArray(new ListItem[size()]);
192 | Arrays.sort(items, c);
193 | super.clear();
194 | super.addAll(Arrays.asList(items));
195 | notifyListChanged(Event.newOther());
196 | }
197 |
198 | public void setChecked(int index, boolean isChecked) {
199 | get(index).setChecked(isChecked);
200 | notifyListChanged(Event.newItemChanged(index));
201 | }
202 |
203 | public void toggleChecked(int index) {
204 | setChecked(index, !get(index).isChecked());
205 | }
206 |
207 | public void move(int oldIndex, int newIndex) {
208 | super.add(newIndex, super.remove(oldIndex));
209 | notifyListChanged(Event.newItemMoved(oldIndex, newIndex));
210 | }
211 |
212 | public void editItem(int index, String newDescription, String newQuantity) {
213 | ListItem listItem = get(index);
214 | listItem.setDescription(newDescription);
215 | listItem.setQuantity(newQuantity);
216 | notifyListChanged(Event.newItemChanged(index));
217 | }
218 |
219 |
220 | public void removeAllCheckedItems() {
221 | Iterator it = iterator();
222 |
223 | while (it.hasNext()) {
224 | ListItem item = it.next();
225 | if (item.isChecked()) {
226 | it.remove();
227 | }
228 | }
229 | notifyListChanged(Event.newOther());
230 | }
231 |
232 | public Set createDescriptionIndex() {
233 | Set descriptionIndex = new HashSet<>();
234 | for (ListItem listItem : this) {
235 | descriptionIndex.add(listItem.getDescription().toLowerCase());
236 | }
237 | return descriptionIndex;
238 | }
239 |
240 | public void addListener(ShoppingListListener listener) {
241 | listeners.add(listener);
242 | }
243 |
244 | public void removeListener(ShoppingListListener listener) {
245 | listeners.remove(listener);
246 | }
247 |
248 | private synchronized int generateID() {
249 | return ++currentID;
250 | }
251 |
252 | private void notifyListChanged(ShoppingList.Event event) {
253 | for (ShoppingListListener listener : listeners) {
254 | listener.onShoppingListUpdate(this, event);
255 | }
256 | }
257 |
258 | public interface ShoppingListListener {
259 | void onShoppingListUpdate(ShoppingList list, ShoppingList.Event event);
260 | }
261 |
262 | public static class Event {
263 | public static final int ITEM_CHANGED = 0b1;
264 | public static final int ITEM_REMOVED = 0b10;
265 | public static final int ITEM_MOVED = 0b100;
266 | public static final int ITEM_INSERTED = 0b1000;
267 | public static final int OTHER = 0xffffffff;
268 |
269 | private final int state;
270 | private int index = -1;
271 | private int oldIndex = -1;
272 | private int newIndex = -1;
273 |
274 | public Event(int state) {
275 | this.state = state;
276 | }
277 |
278 | static Event newOther() {
279 | return new Event(OTHER);
280 | }
281 |
282 | static Event newItemMoved(int oldIndex, int newIndex) {
283 | Event e = new Event(ITEM_MOVED);
284 | e.oldIndex = oldIndex;
285 | e.newIndex = newIndex;
286 | return e;
287 | }
288 |
289 | static Event newItemChanged(int index) {
290 | Event e = new Event(ITEM_CHANGED);
291 | e.index = index;
292 | return e;
293 | }
294 |
295 | static Event newItemRemoved(int index) {
296 | Event e = new Event(ITEM_REMOVED);
297 | e.index = index;
298 | e.oldIndex = index;
299 | return e;
300 | }
301 |
302 | static Event newItemInserted(int index) {
303 | Event e = new Event(ITEM_INSERTED);
304 | e.index = index;
305 | e.newIndex = index;
306 | return e;
307 | }
308 |
309 | public int getState() {
310 | return state;
311 | }
312 |
313 | public int getIndex() {
314 | return index;
315 | }
316 |
317 | public int getOldIndex() {
318 | return oldIndex;
319 | }
320 |
321 | public int getNewIndex() {
322 | return newIndex;
323 | }
324 |
325 | }
326 |
327 | private class Itr implements Iterator {
328 |
329 | private Iterator iterator;
330 |
331 | private Itr(Iterator iterator) {
332 | this.iterator = iterator;
333 | }
334 |
335 | @Override
336 | public boolean hasNext() {
337 | return iterator.hasNext();
338 | }
339 |
340 | @Override
341 | public ListItem next() {
342 | return iterator.next();
343 | }
344 |
345 | @Override
346 | public void remove() {
347 | iterator.remove();
348 | //TODO get index and use Event.newItemRemoved
349 | notifyListChanged(Event.newOther());
350 | }
351 | }
352 |
353 | private class ListItr extends Itr implements ListIterator {
354 |
355 | private ListIterator iterator;
356 |
357 | private ListItr(ListIterator iterator) {
358 | super(iterator);
359 | this.iterator = iterator;
360 | }
361 |
362 | @Override
363 | public boolean hasPrevious() {
364 | return iterator.hasPrevious();
365 | }
366 |
367 | @Override
368 | public ListItem previous() {
369 | return iterator.previous();
370 | }
371 |
372 | @Override
373 | public int nextIndex() {
374 | return iterator.nextIndex();
375 | }
376 |
377 | @Override
378 | public int previousIndex() {
379 | return iterator.previousIndex();
380 | }
381 |
382 | @Override
383 | public void set(ListItem listItem) {
384 | iterator.set(listItem);
385 | //TODO get index and use Event.newItemChanged
386 | notifyListChanged(Event.newOther());
387 | }
388 |
389 | @Override
390 | public void add(ListItem listItem) {
391 | iterator.add(listItem);
392 | //TODO get index and use Event.newItemInserted
393 | notifyListChanged(Event.newOther());
394 | }
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/shoppinglist/ShoppingListException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.shoppinglist;
21 |
22 | public class ShoppingListException extends Exception {
23 | public ShoppingListException(String message) {
24 | super(message);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/shoppinglist/ShoppingListMarshaller.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.shoppinglist;
21 |
22 | import android.support.annotation.NonNull;
23 |
24 | import java.io.BufferedWriter;
25 | import java.io.IOException;
26 | import java.io.OutputStream;
27 | import java.io.OutputStreamWriter;
28 |
29 | public class ShoppingListMarshaller {
30 | public static void marshall(@NonNull OutputStream stream, @NonNull ShoppingList list) throws IOException {
31 |
32 | try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream))) {
33 | writer.write("[ ");
34 | writer.write(list.getName());
35 | writer.write(" ]\n\n");
36 |
37 | for (ListItem item : list) {
38 | String quantity = item.getQuantity();
39 | String description = item.getDescription();
40 |
41 | if (item.isChecked()) {
42 | writer.write("// ");
43 | }
44 |
45 | if (description != null) {
46 | writer.write(description);
47 | }
48 |
49 | if (quantity != null && !quantity.equals("")) {
50 | writer.write(" #");
51 | writer.write(quantity);
52 | }
53 |
54 | writer.write("\n");
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/shoppinglist/ShoppingListService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.shoppinglist;
21 |
22 | import android.app.Service;
23 | import android.content.Intent;
24 | import android.content.SharedPreferences;
25 | import android.os.Binder;
26 | import android.os.IBinder;
27 | import android.preference.PreferenceManager;
28 | import android.support.annotation.Nullable;
29 | import android.util.Log;
30 |
31 | import java.util.Arrays;
32 | import java.util.Comparator;
33 |
34 | /**
35 | * @author Wolfgang Popp.
36 | */
37 | public class ShoppingListService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
38 | private static final String TAG = ShoppingListService.class.getSimpleName();
39 |
40 |
41 | private ShoppingListsManager manager = null;
42 | private final IBinder binder = new ShoppingListBinder();
43 | private SharedPreferences sharedPreferences;
44 | private DirectoryStatus directoryStatus;
45 |
46 | private static final Comparator ignoreCaseComperator = new Comparator() {
47 | @Override
48 | public int compare(String o1, String o2) {
49 | return o1.compareToIgnoreCase(o2);
50 | }
51 | };
52 |
53 | @Nullable
54 | @Override
55 | public IBinder onBind(Intent intent) {
56 | Log.v(TAG, "onBind() called: " + intent.toString());
57 | sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
58 | sharedPreferences.registerOnSharedPreferenceChangeListener(this);
59 |
60 | manager = new ShoppingListsManager();
61 | directoryStatus = new DirectoryStatus(this);
62 | manager.onStart(directoryStatus.getDirectory());
63 |
64 | return binder;
65 | }
66 |
67 | @Override
68 | public boolean onUnbind(Intent intent) {
69 | Log.v(TAG, "onUnbind() called: " + intent.toString());
70 | sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
71 | manager.onStop();
72 | return false;
73 | }
74 |
75 | @Override
76 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
77 | manager.onStop();
78 | directoryStatus = new DirectoryStatus(this);
79 | manager.onStart(directoryStatus.getDirectory());
80 | }
81 |
82 | @Override
83 | public int onStartCommand(Intent intent, int flags, int startId) {
84 | Log.v(TAG, "onStartCommand() called");
85 | return START_NOT_STICKY;
86 | }
87 |
88 | public class ShoppingListBinder extends Binder {
89 |
90 | public boolean usesFallbackDir() {
91 | return directoryStatus.isFallback();
92 | }
93 |
94 | public void addList(String listName) throws ShoppingListException {
95 | manager.addList(listName);
96 | }
97 |
98 | public boolean removeList(String listName) {
99 | return manager.removeList(listName);
100 | }
101 |
102 | @Nullable
103 | public ShoppingList getList(String listName) {
104 | return manager.getList(listName);
105 | }
106 |
107 | public boolean hasList(String listName) {
108 | return manager.hasList(listName);
109 | }
110 |
111 | public String[] getListNames() {
112 | String[] names = manager.getListNames().toArray(new String[manager.size()]);
113 | Arrays.sort(names, ignoreCaseComperator);
114 | return names;
115 | }
116 |
117 | public int indexOf(String listName) {
118 | if (listName == null) {
119 | return -1;
120 | }
121 | return Arrays.binarySearch(getListNames(), listName, ignoreCaseComperator);
122 | }
123 |
124 | public int size() {
125 | return manager.size();
126 | }
127 |
128 | public void addListChangeListener(ListsChangeListener listener) {
129 | manager.setListChangeListener(listener);
130 | }
131 |
132 | public void removeListChangeListener(ListsChangeListener listener) {
133 | manager.removeListChangeListenerListener(listener);
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/shoppinglist/ShoppingListUnmarshaller.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.shoppinglist;
21 |
22 | import java.io.BufferedReader;
23 | import java.io.FileInputStream;
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 | import java.io.InputStreamReader;
27 | import java.util.regex.Matcher;
28 | import java.util.regex.Pattern;
29 |
30 | public class ShoppingListUnmarshaller {
31 | private static final Pattern EMPTY_LINE = Pattern.compile("^\\s*$");
32 | private static final Pattern HEADER = Pattern.compile("\\[(.*)]");
33 |
34 | public static ShoppingList unmarshal(String filename) throws IOException, UnmarshallException {
35 | return unmarshal(new FileInputStream(filename));
36 | }
37 |
38 | public static ShoppingList unmarshal(InputStream inputStream) throws IOException, UnmarshallException {
39 | ShoppingList shoppingList;
40 | String name = null;
41 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
42 | String firstLine = reader.readLine();
43 |
44 | if (firstLine != null) {
45 | Matcher matcher = HEADER.matcher(firstLine);
46 | if (matcher.matches()) {
47 | name = matcher.group(1).trim();
48 | }
49 | }
50 |
51 | if (name == null) {
52 | throw new UnmarshallException("Could not find the name of the list");
53 | }
54 |
55 | shoppingList = new ShoppingList(name);
56 |
57 | String line;
58 | while ((line = reader.readLine()) != null) {
59 | if (!EMPTY_LINE.matcher(line).matches()) {
60 | shoppingList.add(createListItem(line));
61 | }
62 | }
63 |
64 | return shoppingList;
65 | }
66 |
67 | private static ListItem createListItem(String item) {
68 | boolean isChecked = item.startsWith("//");
69 | int index;
70 | String quantity;
71 | String name;
72 |
73 | if (isChecked) {
74 | item = item.substring(2);
75 | }
76 |
77 | index = item.lastIndexOf("#");
78 |
79 | if (index != -1) {
80 | quantity = item.substring(index + 1).trim();
81 | name = item.substring(0, index).trim();
82 | } else {
83 | quantity = "";
84 | name = item.trim();
85 | }
86 |
87 | return new ListItem(isChecked, name.trim(), quantity);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/shoppinglist/ShoppingListsManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.shoppinglist;
21 |
22 | import android.os.FileObserver;
23 | import android.os.SystemClock;
24 | import android.support.annotation.Nullable;
25 | import android.util.Log;
26 |
27 | import java.io.File;
28 | import java.io.FileOutputStream;
29 | import java.io.IOException;
30 | import java.io.OutputStream;
31 | import java.net.URLEncoder;
32 | import java.util.Collection;
33 | import java.util.HashMap;
34 | import java.util.LinkedList;
35 | import java.util.List;
36 | import java.util.Map;
37 | import java.util.Set;
38 |
39 | /**
40 | * @author Wolfgang Popp.
41 | */
42 | class ShoppingListsManager {
43 | private static final String TAG = ShoppingListsManager.class.getSimpleName();
44 | private static final String FILE_ENDING = ".lst";
45 |
46 | private final Map trashcan = new HashMap<>();
47 | private final MetadataContainer shoppingListsMetadata = new MetadataContainer();
48 | private final List listeners = new LinkedList<>();
49 | private FileObserver directoryObserver;
50 | private String directory;
51 |
52 | ShoppingListsManager() {
53 | }
54 |
55 | void setListChangeListener(ListsChangeListener listener) {
56 | this.listeners.add(listener);
57 | }
58 |
59 | void removeListChangeListenerListener(ListsChangeListener listener) {
60 | this.listeners.remove(listener);
61 | }
62 |
63 | void onStart(final String directory) {
64 | this.directory = directory;
65 | directoryObserver = new FileObserver(directory) {
66 | @Override
67 | public void onEvent(int event, @Nullable String path) {
68 | if (path == null) {
69 | return;
70 | }
71 | File file = new File(directory, path);
72 | switch (event) {
73 | case FileObserver.DELETE:
74 | shoppingListsMetadata.removeByFile(file.getPath());
75 | break;
76 | case FileObserver.CREATE:
77 | // workaround: When CREATE is triggered, the file might still be empty.
78 | SystemClock.sleep(100);
79 | loadFromFile(file);
80 | break;
81 | }
82 | }
83 | };
84 |
85 | Log.d(getClass().getSimpleName(), "Initializing from dir " + directory);
86 | maybeAddInitialList();
87 | loadFromDirectory(directory);
88 | directoryObserver.startWatching();
89 | }
90 |
91 | void onStop() {
92 | listeners.clear();
93 | directoryObserver.stopWatching();
94 | directoryObserver = null;
95 |
96 | for (ShoppingListMetadata metadata : trashcan.values()) {
97 | metadata.observer.stopWatching();
98 | }
99 | for (ShoppingListMetadata metadata : shoppingListsMetadata.values()) {
100 | metadata.observer.stopWatching();
101 | }
102 |
103 | try {
104 | writeAllUnsavedChanges();
105 | } catch (IOException e) {
106 | Log.v(getClass().getSimpleName(), "Writing of changes failed", e);
107 | }
108 |
109 | shoppingListsMetadata.clear();
110 | trashcan.clear();
111 | }
112 |
113 | private void maybeAddInitialList() {
114 | boolean foundFile = false;
115 |
116 | for (File file : new File(directory).listFiles()) {
117 | foundFile = foundFile || file.isFile();
118 | }
119 |
120 | if (!foundFile) {
121 | try {
122 | addList("Shopping List");
123 | } catch (ShoppingListException e) {
124 | Log.e(getClass().getSimpleName(), "Failed to add initial list", e);
125 | }
126 | }
127 | }
128 |
129 | private void loadFromFile(File file) {
130 | try {
131 | final ShoppingList list = ShoppingListUnmarshaller.unmarshal(file.getPath());
132 | addShoppingList(list, file.getPath());
133 | Log.v(TAG, "Successfully loaded file: " + file);
134 | } catch (IOException | UnmarshallException e) {
135 | Log.v(getClass().getSimpleName(), "Ignoring file " + file);
136 | }
137 | }
138 |
139 | private void loadFromDirectory(String directory) {
140 | File d = new File(directory);
141 | for (File file : d.listFiles()) {
142 | if (file.isFile()) {
143 | loadFromFile(file);
144 | }
145 | }
146 | }
147 |
148 | private ShoppingListMetadata addShoppingList(ShoppingList list, String filename) {
149 | final ShoppingListMetadata metadata = new ShoppingListMetadata(list, filename);
150 | list.addListener(new ShoppingList.ShoppingListListener() {
151 | @Override
152 | public void onShoppingListUpdate(ShoppingList list, ShoppingList.Event e) {
153 | metadata.isDirty = true;
154 | }
155 | });
156 | setupObserver(metadata);
157 | shoppingListsMetadata.add(metadata);
158 | return metadata;
159 | }
160 |
161 | private void setupObserver(final ShoppingListMetadata metadata) {
162 | FileObserver fileObserver = new FileObserver(metadata.filename) {
163 | @Override
164 | public void onEvent(int event, String path) {
165 | switch (event) {
166 | case FileObserver.CLOSE_WRITE:
167 | try {
168 | ShoppingList list = ShoppingListUnmarshaller.unmarshal(metadata.filename);
169 | metadata.shoppingList.clear();
170 | metadata.shoppingList.addAll(list);
171 | metadata.isDirty = false;
172 |
173 | String oldName = metadata.shoppingList.getName();
174 | rename(oldName, list.getName());
175 | } catch (IOException | UnmarshallException e) {
176 | Log.e(TAG, "FileObserver could not read file.", e);
177 | }
178 | break;
179 | }
180 | }
181 | };
182 | fileObserver.startWatching();
183 | metadata.observer = fileObserver;
184 | }
185 |
186 | private void writeAllUnsavedChanges() throws IOException {
187 | // first empty trashcan and then write lists. This makes sure that a list that has been
188 | // removed and was later re-added is not actually deleted.
189 | for (ShoppingListMetadata metadata : trashcan.values()) {
190 | new File(metadata.filename).delete();
191 | }
192 |
193 | for (ShoppingListMetadata metadata : shoppingListsMetadata.values()) {
194 | if (metadata.isDirty) {
195 | OutputStream os = new FileOutputStream(metadata.filename);
196 | ShoppingListMarshaller.marshall(os, metadata.shoppingList);
197 | Log.d(getClass().getSimpleName(), "Wrote file " + metadata.filename);
198 | }
199 | }
200 | }
201 |
202 | void addList(String name) throws ShoppingListException {
203 | if (hasList(name)) {
204 | throw new ShoppingListException("List already exists");
205 | }
206 |
207 | String filename = new File(this.directory, URLEncoder.encode(name) + FILE_ENDING).getPath();
208 | ShoppingListMetadata metadata = addShoppingList(new ShoppingList(name), filename);
209 | metadata.isDirty = true;
210 | }
211 |
212 | boolean removeList(String name) {
213 | if (hasList(name)) {
214 | ShoppingListMetadata toRemove = shoppingListsMetadata.removeByName(name);
215 | trashcan.put(toRemove.shoppingList.getName(), toRemove);
216 | return true;
217 | }
218 | return false;
219 | }
220 |
221 | @Nullable
222 | ShoppingList getList(String name) {
223 | ShoppingListMetadata metadata = shoppingListsMetadata.getByName(name);
224 | if (metadata != null) {
225 | return metadata.shoppingList;
226 | }
227 | return null;
228 | }
229 |
230 | Set getListNames() {
231 | return shoppingListsMetadata.getListNames();
232 | }
233 |
234 | int size() {
235 | return shoppingListsMetadata.size();
236 | }
237 |
238 | boolean hasList(String name) {
239 | return shoppingListsMetadata.hasName(name);
240 | }
241 |
242 | void rename(String oldName, String newName) {
243 | if (!oldName.equals(newName)) {
244 | ShoppingListMetadata metadata = shoppingListsMetadata.removeByName(oldName);
245 | metadata.shoppingList.setName(newName);
246 | shoppingListsMetadata.add(metadata);
247 | }
248 | }
249 |
250 | private class ShoppingListMetadata {
251 | private final ShoppingList shoppingList;
252 | private final String filename;
253 | private boolean isDirty;
254 | private FileObserver observer;
255 |
256 | private ShoppingListMetadata(ShoppingList shoppingList, String filename) {
257 | this.shoppingList = shoppingList;
258 | this.filename = filename;
259 | this.isDirty = false;
260 | }
261 | }
262 |
263 | private class MetadataContainer {
264 | private Map byName = new HashMap<>();
265 | private Map filenameResolver = new HashMap<>();
266 |
267 | private void add(ShoppingListMetadata metadata) {
268 | String name = metadata.shoppingList.getName();
269 | byName.put(name, metadata);
270 | filenameResolver.put(metadata.filename, name);
271 | notifyListeners();
272 | }
273 |
274 | private void clear() {
275 | filenameResolver.clear();
276 | byName.clear();
277 | notifyListeners();
278 | }
279 |
280 | private ShoppingListMetadata removeByName(String name) {
281 | ShoppingListMetadata toRemove = byName.remove(name);
282 | filenameResolver.remove(toRemove.filename);
283 | notifyListeners();
284 | return toRemove;
285 | }
286 |
287 | private ShoppingListMetadata removeByFile(String filename) {
288 | ShoppingListMetadata toRemove = byName.remove(filenameResolver.remove(filename));
289 | notifyListeners();
290 | return toRemove;
291 | }
292 |
293 | @Nullable
294 | private ShoppingListMetadata getByName(String name) {
295 | return byName.get(name);
296 | }
297 |
298 | private ShoppingListMetadata getByFile(String filename) {
299 | return getByName(filenameResolver.get(filename));
300 | }
301 |
302 | private boolean hasName(String name) {
303 | return byName.containsKey(name);
304 | }
305 |
306 | private Collection values() {
307 | return byName.values();
308 | }
309 |
310 | private Set getListNames() {
311 | return byName.keySet();
312 | }
313 |
314 | private int size() {
315 | return byName.size();
316 | }
317 |
318 | private void notifyListeners() {
319 | for (ListsChangeListener listener : listeners) {
320 | listener.onListsChanged();
321 | }
322 | }
323 | }
324 | }
325 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woefe/shoppinglist/shoppinglist/UnmarshallException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * ShoppingList - A simple shopping list for Android
3 | *
4 | * Copyright (C) 2018. Wolfgang Popp
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.woefe.shoppinglist.shoppinglist;
21 |
22 | /**
23 | * @author Wolfgang Popp
24 | */
25 |
26 | class UnmarshallException extends Exception {
27 | public UnmarshallException(String s) {
28 | super(s);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_done_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_add_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-hdpi/ic_add_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_delete_forever_white_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-hdpi/ic_delete_forever_white_24.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_delete_sweep_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-hdpi/ic_delete_sweep_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_done_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-hdpi/ic_done_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_menu_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-hdpi/ic_menu_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_playlist_add_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-hdpi/ic_playlist_add_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_sd_storage_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-hdpi/ic_sd_storage_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_sort_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-hdpi/ic_sort_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_swap_vert_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-hdpi/ic_swap_vert_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_add_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-mdpi/ic_add_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_delete_forever_white_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-mdpi/ic_delete_forever_white_24.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_delete_sweep_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-mdpi/ic_delete_sweep_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_done_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-mdpi/ic_done_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_menu_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-mdpi/ic_menu_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_playlist_add_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-mdpi/ic_playlist_add_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_sd_storage_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-mdpi/ic_sd_storage_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_sort_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-mdpi/ic_sort_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_swap_vert_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-mdpi/ic_swap_vert_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_add_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xhdpi/ic_add_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_delete_forever_white_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xhdpi/ic_delete_forever_white_24.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_delete_sweep_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xhdpi/ic_delete_sweep_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_done_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xhdpi/ic_done_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_menu_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xhdpi/ic_menu_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_playlist_add_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xhdpi/ic_playlist_add_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_sd_storage_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xhdpi/ic_sd_storage_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_swap_vert_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xhdpi/ic_swap_vert_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_add_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxhdpi/ic_add_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_delete_forever_white_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxhdpi/ic_delete_forever_white_24.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_delete_sweep_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxhdpi/ic_delete_sweep_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_menu_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxhdpi/ic_menu_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_playlist_add_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxhdpi/ic_playlist_add_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_sd_storage_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxhdpi/ic_sd_storage_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_swap_vert_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxhdpi/ic_swap_vert_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_add_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxxhdpi/ic_add_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_delete_forever_white_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxxhdpi/ic_delete_forever_white_24.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_delete_sweep_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxxhdpi/ic_delete_sweep_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_done_black_36dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxxhdpi/ic_done_black_36dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_menu_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxxhdpi/ic_menu_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_playlist_add_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxxhdpi/ic_playlist_add_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_sd_storage_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxxhdpi/ic_sd_storage_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_sort_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxxhdpi/ic_sort_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_swap_vert_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/drawable-xxxhdpi/ic_swap_vert_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/button_add_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/list_activated_background.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_about.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
24 |
25 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
24 |
25 |
26 |
28 |
32 |
33 |
34 |
35 |
39 |
40 |
41 |
47 |
48 |
54 |
55 |
67 |
68 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_directory_chooser.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
25 |
26 |
32 |
33 |
40 |
41 |
45 |
46 |
52 |
53 |
64 |
65 |
72 |
73 |
80 |
81 |
88 |
89 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_text_input.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
24 |
25 |
32 |
33 |
40 |
41 |
50 |
51 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/drawer_list_item.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_invalid.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
24 |
25 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_shoppinglist.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
26 |
27 |
34 |
35 |
46 |
47 |
56 |
57 |
63 |
64 |
71 |
72 |
79 |
80 |
81 |
96 |
97 |
103 |
104 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
27 |
28 |
37 |
38 |
48 |
49 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar_main.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
69 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | Einkaufsliste
22 | Einstellungen
23 | Menge
24 | Neuer Eintrag
25 | Löschen
26 | Bearbeiten
27 | Abgehakte Einträge löschen
28 | Abbrechen
29 | Dateipfad
30 | OK
31 | Alle abgehakten Eintäge löschen?
32 | Name des Eintrags darf nicht leer sein!
33 | Navigationsleiste öffnen
34 | Navigationsleiste schließen
35 | Neue Liste erstellen
36 | Liste löschen
37 | Liste <b>%1$s</b> löschen?
38 | Neue Liste hinzufügen
39 | Name der neuen Liste
40 | Fertig
41 | Tauschen
42 | Es sind keine Listen vorhanden.
43 | Listen
44 | Über
45 | Der Listenname darf nicht leer sein!
46 | Diese Liste existiert bereits
47 | Teilen
48 | Zugriff auf das festgelegte Datenverzeichnis nicht möglich!
49 | Sortieren
50 | A-Z
51 | Z-A
52 | \'%s\' kann nicht erstellt werden
53 | Dateiname enthält ungültiges Zeichen \'/\'
54 | Zugriff auf dieses Verzeichnis wurde verweigert!
55 | Neues Verzeichnis erstellen
56 | Name des neuen Verzeichnisses
57 | Ausgewähltes Verzeichnis:
58 | \'%s\' ist bereits auf der Liste
59 | Abgehakte zuerst
60 | Abgehakte zuletzt
61 | Speicher auswählen
62 | Die Liste kann nicht gelöscht werden
63 | Die Liste kann nicht geteilt werden
64 | <h1>Über</h1>
65 | <h3>Version</h3> Sie benutzen gerade Version <tt>v%1$s</tt>
66 | <h3>Quellcode, Probleme, Mitmachen</h3> Dieses Projekt wird auf <a href="https://github.com/woefe/ShoppingList">Github</a> verwaltet.
67 | <h3>Lizenz</h3> ShoppingList unter <a href="https://raw.githubusercontent.com/woefe/ShoppingList/master/COPYING">GPLv3+</a> lizenziert.
68 | <h3>Autor</h3> Wolfgang Popp
69 | <h3>Mitwirkende</h3> • <b>Pierre Rudloff</b> (Französische Übersetzung)<br/> • <b>unbranched</b> (Italienische Übersetzung)<br/>
70 |
--------------------------------------------------------------------------------
/app/src/main/res/values-fr/strings.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | Liste de courses
22 | Paramètres
23 | Quantité
24 | Nouvel élément
25 | Éditer
26 | Supprimer
27 | Supprimer les éléments cochés
28 | Retirer tous les éléments cochés ?
29 | Annuler
30 | OK
31 | Emplacement du fichier
32 | La description de l\'élément ne peut pas être vide !
33 | Ouvrir le tiroir de navigation
34 | Fermer le tiroir de navigation
35 | Créer une nouvelle liste
36 | Supprimer la liste
37 | Supprimer la liste <b>%1$s</b> ?
38 | Ajouter une nouvelle liste
39 | Nom de la nouvelle liste
40 | Aucune liste n\'est disponible.
41 | Terminé
42 | Inverser
43 | Listes
44 | À propos
45 | Le nom de la liste ne peut pas être vide !
46 | Cette liste existe déjà
47 | Partager
48 | Impossible d\'accéder au répertoire configuré !
49 | Trier
50 | A-Z
51 | Z-A
52 | Impossible d\'accéder au répertoire configuré !
53 | Impossible de créer \'%s\'
54 | Le nom du fichier contient le caractère interdit \'/\'
55 | Sélectionner l\'emplacement de stockage
56 | Créer un nouveau répertoire
57 | Nom du nouveau répertoire
58 | Répertoire sélectionné :
59 | \'%s\' est déjà dans la liste
60 | Cochés en premier
61 | Non-cochés en premier
62 | Impossible de supprimer la liste
63 | Impossible de partager la liste
64 | <h1>À propos</h1>
65 | <h3>Version</h3> Vous utilisez la version <tt>v%1$s</tt>
66 | <h3>Code source, rapports de bug et contribution</h3> Ce projet est hébergé sur <a href="https://github.com/woefe/ShoppingList">Github</a>
67 | <h3>Licence</h3> ShoppingList est publié sous licence <a href="https://raw.githubusercontent.com/woefe/ShoppingList/master/COPYING">GPLv3+</a>
68 | <h3>Auteur</h3> Wolfgang Popp
69 | <h3>Contributeurs</h3> • <b>Pierre Rudloff</b> (traduction française)<br/>• <b>unbranched</b> (traduction italien)<br/>
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/values-it/strings.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | Lista della spesa
22 | Impostazioni
23 | Quantità
24 | Nuovo oggetto
25 | Modifica
26 | Elimina
27 | Elimina oggetti selezionati
28 | Rimuovere tutti gli oggetti selezionati?
29 | Annulla
30 | OK
31 | Percorso file
32 | La descrizione dell\'oggetto non può essere vuota!
33 | Apri pannello navigazione
34 | Chiudi pannello navigazione
35 | Crea nuova lista
36 | Elimina lista
37 | Eliminare la lista <b>%1$s</b>?
38 | Aggiungi una nuova lista
39 | Nome della nuova lista
40 | Nessuna lista disponibile.
41 | Fatto
42 | Scambia
43 | Liste
44 | Al riguardo
45 | Il nome della lista non può essere vuoto!
46 | Questa lista esiste già
47 | Condividi
48 | Impossibile accedere alla cartella configurata!
49 | Ordina
50 | A-Z
51 | Z-A
52 | Impossibile accedere alla cartella selezionata!
53 | Impossibile creare \'%s\'
54 | Il nome del file contiene il carattare non valido \'/\'
55 | Seleziona percorso di archiviazione
56 | Crea nuova cartella
57 | Nome nuova cartella
58 | Seleziona cartella:
59 | \'%s\' è già nella lista
60 | Prima i segnati
61 | Prima i non segnati
62 | Impossibile eliminare la lista
63 | Impossibile condividere la lista
64 | <h1>Al riguardo</h1>
65 | <h3>Versione</h3> Stai usando la versione <tt>v%1$s</tt>
66 | <h3>Codice sorgente, problemi, contributi</h3> Questo progetto viene ospitato su <a href="https://github.com/woefe/ShoppingList">Github</a>
67 | <h3>Licenza</h3> ShoppingList è concesso in licenza <a href="https://raw.githubusercontent.com/woefe/ShoppingList/master/COPYING">GPLv3+</a>
68 | <h3>Autore</h3> Wolfgang Popp
69 | <h3>Collaboratori</h3> • <b>Pierre Rudloff</b> (traduzione in francese)<br/> • <b>unbranched</b> (traduzione in italiano)<br/>
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/values-nl/strings.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | Shopping List
22 | Instellingen
23 | Aantal
24 | Nieuw item
25 | Bewerk
26 | Verwijder
27 | Verwijder aangevinkte items
28 | Verwijder alle aangevinkte items?
29 | Annuleer
30 | Oké
31 | Bestandslocatie
32 | Itembeschrijving mag niet leeg zijn!
33 | Open navigatielade
34 | Sluit navigatielade
35 | Maak nieuwe lijst aan
36 | Verwijder lijst
37 | Verwijder lijst <b>%1$s</b>?
38 | Voeg nieuwe lijst toe
39 | Naam van de nieuwe lijst
40 | Er zijn geen lijsten beschikbaar.
41 | Klaar
42 | Wissel
43 | Lijsten
44 | Over
45 | De naam van de lijst mag niet leeg zijn!
46 | Deze lijst bestaat al
47 | Deel
48 | Geen toegang tot ingestelde map!
49 | Sorteer
50 | A-Z
51 | Z-A
52 | Geen toegang tot geselecteerde map!
53 | Kan \'%s\' niet aanmaken
54 | Bestandsnaam bevat ongeldige karakter \'/\'
55 | Kies opslaglocatie
56 | Maak nieuwe map aan
57 | Naam van nieuwe map
58 | Gekozen map:
59 | \'%s\' staat al op de lijst
60 | Eerste aangevinkt
61 | Eerste uitgevinkt
62 | Kan lijst niet verwijderen
63 | Kan lijst niet delen
64 | <h1>Over</h1>
65 | <h3>Versie</h3> Je gebruikt versie <tt>v%1$s</tt>
66 | <h3>Broncode, Issues, Bijdragen</h3> Dit project is te vinden op <a href="https://github.com/woefe/ShoppingList">Github</a>
67 | <h3>Licentie</h3> ShoppingList heeft de licentie <a href="https://raw.githubusercontent.com/woefe/ShoppingList/master/COPYING">GPLv3+</a>
68 | <h3>Auteur</h3> Wolfgang Popp
69 | <h3>Bijdragen van</h3> • <b>Pierre Rudloff</b> (Franse vertaling)<br/> • <b>unbranched</b> (Italiaanse vertaling)<br/>
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | Список покупок
22 | Настройки
23 | Количество
24 | Новый пункт
25 | Редактировать
26 | Удалить
27 | Удалить отмеченные пункты
28 | Удалить все отмеченные пункты?
29 | Отменить
30 | OK
31 | Расположение файла
32 | Описание пункта не может быть пустым!
33 | Открыть меню
34 | Закрыть меню
35 | Создать новый список
36 | Удалить список
37 | Удалить список <b>%1$s</b>?
38 | Добавление нового списка
39 | Название нового списка
40 | Нет доступных списков.
41 | Выполнено
42 | Переместить
43 | Списки
44 | О приложении
45 | Название списка не может быть пустым!
46 | Такой список уже существует
47 | Поделиться
48 | Нет доступа к настроенной директории!
49 | Отсортировать
50 | А-Я
51 | Я-А
52 | Нет доступа к выбранной директории!
53 | Невозможно создать \'%s\'
54 | Имя файла содержит недопустимый символ \'/\'
55 | Выберите место хранения
56 | Создать новую директорию
57 | Имя новой директории
58 | Выбранная директория:
59 | \'%s\' уже в списке
60 | Сначала отмеченные
61 | Сначала неотмеченные
62 | Невозможно удалить список
63 | Невозможно поделиться списком
64 | <h1>О приложении</h1>
65 | <h3>Версия</h3> Вы используете версию <tt>v%1$s</tt>
66 | <h3>Исходный код, Проблемы, Содействие</h3> Этот проект находится на <a href="https://github.com/woefe/ShoppingList">Github</a>
67 | <h3>Лицензия</h3> ShoppingList лицензируется согласно <a href="https://raw.githubusercontent.com/woefe/ShoppingList/master/COPYING">GPLv3+</a>
68 | <h3>Автор</h3> Wolfgang Popp
69 | <h3>Внёсшие вклад</h3> • <b>Pierre Rudloff</b> (перевод на французский)<br/>
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | #000000
22 | #cccccc
23 | @android:color/background_light
24 | #388e3c
25 | #1b5e20
26 | #ffc046
27 | #ff8f00
28 | #FF5722
29 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #388E3C
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 | Shopping List
22 | Settings
23 | Quantity
24 | New Item
25 | Edit
26 | Delete
27 | Delete checked items
28 | Remove all checked items?
29 | Cancel
30 | OK
31 | File location
32 | Item description may not be empty!
33 | Open navigation drawer
34 | Close navigation drawer
35 | Create new list
36 | Delete list
37 | Delete list <b>%1$s</b>?
38 | Add a new list
39 | Name of new list
40 | No lists are available.
41 | Done
42 | Swap
43 | Lists
44 | About
45 | The list name may not be empty!
46 | This list already exists
47 | Share
48 | Cannot access configured directory!
49 | Sort
50 | A-Z
51 | Z-A
52 | Cannot access selected directory!
53 | Cannot create \'%s\'
54 | Filename contains illegal character \'/\'
55 | Select storage location
56 | Create new directory
57 | Name of new directory
58 | Selected directory:
59 | \'%s\' is already on the list
60 | Checked first
61 | Unchecked first
62 | Cannot delete list
63 | Cannot share list
64 | <h1>About</h1>
65 | <h3>Version</h3> You are using version <tt>v%1$s</tt>
66 | <h3>Source code, Issues, Contributing</h3> This project is hosted on <a href="https://github.com/woefe/ShoppingList">Github</a>
67 | <h3>License</h3> ShoppingList is licensed under <a href="https://raw.githubusercontent.com/woefe/ShoppingList/master/COPYING">GPLv3+</a>
68 | <h3>Author</h3> Wolfgang Popp
69 | <h3>Contributors</h3> • <b>Pierre Rudloff</b> (French translation)<br/> • <b>unbranched</b> (Italian translation)<br/>
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
32 |
33 |
42 |
43 |
53 |
54 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
25 |
26 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.4.1'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | google()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/full_description.txt:
--------------------------------------------------------------------------------
1 | Verwalte Deine Einkaufs- und andere Listen.
2 | Die Listen werden in einer klaren, menschenlesbaren Syntax in normalen Textdateien gespeichert. Das erlaubt es sie mit anderen Geräten zu synchronisieren, z.B. mit [https://syncthing.net/ Syncthing] oder [https://nextcloud.com/ Nextcloud].
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/short_description.txt:
--------------------------------------------------------------------------------
1 | (Einkaufs)Listen verwalten
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Manage your shopping lists and other types of lists.
2 | The lists are stored as simple text files and use a simple, human-readable syntax. This allows you to synchronize them with other devices by using applications such as [https://syncthing.net/ Syncthing] and [https://nextcloud.com/ Nextcloud].
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/00_lists.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/fastlane/metadata/android/en-US/images/phoneScreenshots/00_lists.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/01_list_content.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/fastlane/metadata/android/en-US/images/phoneScreenshots/01_list_content.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Manage (grocery) lists
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woefe/ShoppingList/c4da457bb1d78aa14be5b5d56404f81b0b244861/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jul 13 18:21:48 CEST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------