2 * Copyright (C) 2016 The Android Open Source Project
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
15 package com.android.egg.neko;
17 import android.Manifest;
18 import android.app.ActionBar;
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.DialogInterface.OnClickListener;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.graphics.Bitmap;
27 import android.graphics.Color;
28 import android.graphics.drawable.Drawable;
29 import android.media.MediaScannerConnection;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.Environment;
33 import android.provider.MediaStore.Images;
34 import android.support.v7.widget.GridLayoutManager;
35 import android.support.v7.widget.RecyclerView;
36 import android.util.Log;
37 import android.view.ContextThemeWrapper;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.View.OnLongClickListener;
41 import android.view.ViewGroup;
42 import android.widget.EditText;
43 import android.widget.ImageView;
44 import android.widget.TextView;
46 import com.android.egg.R;
47 import com.android.egg.neko.PrefState.PrefsListener;
48 import com.android.internal.logging.MetricsLogger;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.io.OutputStream;
54 import java.util.Collections;
55 import java.util.Comparator;
56 import java.util.List;
58 public class NekoLand extends Activity implements PrefsListener {
59 public static boolean DEBUG = false;
60 public static boolean DEBUG_NOTIFICATIONS = false;
62 private static final int EXPORT_BITMAP_SIZE = 600;
64 private static final int STORAGE_PERM_REQUEST = 123;
66 private static boolean CAT_GEN = false;
67 private PrefState mPrefs;
68 private CatAdapter mAdapter;
69 private Cat mPendingShareCat;
73 public void onCreate(Bundle savedInstanceState) {
74 super.onCreate(savedInstanceState);
75 setContentView(R.layout.neko_activity);
76 final ActionBar actionBar = getActionBar();
77 if (actionBar != null) {
78 actionBar.setLogo(Cat.create(this));
79 actionBar.setDisplayUseLogoEnabled(false);
80 actionBar.setDisplayShowHomeEnabled(true);
83 mPrefs = new PrefState(this);
84 mPrefs.setListener(this);
85 final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.holder);
86 mAdapter = new CatAdapter();
87 recyclerView.setAdapter(mAdapter);
88 recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
89 int numCats = updateCats();
90 MetricsLogger.histogram(this, "egg_neko_visit_gallery", numCats);
94 protected void onDestroy() {
96 mPrefs.setListener(null);
99 private int updateCats() {
103 for (int i = 0; i < cats.length; i++) {
104 cats[i] = Cat.create(this);
107 final float[] hsv = new float[3];
108 List<Cat> list = mPrefs.getCats();
109 Collections.sort(list, new Comparator<Cat>() {
111 public int compare(Cat cat, Cat cat2) {
112 Color.colorToHSV(cat.getBodyColor(), hsv);
113 float bodyH1 = hsv[0];
114 Color.colorToHSV(cat2.getBodyColor(), hsv);
115 float bodyH2 = hsv[0];
116 return Float.compare(bodyH1, bodyH2);
119 cats = list.toArray(new Cat[0]);
121 mAdapter.setCats(cats);
125 private void onCatClick(Cat cat) {
128 new AlertDialog.Builder(NekoLand.this)
129 .setTitle("Cat added")
130 .setPositiveButton(android.R.string.ok, null)
135 // noman.notify(1, cat.buildNotification(NekoLand.this).build());
138 private void onCatRemove(Cat cat) {
140 mPrefs.removeCat(cat);
143 private void showNameDialog(final Cat cat) {
144 final Context context = new ContextThemeWrapper(this,
145 android.R.style.Theme_Material_Light_Dialog_NoActionBar);
146 // TODO: Move to XML, add correct margins.
147 View view = LayoutInflater.from(context).inflate(R.layout.edit_text, null);
148 final EditText text = (EditText) view.findViewById(android.R.id.edit);
149 text.setText(cat.getName());
150 text.setSelection(cat.getName().length());
151 final int size = context.getResources()
152 .getDimensionPixelSize(android.R.dimen.app_icon_size);
153 Drawable catIcon = cat.createIcon(this, size, size).loadDrawable(this);
154 new AlertDialog.Builder(context)
158 .setPositiveButton(android.R.string.ok, new OnClickListener() {
160 public void onClick(DialogInterface dialog, int which) {
161 cat.logRename(context);
162 cat.setName(text.getText().toString().trim());
169 public void onPrefsChanged() {
173 private class CatAdapter extends RecyclerView.Adapter<CatHolder> {
177 public void setCats(Cat[] cats) {
179 notifyDataSetChanged();
183 public CatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
184 return new CatHolder(LayoutInflater.from(parent.getContext())
185 .inflate(R.layout.cat_view, parent, false));
188 private void setContextGroupVisible(final CatHolder holder, boolean vis) {
189 final View group = holder.contextGroup;
190 if (vis && group.getVisibility() != View.VISIBLE) {
192 group.setVisibility(View.VISIBLE);
193 group.animate().alpha(1.0f).setDuration(333);
194 Runnable hideAction = new Runnable() {
197 setContextGroupVisible(holder, false);
200 group.setTag(hideAction);
201 group.postDelayed(hideAction, 5000);
202 } else if (!vis && group.getVisibility() == View.VISIBLE) {
203 group.removeCallbacks((Runnable) group.getTag());
204 group.animate().alpha(0f).setDuration(250).withEndAction(new Runnable() {
207 group.setVisibility(View.INVISIBLE);
214 public void onBindViewHolder(final CatHolder holder, int position) {
215 Context context = holder.itemView.getContext();
216 final int size = context.getResources().getDimensionPixelSize(R.dimen.neko_display_size);
217 holder.imageView.setImageIcon(mCats[position].createIcon(context, size, size));
218 holder.textView.setText(mCats[position].getName());
219 holder.itemView.setOnClickListener(new View.OnClickListener() {
221 public void onClick(View v) {
222 onCatClick(mCats[holder.getAdapterPosition()]);
225 holder.itemView.setOnLongClickListener(new OnLongClickListener() {
227 public boolean onLongClick(View v) {
228 setContextGroupVisible(holder, true);
232 holder.delete.setOnClickListener(new View.OnClickListener() {
234 public void onClick(View v) {
235 setContextGroupVisible(holder, false);
236 new AlertDialog.Builder(NekoLand.this)
237 .setTitle(getString(R.string.confirm_delete, mCats[position].getName()))
238 .setNegativeButton(android.R.string.cancel, null)
239 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
241 public void onClick(DialogInterface dialog, int which) {
242 onCatRemove(mCats[holder.getAdapterPosition()]);
248 holder.share.setOnClickListener(new View.OnClickListener() {
250 public void onClick(View v) {
251 setContextGroupVisible(holder, false);
252 Cat cat = mCats[holder.getAdapterPosition()];
253 if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
254 != PackageManager.PERMISSION_GRANTED) {
255 mPendingShareCat = cat;
257 new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
258 STORAGE_PERM_REQUEST);
267 public int getItemCount() {
272 private void shareCat(Cat cat) {
273 final File dir = new File(
274 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
275 getString(R.string.directory_name));
276 if (!dir.exists() && !dir.mkdirs()) {
277 Log.e("NekoLand", "save: error: can't create Pictures directory");
280 final File png = new File(dir, cat.getName().replaceAll("[/ #:]+", "_") + ".png");
281 Bitmap bitmap = cat.createBitmap(EXPORT_BITMAP_SIZE, EXPORT_BITMAP_SIZE);
282 if (bitmap != null) {
284 OutputStream os = new FileOutputStream(png);
285 bitmap.compress(Bitmap.CompressFormat.PNG, 0, os);
287 MediaScannerConnection.scanFile(
289 new String[] {png.toString()},
290 new String[] {"image/png"},
292 Uri uri = Uri.fromFile(png);
293 Intent intent = new Intent(Intent.ACTION_SEND);
294 intent.putExtra(Intent.EXTRA_STREAM, uri);
295 intent.putExtra(Intent.EXTRA_SUBJECT, cat.getName());
296 intent.setType("image/png");
297 startActivity(Intent.createChooser(intent, null));
299 } catch (IOException e) {
300 Log.e("NekoLand", "save: error: " + e);
306 public void onRequestPermissionsResult(int requestCode,
307 String permissions[], int[] grantResults) {
308 if (requestCode == STORAGE_PERM_REQUEST) {
309 if (mPendingShareCat != null) {
310 shareCat(mPendingShareCat);
311 mPendingShareCat = null;
316 private static class CatHolder extends RecyclerView.ViewHolder {
317 private final ImageView imageView;
318 private final TextView textView;
319 private final View contextGroup;
320 private final View delete;
321 private final View share;
323 public CatHolder(View itemView) {
325 imageView = (ImageView) itemView.findViewById(android.R.id.icon);
326 textView = (TextView) itemView.findViewById(android.R.id.title);
327 contextGroup = itemView.findViewById(R.id.contextGroup);
328 delete = itemView.findViewById(android.R.id.closeButton);
329 share = itemView.findViewById(android.R.id.shareText);