├── .gitignore ├── LICENSE ├── PRIVACY.md ├── README.md ├── app ├── build.gradle ├── libs │ └── json-simple.jar ├── lint.xml ├── proguard.txt └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ ├── androidx │ │ └── viewpager │ │ │ └── widget │ │ │ └── UnderlinePagerTitleStrip.java │ └── com │ │ └── cypressworks │ │ └── mensaplan │ │ ├── AndroidV21Helper.java │ │ ├── BackupAgent.java │ │ ├── DayChangeListener.java │ │ ├── HappyCowActivity.java │ │ ├── MainActivity.java │ │ ├── MensaWidgetProvider.java │ │ ├── MensaWidgetService.java │ │ ├── MyApplication.java │ │ ├── OnPageChangeAdapter.java │ │ ├── PlanFragmentPagerAdapter.java │ │ ├── RangeTimePickerDialog.java │ │ ├── ScrollListener.java │ │ ├── Tools.java │ │ ├── Views.java │ │ ├── WebCamActivity.java │ │ ├── food │ │ ├── Line.java │ │ ├── Meal.java │ │ ├── Plan.java │ │ ├── PlanAdapter.java │ │ ├── PlanFragment.java │ │ └── likes │ │ │ ├── LikeManager.java │ │ │ └── LikeStatus.java │ │ ├── planmanager │ │ ├── AdenauerPlanManager.java │ │ ├── AkkStudentenWerkPlanManager.java │ │ ├── CafeteriaMoltkePlanManager.java │ │ ├── ErzbergerPlanManager.java │ │ ├── GottesauePlanManager.java │ │ ├── HolzgartenstrPlanManager.java │ │ ├── MensaDropdownAdapter.java │ │ ├── MoltkePlanManager.java │ │ ├── PforzheimPlanManager.java │ │ └── PlanManager.java │ │ └── util │ │ ├── SpannableStringBuilderHelper.java │ │ └── StringUtils.java │ └── res │ ├── drawable-anydpi │ ├── ic_action_more.xml │ ├── thumbs_down.xml │ ├── thumbs_undecided.xml │ └── thumbs_up.xml │ ├── drawable-hdpi │ ├── ic_action_more.png │ ├── icon.png │ ├── icon_white.png │ ├── thumbs_down.png │ ├── thumbs_undecided.png │ └── thumbs_up.png │ ├── drawable-mdpi │ ├── ic_action_more.png │ ├── icon.png │ ├── icon_white.png │ ├── thumbs_down.png │ ├── thumbs_undecided.png │ └── thumbs_up.png │ ├── drawable-nodpi │ └── widget_preview.png │ ├── drawable-xhdpi │ ├── ic_action_more.png │ ├── icon.png │ ├── icon_white.png │ ├── thumbs_down.png │ ├── thumbs_undecided.png │ └── thumbs_up.png │ ├── drawable-xxhdpi │ ├── heart.png │ ├── ic_action_more.png │ ├── ic_meal_bio.png │ ├── ic_meal_cow.png │ ├── ic_meal_cow_aw.png │ ├── ic_meal_fish.png │ ├── ic_meal_pork.png │ ├── ic_meal_veg.png │ ├── ic_meal_vegan.png │ ├── icon.png │ ├── icon_white.png │ ├── thumbs_down.png │ ├── thumbs_undecided.png │ └── thumbs_up.png │ ├── drawable-xxxhdpi │ └── icon.png │ ├── drawable │ ├── ic_action_calendar_day.xml │ ├── ic_action_refresh.xml │ ├── list_selector.xml │ ├── shadow_vertical.xml │ └── widget_header_selector.xml │ ├── layout-v21 │ └── like_button.xml │ ├── layout │ ├── actionitem_progress.xml │ ├── activity_notification_settings.xml │ ├── happy_cows_layout.xml │ ├── layout_meal_types.xml │ ├── like_button.xml │ ├── list_header.xml │ ├── list_item.xml │ ├── main_new.xml │ ├── spinner_dropdown_item.xml │ ├── spinner_title_item.xml │ ├── textview_drawer.xml │ ├── view_shadow.xml │ ├── webcam_activity.xml │ ├── widget_layout.xml │ ├── widget_list_header.xml │ └── widget_list_item.xml │ ├── menu │ ├── mealcontextmenu.xml │ ├── optionsmenu_frag.xml │ └── optionsmenu_new.xml │ ├── values-sw600dp │ └── dimens.xml │ ├── values │ ├── colors_example.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── mensa_appwidget_info.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── green_button.png ├── green_button.psd ├── icon.psd ├── icon512.png ├── icon_new.psd ├── meal_icons.psd ├── red_button.png └── red_shining_button.png ├── screenshots ├── device-2014-12-16-233433.png ├── device-2014-12-16-233453.png └── device-2014-12-16-233723.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | /local.properties 5 | *.iml 6 | /motivkey.keystore 7 | /signing.properties 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | Die App KIT Mensa App erhebt und verarbeitet keine personenbezogenen Daten. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KitMensaAndroid 2 | 3 | https://play.google.com/store/apps/details?id=com.cypressworks.mensaplan 4 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | 5 | signingConfigs { 6 | release { 7 | storeFile file('../motivkey.keystore') 8 | } 9 | } 10 | 11 | defaultConfig { 12 | applicationId 'com.cypressworks.mensaplan' 13 | minSdkVersion 14 14 | targetSdkVersion 34 15 | versionCode 34 16 | versionName "2.5.3" 17 | } 18 | 19 | compileSdk 34 20 | 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_8 23 | targetCompatibility JavaVersion.VERSION_1_8 24 | } 25 | buildTypes { 26 | release { 27 | minifyEnabled true 28 | shrinkResources true 29 | signingConfig signingConfigs.release 30 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.txt' 31 | } 32 | } 33 | productFlavors { 34 | } 35 | packagingOptions { 36 | resources { 37 | excludes += ['META-INF/services/javax.annotation.processing.Processor'] 38 | } 39 | } 40 | 41 | 42 | namespace 'com.cypressworks.mensaplan' 43 | lint { 44 | disable 'InvalidPackage' 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 50 | implementation 'androidx.appcompat:appcompat:1.6.1' 51 | implementation 'org.jsoup:jsoup:1.15.3' 52 | implementation 'com.nineoldandroids:library:2.4.0' 53 | implementation files('libs/json-simple.jar') 54 | } 55 | 56 | def props = new Properties() 57 | def propFile = new File('signing.properties') 58 | 59 | if (propFile.canRead()) { 60 | props.load(new FileInputStream(propFile)) 61 | 62 | if (props != null && 63 | props.containsKey('STORE_PASSWORD') && 64 | props.containsKey('KEY_ALIAS') && 65 | props.containsKey('KEY_PASSWORD')) { 66 | android.signingConfigs.release.storePassword = props['STORE_PASSWORD'] 67 | android.signingConfigs.release.keyAlias = props['KEY_ALIAS'] 68 | android.signingConfigs.release.keyPassword = props['KEY_PASSWORD'] 69 | } 70 | } else { 71 | println 'signing.properties not found' 72 | android.buildTypes.release.signingConfig = null 73 | } 74 | -------------------------------------------------------------------------------- /app/libs/json-simple.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/libs/json-simple.jar -------------------------------------------------------------------------------- /app/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/proguard.txt: -------------------------------------------------------------------------------- 1 | -dontobfuscate 2 | 3 | -keep public class * extends com.cypressworks.mensaplan.planmanager.PlanManager 4 | 5 | -dontwarn java.lang.invoke** 6 | -dontwarn javax.annotation.Nullable 7 | -dontwarn javax.annotation.ParametersAreNonnullByDefault -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 19 | 20 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 41 | 42 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/java/androidx/viewpager/widget/UnderlinePagerTitleStrip.java: -------------------------------------------------------------------------------- 1 | package androidx.viewpager.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | 9 | import com.cypressworks.mensaplan.R; 10 | 11 | /** 12 | * Created by Kirill on 29.10.2014. 13 | */ 14 | public class UnderlinePagerTitleStrip extends PagerTitleStrip { 15 | 16 | private final Paint mTabPaint = new Paint(); 17 | private int mTabPadding; 18 | private int mIndicatorHeight; 19 | private int mTabAlpha = 0xFF; 20 | private int mIndicatorColor; 21 | 22 | public UnderlinePagerTitleStrip(final Context context) { 23 | super(context); 24 | init(); 25 | } 26 | 27 | public UnderlinePagerTitleStrip(final Context context, final AttributeSet attrs) { 28 | super(context, attrs); 29 | init(); 30 | } 31 | 32 | private void init() { 33 | Resources res = getResources(); 34 | mTabPadding = (int) res.getDimension(R.dimen.tab_padding); 35 | mIndicatorHeight = (int) res.getDimension(R.dimen.indicator_height); 36 | mIndicatorColor = res.getColor(R.color.accent); 37 | } 38 | 39 | @Override 40 | protected void onDraw(final Canvas canvas) { 41 | super.onDraw(canvas); 42 | 43 | final int bottom = getHeight(); 44 | final int left = mCurrText.getLeft() - mTabPadding; 45 | final int right = mCurrText.getRight() + mTabPadding; 46 | final int top = bottom - mIndicatorHeight; 47 | mTabPaint.setColor(mTabAlpha << 24 | (mIndicatorColor & 0xFFFFFF)); 48 | canvas.drawRect(left, top, right, bottom, mTabPaint); 49 | } 50 | 51 | @Override 52 | void updateTextPositions(int position, float positionOffset, boolean force) { 53 | super.updateTextPositions(position, positionOffset, force); 54 | mTabAlpha = (int) (Math.abs(positionOffset - 0.5f) * 2 * 0xFF); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/AndroidV21Helper.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.app.ActivityManager; 6 | import android.graphics.BitmapFactory; 7 | import android.os.Build; 8 | 9 | /** 10 | * Created by Kirill on 28.10.2014. 11 | */ 12 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 13 | public class AndroidV21Helper { 14 | 15 | public static void setTaskDescription( 16 | final Activity activity, final String label, final int icon, final int color) { 17 | activity.setTaskDescription(new ActivityManager.TaskDescription(label, 18 | BitmapFactory.decodeResource( 19 | activity.getResources(), 20 | icon), color)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/BackupAgent.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.app.backup.BackupAgentHelper; 4 | import android.app.backup.FileBackupHelper; 5 | import android.app.backup.SharedPreferencesBackupHelper; 6 | import com.cypressworks.mensaplan.food.likes.LikeManager; 7 | 8 | /** 9 | * Created by Kirill on 30.05.2016. 10 | */ 11 | public class BackupAgent extends BackupAgentHelper { 12 | 13 | @Override 14 | public void onCreate() { 15 | addHelper("collected", new FileBackupHelper(this, HappyCowActivity.FILE_NAME_COLLECTED)); 16 | addHelper("prefs", 17 | new SharedPreferencesBackupHelper(this, getPackageName() + "_preferences")); 18 | addHelper("likes", new SharedPreferencesBackupHelper(this, LikeManager.LIKES_PREFS)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/DayChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.preference.PreferenceManager; 8 | 9 | /** 10 | * @author Kirill Rakhman 11 | */ 12 | public class DayChangeListener extends BroadcastReceiver { 13 | 14 | @Override 15 | public void onReceive(final Context context, final Intent intent) { 16 | MensaWidgetProvider.updateWidgets(context); 17 | 18 | final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 19 | prefs.edit().putBoolean("dayChanged", true).apply(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/HappyCowActivity.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.app.Activity; 4 | import android.app.backup.BackupManager; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.res.Resources; 8 | import android.graphics.Color; 9 | import android.graphics.drawable.ColorDrawable; 10 | import android.graphics.drawable.Drawable; 11 | import android.graphics.drawable.TransitionDrawable; 12 | import android.os.Bundle; 13 | import android.text.Spannable; 14 | import android.text.SpannableString; 15 | import android.text.style.ForegroundColorSpan; 16 | import android.view.View; 17 | import android.view.ViewTreeObserver; 18 | import android.view.animation.DecelerateInterpolator; 19 | import android.view.animation.Interpolator; 20 | import android.widget.TextView; 21 | 22 | import com.cypressworks.mensaplan.planmanager.PlanManager; 23 | import com.nineoldandroids.animation.Animator; 24 | import com.nineoldandroids.animation.AnimatorListenerAdapter; 25 | import com.nineoldandroids.view.ViewHelper; 26 | import com.nineoldandroids.view.ViewPropertyAnimator; 27 | 28 | import java.io.File; 29 | import java.io.IOException; 30 | import java.io.Serializable; 31 | import java.util.Calendar; 32 | import java.util.HashSet; 33 | 34 | import androidx.annotation.NonNull; 35 | 36 | /** 37 | * @author Kirill Rakhman 38 | */ 39 | public class HappyCowActivity extends Activity { 40 | 41 | public static final String FILE_NAME_COLLECTED = "collected_cows"; 42 | 43 | @NonNull 44 | public static File getCollectedFile(final Context c) { 45 | return new File(c.getFilesDir(), FILE_NAME_COLLECTED); 46 | } 47 | 48 | public static final String EXTRA_MENSA_CLASS = "EXTRA_MENSA_CLASS"; 49 | public static final String EXTRA_DAY = "EXTRA_DAY"; 50 | public static final String EXTRA_ITEM = "EXTRA_ITEM"; 51 | 52 | private static final Interpolator sDecelerator = new DecelerateInterpolator(); 53 | 54 | private File collectedFile; 55 | private HashSet collected; 56 | private Resources res; 57 | 58 | private TextView gratulations, total; 59 | private View layout; 60 | private View background; 61 | 62 | private String PACKAGE_NAME; 63 | 64 | @SuppressWarnings("unchecked") 65 | @Override 66 | protected void onCreate(final Bundle savedInstanceState) { 67 | super.onCreate(savedInstanceState); 68 | 69 | PACKAGE_NAME = getPackageName(); 70 | res = getResources(); 71 | 72 | setContentView(R.layout.happy_cows_layout); 73 | gratulations = Views.findViewById(this, R.id.textViewGratulations); 74 | total = Views.findViewById(this, R.id.textViewTotal); 75 | 76 | layout = findViewById(R.id.layoutHappyCowDialog); 77 | background = findViewById(R.id.layoutBackground); 78 | background.setOnClickListener(v -> animateOut()); 79 | 80 | collectedFile = getCollectedFile(this); 81 | if (collectedFile.exists()) { 82 | try { 83 | collected = Tools.readObject(collectedFile); 84 | } catch (final Exception e) { 85 | collected = new HashSet<>(); 86 | } 87 | } else { 88 | collected = new HashSet<>(); 89 | } 90 | 91 | final Intent intent = getIntent(); 92 | 93 | if (intent != null) { 94 | final Bundle extras = intent.getExtras(); 95 | 96 | if (extras != null) { 97 | final Class planClass = (Class) extras.getSerializable( 98 | EXTRA_MENSA_CLASS); 99 | final Calendar date = (Calendar) extras.getSerializable(EXTRA_DAY); 100 | final int item = extras.getInt(EXTRA_ITEM); 101 | 102 | if (planClass != null && date != null) { 103 | checkCow(planClass, date, item); 104 | } 105 | 106 | if (savedInstanceState == null) { 107 | animateIn(extras); 108 | } 109 | } 110 | } 111 | 112 | setCollectedCount(); 113 | 114 | } 115 | 116 | private void animateIn(final Bundle extras) { 117 | final int thumbnailTop = extras.getInt(PACKAGE_NAME + ".top"); 118 | final int thumbnailLeft = extras.getInt(PACKAGE_NAME + ".left"); 119 | final int thumbnailWidth = extras.getInt(PACKAGE_NAME + ".width"); 120 | final int thumbnailHeight = extras.getInt(PACKAGE_NAME + ".height"); 121 | 122 | final ViewTreeObserver observer = layout.getViewTreeObserver(); 123 | observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 124 | 125 | @Override 126 | public boolean onPreDraw() { 127 | layout.getViewTreeObserver().removeOnPreDrawListener(this); 128 | 129 | final int[] screenLocation = new int[2]; 130 | layout.getLocationOnScreen(screenLocation); 131 | final int mLeftDelta = thumbnailLeft - screenLocation[0]; 132 | final int mTopDelta = thumbnailTop - screenLocation[1]; 133 | 134 | // Scale factors to make the large version the same 135 | // size as the 136 | // thumbnail 137 | final float mWidthScale = (float) thumbnailWidth / layout.getWidth(); 138 | final float mHeightScale = (float) thumbnailHeight / layout.getHeight(); 139 | 140 | ViewHelper.setPivotX(layout, 0); 141 | ViewHelper.setPivotY(layout, 0); 142 | ViewHelper.setScaleX(layout, mWidthScale); 143 | ViewHelper.setScaleY(layout, mHeightScale); 144 | ViewHelper.setTranslationX(layout, mLeftDelta); 145 | ViewHelper.setTranslationY(layout, mTopDelta); 146 | 147 | // Animate scale and translation to go from 148 | // thumbnail to full 149 | // size 150 | ViewPropertyAnimator.animate(layout).scaleX(1).scaleY(1).translationX( 151 | 0).translationY(0).setInterpolator(sDecelerator).start(); 152 | 153 | final TransitionDrawable backDrawable = new TransitionDrawable( 154 | new Drawable[]{new ColorDrawable( 155 | Color.parseColor("#00000000")), new ColorDrawable( 156 | Color.parseColor("#99000000"))}); 157 | background.setBackgroundDrawable(backDrawable); 158 | backDrawable.startTransition(300); 159 | 160 | return true; 161 | } 162 | }); 163 | } 164 | 165 | @Override 166 | public void onBackPressed() { 167 | animateOut(); 168 | } 169 | 170 | private void animateOut() { 171 | final ViewPropertyAnimator anim = ViewPropertyAnimator.animate(background).alpha(0f); 172 | anim.setListener(new AnimatorListenerAdapter() { 173 | @Override 174 | public void onAnimationEnd(final Animator animation) { 175 | finish(); 176 | } 177 | }); 178 | anim.start(); 179 | } 180 | 181 | @Override 182 | public void finish() { 183 | super.finish(); 184 | 185 | // override transitions to skip the standard window animations 186 | overridePendingTransition(0, 0); 187 | } 188 | 189 | private void setCollectedCount() { 190 | final int num = collected.size(); 191 | final String text = res.getQuantityString(R.plurals.happy_cow_total, num, num); 192 | final String numString = String.valueOf(num); 193 | final int numberPos = text.indexOf(numString); 194 | 195 | final Spannable spanned = new SpannableString(text); 196 | spanned.setSpan(new ForegroundColorSpan(Color.parseColor("#cc0000")), numberPos, 197 | numberPos + numString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 198 | 199 | total.setText(spanned); 200 | } 201 | 202 | private void checkCow( 203 | final Class planClass, final Calendar date, final int item) { 204 | 205 | final CowHolder holder = new CowHolder(); 206 | holder.mensa = PlanManager.getInstance(planClass, this).getProviderName(); 207 | holder.day = date.get(Calendar.DAY_OF_YEAR); 208 | holder.year = date.get(Calendar.YEAR); 209 | holder.item = item; 210 | 211 | if (!collected.contains(holder)) { 212 | collected.add(holder); 213 | try { 214 | Tools.writeObject(collected, collectedFile); 215 | new BackupManager(this).dataChanged(); 216 | } catch (final IOException e) { 217 | e.printStackTrace(); 218 | } 219 | 220 | gratulations.setText(R.string.happy_cow_gratulations); 221 | } else { 222 | gratulations.setText(R.string.happy_cow_already_found); 223 | } 224 | 225 | gratulations.setVisibility(View.VISIBLE); 226 | } 227 | 228 | private static class CowHolder implements Serializable { 229 | private static final long serialVersionUID = 2210140636911080441L; 230 | 231 | String mensa; 232 | int year, day, item; 233 | 234 | @Override 235 | public int hashCode() { 236 | final int prime = 31; 237 | int result = 1; 238 | result = prime * result + day; 239 | result = prime * result + item; 240 | result = prime * result + ((mensa == null) ? 0 : mensa.hashCode()); 241 | result = prime * result + year; 242 | return result; 243 | } 244 | 245 | @Override 246 | public boolean equals(final Object obj) { 247 | if (this == obj) { 248 | return true; 249 | } 250 | if (obj == null) { 251 | return false; 252 | } 253 | if (!(obj instanceof CowHolder)) { 254 | return false; 255 | } 256 | final CowHolder other = (CowHolder) obj; 257 | if (day != other.day) { 258 | return false; 259 | } 260 | if (item != other.item) { 261 | return false; 262 | } 263 | if (mensa == null) { 264 | if (other.mensa != null) { 265 | return false; 266 | } 267 | } else if (!mensa.equals(other.mensa)) { 268 | return false; 269 | } 270 | return year == other.year; 271 | } 272 | 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.backup.BackupManager; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.content.pm.PackageInfo; 8 | import android.content.pm.PackageManager.NameNotFoundException; 9 | import android.content.res.Configuration; 10 | import android.content.res.Resources; 11 | import android.graphics.Color; 12 | import android.os.Build; 13 | import android.os.Bundle; 14 | import android.preference.PreferenceManager; 15 | import android.util.Log; 16 | import android.util.TypedValue; 17 | import android.view.Gravity; 18 | import android.view.Menu; 19 | import android.view.MenuInflater; 20 | import android.view.MenuItem; 21 | import android.view.View; 22 | import android.view.animation.AccelerateDecelerateInterpolator; 23 | import android.widget.ListView; 24 | 25 | import com.cypressworks.mensaplan.planmanager.MensaDropdownAdapter; 26 | import com.cypressworks.mensaplan.planmanager.PlanManager; 27 | import com.nineoldandroids.animation.ObjectAnimator; 28 | 29 | import java.io.File; 30 | import java.util.Calendar; 31 | 32 | import androidx.annotation.NonNull; 33 | import androidx.appcompat.app.ActionBar; 34 | import androidx.appcompat.app.ActionBarDrawerToggle; 35 | import androidx.appcompat.app.AppCompatActivity; 36 | import androidx.drawerlayout.widget.DrawerLayout; 37 | import androidx.viewpager.widget.PagerTitleStrip; 38 | import androidx.viewpager.widget.ViewPager; 39 | import androidx.viewpager.widget.ViewPager.OnPageChangeListener; 40 | 41 | /** 42 | * @author Kirill Rakhman 43 | */ 44 | public class MainActivity extends AppCompatActivity implements ScrollListener { 45 | 46 | public static final String PREF_MENSA_NUM = "pref_mensa_num"; 47 | public static final String PACKAGE_WEAR = "com.google.android.wearable.app"; 48 | 49 | private MensaDropdownAdapter mensaAdapter; 50 | private ViewPager viewPager; 51 | private ListView listDrawer; 52 | private DrawerLayout drawerLayout; 53 | 54 | private Calendar today = Calendar.getInstance(); 55 | private int currentPagerPosition; 56 | private SharedPreferences prefs; 57 | private ActionBarDrawerToggle mDrawerToggle; 58 | 59 | @SuppressLint({"NewApi", "CommitPrefEdits"}) 60 | @Override 61 | protected void onCreate(final Bundle arg0) { 62 | super.onCreate(arg0); 63 | 64 | mensaAdapter = new MensaDropdownAdapter(this); 65 | currentPagerPosition = PlanFragmentPagerAdapter.getDefaultPosition(); 66 | prefs = PreferenceManager.getDefaultSharedPreferences(this); 67 | prefs.edit().putBoolean("dayChanged", false).apply(); 68 | 69 | makeStartUpCleaning(); 70 | 71 | setContentView(R.layout.main_new); 72 | 73 | viewPager = Views.findViewById(this, R.id.pager); 74 | viewPager.setPageMargin(getResources().getDimensionPixelSize(R.dimen.pager_margin)); 75 | viewPager.addOnPageChangeListener(onPageChangeListener); 76 | 77 | drawerLayout = Views.findViewById(this, R.id.drawer_layout); 78 | listDrawer = Views.findViewById(this, R.id.left_drawer); 79 | prepareDrawer(); 80 | 81 | final PagerTitleStrip titleStrip = Views.findViewById(this, R.id.titleStrip); 82 | titleStrip.setTextColor(Color.WHITE); 83 | 84 | prepareActionBar(); 85 | 86 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 87 | setTaskDescription(); 88 | } 89 | 90 | if (!prefs.getBoolean("backed_up", false) && HappyCowActivity.getCollectedFile( 91 | this).exists()) { 92 | prefs.edit().putBoolean("backed_up", true).apply(); 93 | new BackupManager(this).dataChanged(); 94 | } 95 | } 96 | 97 | private void setTaskDescription() { 98 | final TypedValue typedValue = new TypedValue(); 99 | final Resources.Theme theme = getTheme(); 100 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 101 | theme.resolveAttribute(android.R.attr.colorPrimary, typedValue, true); 102 | AndroidV21Helper.setTaskDescription(this, null, R.drawable.icon_white, typedValue.data); 103 | } 104 | } 105 | 106 | @Override 107 | protected void onResume() { 108 | super.onResume(); 109 | 110 | if (prefs.getBoolean("dayChanged", false)) { 111 | prefs.edit().putBoolean("dayChanged", true).apply(); 112 | 113 | today = Calendar.getInstance(); 114 | currentPagerPosition = PlanFragmentPagerAdapter.getDefaultPosition(); 115 | 116 | final int defaultItem = PreferenceManager.getDefaultSharedPreferences(this).getInt( 117 | PREF_MENSA_NUM, 0); 118 | onMensaSelected(defaultItem); 119 | } 120 | } 121 | 122 | @Override 123 | protected void onPostCreate(final Bundle savedInstanceState) { 124 | super.onPostCreate(savedInstanceState); 125 | // Sync the toggle state after onRestoreInstanceState has occurred. 126 | mDrawerToggle.syncState(); 127 | } 128 | 129 | @Override 130 | public void onConfigurationChanged(@NonNull final Configuration newConfig) { 131 | super.onConfigurationChanged(newConfig); 132 | mDrawerToggle.onConfigurationChanged(newConfig); 133 | } 134 | 135 | @Override 136 | public void onBackPressed() { 137 | if (drawerLayout.isDrawerOpen(Gravity.LEFT)) { 138 | drawerLayout.closeDrawers(); 139 | return; 140 | } 141 | 142 | super.onBackPressed(); 143 | } 144 | 145 | private void makeStartUpCleaning() { 146 | final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 147 | final int lastClean = prefs.getInt("lastClean", -1); 148 | final int version = getVersionNumber(); 149 | 150 | if (lastClean < version) { 151 | prefs.edit().putInt("lastClean", version).apply(); 152 | 153 | final File cacheDir = getCacheDir(); 154 | 155 | for (final File file : cacheDir.listFiles()) { 156 | if (file.isDirectory()) { 157 | Tools.deleteFolder(file); 158 | } else { 159 | file.delete(); 160 | } 161 | } 162 | } else { 163 | for (int i = 0; i < mensaAdapter.getCount(); i++) { 164 | PlanManager.getInstance(mensaAdapter.getItem(i), this).clearCache(8); 165 | } 166 | } 167 | 168 | // final boolean notificationDialogShown = prefs.getBoolean("notification_dialog_shown", 169 | // false); 170 | // 171 | // if (!notificationDialogShown) { 172 | // prefs.edit().putBoolean("notification_dialog_shown", true).apply(); 173 | // 174 | // final List installedApplications = getPackageManager().getInstalledApplications( 175 | // 0); 176 | // 177 | // for (final ApplicationInfo info : installedApplications) { 178 | // if (info.packageName.equals(PACKAGE_WEAR)) { 179 | // new AlertDialog.Builder(this) // 180 | // .setTitle(R.string.notification_question_title) // 181 | // .setMessage(R.string.notification_question) // 182 | // .setPositiveButton(android.R.string.yes, 183 | // (dialog, which) -> startActivity(new Intent(this, 184 | // NotificationSettingsActivity.class))) // 185 | // .setNegativeButton(android.R.string.cancel, null) // 186 | // .show(); 187 | // return; 188 | // } 189 | // } 190 | // } 191 | 192 | } 193 | 194 | private int getVersionNumber() { 195 | try { 196 | final PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); 197 | return packageInfo.versionCode; 198 | } catch (final NameNotFoundException e) { 199 | throw new RuntimeException(e); 200 | } 201 | } 202 | 203 | private final OnPageChangeListener onPageChangeListener = new OnPageChangeAdapter() { 204 | @Override 205 | public void onPageSelected(final int position) { 206 | currentPagerPosition = position; 207 | log("Page changed: " + currentPagerPosition); 208 | } 209 | }; 210 | 211 | private void prepareActionBar() { 212 | final ActionBar actionBar = getSupportActionBar(); 213 | 214 | actionBar.setElevation(0); 215 | 216 | } 217 | 218 | private void prepareDrawer() { 219 | mDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.open_drawer, 220 | R.string.close_drawer) { 221 | public void onDrawerClosed(final View view) { 222 | super.onDrawerClosed(view); 223 | } 224 | 225 | /** Called when a drawer has settled in a completely open state. */ 226 | public void onDrawerOpened(final View drawerView) { 227 | super.onDrawerOpened(drawerView); 228 | } 229 | 230 | }; 231 | 232 | drawerLayout.addDrawerListener(mDrawerToggle); 233 | 234 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 235 | getSupportActionBar().setHomeButtonEnabled(true); 236 | 237 | final int defaultItem = PreferenceManager.getDefaultSharedPreferences(this).getInt( 238 | PREF_MENSA_NUM, 0); 239 | 240 | listDrawer.setAdapter(mensaAdapter); 241 | listDrawer.setOnItemClickListener((parent, view, position, id) -> { 242 | onMensaSelected(position); 243 | drawerLayout.closeDrawers(); 244 | }); 245 | 246 | onMensaSelected(defaultItem); 247 | } 248 | 249 | private void onMensaSelected(final int itemPosition) { 250 | PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(PREF_MENSA_NUM, 251 | itemPosition).apply(); 252 | new BackupManager(this).dataChanged(); 253 | 254 | final Class managerClass = mensaAdapter.getItem(itemPosition); 255 | 256 | final PlanFragmentPagerAdapter pagerAdapter = new PlanFragmentPagerAdapter(this, 257 | getSupportFragmentManager(), 258 | today, 259 | managerClass); 260 | 261 | viewPager.setAdapter(pagerAdapter); 262 | viewPager.removeOnPageChangeListener(onPageChangeListener); 263 | viewPager.setCurrentItem(currentPagerPosition); 264 | log("Setting current item: " + currentPagerPosition); 265 | viewPager.addOnPageChangeListener(onPageChangeListener); 266 | 267 | MensaWidgetProvider.updateWidgets(this); 268 | 269 | getSupportActionBar().setTitle(mensaAdapter.getName(itemPosition)); 270 | } 271 | 272 | @Override 273 | public boolean onCreateOptionsMenu(final Menu menu) { 274 | final MenuInflater inflater = getMenuInflater(); 275 | inflater.inflate(R.menu.optionsmenu_new, menu); 276 | 277 | return true; 278 | } 279 | 280 | @Override 281 | public void supportInvalidateOptionsMenu() { 282 | // unschöner Hack, aber scheinbar verhindert das, dass beim Start das 283 | // "today" action item fehlt 284 | viewPager.post(MainActivity.super::supportInvalidateOptionsMenu); 285 | } 286 | 287 | @Override 288 | public boolean onOptionsItemSelected(@NonNull final MenuItem item) { 289 | 290 | if (mDrawerToggle.onOptionsItemSelected(item)) { 291 | return true; 292 | } 293 | 294 | int itemId = item.getItemId(); 295 | if (itemId == R.id.webcam) { 296 | final Intent webcamActivity = new Intent(getBaseContext(), WebCamActivity.class); 297 | startActivity(webcamActivity); 298 | return true; 299 | } else if (itemId == R.id.today) { 300 | scrollToToday(); 301 | return true; 302 | } 303 | 304 | return false; 305 | } 306 | 307 | private void scrollToToday() { 308 | final int todayPosition = PlanFragmentPagerAdapter.getDefaultPosition(); 309 | 310 | if (viewPager.getCurrentItem() != todayPosition) { 311 | viewPager.setCurrentItem(todayPosition); 312 | } else { 313 | 314 | final View view = viewPager.getChildAt(1); 315 | 316 | final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationX", 0, dp2px(0), 317 | dp2px(10), dp2px(0), dp2px(-10), 318 | dp2px(0), dp2px(2), 319 | dp2px(0)).setDuration(300); 320 | 321 | anim.setInterpolator(new AccelerateDecelerateInterpolator()); 322 | anim.start(); 323 | } 324 | } 325 | 326 | @Override 327 | public void onScrolled(final int yScroll) { 328 | // if (VERSION.SDK_INT >= 21) { 329 | // float elevation = titleStripMaxElevation * Math.min(1f, 330 | // yScroll / titleStripMaxElevationScroll); 331 | // AndroidV21Helper.setElevation(titleStrip, elevation); 332 | // } 333 | } 334 | 335 | private float dp2px(final float dp) { 336 | final Resources r = getResources(); 337 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics()); 338 | } 339 | 340 | void log(final String msg) { 341 | if (BuildConfig.DEBUG) { 342 | Log.d(getClass().getSimpleName(), msg); 343 | } 344 | } 345 | 346 | @SuppressLint("MissingSuperCall") 347 | @Override 348 | protected void onSaveInstanceState(@NonNull final Bundle outState) { 349 | // Verhinden, dass der ViewPager seine Position speichern. Wir wollen 350 | // immer bei heute beginnen. 351 | } 352 | 353 | } 354 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/MensaWidgetProvider.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.app.PendingIntent; 4 | import android.appwidget.AppWidgetManager; 5 | import android.appwidget.AppWidgetProvider; 6 | import android.content.ComponentName; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.net.Uri; 10 | import android.preference.PreferenceManager; 11 | import android.util.Log; 12 | import android.widget.RemoteViews; 13 | 14 | import com.cypressworks.mensaplan.planmanager.MensaDropdownAdapter; 15 | 16 | import java.text.SimpleDateFormat; 17 | import java.util.Date; 18 | import java.util.Locale; 19 | 20 | /** 21 | * @author Kirill Rakhman 22 | */ 23 | public class MensaWidgetProvider extends AppWidgetProvider { 24 | 25 | private static final SimpleDateFormat weekDayDateFormat = new SimpleDateFormat("E (dd.MM.)", 26 | Locale.GERMANY); 27 | 28 | private static final String ACTION_RELOAD = "com.cypressworks.mensaplan.REFRESH"; 29 | 30 | @Override 31 | public void onReceive(final Context context, final Intent intent) { 32 | final String action = intent.getAction(); 33 | 34 | if (action != null && action.equals(ACTION_RELOAD)) { 35 | log("Reloading data for widget"); 36 | 37 | PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("reload", 38 | true).putBoolean( 39 | "dayChanged", true).apply(); 40 | 41 | updateWidgets(context); 42 | } else { 43 | super.onReceive(context, intent); 44 | } 45 | } 46 | 47 | @Override 48 | public void onUpdate( 49 | final Context context, final AppWidgetManager appWidgetManager, 50 | final int[] appWidgetIds) { 51 | // update each of the app widgets with the remote adapter 52 | for (final int appWidgetId : appWidgetIds) { 53 | final RemoteViews rv = new RemoteViews(context.getPackageName(), 54 | R.layout.widget_layout); 55 | 56 | // header 57 | final String mensaName = MensaDropdownAdapter.getManagerFromPreferences( 58 | context).getFullProviderName(); 59 | rv.setTextViewText(R.id.textViewMensa, mensaName); 60 | 61 | rv.setTextViewText(R.id.textViewDay, weekDayDateFormat.format(new Date())); 62 | 63 | final Intent refreshIntent = new Intent(context, getClass()); 64 | refreshIntent.setAction(ACTION_RELOAD); 65 | final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(context, 0, 66 | refreshIntent, 67 | PendingIntent.FLAG_IMMUTABLE); 68 | rv.setOnClickPendingIntent(R.id.widgetButtonRefresh, refreshPendingIntent); 69 | 70 | // reload button 71 | final Intent mainActIntent = new Intent(context, MainActivity.class); 72 | final PendingIntent mainPendingIntent = PendingIntent.getActivity(context, 0, 73 | mainActIntent, 74 | PendingIntent.FLAG_IMMUTABLE); 75 | rv.setOnClickPendingIntent(R.id.LinearLayoutHeader, mainPendingIntent); 76 | 77 | // list 78 | final Intent widgetServiceIntent = new Intent(context, MensaWidgetService.class); 79 | widgetServiceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 80 | widgetServiceIntent.setData( 81 | Uri.parse(widgetServiceIntent.toUri(Intent.URI_INTENT_SCHEME))); 82 | rv.setRemoteAdapter(appWidgetId, R.id.listViewWidget, widgetServiceIntent); 83 | 84 | appWidgetManager.updateAppWidget(appWidgetId, rv); 85 | } 86 | super.onUpdate(context, appWidgetManager, appWidgetIds); 87 | } 88 | 89 | void log(final String msg) { 90 | if (BuildConfig.DEBUG) { 91 | Log.d(getClass().getSimpleName(), msg); 92 | } 93 | } 94 | 95 | public static void updateWidgets(final Context c) { 96 | 97 | final AppWidgetManager widgetManager = AppWidgetManager.getInstance(c); 98 | final int[] ids = widgetManager.getAppWidgetIds( 99 | new ComponentName(c, MensaWidgetProvider.class)); 100 | 101 | final Intent intent = new Intent(c, MensaWidgetProvider.class); 102 | intent.setAction("android.appwidget.action.APPWIDGET_UPDATE"); 103 | intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids); 104 | 105 | c.sendBroadcast(intent); 106 | 107 | widgetManager.notifyAppWidgetViewDataChanged(ids, R.id.listViewWidget); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/MensaWidgetService.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.graphics.Color; 7 | import android.preference.PreferenceManager; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.widget.RemoteViews; 11 | import android.widget.RemoteViewsService; 12 | 13 | import com.cypressworks.mensaplan.food.Line; 14 | import com.cypressworks.mensaplan.food.Meal; 15 | import com.cypressworks.mensaplan.food.Plan; 16 | import com.cypressworks.mensaplan.food.likes.LikeManager; 17 | import com.cypressworks.mensaplan.food.likes.LikeStatus; 18 | import com.cypressworks.mensaplan.planmanager.MensaDropdownAdapter; 19 | import com.cypressworks.mensaplan.planmanager.PlanManager; 20 | import com.cypressworks.mensaplan.util.StringUtils; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Calendar; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | import androidx.core.content.ContextCompat; 28 | 29 | /** 30 | * @author Kirill Rakhman 31 | */ 32 | public class MensaWidgetService extends RemoteViewsService { 33 | 34 | @Override 35 | public RemoteViewsFactory onGetViewFactory(final Intent intent) { 36 | return new MensaRemoteViewsFactory(getApplicationContext()); 37 | } 38 | 39 | static class MensaRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { 40 | private final Context c; 41 | private List items = Collections.emptyList(); 42 | private final SharedPreferences prefs; 43 | private final LikeManager likeManager; 44 | 45 | MensaRemoteViewsFactory(final Context c) { 46 | this.c = c; 47 | this.prefs = PreferenceManager.getDefaultSharedPreferences(c); 48 | likeManager = new LikeManager(c); 49 | } 50 | 51 | @Override 52 | public void onCreate() { 53 | } 54 | 55 | @Override 56 | public void onDestroy() { 57 | } 58 | 59 | @Override 60 | public void onDataSetChanged() { 61 | log("onDataSetChanged"); 62 | final PlanManager manager = MensaDropdownAdapter.getManagerFromPreferences(c); 63 | 64 | boolean reload = false; 65 | if (prefs.getBoolean("reload", false)) { 66 | log("Requesting fresh data for widget list"); 67 | prefs.edit().putBoolean("reload", false).apply(); 68 | reload = true; 69 | } 70 | 71 | final Plan plan = manager.getPlan(Calendar.getInstance(), reload); 72 | 73 | final List newItems = new ArrayList<>(); 74 | for (final Line line : plan) { 75 | newItems.add(line); 76 | for (final Meal meal : line) { 77 | newItems.add(meal); 78 | } 79 | } 80 | 81 | log(newItems.size() + " items"); 82 | 83 | this.items = newItems; 84 | } 85 | 86 | @Override 87 | public int getCount() { 88 | if (items.isEmpty()) { 89 | return 1; 90 | } else { 91 | return items.size(); 92 | } 93 | } 94 | 95 | @Override 96 | public RemoteViews getViewAt(final int position) { 97 | log("Get widget view at " + position); 98 | final RemoteViews rv; 99 | 100 | if (items.isEmpty()) { 101 | rv = new RemoteViews(c.getPackageName(), android.R.layout.simple_list_item_1); 102 | rv.setTextViewText(android.R.id.text1, c.getString(R.string.no_plan_header)); 103 | } else { 104 | 105 | final Object object = items.get(position); 106 | if (object instanceof Line) { 107 | final Line line = (Line) object; 108 | 109 | rv = new RemoteViews(c.getPackageName(), R.layout.widget_list_header); 110 | rv.setTextViewText(R.id.list_header_title, line.getName()); 111 | } else if (object instanceof Meal) { 112 | final Meal meal = (Meal) object; 113 | 114 | rv = new RemoteViews(c.getPackageName(), R.layout.widget_list_item); 115 | rv.setTextViewText(R.id.textName, StringUtils.sanitize(meal.getMeal())); 116 | final String dish = StringUtils.sanitize(meal.getDish()); 117 | if (!"".equals(dish)) { 118 | rv.setTextViewText(R.id.textSubName, dish); 119 | rv.setViewVisibility(R.id.textSubName, View.VISIBLE); 120 | } else { 121 | rv.setViewVisibility(R.id.textSubName, View.GONE); 122 | } 123 | rv.setTextViewText(R.id.textPrice, meal.getPrice()); 124 | 125 | rv.setViewVisibility(R.id.imageBio, meal.isBio() ? View.VISIBLE : View.GONE); 126 | rv.setViewVisibility(R.id.imageFish, meal.isFish() ? View.VISIBLE : View.GONE); 127 | rv.setViewVisibility(R.id.imagePork, meal.isPork() ? View.VISIBLE : View.GONE); 128 | rv.setViewVisibility(R.id.imageCow, meal.isCow() ? View.VISIBLE : View.GONE); 129 | rv.setViewVisibility(R.id.imageCow_aw, 130 | meal.isCow_aw() ? View.VISIBLE : View.GONE); 131 | rv.setViewVisibility(R.id.imageVegan, 132 | meal.isVegan() ? View.VISIBLE : View.GONE); 133 | rv.setViewVisibility(R.id.imageVeg, meal.isVeg() ? View.VISIBLE : View.GONE); 134 | switch (likeManager.getLikeStatus(meal.getMeal())) { 135 | case LikeStatus.LIKED: { 136 | rv.setInt(R.id.LinearLayout1, "setBackgroundColor", ContextCompat.getColor(c, R.color.transparent_green)); 137 | break; 138 | } 139 | case LikeStatus.DISLIKED: { 140 | rv.setInt(R.id.LinearLayout1, "setBackgroundColor", ContextCompat.getColor(c, R.color.transparent_red)); 141 | break; 142 | } 143 | case LikeStatus.NO_LIKE_INFO: { 144 | rv.setInt(R.id.LinearLayout1, "setBackgroundColor", Color.TRANSPARENT); 145 | break; 146 | } 147 | default: 148 | } 149 | } else { 150 | throw new AssertionError(); 151 | } 152 | } 153 | 154 | return rv; 155 | } 156 | 157 | @Override 158 | public RemoteViews getLoadingView() { 159 | return null; 160 | } 161 | 162 | @Override 163 | public int getViewTypeCount() { 164 | return 2; 165 | } 166 | 167 | @Override 168 | public long getItemId(final int position) { 169 | return position; 170 | } 171 | 172 | @Override 173 | public boolean hasStableIds() { 174 | return true; 175 | } 176 | 177 | void log(final String msg) { 178 | if (BuildConfig.DEBUG) { 179 | Log.d(getClass().getSimpleName(), msg); 180 | } 181 | } 182 | 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.app.Application; 4 | 5 | /** 6 | * @author Kirill Rakhman 7 | */ 8 | public class MyApplication extends Application { 9 | 10 | public static MyApplication instance; 11 | 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | 16 | instance = this; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/OnPageChangeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import androidx.viewpager.widget.ViewPager.OnPageChangeListener; 4 | 5 | /** 6 | * @author Kirill Rakhman 7 | */ 8 | abstract class OnPageChangeAdapter implements OnPageChangeListener { 9 | 10 | @Override 11 | public void onPageScrollStateChanged(final int arg0) { 12 | } 13 | 14 | @Override 15 | public void onPageScrolled(final int arg0, final float arg1, final int arg2) { 16 | } 17 | 18 | @Override 19 | public void onPageSelected(final int arg0) { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/PlanFragmentPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.content.Context; 4 | import android.os.Parcelable; 5 | import android.util.Log; 6 | 7 | import com.cypressworks.mensaplan.food.PlanFragment; 8 | import com.cypressworks.mensaplan.planmanager.PlanManager; 9 | 10 | import java.text.SimpleDateFormat; 11 | import java.util.Calendar; 12 | import java.util.Locale; 13 | 14 | import androidx.annotation.NonNull; 15 | import androidx.fragment.app.Fragment; 16 | import androidx.fragment.app.FragmentManager; 17 | import androidx.fragment.app.FragmentStatePagerAdapter; 18 | 19 | /** 20 | * @author Kirill Rakhman 21 | */ 22 | class PlanFragmentPagerAdapter extends FragmentStatePagerAdapter { 23 | 24 | private static final SimpleDateFormat weekDayDateFormat = new SimpleDateFormat("E (dd.MM.)", 25 | Locale.GERMANY); 26 | private static final SimpleDateFormat weekDayFormat = new SimpleDateFormat("E", Locale.GERMANY); 27 | 28 | private final Calendar startDate; 29 | private final Class managerClass; 30 | 31 | private static final int daysInFuture = 7; 32 | private static final int daysInPast = 7; 33 | 34 | private String[] names; 35 | 36 | public PlanFragmentPagerAdapter( 37 | final Context context, final FragmentManager fm, final Calendar startDate, 38 | final Class managerClass) { 39 | super(fm); 40 | 41 | this.startDate = startDate; 42 | this.managerClass = managerClass; 43 | 44 | generateTitles(context); 45 | } 46 | 47 | @NonNull 48 | @Override 49 | public Fragment getItem(final int position) { 50 | 51 | final Calendar cal = getDateAtPosition(position); 52 | return PlanFragment.getInstance(cal, managerClass); 53 | } 54 | 55 | @Override 56 | public int getCount() { 57 | return daysInPast + 1 + daysInFuture; 58 | } 59 | 60 | @Override 61 | public CharSequence getPageTitle(final int position) { 62 | return names[position]; 63 | } 64 | 65 | @Override 66 | public Parcelable saveState() { 67 | return null; 68 | } 69 | 70 | Calendar getDateAtPosition(final int position) { 71 | // relative position zu Mitte bestimmen 72 | final int relativeToToday = position - daysInPast; 73 | 74 | // Calendar Instanz für diesen Tag 75 | final Calendar cal = (Calendar) startDate.clone(); 76 | cal.add(Calendar.DAY_OF_MONTH, relativeToToday); 77 | return cal; 78 | } 79 | 80 | public static int getDefaultPosition() { 81 | return daysInPast; 82 | } 83 | 84 | private void generateTitles(final Context context) { 85 | final int count = getCount(); 86 | this.names = new String[count]; 87 | 88 | final Calendar today = Calendar.getInstance(); 89 | 90 | for (int i = 0; i < count; i++) { 91 | final Calendar dateAtPosiion = getDateAtPosition(i); 92 | 93 | if (isToday(dateAtPosiion, today)) { 94 | names[i] = weekDayFormat.format(dateAtPosiion.getTime()) + " (" + context.getString( 95 | R.string.date_today) + ")"; 96 | } else { 97 | names[i] = weekDayDateFormat.format(dateAtPosiion.getTime()); 98 | } 99 | } 100 | } 101 | 102 | private static boolean isToday(final Calendar cal, final Calendar today) { 103 | 104 | return (cal.get(Calendar.ERA) == today.get(Calendar.ERA) // 105 | && cal.get(Calendar.YEAR) == today.get(Calendar.YEAR) // 106 | && cal.get(Calendar.DAY_OF_YEAR) == today.get(Calendar.DAY_OF_YEAR)); 107 | } 108 | 109 | protected void log(final String msg) { 110 | if (BuildConfig.DEBUG) { 111 | Log.d(getClass().getSimpleName(), msg); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/RangeTimePickerDialog.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.app.TimePickerDialog; 4 | import android.content.Context; 5 | import android.widget.TimePicker; 6 | 7 | import java.lang.reflect.Field; 8 | import java.text.DateFormat; 9 | import java.util.Calendar; 10 | 11 | /** 12 | * A time dialog that allows setting a min and max time. 13 | */ 14 | public class RangeTimePickerDialog extends TimePickerDialog { 15 | 16 | private int minHour = -1; 17 | private int minMinute = -1; 18 | 19 | private int maxHour = 25; 20 | private int maxMinute = 25; 21 | 22 | private int currentHour; 23 | private int currentMinute; 24 | 25 | private Calendar calendar = Calendar.getInstance(); 26 | private DateFormat dateFormat; 27 | 28 | public RangeTimePickerDialog( 29 | Context context, OnTimeSetListener callBack, int hourOfDay, int minute, 30 | boolean is24HourView) { 31 | super(context, callBack, hourOfDay, minute, is24HourView); 32 | currentHour = hourOfDay; 33 | currentMinute = minute; 34 | dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT); 35 | 36 | try { 37 | Class superclass = getClass().getSuperclass(); 38 | Field mTimePickerField = superclass.getDeclaredField("mTimePicker"); 39 | mTimePickerField.setAccessible(true); 40 | TimePicker mTimePicker = (TimePicker) mTimePickerField.get(this); 41 | mTimePicker.setOnTimeChangedListener(this); 42 | } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ignored) { 43 | } 44 | } 45 | 46 | public void setMin(int hour, int minute) { 47 | minHour = hour; 48 | minMinute = minute; 49 | } 50 | 51 | public void setMax(int hour, int minute) { 52 | maxHour = hour; 53 | maxMinute = minute; 54 | } 55 | 56 | @Override 57 | public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { 58 | 59 | boolean validTime = true; 60 | if (hourOfDay < minHour || (hourOfDay == minHour && minute < minMinute)) { 61 | validTime = false; 62 | } 63 | 64 | if (hourOfDay > maxHour || (hourOfDay == maxHour && minute > maxMinute)) { 65 | validTime = false; 66 | } 67 | 68 | if (validTime) { 69 | currentHour = hourOfDay; 70 | currentMinute = minute; 71 | } 72 | 73 | updateTime(currentHour, currentMinute); 74 | updateDialogTitle(view, currentHour, currentMinute); 75 | } 76 | 77 | private void updateDialogTitle(TimePicker timePicker, int hourOfDay, int minute) { 78 | calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); 79 | calendar.set(Calendar.MINUTE, minute); 80 | String title = dateFormat.format(calendar.getTime()); 81 | setTitle(title); 82 | } 83 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/ScrollListener.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | /** 4 | * Created by Kirill on 28.10.2014. 5 | */ 6 | public interface ScrollListener { 7 | void onScrolled(int yScroll); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/Tools.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.io.ObjectInputStream; 8 | import java.io.ObjectOutputStream; 9 | import java.io.Serializable; 10 | 11 | /** 12 | * @author Kirill Rakhman 13 | */ 14 | public class Tools { 15 | private Tools() { 16 | } 17 | 18 | public static void deleteFolder(final File folder) { 19 | final File[] files = folder.listFiles(); 20 | if (files != null) { // some JVMs return null for empty dirs 21 | for (final File f : files) { 22 | if (f.isDirectory()) { 23 | deleteFolder(f); 24 | } else { 25 | f.delete(); 26 | } 27 | } 28 | } 29 | folder.delete(); 30 | } 31 | 32 | public static void writeObject( 33 | final Serializable object, final File target) throws IOException { 34 | 35 | if (target.exists()) { 36 | target.delete(); 37 | } 38 | 39 | final FileOutputStream fos = new FileOutputStream(target); 40 | final ObjectOutputStream oos = new ObjectOutputStream(fos); 41 | 42 | oos.writeObject(object); 43 | oos.flush(); 44 | oos.close(); 45 | 46 | } 47 | 48 | @SuppressWarnings("unchecked") 49 | public static T readObject( 50 | final File file) throws IOException, ClassNotFoundException { 51 | 52 | final FileInputStream fis = new FileInputStream(file); 53 | final ObjectInputStream ois = new ObjectInputStream(fis); 54 | final Object o = ois.readObject(); 55 | ois.close(); 56 | 57 | return (T) o; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/Views.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.app.Activity; 4 | import android.view.View; 5 | 6 | /** 7 | * @author Kirill Rakhman 8 | */ 9 | class Views { 10 | private Views() { 11 | } 12 | 13 | @SuppressWarnings("unchecked") 14 | public static T findViewById(final View v, final int id) { 15 | return (T) v.findViewById(id); 16 | } 17 | 18 | @SuppressWarnings("unchecked") 19 | public static T findViewById(final Activity a, final int id) { 20 | return (T) a.findViewById(id); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/WebCamActivity.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan; 2 | 3 | import android.os.Bundle; 4 | import android.view.MenuItem; 5 | import android.webkit.WebView; 6 | 7 | import androidx.appcompat.app.ActionBar; 8 | import androidx.appcompat.app.AppCompatActivity; 9 | 10 | /** 11 | * @author Kirill Rakhman 12 | */ 13 | public class WebCamActivity extends AppCompatActivity { 14 | private static final String webcamURL = "https://www.sw-ka.de/de/essen/livecams/popup/?page=1"; 15 | 16 | @Override 17 | protected void onCreate(final Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.webcam_activity); 20 | final WebView w = findViewById(R.id.webkitWebView1); 21 | w.getSettings().setUserAgentString( 22 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0"); 23 | w.loadUrl(webcamURL); 24 | 25 | final ActionBar actionBar = getSupportActionBar(); 26 | actionBar.setIcon(R.drawable.icon_white); 27 | actionBar.setHomeButtonEnabled(true); 28 | actionBar.setDisplayHomeAsUpEnabled(true); 29 | 30 | } 31 | 32 | @Override 33 | public boolean onOptionsItemSelected(final MenuItem item) { 34 | if (item.getItemId() == android.R.id.home) { 35 | finish(); 36 | return true; 37 | } 38 | 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/food/Line.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.food; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | /** 11 | * Klasse, die eine Linie in einer Mensa darstellt. Verwaltet die Speisen und 12 | * ihre Preise. 13 | * 14 | * @author Kirill Rakhman 15 | */ 16 | public class Line implements Serializable, Iterable { 17 | private static final long serialVersionUID = 4830649237876805212L; 18 | private final List meals; 19 | private final String name; 20 | 21 | /** 22 | * Erzeugt ein Objekt der Klasse {@link Line} ohne Eintr�ge. 23 | * 24 | * @param name Der Name der Linie. 25 | */ 26 | public Line(final String name) { 27 | this(new ArrayList<>(), name); 28 | } 29 | 30 | /** 31 | * Erzeugt ein Objekt der Klasse {@link Line}. 32 | * 33 | * @param meals Die Speisen der Linie. 34 | * @param name Der Name der Linie. 35 | */ 36 | private Line(final List meals, final String name) { 37 | this.name = name; 38 | this.meals = meals; 39 | } 40 | 41 | /** 42 | * Gibt die Speisen zur�ck. 43 | * 44 | * @return die Speisen als {@link List}. 45 | */ 46 | List getMeals() { 47 | return meals; 48 | } 49 | 50 | public void addMeal(final Meal meal) { 51 | meals.add(meal); 52 | } 53 | 54 | /** 55 | * Gibt den Namen der Linie zur�ck. 56 | * 57 | * @return der Name als {@link String} 58 | */ 59 | public String getName() { 60 | return name; 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | final int prime = 31; 66 | int result = 1; 67 | result = prime * result + ((meals == null) ? 0 : meals.hashCode()); 68 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 69 | return result; 70 | } 71 | 72 | @Override 73 | public boolean equals(final Object obj) { 74 | if (this == obj) { 75 | return true; 76 | } 77 | if (obj == null) { 78 | return false; 79 | } 80 | if (!(obj instanceof Line)) { 81 | return false; 82 | } 83 | final Line other = (Line) obj; 84 | if (meals == null) { 85 | if (other.meals != null) { 86 | return false; 87 | } 88 | } else if (!meals.equals(other.meals)) { 89 | return false; 90 | } 91 | if (name == null) { 92 | return other.name == null; 93 | } else 94 | return name.equals(other.name); 95 | } 96 | 97 | @NonNull 98 | @Override 99 | public Iterator iterator() { 100 | return getMeals().iterator(); 101 | } 102 | 103 | public int size() { 104 | return meals.size(); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/food/Meal.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.food; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Klasse, die ein Gericht darstellt. 7 | * 8 | * @author Kirill Rakhman 9 | */ 10 | public class Meal implements Serializable { 11 | 12 | public static final String NO_MEAL = "Linie geschlossen"; 13 | 14 | private static final long serialVersionUID = -6729670284326867259L; 15 | private final String meal; 16 | private final String dish; 17 | private final String price; 18 | 19 | private boolean bio = false; 20 | private boolean fish = false; 21 | private boolean pork = false; 22 | private boolean cow = false; 23 | private boolean cow_aw = false; 24 | private boolean vegan = false; 25 | private boolean veg = false; 26 | private boolean likeable = true; 27 | 28 | public Meal(final String meal, final String dish, final String price) { 29 | super(); 30 | this.meal = meal; 31 | this.dish = dish; 32 | this.price = price; 33 | } 34 | 35 | public String getMeal() { 36 | return meal; 37 | } 38 | 39 | public String getDish() { 40 | return dish; 41 | } 42 | 43 | public String getName() { 44 | if (meal != null || dish != null) { 45 | return (meal + " " + dish).trim(); 46 | } else { 47 | return "-"; 48 | } 49 | } 50 | 51 | /** 52 | * Gibt den Preis zur�ck. 53 | * 54 | * @return der Preis als {@link String} 55 | */ 56 | public String getPrice() { 57 | return price; 58 | } 59 | 60 | public boolean isBio() { 61 | return bio; 62 | } 63 | 64 | public void setBio(final boolean bio) { 65 | this.bio = bio; 66 | } 67 | 68 | public boolean isFish() { 69 | return fish; 70 | } 71 | 72 | public void setFish(final boolean fish) { 73 | this.fish = fish; 74 | } 75 | 76 | public boolean isPork() { 77 | return pork; 78 | } 79 | 80 | public void setPork(final boolean pork) { 81 | this.pork = pork; 82 | } 83 | 84 | public boolean isCow() { 85 | return cow; 86 | } 87 | 88 | public void setCow(final boolean cow) { 89 | this.cow = cow; 90 | } 91 | 92 | public boolean isCow_aw() { 93 | return cow_aw; 94 | } 95 | 96 | public void setCow_aw(final boolean cow_aw) { 97 | this.cow_aw = cow_aw; 98 | } 99 | 100 | public boolean isVegan() { 101 | return vegan; 102 | } 103 | 104 | public void setVegan(final boolean vegan) { 105 | this.vegan = vegan; 106 | } 107 | 108 | public boolean isVeg() { 109 | return veg; 110 | } 111 | 112 | public void setVeg(final boolean veg) { 113 | this.veg = veg; 114 | } 115 | 116 | public static long getSerialversionuid() { 117 | return serialVersionUID; 118 | } 119 | 120 | public void setLikeable(final boolean likeable) { 121 | this.likeable = likeable; 122 | } 123 | 124 | public boolean isLikeable() { 125 | return likeable; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/food/Plan.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.food; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | /** 11 | * Klasse, die einen Speiseplan darstellt. Verwaltet Linien und ihre Gerichte. 12 | * 13 | * @author Kirill Rakhman 14 | */ 15 | public class Plan implements Serializable, Iterable { 16 | private static final long serialVersionUID = -1058026007324238622L; 17 | private final List lines; 18 | private int totalCount; 19 | private final long timestamp; 20 | 21 | /** 22 | * Erzeugt einen leeren Plan ohne Mahlzeiten 23 | */ 24 | public Plan() { 25 | this(new ArrayList<>()); 26 | } 27 | 28 | /** 29 | * Erzeugt einen neuen Plan aus den gegebenen Linien. 30 | */ 31 | public Plan(final List lines) { 32 | this.lines = lines; 33 | 34 | for (final Line line : lines) { 35 | totalCount++; 36 | for (@SuppressWarnings("unused") final Meal meal : line) { 37 | totalCount++; 38 | } 39 | } 40 | 41 | timestamp = System.currentTimeMillis(); 42 | } 43 | 44 | /** 45 | * Gibt die Linien zur�ck. 46 | * 47 | * @return die Linien als {@link List} 48 | */ 49 | List getLines() { 50 | return lines; 51 | } 52 | 53 | /** 54 | * Gibt an, ob der Plan leer ist. 55 | * 56 | * @return true, falls er keine Linien enth�lt. 57 | */ 58 | public boolean isEmpty() { 59 | return lines.isEmpty(); 60 | } 61 | 62 | public int getTotalItemsCount() { 63 | return totalCount; 64 | } 65 | 66 | public long getTimestamp() { 67 | return timestamp; 68 | } 69 | 70 | public int getAgeInDays() { 71 | final long diff = System.currentTimeMillis() - timestamp; 72 | 73 | return (int) (diff / (1000L * 60 * 60 * 24)); 74 | } 75 | 76 | @NonNull 77 | @Override 78 | public Iterator iterator() { 79 | return getLines().iterator(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/food/PlanAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.food; 2 | 3 | import android.app.backup.BackupManager; 4 | import android.content.Context; 5 | import android.graphics.Color; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.BaseAdapter; 10 | import android.widget.ImageButton; 11 | import android.widget.ImageView; 12 | import android.widget.TextView; 13 | 14 | import com.cypressworks.mensaplan.food.likes.LikeManager; 15 | import com.cypressworks.mensaplan.R; 16 | import com.cypressworks.mensaplan.food.likes.LikeStatus; 17 | import com.cypressworks.mensaplan.util.StringUtils; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * Adapter f�r Darstellung von Eintr�gen vom Typ {@link Meal}. 24 | * 25 | * @author Kirill Rakhman 26 | */ 27 | class PlanAdapter extends BaseAdapter { 28 | 29 | private final Context c; 30 | 31 | private final LikeManager likeManager; 32 | 33 | private final List items; 34 | private final int[] types; 35 | 36 | private static final int TYPE_LINE = 0, TYPE_MEAL = 1; 37 | 38 | public PlanAdapter(final Context context, final Plan plan) { 39 | this.c = context; 40 | likeManager = new LikeManager(context); 41 | items = new ArrayList<>(); 42 | types = new int[plan.getTotalItemsCount()]; 43 | int index = 0; 44 | for (final Line line : plan) { 45 | items.add(line); 46 | types[index++] = TYPE_LINE; 47 | for (final Meal meal : line) { 48 | items.add(meal); 49 | types[index++] = TYPE_MEAL; 50 | } 51 | } 52 | 53 | } 54 | 55 | @Override 56 | public View getView(final int position, View v, final ViewGroup parent) { 57 | 58 | if (types[position] == TYPE_LINE) { 59 | // Header 60 | final HeaderHolder tag; 61 | if (v == null) { 62 | final LayoutInflater vi = LayoutInflater.from(c); 63 | v = vi.inflate(R.layout.list_header, parent, false); 64 | tag = new HeaderHolder(v); 65 | v.setTag(tag); 66 | } else { 67 | tag = (HeaderHolder) v.getTag(); 68 | } 69 | final Line line = (Line) items.get(position); 70 | if (position == 0) { 71 | tag.separator.setVisibility(View.INVISIBLE); 72 | } else { 73 | tag.separator.setVisibility(View.VISIBLE); 74 | } 75 | tag.name.setText(line.getName()); 76 | } else { 77 | // Item 78 | final ItemHolder tag; 79 | if (v == null) { 80 | final LayoutInflater vi = LayoutInflater.from(c); 81 | v = vi.inflate(R.layout.list_item, parent, false); 82 | tag = new ItemHolder(v); 83 | v.setTag(tag); 84 | } else { 85 | tag = (ItemHolder) v.getTag(); 86 | } 87 | final Meal meal = (Meal) items.get(position); 88 | tag.name.setText(StringUtils.sanitize(meal.getMeal())); 89 | 90 | final String dish = StringUtils.sanitize(meal.getDish()); 91 | tag.subName.setText(dish); 92 | tag.subName.setVisibility("".equals(dish) ? View.GONE : View.VISIBLE); 93 | 94 | tag.price.setText(meal.getPrice()); 95 | if (meal.isLikeable()) { 96 | tag.likeButton.setVisibility(View.VISIBLE); 97 | } else { 98 | tag.likeButton.setVisibility(View.INVISIBLE); 99 | } 100 | 101 | tag.bio.setVisibility(meal.isBio() ? View.VISIBLE : View.GONE); 102 | tag.fish.setVisibility(meal.isFish() ? View.VISIBLE : View.GONE); 103 | tag.pork.setVisibility(meal.isPork() ? View.VISIBLE : View.GONE); 104 | tag.cow.setVisibility(meal.isCow() ? View.VISIBLE : View.GONE); 105 | tag.cow_aw.setVisibility(meal.isCow_aw() ? View.VISIBLE : View.GONE); 106 | tag.vegan.setVisibility(meal.isVegan() ? View.VISIBLE : View.GONE); 107 | tag.veg.setVisibility(meal.isVeg() ? View.VISIBLE : View.GONE); 108 | tag.updateBackground(); 109 | 110 | // if (position % 2 == 1) { 111 | // tag.layout.setBackgroundColor(Color.LTGRAY); 112 | // } else { 113 | // tag.layout.setBackgroundColor(Color.TRANSPARENT); 114 | // } 115 | } 116 | 117 | return v; 118 | } 119 | 120 | @Override 121 | public int getCount() { 122 | return items.size(); 123 | } 124 | 125 | @Override 126 | public Object getItem(final int position) { 127 | return items.get(position); 128 | } 129 | 130 | @Override 131 | public long getItemId(final int position) { 132 | return position; 133 | } 134 | 135 | @Override 136 | public boolean hasStableIds() { 137 | return true; 138 | } 139 | 140 | @Override 141 | public int getItemViewType(final int position) { 142 | return types[position]; 143 | } 144 | 145 | @Override 146 | public int getViewTypeCount() { 147 | return 2; 148 | } 149 | 150 | @Override 151 | public boolean areAllItemsEnabled() { 152 | return false; 153 | } 154 | 155 | @Override 156 | public boolean isEnabled(final int position) { 157 | return types[position] == TYPE_MEAL; 158 | } 159 | 160 | private static class HeaderHolder { 161 | final View separator; 162 | final TextView name; 163 | 164 | HeaderHolder(final View v) { 165 | separator = v.findViewById(R.id.list_header_separator); 166 | name = v.findViewById(R.id.list_header_title); 167 | } 168 | } 169 | 170 | private class ItemHolder { 171 | final TextView name; 172 | final TextView subName; 173 | final TextView price; 174 | final ImageView bio, fish, pork, cow, cow_aw, vegan, veg; 175 | final ImageButton likeButton; 176 | final View layout; 177 | 178 | ItemHolder(final View v) { 179 | layout = v; 180 | name = v.findViewById(R.id.textName); 181 | subName = v.findViewById(R.id.textSubName); 182 | price = v.findViewById(R.id.textPrice); 183 | likeButton = v.findViewById(R.id.buttonLikeStatus); 184 | likeButton.setOnClickListener(view -> { 185 | toggleLikeButton(); 186 | updateBackground(); 187 | (new BackupManager(v.getContext())).dataChanged(); 188 | }); 189 | bio = v.findViewById(R.id.imageBio); 190 | fish = v.findViewById(R.id.imageFish); 191 | pork = v.findViewById(R.id.imagePork); 192 | cow = v.findViewById(R.id.imageCow); 193 | cow_aw = v.findViewById(R.id.imageCow_aw); 194 | vegan = v.findViewById(R.id.imageVegan); 195 | veg = v.findViewById(R.id.imageVeg); 196 | } 197 | 198 | private void toggleLikeButton() { 199 | likeManager.toggle(name.getText().toString()); 200 | } 201 | 202 | private void updateBackground() { 203 | switch (likeManager.getLikeStatus(name.getText().toString())) { 204 | case LikeStatus.LIKED: { 205 | likeButton.setImageResource(R.drawable.thumbs_up); 206 | layout.setBackgroundColor(layout.getContext().getResources().getColor(R.color.transparent_green)); 207 | likeButton.setColorFilter(Color.GREEN); 208 | break; 209 | } 210 | case LikeStatus.DISLIKED: { 211 | likeButton.setImageResource(R.drawable.thumbs_down); 212 | layout.setBackgroundColor(layout.getContext().getResources().getColor(R.color.transparent_red)); 213 | likeButton.setColorFilter(Color.RED); 214 | break; 215 | } 216 | case LikeStatus.NO_LIKE_INFO: { 217 | likeButton.setImageResource(R.drawable.thumbs_undecided); 218 | layout.setBackgroundColor(Color.TRANSPARENT); 219 | likeButton.setColorFilter(layout.getContext().getResources().getColor(R.color.dark_grey)); 220 | break; 221 | } 222 | default: 223 | } 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/food/PlanFragment.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.food; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.AsyncTask; 8 | import android.os.Build.VERSION; 9 | import android.os.Bundle; 10 | import android.util.Log; 11 | import android.view.ContextMenu; 12 | import android.view.ContextMenu.ContextMenuInfo; 13 | import android.view.LayoutInflater; 14 | import android.view.Menu; 15 | import android.view.MenuInflater; 16 | import android.view.MenuItem; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.widget.AbsListView; 20 | import android.widget.AdapterView; 21 | import android.widget.AdapterView.OnItemClickListener; 22 | import android.widget.ListAdapter; 23 | import android.widget.ListView; 24 | 25 | import com.cypressworks.mensaplan.BuildConfig; 26 | import com.cypressworks.mensaplan.HappyCowActivity; 27 | import com.cypressworks.mensaplan.R; 28 | import com.cypressworks.mensaplan.ScrollListener; 29 | import com.cypressworks.mensaplan.planmanager.PlanManager; 30 | import com.nineoldandroids.animation.Animator; 31 | import com.nineoldandroids.animation.AnimatorListenerAdapter; 32 | import com.nineoldandroids.animation.ObjectAnimator; 33 | import com.nineoldandroids.view.ViewPropertyAnimator; 34 | 35 | import java.util.Calendar; 36 | 37 | import androidx.annotation.NonNull; 38 | import androidx.fragment.app.ListFragment; 39 | 40 | /** 41 | * @author Kirill Rakhman 42 | */ 43 | public class PlanFragment extends ListFragment implements OnItemClickListener { 44 | 45 | private boolean isVisibleToUser; 46 | 47 | public static PlanFragment getInstance( 48 | final Calendar cal, final Class managerClass) { 49 | final PlanFragment planFragment = new PlanFragment(); 50 | 51 | final Bundle bundle = new Bundle(); 52 | bundle.putSerializable("cal", cal); 53 | bundle.putSerializable("managerClass", managerClass); 54 | 55 | planFragment.setArguments(bundle); 56 | return planFragment; 57 | } 58 | 59 | private Class managerClass; 60 | private Calendar date; 61 | private PlanManager planManager; 62 | private MenuItem menuReload; 63 | 64 | private ListView listView; 65 | private ScrollListener scrollListener; 66 | 67 | @Override 68 | public void onCreate(final Bundle savedInstanceState) { 69 | super.onCreate(savedInstanceState); 70 | setHasOptionsMenu(true); 71 | } 72 | 73 | @Override 74 | public View onCreateView( 75 | @NonNull final LayoutInflater inflater, final ViewGroup container, 76 | final Bundle savedInstanceState) { 77 | ViewGroup view = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState); 78 | 79 | if (VERSION.SDK_INT < 21) { 80 | inflater.inflate(R.layout.view_shadow, view); 81 | } 82 | 83 | return view; 84 | } 85 | 86 | @Override 87 | public void onCreateOptionsMenu(@NonNull final Menu menu, final MenuInflater inflater) { 88 | inflater.inflate(R.menu.optionsmenu_frag, menu); 89 | menuReload = menu.findItem(R.id.reload); 90 | } 91 | 92 | @Override 93 | public boolean onOptionsItemSelected(final MenuItem item) { 94 | if (item.getItemId() == R.id.reload) { 95 | new PopulateListTask(getActivity(), date, true).execute(); 96 | return true; 97 | } 98 | 99 | return super.onOptionsItemSelected(item); 100 | } 101 | 102 | @SuppressWarnings("unchecked") 103 | @Override 104 | public void onViewCreated(@NonNull final View view, final Bundle savedInstanceState) { 105 | super.onViewCreated(view, savedInstanceState); 106 | final Context c = requireActivity(); 107 | 108 | listView = getListView(); 109 | // listView.setSelector(R.drawable.list_selector); 110 | listView.setDivider(null); 111 | listView.setDividerHeight(0); 112 | 113 | listView.setOnScrollListener(new AbsListView.OnScrollListener() { 114 | @Override 115 | public void onScrollStateChanged(final AbsListView view, final int scrollState) { 116 | } 117 | 118 | @Override 119 | public void onScroll( 120 | final AbsListView view, final int firstVisibleItem, final int visibleItemCount, 121 | final int totalItemCount) { 122 | if (scrollListener != null) { 123 | notifyScrollListener(); 124 | } 125 | } 126 | }); 127 | 128 | setEmptyText(c.getString(R.string.no_plan_header)); 129 | 130 | registerForContextMenu(listView); 131 | 132 | final Bundle args = getArguments(); 133 | date = (Calendar) args.getSerializable("cal"); 134 | managerClass = (Class) args.getSerializable("managerClass"); 135 | planManager = PlanManager.getInstance(managerClass, c); 136 | 137 | getListView().setOnItemClickListener(this); 138 | new PopulateListTask(c, date, false).execute(); 139 | } 140 | 141 | @Override 142 | public void onCreateContextMenu( 143 | @NonNull final ContextMenu menu, @NonNull final View v, final ContextMenuInfo menuInfo) { 144 | final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; 145 | 146 | // Gericht, auf das geklickt wurde 147 | final Object itemAtPosition = getListView().getItemAtPosition(info.position); 148 | 149 | if (!(itemAtPosition instanceof Meal)) { 150 | return; 151 | } 152 | 153 | final Meal m = (Meal) itemAtPosition; 154 | 155 | if (Meal.NO_MEAL.equals(m.getMeal())) { 156 | return; 157 | } 158 | 159 | // Contextmenü aus xml 160 | final android.view.MenuInflater inflater = requireActivity().getMenuInflater(); 161 | inflater.inflate(R.menu.mealcontextmenu, menu); 162 | 163 | // Titel des Menüs ist Name des Gerichts 164 | menu.setHeaderTitle(m.getName()); 165 | } 166 | 167 | @Override 168 | public boolean onContextItemSelected(@NonNull final android.view.MenuItem item) { 169 | 170 | final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 171 | String url; 172 | 173 | int itemId = item.getItemId(); 174 | if (itemId == R.id.goolemeal) { 175 | url = "http://www.google.com/m/search?q=%q%"; 176 | } else if (itemId == R.id.googlemailpic) { 177 | url = "http://www.google.com/m/search?q=%q%&tbm=isch"; 178 | } else { 179 | return true; 180 | } 181 | try { 182 | // Gericht, auf das geklickt wurde 183 | final Meal m = (Meal) getListView().getItemAtPosition(info.position); 184 | 185 | url = url.replace("%q%", m.getName()); 186 | 187 | final Intent i = new Intent("android.intent.action.VIEW", Uri.parse(url)); 188 | 189 | startActivity(i); 190 | 191 | return true; 192 | } catch (final Exception e) { 193 | e.printStackTrace(); 194 | return false; 195 | } 196 | 197 | } 198 | 199 | @Override 200 | public void setUserVisibleHint(final boolean isVisibleToUser) { 201 | this.isVisibleToUser = isVisibleToUser; 202 | super.setUserVisibleHint(isVisibleToUser); 203 | } 204 | 205 | @Override 206 | public void onResume() { 207 | super.onResume(); 208 | scrollListener = (ScrollListener) getActivity(); 209 | 210 | if (scrollListener != null) { 211 | notifyScrollListener(); 212 | } 213 | } 214 | 215 | @Override 216 | public void onPause() { 217 | scrollListener = null; 218 | super.onPause(); 219 | } 220 | 221 | private void notifyScrollListener() { 222 | if (!isVisibleToUser || listView.getChildCount() == 0) { 223 | return; 224 | } 225 | 226 | int firstVisiblePosition = listView.getFirstVisiblePosition(); 227 | 228 | View firstChild = listView.getChildAt(0); 229 | if (firstVisiblePosition == 0) { 230 | scrollListener.onScrolled(-firstChild.getTop()); 231 | } else { 232 | scrollListener.onScrolled( 233 | -firstChild.getTop() + firstChild.getHeight() * firstVisiblePosition); 234 | } 235 | } 236 | 237 | private final class PopulateListTask extends AsyncTask { 238 | private final Calendar calendar; 239 | private final Context c; 240 | private final boolean reload; 241 | 242 | private PopulateListTask(final Context c, final Calendar calendar, final boolean reload) { 243 | this.c = c; 244 | this.calendar = calendar; 245 | this.reload = reload; 246 | } 247 | 248 | @Override 249 | protected void onPreExecute() { 250 | if (menuReload != null) { 251 | menuReload.setActionView(R.layout.actionitem_progress); 252 | } 253 | 254 | final Plan cachedPlan = planManager.getPlanFromCache(date); 255 | 256 | if (!reload && cachedPlan != null && !cachedPlan.isEmpty()) { 257 | setListAdapter(new PlanAdapter(c, cachedPlan)); 258 | } 259 | } 260 | 261 | @Override 262 | protected ListAdapter doInBackground(final Boolean... params) { 263 | 264 | final Plan plan = planManager.getPlan(calendar, reload); 265 | 266 | return new PlanAdapter(c, plan); 267 | } 268 | 269 | @Override 270 | protected void onPostExecute(final ListAdapter result) { 271 | setListAdapter(result); 272 | 273 | if (menuReload != null) { 274 | menuReload.setActionView(null); 275 | } 276 | } 277 | } 278 | 279 | @SuppressWarnings("UnusedDeclaration") 280 | protected void log(final Object msg) { 281 | if (BuildConfig.DEBUG) { 282 | Log.d(getClass().getSimpleName(), String.valueOf(msg)); 283 | } 284 | } 285 | 286 | @Override 287 | public void onItemClick( 288 | final AdapterView parent, final View view, final int position, final long id) { 289 | final Object item = parent.getItemAtPosition(position); 290 | if (item instanceof Meal) { 291 | final Meal meal = (Meal) item; 292 | 293 | if (meal.isCow_aw()) { 294 | final Activity activity = requireActivity(); 295 | 296 | final Intent i = new Intent(activity, HappyCowActivity.class); 297 | i.putExtra(HappyCowActivity.EXTRA_MENSA_CLASS, managerClass); 298 | i.putExtra(HappyCowActivity.EXTRA_DAY, date); 299 | i.putExtra(HappyCowActivity.EXTRA_ITEM, position); 300 | 301 | final int[] screenLocation = new int[2]; 302 | 303 | final View cowView = view.findViewById(R.id.imageCow_aw); 304 | cowView.getLocationOnScreen(screenLocation); 305 | final int orientation = getResources().getConfiguration().orientation; 306 | 307 | final String PACKAGE = activity.getPackageName(); 308 | 309 | i.putExtra(PACKAGE + ".orientation", orientation).putExtra(PACKAGE + ".left", 310 | screenLocation[0]).putExtra( 311 | PACKAGE + ".top", screenLocation[1]).putExtra(PACKAGE + ".width", 312 | cowView.getWidth()).putExtra( 313 | PACKAGE + ".height", cowView.getHeight()); 314 | 315 | startActivity(i); 316 | 317 | activity.overridePendingTransition(0, 0); 318 | 319 | // animate cow 320 | View viewParent = cowView; 321 | do { 322 | viewParent = (View) viewParent.getParent(); 323 | ((ViewGroup) viewParent).setClipChildren(false); 324 | ((ViewGroup) viewParent).setClipToPadding(false); 325 | } while (viewParent != view); 326 | 327 | ObjectAnimator.ofFloat(cowView, "scaleX", 1f, 1.5f, 1f).setDuration(1000).start(); 328 | ObjectAnimator.ofFloat(cowView, "scaleY", 1f, 1.5f, 1f).setDuration(1000).start(); 329 | ObjectAnimator.ofFloat(cowView, "translationY", 0f, 330 | -getResources().getDimension(R.dimen.cow_jump), 331 | 0f).setDuration(1000).start(); 332 | final ViewPropertyAnimator rotation = ViewPropertyAnimator.animate( 333 | cowView).rotationBy(360).setDuration(1000); 334 | rotation.setListener(new AnimatorListenerAdapter() { 335 | @Override 336 | public void onAnimationEnd(final Animator animation) { 337 | View viewParent = cowView; 338 | do { 339 | viewParent = (View) viewParent.getParent(); 340 | ((ViewGroup) viewParent).setClipChildren(true); 341 | ((ViewGroup) viewParent).setClipToPadding(false); 342 | } while (viewParent != view); 343 | } 344 | }); 345 | rotation.start(); 346 | } 347 | 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/food/likes/LikeManager.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.food.likes; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | public class LikeManager { 7 | 8 | public static final String LIKES_PREFS = "mealLikes"; 9 | private final SharedPreferences prefs; 10 | 11 | public LikeManager(Context context) { 12 | this.prefs = context.getSharedPreferences(LIKES_PREFS, Context.MODE_PRIVATE); 13 | } 14 | 15 | public void like(String meal) { 16 | putLikeState(meal, LikeStatus.LIKED); 17 | } 18 | 19 | public void dislike(String meal) { 20 | putLikeState(meal, LikeStatus.DISLIKED); 21 | } 22 | 23 | public void reset(String meal) { 24 | putLikeState(meal, LikeStatus.NO_LIKE_INFO); 25 | } 26 | 27 | public void toggle(String meal) { 28 | switch (prefs.getInt(meal, LikeStatus.NO_LIKE_INFO)) { 29 | case LikeStatus.DISLIKED: reset(meal); break; 30 | case LikeStatus.NO_LIKE_INFO: like(meal); break; 31 | case LikeStatus.LIKED: dislike(meal); break; 32 | default: 33 | } 34 | } 35 | 36 | public @LikeStatus int getLikeStatus(String meal) { 37 | return prefs.getInt(meal, LikeStatus.NO_LIKE_INFO); 38 | } 39 | 40 | private void putLikeState(String meal, @LikeStatus int likeStatus) { 41 | prefs.edit().putInt(meal, likeStatus).apply(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/food/likes/LikeStatus.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.food.likes; 2 | 3 | import com.cypressworks.mensaplan.food.Meal; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | import androidx.annotation.IntDef; 9 | /** 10 | * Represents the like state of a {@link Meal}. 11 | * Realised with integer states instead of an enum because enums 12 | * are discouraged in Android App Development 13 | */ 14 | @Retention(RetentionPolicy.SOURCE) 15 | @IntDef({LikeStatus.LIKED, LikeStatus.DISLIKED, LikeStatus.NO_LIKE_INFO}) 16 | public @interface LikeStatus { 17 | int LIKED = 1; 18 | int DISLIKED = -1; 19 | int NO_LIKE_INFO = 0; 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/planmanager/AdenauerPlanManager.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.planmanager; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class AdenauerPlanManager extends AkkStudentenWerkPlanManager { 9 | private final static String providerName = "adenauer"; 10 | public final static String fullProviderName = "Mensa am Adenauer Ring"; 11 | private final static String studentenWerkKey = "adenauerring"; 12 | 13 | private static final Map lineNames = new HashMap<>(); 14 | 15 | static { 16 | lineNames.put("l1", "Linie 1"); 17 | lineNames.put("l2", "Linie 2"); 18 | lineNames.put("l3", "Linie 3"); 19 | lineNames.put("l45", "Linie 4/5"); 20 | lineNames.put("schnitzelbar", "Schnitzelbar"); 21 | lineNames.put("update", "L6 Update"); 22 | lineNames.put("abend", "Abend"); 23 | lineNames.put("aktion", "[kœri]werk"); 24 | lineNames.put("heisstheke", "Cafeteria Heiße Theke"); 25 | lineNames.put("nmtisch", "Cafeteria ab 14:30"); 26 | lineNames.put("pizza", "[pizza]werk Pizza"); 27 | lineNames.put("pasta", "[pizza]werk Pasta"); 28 | lineNames.put("salat_dessert", "[pizza]werk Salate / Vorspeisen"); 29 | } 30 | 31 | public AdenauerPlanManager(final Context c) { 32 | super(c); 33 | } 34 | 35 | @Override 36 | protected String getStudentenwerkKey() { 37 | return studentenWerkKey; 38 | } 39 | 40 | @Override 41 | public String getFullProviderName() { 42 | return fullProviderName; 43 | } 44 | 45 | @Override 46 | public String getProviderName() { 47 | return providerName; 48 | } 49 | 50 | @Override 51 | protected String getLineName(final String key) { 52 | return lineNames.get(key); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/planmanager/AkkStudentenWerkPlanManager.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.planmanager; 2 | 3 | import android.content.Context; 4 | 5 | import com.cypressworks.mensaplan.MensaWidgetProvider; 6 | import com.cypressworks.mensaplan.MyApplication; 7 | import com.cypressworks.mensaplan.food.Line; 8 | import com.cypressworks.mensaplan.food.Meal; 9 | import com.cypressworks.mensaplan.food.Plan; 10 | 11 | import org.json.simple.JSONArray; 12 | import org.json.simple.JSONObject; 13 | import org.json.simple.parser.JSONParser; 14 | import org.json.simple.parser.ParseException; 15 | import org.jsoup.Jsoup; 16 | import org.jsoup.nodes.Document; 17 | import org.jsoup.nodes.Element; 18 | import org.jsoup.select.Elements; 19 | 20 | import java.io.BufferedReader; 21 | import java.io.IOException; 22 | import java.io.InputStreamReader; 23 | import java.net.Authenticator; 24 | import java.net.HttpURLConnection; 25 | import java.net.PasswordAuthentication; 26 | import java.net.URL; 27 | import java.text.DecimalFormat; 28 | import java.text.SimpleDateFormat; 29 | import java.util.ArrayList; 30 | import java.util.Arrays; 31 | import java.util.Calendar; 32 | import java.util.GregorianCalendar; 33 | import java.util.List; 34 | import java.util.Locale; 35 | import java.util.Map; 36 | 37 | /** 38 | * @author Kirill Rakhman 39 | */ 40 | public abstract class AkkStudentenWerkPlanManager extends PlanManager { 41 | private static final String blankStudentenwerkURL = "https://www.sw-ka.de/de/hochschulgastronomie/speiseplan/mensa_adenauerring/?view=ok&STYLE=popup_plain&c=%KEY%&p=1&kw=%KW%"; 42 | private static final String studentenwerkRestURL = "https://www.sw-ka.de/json_interface/canteen/?mensa[]=%KEY%"; 43 | 44 | private static final SimpleDateFormat dayMonthFormat = new SimpleDateFormat("dd.MM", 45 | Locale.GERMANY); 46 | private final static DecimalFormat priceFormat = new DecimalFormat("0.00 €"); 47 | 48 | AkkStudentenWerkPlanManager(final Context c) { 49 | super(c); 50 | } 51 | 52 | abstract protected String getStudentenwerkKey(); 53 | 54 | abstract protected String getLineName(String key); 55 | 56 | @Override 57 | protected final Plan downloadPlan(final Calendar date) { 58 | 59 | Plan plan = null; 60 | 61 | try { 62 | plan = getFromRestApi(date); 63 | } catch (final IOException | ParseException e) { 64 | e.printStackTrace(); 65 | } 66 | 67 | if (plan == null && getRelativeDate(date) != RelativeDate.PAST) { 68 | try { 69 | plan = parseFromStudentenwerkWeb(date); 70 | } catch (final IOException e) { 71 | plan = new Plan(); 72 | e.printStackTrace(); 73 | } 74 | } 75 | 76 | if (plan == null) { 77 | plan = cachePlan(new Plan(), date); 78 | } 79 | 80 | return plan; 81 | 82 | } 83 | 84 | @SuppressWarnings("unchecked") 85 | private Plan getFromRestApi(final Calendar getDate) throws IOException, ParseException { 86 | Plan returnPlan = null; 87 | 88 | final String key = getStudentenwerkKey(); 89 | final URL url = new URL(studentenwerkRestURL.replace("%KEY%", key)); 90 | final String jsonString = downloadString(url); 91 | 92 | final JSONParser parser = new JSONParser(); 93 | final JSONObject json = (JSONObject) ((JSONObject) parser.parse(jsonString)).get(key); 94 | 95 | for (final Map.Entry entry : (Iterable>) json.entrySet()) { 96 | final Calendar date = new GregorianCalendar(); 97 | date.setTimeInMillis(Long.parseLong(entry.getKey()) * 1000L); 98 | 99 | final List lines = new ArrayList<>(); 100 | 101 | final JSONObject dayObject = entry.getValue(); 102 | for (final String lineKey : (Iterable) dayObject.keySet()) { 103 | final String lineName = getLineName(lineKey); 104 | final Line line = new Line(lineName); 105 | 106 | final JSONArray mealArray = (JSONArray) dayObject.get(lineKey); 107 | for (Object aMealArray : mealArray) { 108 | final JSONObject mealObject = (JSONObject) aMealArray; 109 | 110 | String price = (String) mealObject.get("info"); 111 | final Object price_1 = mealObject.get("price_1"); 112 | if (price_1 != null) { 113 | price += " " + priceFormat.format(price_1).replace(".", ",").trim(); 114 | } 115 | final String mealName = mealObject.optString("meal"); 116 | final Meal meal; 117 | if ("".equals(mealName)) { 118 | meal = new Meal(Meal.NO_MEAL, "", ""); 119 | meal.setLikeable(false); 120 | } else { 121 | meal = new Meal(mealObject.optString("meal"), mealObject.optString("dish"), price); 122 | meal.setBio(mealObject.optBoolean("bio")); 123 | meal.setFish(mealObject.optBoolean("fish")); 124 | meal.setPork(mealObject.optBoolean("pork") || mealObject.optBoolean("pork_aw")); 125 | meal.setCow(mealObject.optBoolean("cow")); 126 | meal.setCow_aw(mealObject.optBoolean("cow_aw")); 127 | meal.setVegan(mealObject.optBoolean("vegan")); 128 | meal.setVeg(mealObject.optBoolean("veg")); 129 | } 130 | 131 | line.addMeal(meal); 132 | } 133 | lines.add(line); 134 | 135 | } 136 | 137 | final Plan cachedPlan = cachePlan(new Plan(lines), date); 138 | 139 | if (getDate.get(Calendar.ERA) == date.get(Calendar.ERA) // 140 | && getDate.get(Calendar.YEAR) == date.get(Calendar.YEAR) // 141 | && getDate.get(Calendar.DAY_OF_YEAR) == date.get(Calendar.DAY_OF_YEAR)) { 142 | returnPlan = cachedPlan; 143 | } 144 | 145 | } 146 | 147 | MensaWidgetProvider.updateWidgets(MyApplication.instance); 148 | 149 | return returnPlan; 150 | } 151 | 152 | private Plan parseFromStudentenwerkWeb(final Calendar date) throws IOException { 153 | 154 | final String weekString = String.valueOf(date.get(Calendar.WEEK_OF_YEAR)); 155 | 156 | final URL url = new URL(blankStudentenwerkURL.replace("%KW%", weekString).replace("%KEY%", 157 | getStudentenwerkKey())); 158 | 159 | final Document doc = Jsoup.parse(downloadString(url)); 160 | 161 | // gesuchter Tag 162 | final String dateHeading = dayMonthFormat.format(date.getTime()); 163 | final Elements dayElements = doc.select("h1:contains(" + dateHeading + ") + table"); 164 | if (dayElements.isEmpty()) { 165 | return null; 166 | } 167 | 168 | final Element dayElement = dayElements.get(0); 169 | 170 | // alle Linien 171 | final Elements lineElements = dayElement.select("td[width=20%] + td"); 172 | 173 | final List lines = new ArrayList<>(); 174 | 175 | for (final Element lineElement : lineElements) { 176 | final String lineName = lineElement.previousElementSibling().ownText(); 177 | final Line line = new Line(lineName); 178 | 179 | // alle Gerichte 180 | final Elements mealElements = lineElement.select("tr"); 181 | 182 | for (final Element mealElement : mealElements) { 183 | final String type = mealElement.select("td:nth-child(1)").text(); 184 | final String mealName = mealElement.select("td:nth-child(2) span.bg b").text(); 185 | final String dish = mealElement.select("td:nth-child(2) span.bg span").text(); 186 | final String mealPrice = mealElement.select("td:nth-child(3) span.bg").text(); 187 | 188 | final Meal meal = new Meal(mealName, dish, mealPrice); 189 | 190 | if (type != null && type.startsWith("[") && type.endsWith("]")) { 191 | List parts = Arrays.asList(type.substring(1, type.length() - 1).split(",")); 192 | 193 | // if (parts.contains("")) meal.setBio(true); 194 | if (parts.contains("MSC")) 195 | meal.setFish(true); 196 | if (parts.contains("S")) 197 | meal.setPork(true); 198 | if (parts.contains("R")) 199 | meal.setCow(true); 200 | if (parts.contains("RAT")) 201 | meal.setCow_aw(true); 202 | if (parts.contains("VG")) 203 | meal.setVegan(true); 204 | if (parts.contains("VEG")) 205 | meal.setVeg(true); 206 | } 207 | 208 | line.addMeal(meal); 209 | } 210 | 211 | lines.add(line); 212 | } 213 | 214 | return cachePlan(new Plan(lines), date); 215 | } 216 | 217 | private static String downloadString(final URL url) throws IOException { 218 | Authenticator.setDefault(new Authenticator() { 219 | @Override 220 | protected PasswordAuthentication getPasswordAuthentication() { 221 | return new PasswordAuthentication("jsonapi", "AhVai6OoCh3Quoo6ji".toCharArray()); 222 | } 223 | }); 224 | 225 | final HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 226 | conn.setRequestProperty("User-Agent", 227 | "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0"); 228 | // conn.setRequestProperty("Content-Type", "utf-8"); 229 | // final String contentEncoding = conn.getContentEncoding(); 230 | 231 | StringBuilder html = new StringBuilder(); 232 | 233 | try (BufferedReader reader = new BufferedReader( 234 | new InputStreamReader(conn.getInputStream()))) { 235 | String s; 236 | 237 | while ((s = reader.readLine()) != null) { 238 | html.append(s); 239 | } 240 | 241 | } 242 | 243 | return html.toString(); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/planmanager/CafeteriaMoltkePlanManager.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.planmanager; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class CafeteriaMoltkePlanManager extends AkkStudentenWerkPlanManager { 9 | private final static String providerName = "x1moltkestrasse"; 10 | public final static String fullProviderName = "Caféteria Moltkestraße 30"; 11 | private final static String studentenWerkKey = "x1moltkestrasse"; 12 | 13 | private static final Map lineNames = new HashMap<>(); 14 | 15 | static { 16 | lineNames.put("gut", "Gut & Günstig"); 17 | } 18 | 19 | public CafeteriaMoltkePlanManager(final Context c) { 20 | super(c); 21 | } 22 | 23 | @Override 24 | protected String getStudentenwerkKey() { 25 | return studentenWerkKey; 26 | } 27 | 28 | @Override 29 | public String getFullProviderName() { 30 | return fullProviderName; 31 | } 32 | 33 | @Override 34 | public String getProviderName() { 35 | return providerName; 36 | } 37 | 38 | @Override 39 | protected String getLineName(final String key) { 40 | return lineNames.get(key); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/planmanager/ErzbergerPlanManager.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.planmanager; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class ErzbergerPlanManager extends AkkStudentenWerkPlanManager { 9 | private final static String providerName = "erzberger"; 10 | public final static String fullProviderName = "Mensa Erzbergerstraße"; 11 | private final static String studentenWerkKey = "erzberger"; 12 | 13 | private static final Map lineNames = new HashMap<>(); 14 | 15 | static { 16 | lineNames.put("wahl1", "Wahlessen 1"); 17 | lineNames.put("wahl2", "Wahlessen 2"); 18 | lineNames.put("wahl3", "Wahlessen 3"); 19 | } 20 | 21 | @Override 22 | protected String getLineName(final String key) { 23 | return lineNames.get(key); 24 | } 25 | 26 | public ErzbergerPlanManager(final Context c) { 27 | super(c); 28 | } 29 | 30 | @Override 31 | protected String getStudentenwerkKey() { 32 | return studentenWerkKey; 33 | } 34 | 35 | @Override 36 | public String getFullProviderName() { 37 | return fullProviderName; 38 | } 39 | 40 | @Override 41 | public String getProviderName() { 42 | return providerName; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/planmanager/GottesauePlanManager.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.planmanager; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class GottesauePlanManager extends AkkStudentenWerkPlanManager { 9 | private final static String providerName = "gottesaue"; 10 | public final static String fullProviderName = "Mensa Schloss Gottesaue"; 11 | private final static String studentenWerkKey = "gottesaue"; 12 | 13 | private static final Map lineNames = new HashMap<>(); 14 | 15 | static { 16 | lineNames.put("wahl1", "Wahlessen 1"); 17 | lineNames.put("wahl2", "Wahlessen 2"); 18 | } 19 | 20 | public GottesauePlanManager(final Context c) { 21 | super(c); 22 | } 23 | 24 | @Override 25 | protected String getStudentenwerkKey() { 26 | return studentenWerkKey; 27 | } 28 | 29 | @Override 30 | public String getFullProviderName() { 31 | return fullProviderName; 32 | } 33 | 34 | @Override 35 | public String getProviderName() { 36 | return providerName; 37 | } 38 | 39 | @Override 40 | protected String getLineName(final String key) { 41 | return lineNames.get(key); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/planmanager/HolzgartenstrPlanManager.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.planmanager; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class HolzgartenstrPlanManager extends AkkStudentenWerkPlanManager { 9 | private final static String providerName = "holzgarten"; 10 | public final static String fullProviderName = "Mensa I Holzgartenstraße (Stuttgart-Mitte) "; 11 | private final static String studentenWerkKey = "holzgarten"; 12 | 13 | private static final Map lineNames = new HashMap<>(); 14 | 15 | static { 16 | lineNames.put("gut", "Gut & Günstig 1"); 17 | lineNames.put("gut1", "Gut & Günstig 1"); 18 | lineNames.put("gut2", "Gut & Günstig 2"); 19 | } 20 | 21 | public HolzgartenstrPlanManager(final Context c) { 22 | super(c); 23 | } 24 | 25 | @Override 26 | protected String getStudentenwerkKey() { 27 | return studentenWerkKey; 28 | } 29 | 30 | @Override 31 | public String getFullProviderName() { 32 | return fullProviderName; 33 | } 34 | 35 | @Override 36 | public String getProviderName() { 37 | return providerName; 38 | } 39 | 40 | @Override 41 | protected String getLineName(final String key) { 42 | return lineNames.get(key); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/planmanager/MensaDropdownAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.planmanager; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.content.res.Resources; 6 | import android.content.res.TypedArray; 7 | import android.preference.PreferenceManager; 8 | import android.util.TypedValue; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.BaseAdapter; 13 | import android.widget.TextView; 14 | 15 | import com.cypressworks.mensaplan.MainActivity; 16 | import com.cypressworks.mensaplan.R; 17 | 18 | /** 19 | * @author Kirill Rakhman 20 | */ 21 | public class MensaDropdownAdapter extends BaseAdapter implements 22 | SharedPreferences.OnSharedPreferenceChangeListener { 23 | private final Context context; 24 | private int selectedMensa; 25 | private final int accentColor; 26 | 27 | public MensaDropdownAdapter(final Context context) { 28 | this.context = context; 29 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 30 | prefs.registerOnSharedPreferenceChangeListener(this); 31 | selectedMensa = prefs.getInt(MainActivity.PREF_MENSA_NUM, 0); 32 | 33 | accentColor = context.getResources().getColor(R.color.accent); 34 | } 35 | 36 | @SuppressWarnings("rawtypes") private static final Class[] classes = {// 37 | AdenauerPlanManager.class,// 38 | MoltkePlanManager.class,// 39 | ErzbergerPlanManager.class,// 40 | GottesauePlanManager.class, // 41 | PforzheimPlanManager.class, // 42 | HolzgartenstrPlanManager.class, // 43 | CafeteriaMoltkePlanManager.class}; 44 | 45 | private static final String[] names = {// 46 | AdenauerPlanManager.fullProviderName,// 47 | MoltkePlanManager.fullProviderName,// 48 | ErzbergerPlanManager.fullProviderName,// 49 | GottesauePlanManager.fullProviderName, // 50 | PforzheimPlanManager.fullProviderName, // 51 | HolzgartenstrPlanManager.fullProviderName, // 52 | CafeteriaMoltkePlanManager.fullProviderName}; 53 | 54 | @SuppressWarnings("unchecked") 55 | public static PlanManager getManagerFromPreferences(final Context c) { 56 | final int index = PreferenceManager.getDefaultSharedPreferences(c).getInt( 57 | MainActivity.PREF_MENSA_NUM, 0); 58 | 59 | return PlanManager.getInstance(classes[index], c); 60 | } 61 | 62 | @Override 63 | public int getCount() { 64 | return names.length; 65 | } 66 | 67 | @SuppressWarnings("unchecked") 68 | @Override 69 | public Class getItem(final int position) { 70 | return classes[position]; 71 | } 72 | 73 | @Override 74 | public long getItemId(final int position) { 75 | return position; 76 | } 77 | 78 | @Override 79 | public View getView(final int position, final View convertView, final ViewGroup parent) { 80 | return makeLayout(position, convertView, parent, R.layout.textview_drawer); 81 | } 82 | 83 | private View makeLayout( 84 | final int position, final View convertView, final ViewGroup parent, final int layout) { 85 | TextView tv; 86 | if (convertView != null) { 87 | tv = (TextView) convertView; 88 | } else { 89 | tv = (TextView) LayoutInflater.from(context).inflate(layout, parent, false); 90 | } 91 | tv.setText(getName(position)); 92 | if (position == selectedMensa) { 93 | tv.setTextColor(accentColor); 94 | } else { 95 | tv.setTextColor(primaryTextColor()); 96 | } 97 | 98 | return tv; 99 | } 100 | 101 | public String getName(final int position) { 102 | return names[position]; 103 | } 104 | 105 | @Override 106 | public void onSharedPreferenceChanged( 107 | final SharedPreferences prefs, final String key) { 108 | if (key.equals(MainActivity.PREF_MENSA_NUM)) { 109 | selectedMensa = prefs.getInt(MainActivity.PREF_MENSA_NUM, 0); 110 | notifyDataSetChanged(); 111 | } 112 | } 113 | 114 | private int primaryTextColor() { 115 | TypedValue typedValue = new TypedValue(); 116 | Resources.Theme theme = context.getTheme(); 117 | theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true); 118 | TypedArray arr = context.obtainStyledAttributes(typedValue.data, 119 | new int[]{android.R.attr.textColorPrimary}); 120 | int primaryColor = arr.getColor(0, -1); 121 | arr.recycle(); 122 | return primaryColor; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/planmanager/MoltkePlanManager.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.planmanager; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class MoltkePlanManager extends AkkStudentenWerkPlanManager { 9 | private final static String providerName = "moltke"; 10 | public final static String fullProviderName = "Mensa Moltkestraße"; 11 | private final static String studentenWerkKey = "moltke"; 12 | 13 | private static final Map lineNames = new HashMap<>(); 14 | 15 | static { 16 | lineNames.put("wahl1", "Wahlessen 1"); 17 | lineNames.put("wahl2", "Wahlessen 2"); 18 | lineNames.put("aktion", "Aktionstheke"); 19 | lineNames.put("gut", "Gut & Günstig"); 20 | lineNames.put("buffet", "Buffet"); 21 | lineNames.put("schnitzelbar", "Schnitzelbar"); 22 | } 23 | 24 | public MoltkePlanManager(final Context c) { 25 | super(c); 26 | } 27 | 28 | @Override 29 | protected String getStudentenwerkKey() { 30 | return studentenWerkKey; 31 | } 32 | 33 | @Override 34 | public String getFullProviderName() { 35 | return fullProviderName; 36 | } 37 | 38 | @Override 39 | public String getProviderName() { 40 | return providerName; 41 | } 42 | 43 | @Override 44 | protected String getLineName(final String key) { 45 | return lineNames.get(key); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/planmanager/PforzheimPlanManager.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.planmanager; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class PforzheimPlanManager extends AkkStudentenWerkPlanManager { 9 | private final static String providerName = "tiefenbronner"; 10 | public final static String fullProviderName = "Mensa Fachhochschule Pforzheim"; 11 | private final static String studentenWerkKey = "tiefenbronner"; 12 | 13 | private static final Map lineNames = new HashMap<>(); 14 | 15 | static { 16 | lineNames.put("wahl1", "Wahlessen 1"); 17 | lineNames.put("wahl2", "Wahlessen 2"); 18 | lineNames.put("gut", "Gut & Günstig"); 19 | lineNames.put("buffet", "Buffet"); 20 | } 21 | 22 | public PforzheimPlanManager(final Context c) { 23 | super(c); 24 | } 25 | 26 | @Override 27 | protected String getStudentenwerkKey() { 28 | return studentenWerkKey; 29 | } 30 | 31 | @Override 32 | public String getFullProviderName() { 33 | return fullProviderName; 34 | } 35 | 36 | @Override 37 | public String getProviderName() { 38 | return providerName; 39 | } 40 | 41 | @Override 42 | protected String getLineName(final String key) { 43 | return lineNames.get(key); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/planmanager/PlanManager.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.planmanager; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.cypressworks.mensaplan.BuildConfig; 7 | import com.cypressworks.mensaplan.Tools; 8 | import com.cypressworks.mensaplan.food.Plan; 9 | 10 | import java.io.File; 11 | import java.text.SimpleDateFormat; 12 | import java.util.Calendar; 13 | import java.util.Date; 14 | import java.util.HashMap; 15 | import java.util.Locale; 16 | import java.util.Map; 17 | 18 | /** 19 | * @author Kirill Rakhman 20 | */ 21 | public abstract class PlanManager { 22 | 23 | private interface PlanManagerFactory { 24 | PlanManager create(Context c); 25 | } 26 | 27 | private static final Map, PlanManager> managerCache = new HashMap<>(); 28 | private static final Map, PlanManagerFactory> managerFactories = new HashMap<>(); 29 | 30 | static { 31 | managerFactories.put(AdenauerPlanManager.class, AdenauerPlanManager::new); 32 | managerFactories.put(MoltkePlanManager.class, MoltkePlanManager::new); 33 | managerFactories.put(ErzbergerPlanManager.class, ErzbergerPlanManager::new); 34 | managerFactories.put(GottesauePlanManager.class, GottesauePlanManager::new); 35 | managerFactories.put(PforzheimPlanManager.class, PforzheimPlanManager::new); 36 | managerFactories.put(HolzgartenstrPlanManager.class, HolzgartenstrPlanManager::new); 37 | managerFactories.put(CafeteriaMoltkePlanManager.class, CafeteriaMoltkePlanManager::new); 38 | } 39 | 40 | public static synchronized PlanManager getInstance( 41 | final Class clazz, final Context c) { 42 | if (!managerCache.containsKey(clazz)) { 43 | final PlanManager manager = managerFactories.get(clazz).create(c); 44 | managerCache.put(clazz, manager); 45 | } 46 | return managerCache.get(clazz); 47 | 48 | } 49 | 50 | public enum RelativeDate { 51 | PAST, TODAY, FUTURE 52 | } 53 | 54 | private static final SimpleDateFormat dayMonthYearFormat = new SimpleDateFormat("dd.MM.yyyy", 55 | Locale.GERMANY); 56 | 57 | static RelativeDate getRelativeDate(final Calendar date) { 58 | final Calendar today = Calendar.getInstance(); 59 | if (date.after(today)) { 60 | // ############## Schauen, ob in der Zukunft 61 | return RelativeDate.FUTURE; 62 | 63 | } else if (date.get(Calendar.ERA) == today.get(Calendar.ERA) && date.get( 64 | Calendar.YEAR) == today.get(Calendar.YEAR) && date.get( 65 | Calendar.DAY_OF_YEAR) == today.get(Calendar.DAY_OF_YEAR)) { 66 | // ################# Schauen, ob heute 67 | return RelativeDate.TODAY; 68 | 69 | } else { 70 | // ################## von AKK laden 71 | return RelativeDate.PAST; 72 | 73 | } 74 | } 75 | 76 | private final Context c; 77 | 78 | PlanManager(final Context c) { 79 | this.c = c; 80 | } 81 | 82 | public synchronized Plan getPlan(final Calendar date, final boolean reload) { 83 | if (!reload && existsInCache(date)) { 84 | // Wenn im Cache, dann einfach lesen und zurückgeben 85 | final Plan planFromCache = getPlanFromCache(date); 86 | 87 | if (planFromCache == null || planFromCache.getAgeInDays() >= 1) { 88 | log("Plan not cached or too old for " + dayMonthYearFormat.format(date.getTime())); 89 | return downloadPlan(date); 90 | } else { 91 | return planFromCache; 92 | } 93 | } else { 94 | // Existiert nicht im Cache oder reload => downloaden 95 | log("(Re)loading plan for " + dayMonthYearFormat.format(date.getTime())); 96 | return downloadPlan(date); 97 | } 98 | 99 | } 100 | 101 | public Plan getPlanFromCache(final Calendar date) { 102 | final File file = getCacheFile(date); 103 | 104 | if (!file.exists()) { 105 | return null; 106 | } 107 | 108 | try { 109 | return Tools.readObject(file); 110 | } catch (final Exception e) { 111 | e.printStackTrace(); 112 | file.delete(); 113 | return null; 114 | } 115 | 116 | } 117 | 118 | final Plan cachePlan(final Plan plan, final Calendar date) { 119 | 120 | try { 121 | final File target = getCacheFile(date); 122 | Tools.writeObject(plan, target); 123 | } catch (final Exception e) { 124 | e.printStackTrace(); 125 | } 126 | 127 | return plan; 128 | } 129 | 130 | public final void clearCache(final int leaveDays) { 131 | // TODO clean cache 132 | // Referenzdatum anlegen 133 | Calendar cref = Calendar.getInstance(Locale.GERMANY); 134 | cref.add(Calendar.DAY_OF_YEAR, -leaveDays); 135 | cref.set(Calendar.HOUR_OF_DAY, 23); 136 | cref.set(Calendar.MINUTE, 59); 137 | cref.set(Calendar.SECOND, 59); 138 | final Date dref1 = cref.getTime(); 139 | 140 | cref = Calendar.getInstance(Locale.GERMANY); 141 | cref.add(Calendar.DAY_OF_YEAR, leaveDays); 142 | cref.set(Calendar.HOUR_OF_DAY, 0); 143 | cref.set(Calendar.MINUTE, 0); 144 | cref.set(Calendar.SECOND, 0); 145 | final Date dref2 = cref.getTime(); 146 | 147 | final File[] listFiles = getCacheDir().listFiles(); 148 | for (final File f : listFiles) { 149 | try { 150 | // Datum der Datei parsen 151 | final Date d = dayMonthYearFormat.parse(f.getName()); 152 | 153 | // Vergleichen und ggf. löschen 154 | if (d.before(dref1) || d.after(dref2)) { 155 | f.delete(); 156 | } 157 | 158 | } catch (final java.text.ParseException e) { 159 | e.printStackTrace(); 160 | f.delete(); 161 | } 162 | } 163 | } 164 | 165 | boolean existsInCache(final Calendar date) { 166 | final File target = getCacheFile(date); 167 | return target.exists(); 168 | } 169 | 170 | private File getCacheDir() { 171 | final File dir = new File(this.c.getCacheDir(), getProviderName()); 172 | if (dir.exists() && dir.isFile()) { 173 | dir.delete(); 174 | } 175 | dir.mkdirs(); 176 | return dir; 177 | } 178 | 179 | private File getCacheFile(final Calendar date) { 180 | final String fname = dayMonthYearFormat.format(date.getTime()); 181 | return new File(getCacheDir(), fname); 182 | } 183 | 184 | abstract protected Plan downloadPlan(final Calendar date); 185 | 186 | abstract public String getFullProviderName(); 187 | 188 | abstract public String getProviderName(); 189 | 190 | void log(final String msg) { 191 | if (BuildConfig.DEBUG) { 192 | Log.d(getClass().getSimpleName(), msg); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/util/SpannableStringBuilderHelper.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.util; 2 | 3 | import android.text.SpannableStringBuilder; 4 | 5 | /** 6 | * Created by Kirill on 07.11.2014. 7 | */ 8 | public class SpannableStringBuilderHelper { 9 | private SpannableStringBuilder sb = new SpannableStringBuilder(); 10 | 11 | public SpannableStringBuilder getSpannableStringBuilder() { 12 | return sb; 13 | } 14 | 15 | public SpannableStringBuilderHelper append( 16 | final CharSequence text, final Object what, final int flags) { 17 | final int start = sb.length(); 18 | sb.append(text); 19 | sb.setSpan(what, start, sb.length(), flags); 20 | return this; 21 | } 22 | 23 | public SpannableStringBuilder append(final CharSequence text) { 24 | return sb.append(text); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/cypressworks/mensaplan/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.cypressworks.mensaplan.util; 2 | 3 | /** 4 | * Created by Kirill on 05.12.2014. 5 | */ 6 | public class StringUtils { 7 | 8 | public static String sanitize(final String string) { 9 | if (string == null) { 10 | return ""; 11 | } else { 12 | return string.trim(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_action_more.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/thumbs_down.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/thumbs_undecided.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/thumbs_up.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-hdpi/ic_action_more.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-hdpi/icon_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/thumbs_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-hdpi/thumbs_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/thumbs_undecided.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-hdpi/thumbs_undecided.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/thumbs_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-hdpi/thumbs_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-mdpi/ic_action_more.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-mdpi/icon_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/thumbs_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-mdpi/thumbs_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/thumbs_undecided.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-mdpi/thumbs_undecided.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/thumbs_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-mdpi/thumbs_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/widget_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-nodpi/widget_preview.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xhdpi/ic_action_more.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xhdpi/icon_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/thumbs_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xhdpi/thumbs_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/thumbs_undecided.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xhdpi/thumbs_undecided.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/thumbs_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xhdpi/thumbs_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/heart.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/ic_action_more.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_meal_bio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/ic_meal_bio.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_meal_cow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/ic_meal_cow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_meal_cow_aw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/ic_meal_cow_aw.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_meal_fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/ic_meal_fish.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_meal_pork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/ic_meal_pork.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_meal_veg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/ic_meal_veg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_meal_vegan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/ic_meal_vegan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/icon_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/thumbs_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/thumbs_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/thumbs_undecided.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/thumbs_undecided.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/thumbs_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxhdpi/thumbs_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypressious/KitMensaAndroid/9bf4b8bee9c9f4655e4099362353a6f824613475/app/src/main/res/drawable-xxxhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_calendar_day.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_refresh.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shadow_vertical.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/widget_header_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout-v21/like_button.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/actionitem_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_notification_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 19 | 20 | 29 | 30 | 37 | 38 | 44 | 45 |