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
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;
27import android.graphics.Color;
28import 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;
34import android.support.v7.widget.GridLayoutManager;
35import android.support.v7.widget.RecyclerView;
36import android.util.Log;
37import android.view.ContextThemeWrapper;
38import android.view.LayoutInflater;
39import android.view.View;
40import android.view.View.OnLongClickListener;
41import android.view.ViewGroup;
42import android.widget.EditText;
43import android.widget.ImageView;
44import android.widget.TextView;
45
46import com.android.egg.R;
47import com.android.egg.neko.PrefState.PrefsListener;
48import com.android.internal.logging.MetricsLogger;
49
50import java.io.File;
51import java.io.FileOutputStream;
52import java.io.IOException;
53import java.io.OutputStream;
54import java.util.Collections;
55import java.util.Comparator;
56import java.util.List;
57
58public 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