1 /* 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 15 package com.android.egg.neko; 16 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; 45 46 import com.android.egg.R; 47 import com.android.egg.neko.PrefState.PrefsListener; 48 import com.android.internal.logging.MetricsLogger; 49 50 import java.io.File; 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; 57 58 public class NekoLand extends Activity implements PrefsListener { 59 public static boolean DEBUG = false; 60 public static boolean DEBUG_NOTIFICATIONS = false; 61 62 private static final int EXPORT_BITMAP_SIZE = 600; 63 64 private static final int STORAGE_PERM_REQUEST = 123; 65 66 private static boolean CAT_GEN = false; 67 private PrefState mPrefs; 68 private CatAdapter mAdapter; 69 private Cat mPendingShareCat; 70 71 72 @Override 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); 81 } 82 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); 91 } 92 93 @Override 94 protected void onDestroy() { 95 super.onDestroy(); 96 mPrefs.setListener(null); 97 } 98 99 private int updateCats() { 100 Cat[] cats; 101 if (CAT_GEN) { 102 cats = new Cat[50]; 103 for (int i = 0; i < cats.length; i++) { 104 cats[i] = Cat.create(this); 105 } 106 } else { 107 final float[] hsv = new float[3]; 108 List<Cat> list = mPrefs.getCats(); 109 Collections.sort(list, new Comparator<Cat>() { 110 @Override 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); 117 } 118 }); 119 cats = list.toArray(new Cat[0]); 120 } 121 mAdapter.setCats(cats); 122 return cats.length; 123 } 124 125 private void onCatClick(Cat cat) { 126 if (CAT_GEN) { 127 mPrefs.addCat(cat); 128 new AlertDialog.Builder(NekoLand.this) 129 .setTitle("Cat added") 130 .setPositiveButton(android.R.string.ok, null) 131 .show(); 132 } else { 133 showNameDialog(cat); 134 } 135 // noman.notify(1, cat.buildNotification(NekoLand.this).build()); 136 } 137 138 private void onCatRemove(Cat cat) { 139 cat.logRemove(this); 140 mPrefs.removeCat(cat); 141 } 142 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) 155 .setTitle(" ") 156 .setIcon(catIcon) 157 .setView(view) 158 .setPositiveButton(android.R.string.ok, new OnClickListener() { 159 @Override 160 public void onClick(DialogInterface dialog, int which) { 161 cat.logRename(context); 162 cat.setName(text.getText().toString().trim()); 163 mPrefs.addCat(cat); 164 } 165 }).show(); 166 } 167 168 @Override 169 public void onPrefsChanged() { 170 updateCats(); 171 } 172 173 private class CatAdapter extends RecyclerView.Adapter<CatHolder> { 174 175 private Cat[] mCats; 176 177 public void setCats(Cat[] cats) { 178 mCats = cats; 179 notifyDataSetChanged(); 180 } 181 182 @Override 183 public CatHolder onCreateViewHolder(ViewGroup parent, int viewType) { 184 return new CatHolder(LayoutInflater.from(parent.getContext()) 185 .inflate(R.layout.cat_view, parent, false)); 186 } 187 188 private void setContextGroupVisible(final CatHolder holder, boolean vis) { 189 final View group = holder.contextGroup; 190 if (vis && group.getVisibility() != View.VISIBLE) { 191 group.setAlpha(0); 192 group.setVisibility(View.VISIBLE); 193 group.animate().alpha(1.0f).setDuration(333); 194 Runnable hideAction = new Runnable() { 195 @Override 196 public void run() { 197 setContextGroupVisible(holder, false); 198 } 199 }; 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() { 205 @Override 206 public void run() { 207 group.setVisibility(View.INVISIBLE); 208 } 209 }); 210 } 211 } 212 213 @Override 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() { 220 @Override 221 public void onClick(View v) { 222 onCatClick(mCats[holder.getAdapterPosition()]); 223 } 224 }); 225 holder.itemView.setOnLongClickListener(new OnLongClickListener() { 226 @Override 227 public boolean onLongClick(View v) { 228 setContextGroupVisible(holder, true); 229 return true; 230 } 231 }); 232 holder.delete.setOnClickListener(new View.OnClickListener() { 233 @Override 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() { 240 @Override 241 public void onClick(DialogInterface dialog, int which) { 242 onCatRemove(mCats[holder.getAdapterPosition()]); 243 } 244 }) 245 .show(); 246 } 247 }); 248 holder.share.setOnClickListener(new View.OnClickListener() { 249 @Override 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; 256 requestPermissions( 257 new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 258 STORAGE_PERM_REQUEST); 259 return; 260 } 261 shareCat(cat); 262 } 263 }); 264 } 265 266 @Override 267 public int getItemCount() { 268 return mCats.length; 269 } 270 } 271 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"); 278 return; 279 } 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) { 283 try { 284 OutputStream os = new FileOutputStream(png); 285 bitmap.compress(Bitmap.CompressFormat.PNG, 0, os); 286 os.close(); 287 MediaScannerConnection.scanFile( 288 this, 289 new String[] {png.toString()}, 290 new String[] {"image/png"}, 291 null); 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)); 298 cat.logShare(this); 299 } catch (IOException e) { 300 Log.e("NekoLand", "save: error: " + e); 301 } 302 } 303 } 304 305 @Override 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; 312 } 313 } 314 } 315 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; 322 323 public CatHolder(View itemView) { 324 super(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); 330 } 331 } 332 } 333