blob: d2e37d8779d196985b3fc286d9984342e7d16e73 [file] [log] [blame]
Dan Sandler27a9fcc2016-06-22 00:05:11 -04001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.egg.neko;
16
17import android.Manifest;
18import android.app.ActionBar;
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.DialogInterface.OnClickListener;
24import android.content.Intent;
25import android.content.pm.PackageManager;
26import android.graphics.Bitmap;
Jason Monk753762c2016-07-15 14:51:34 -040027import android.graphics.Color;
Dan Sandler27a9fcc2016-06-22 00:05:11 -040028import android.graphics.drawable.Drawable;
29import android.media.MediaScannerConnection;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Environment;
33import android.provider.MediaStore.Images;
Dan Sandler7bcd6aa2017-09-04 21:36:01 -040034import android.support.v4.content.FileProvider;
Dan Sandler27a9fcc2016-06-22 00:05:11 -040035import android.support.v7.widget.GridLayoutManager;
36import android.support.v7.widget.RecyclerView;
37import android.util.Log;
38import android.view.ContextThemeWrapper;
39import android.view.LayoutInflater;
40import android.view.View;
41import android.view.View.OnLongClickListener;
42import android.view.ViewGroup;
43import android.widget.EditText;
44import android.widget.ImageView;
45import android.widget.TextView;
46
47import com.android.egg.R;
48import com.android.egg.neko.PrefState.PrefsListener;
Chris Wrena801d402016-06-29 11:30:59 -040049import com.android.internal.logging.MetricsLogger;
Dan Sandler27a9fcc2016-06-22 00:05:11 -040050
51import java.io.File;
52import java.io.FileOutputStream;
53import java.io.IOException;
54import java.io.OutputStream;
Jason Monk753762c2016-07-15 14:51:34 -040055import java.util.Collections;
56import java.util.Comparator;
57import java.util.List;
Dan Sandler27a9fcc2016-06-22 00:05:11 -040058
59public class NekoLand extends Activity implements PrefsListener {
Dan Sandler7bcd6aa2017-09-04 21:36:01 -040060 public static String CHAN_ID = "EGG";
61
Dan Sandler27a9fcc2016-06-22 00:05:11 -040062 public static boolean DEBUG = false;
63 public static boolean DEBUG_NOTIFICATIONS = false;
64
Dan Sandler4a1bcd92016-07-16 00:39:21 -040065 private static final int EXPORT_BITMAP_SIZE = 600;
66
Dan Sandler27a9fcc2016-06-22 00:05:11 -040067 private static final int STORAGE_PERM_REQUEST = 123;
68
69 private static boolean CAT_GEN = false;
70 private PrefState mPrefs;
71 private CatAdapter mAdapter;
72 private Cat mPendingShareCat;
73
74
75 @Override
76 public void onCreate(Bundle savedInstanceState) {
77 super.onCreate(savedInstanceState);
78 setContentView(R.layout.neko_activity);
79 final ActionBar actionBar = getActionBar();
80 if (actionBar != null) {
81 actionBar.setLogo(Cat.create(this));
82 actionBar.setDisplayUseLogoEnabled(false);
83 actionBar.setDisplayShowHomeEnabled(true);
84 }
85
86 mPrefs = new PrefState(this);
87 mPrefs.setListener(this);
Alan Viverette51efddb2017-04-05 10:00:01 -040088 final RecyclerView recyclerView = findViewById(R.id.holder);
Dan Sandler27a9fcc2016-06-22 00:05:11 -040089 mAdapter = new CatAdapter();
90 recyclerView.setAdapter(mAdapter);
91 recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
Chris Wrena801d402016-06-29 11:30:59 -040092 int numCats = updateCats();
93 MetricsLogger.histogram(this, "egg_neko_visit_gallery", numCats);
Dan Sandler27a9fcc2016-06-22 00:05:11 -040094 }
95
96 @Override
97 protected void onDestroy() {
98 super.onDestroy();
99 mPrefs.setListener(null);
100 }
101
Chris Wrena801d402016-06-29 11:30:59 -0400102 private int updateCats() {
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400103 Cat[] cats;
104 if (CAT_GEN) {
105 cats = new Cat[50];
106 for (int i = 0; i < cats.length; i++) {
107 cats[i] = Cat.create(this);
108 }
109 } else {
Jason Monk753762c2016-07-15 14:51:34 -0400110 final float[] hsv = new float[3];
111 List<Cat> list = mPrefs.getCats();
112 Collections.sort(list, new Comparator<Cat>() {
113 @Override
114 public int compare(Cat cat, Cat cat2) {
115 Color.colorToHSV(cat.getBodyColor(), hsv);
116 float bodyH1 = hsv[0];
117 Color.colorToHSV(cat2.getBodyColor(), hsv);
118 float bodyH2 = hsv[0];
119 return Float.compare(bodyH1, bodyH2);
120 }
121 });
122 cats = list.toArray(new Cat[0]);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400123 }
124 mAdapter.setCats(cats);
Chris Wrena801d402016-06-29 11:30:59 -0400125 return cats.length;
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400126 }
127
128 private void onCatClick(Cat cat) {
129 if (CAT_GEN) {
130 mPrefs.addCat(cat);
131 new AlertDialog.Builder(NekoLand.this)
132 .setTitle("Cat added")
133 .setPositiveButton(android.R.string.ok, null)
134 .show();
135 } else {
136 showNameDialog(cat);
137 }
138// noman.notify(1, cat.buildNotification(NekoLand.this).build());
139 }
140
141 private void onCatRemove(Cat cat) {
Chris Wrena801d402016-06-29 11:30:59 -0400142 cat.logRemove(this);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400143 mPrefs.removeCat(cat);
144 }
145
146 private void showNameDialog(final Cat cat) {
Chris Wrena801d402016-06-29 11:30:59 -0400147 final Context context = new ContextThemeWrapper(this,
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400148 android.R.style.Theme_Material_Light_Dialog_NoActionBar);
149 // TODO: Move to XML, add correct margins.
150 View view = LayoutInflater.from(context).inflate(R.layout.edit_text, null);
151 final EditText text = (EditText) view.findViewById(android.R.id.edit);
152 text.setText(cat.getName());
153 text.setSelection(cat.getName().length());
Dan Sandler1e529092016-07-16 01:01:19 -0400154 final int size = context.getResources()
155 .getDimensionPixelSize(android.R.dimen.app_icon_size);
156 Drawable catIcon = cat.createIcon(this, size, size).loadDrawable(this);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400157 new AlertDialog.Builder(context)
158 .setTitle(" ")
159 .setIcon(catIcon)
160 .setView(view)
161 .setPositiveButton(android.R.string.ok, new OnClickListener() {
162 @Override
163 public void onClick(DialogInterface dialog, int which) {
Chris Wren1bdc7702016-07-01 14:06:40 -0400164 cat.logRename(context);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400165 cat.setName(text.getText().toString().trim());
166 mPrefs.addCat(cat);
167 }
168 }).show();
169 }
170
171 @Override
172 public void onPrefsChanged() {
173 updateCats();
174 }
175
176 private class CatAdapter extends RecyclerView.Adapter<CatHolder> {
177
178 private Cat[] mCats;
179
180 public void setCats(Cat[] cats) {
181 mCats = cats;
182 notifyDataSetChanged();
183 }
184
185 @Override
186 public CatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
187 return new CatHolder(LayoutInflater.from(parent.getContext())
188 .inflate(R.layout.cat_view, parent, false));
189 }
190
Dan Sandler4a1bcd92016-07-16 00:39:21 -0400191 private void setContextGroupVisible(final CatHolder holder, boolean vis) {
192 final View group = holder.contextGroup;
193 if (vis && group.getVisibility() != View.VISIBLE) {
194 group.setAlpha(0);
195 group.setVisibility(View.VISIBLE);
196 group.animate().alpha(1.0f).setDuration(333);
197 Runnable hideAction = new Runnable() {
198 @Override
199 public void run() {
200 setContextGroupVisible(holder, false);
201 }
202 };
203 group.setTag(hideAction);
204 group.postDelayed(hideAction, 5000);
205 } else if (!vis && group.getVisibility() == View.VISIBLE) {
206 group.removeCallbacks((Runnable) group.getTag());
207 group.animate().alpha(0f).setDuration(250).withEndAction(new Runnable() {
208 @Override
209 public void run() {
210 group.setVisibility(View.INVISIBLE);
211 }
212 });
213 }
214 }
215
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400216 @Override
217 public void onBindViewHolder(final CatHolder holder, int position) {
218 Context context = holder.itemView.getContext();
Dan Sandler1e529092016-07-16 01:01:19 -0400219 final int size = context.getResources().getDimensionPixelSize(R.dimen.neko_display_size);
220 holder.imageView.setImageIcon(mCats[position].createIcon(context, size, size));
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400221 holder.textView.setText(mCats[position].getName());
222 holder.itemView.setOnClickListener(new View.OnClickListener() {
223 @Override
224 public void onClick(View v) {
225 onCatClick(mCats[holder.getAdapterPosition()]);
226 }
227 });
228 holder.itemView.setOnLongClickListener(new OnLongClickListener() {
229 @Override
230 public boolean onLongClick(View v) {
Dan Sandler4a1bcd92016-07-16 00:39:21 -0400231 setContextGroupVisible(holder, true);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400232 return true;
233 }
234 });
235 holder.delete.setOnClickListener(new View.OnClickListener() {
236 @Override
237 public void onClick(View v) {
Dan Sandler4a1bcd92016-07-16 00:39:21 -0400238 setContextGroupVisible(holder, false);
239 new AlertDialog.Builder(NekoLand.this)
240 .setTitle(getString(R.string.confirm_delete, mCats[position].getName()))
241 .setNegativeButton(android.R.string.cancel, null)
242 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
243 @Override
244 public void onClick(DialogInterface dialog, int which) {
245 onCatRemove(mCats[holder.getAdapterPosition()]);
246 }
247 })
248 .show();
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400249 }
250 });
251 holder.share.setOnClickListener(new View.OnClickListener() {
252 @Override
253 public void onClick(View v) {
Dan Sandler4a1bcd92016-07-16 00:39:21 -0400254 setContextGroupVisible(holder, false);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400255 Cat cat = mCats[holder.getAdapterPosition()];
256 if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
257 != PackageManager.PERMISSION_GRANTED) {
258 mPendingShareCat = cat;
259 requestPermissions(
260 new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
261 STORAGE_PERM_REQUEST);
262 return;
263 }
264 shareCat(cat);
265 }
266 });
267 }
268
269 @Override
270 public int getItemCount() {
271 return mCats.length;
272 }
273 }
274
275 private void shareCat(Cat cat) {
276 final File dir = new File(
277 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
278 getString(R.string.directory_name));
279 if (!dir.exists() && !dir.mkdirs()) {
280 Log.e("NekoLand", "save: error: can't create Pictures directory");
281 return;
282 }
283 final File png = new File(dir, cat.getName().replaceAll("[/ #:]+", "_") + ".png");
Dan Sandler4a1bcd92016-07-16 00:39:21 -0400284 Bitmap bitmap = cat.createBitmap(EXPORT_BITMAP_SIZE, EXPORT_BITMAP_SIZE);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400285 if (bitmap != null) {
286 try {
287 OutputStream os = new FileOutputStream(png);
288 bitmap.compress(Bitmap.CompressFormat.PNG, 0, os);
289 os.close();
290 MediaScannerConnection.scanFile(
291 this,
292 new String[] {png.toString()},
293 new String[] {"image/png"},
294 null);
Dan Sandler7bcd6aa2017-09-04 21:36:01 -0400295 Log.v("Neko", "cat file: " + png);
296 Uri uri = FileProvider.getUriForFile(this, "com.android.egg.fileprovider", png);
297 Log.v("Neko", "cat uri: " + uri);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400298 Intent intent = new Intent(Intent.ACTION_SEND);
299 intent.putExtra(Intent.EXTRA_STREAM, uri);
300 intent.putExtra(Intent.EXTRA_SUBJECT, cat.getName());
Dan Sandler7bcd6aa2017-09-04 21:36:01 -0400301 intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400302 intent.setType("image/png");
303 startActivity(Intent.createChooser(intent, null));
Chris Wrena801d402016-06-29 11:30:59 -0400304 cat.logShare(this);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400305 } catch (IOException e) {
306 Log.e("NekoLand", "save: error: " + e);
307 }
308 }
309 }
310
311 @Override
312 public void onRequestPermissionsResult(int requestCode,
313 String permissions[], int[] grantResults) {
314 if (requestCode == STORAGE_PERM_REQUEST) {
315 if (mPendingShareCat != null) {
316 shareCat(mPendingShareCat);
317 mPendingShareCat = null;
318 }
319 }
320 }
321
322 private static class CatHolder extends RecyclerView.ViewHolder {
323 private final ImageView imageView;
324 private final TextView textView;
325 private final View contextGroup;
326 private final View delete;
327 private final View share;
328
329 public CatHolder(View itemView) {
330 super(itemView);
331 imageView = (ImageView) itemView.findViewById(android.R.id.icon);
332 textView = (TextView) itemView.findViewById(android.R.id.title);
333 contextGroup = itemView.findViewById(R.id.contextGroup);
334 delete = itemView.findViewById(android.R.id.closeButton);
335 share = itemView.findViewById(android.R.id.shareText);
336 }
337 }
338}