• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Commit MetaInfo

Revision3b98784b5fa50568016a79eecdc049064239c6ab (tree)
Time2017-08-23 03:42:44
AuthorHMML <hmml3939@gmai...>
CommiterHMML

Log Message

Theme downloader impl, first worked version. (WIP on UI!)

Change Summary

Incremental Difference

--- a/app/build.gradle
+++ b/app/build.gradle
@@ -29,10 +29,10 @@ dependencies {
2929 exclude group: 'com.android.support', module: 'support-annotations'
3030 })
3131 compile 'com.android.support:appcompat-v7:25.1.0'
32- compile 'com.google.android.gms:play-services-location:8.+'
32+ compile 'com.google.android.gms:play-services-location:8.4.0'
3333 compile 'com.koushikdutta.ion:ion:2.+'
3434 compile 'com.android.support:design:25.3.1'
35- testCompile 'junit:junit:4.12'
3635 compile 'com.android.support.constraint:constraint-layout:1.0.2'
3736 compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
37+ testCompile 'junit:junit:4.12'
3838 }
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,16 +2,19 @@
22 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
33 package="cloud.hmml.mmw">
44
5+ <uses-permission android:name="android.permission.INTERNET"/>
6+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
7+
58 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
69 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
710
811 <application
12+ android:name=".MainApplication"
913 android:allowBackup="true"
1014 android:icon="@mipmap/ic_launcher"
1115 android:label="@string/app_name"
1216 android:supportsRtl="true"
13- android:theme="@style/AppTheme"
14- android:name="cloud.hmml.mmw.MainApplication">
17+ android:theme="@style/AppTheme.NoActionBar">
1518
1619 <!--
1720 <activity android:name=".WelcomeActivity">
@@ -43,8 +46,10 @@
4346 </activity>
4447 <activity
4548 android:name=".ThemePickerActivity"
46- android:label="@string/title_activity_theme_picker"
47- android:theme="@style/AppTheme.NoActionBar"></activity>
49+ android:label="@string/title_activity_theme_picker" />
50+ <activity android:name=".ThemeDownloadActivity"
51+ android:label="@string/title_activity_theme_download"
52+ />
4853 </application>
4954
5055 </manifest>
\ No newline at end of file
--- a/app/src/main/java/cloud/hmml/mmw/MainApplication.java
+++ b/app/src/main/java/cloud/hmml/mmw/MainApplication.java
@@ -9,6 +9,7 @@ import android.graphics.BitmapFactory;
99 import android.net.Uri;
1010
1111 import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache;
12+import com.nostra13.universalimageloader.core.DisplayImageOptions;
1213 import com.nostra13.universalimageloader.core.ImageLoader;
1314 import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
1415 import com.nostra13.universalimageloader.core.decode.ImageDecoder;
@@ -51,10 +52,12 @@ public class MainApplication extends Application {
5152 @Override
5253 public void onCreate() {
5354 super.onCreate();
55+ DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
56+ .cacheInMemory(true)
57+ .build();
5458 ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
55- .memoryCache(new LruMemoryCache(5 * 1024 * 1024))
56- .memoryCacheSize(5 * 1024 * 1024)
5759 .imageDownloader(new MmwImageDownloader(getApplicationContext()))
60+ .defaultDisplayImageOptions(defaultOptions)
5861 .build();
5962 ImageLoader.getInstance().init(config);
6063
--- a/app/src/main/java/cloud/hmml/mmw/Theme.java
+++ b/app/src/main/java/cloud/hmml/mmw/Theme.java
@@ -7,13 +7,17 @@ import android.content.res.Resources;
77 import android.graphics.Bitmap;
88 import android.graphics.BitmapFactory;
99 import android.net.Uri;
10+import android.support.v7.app.ActionBarDrawerToggle;
1011 import android.util.Log;
1112
1213 import net.arnx.jsonic.JSON;
1314
15+import java.io.BufferedInputStream;
16+import java.io.ByteArrayOutputStream;
1417 import java.io.File;
1518 import java.io.FileInputStream;
1619 import java.io.FileNotFoundException;
20+import java.io.FileOutputStream;
1721 import java.io.IOException;
1822 import java.io.InputStream;
1923 import java.lang.ref.WeakReference;
@@ -21,6 +25,10 @@ import java.util.ArrayList;
2125 import java.util.HashMap;
2226 import java.util.List;
2327 import java.util.Map;
28+import java.util.zip.ZipEntry;
29+import java.util.zip.ZipInputStream;
30+
31+import static android.R.attr.path;
2432
2533
2634 public class Theme {
@@ -28,6 +36,7 @@ public class Theme {
2836 public static final int TYPE_BUILTIN = 1;
2937 public static final int TYPE_MIKU_WEATHER = 2;
3038 public static final int TYPE_IN_STORAGE = 3;
39+ public static final String STORAGE_DIR = "themes";
3140
3241 public static final String W_CLEAR_D = "d";
3342 public static final String W_CLEAR_N = "n";
@@ -38,11 +47,12 @@ public class Theme {
3847
3948 protected int type = TYPE_BUILTIN;
4049 public int schema_version = 1;
41- public int id;
50+ public int id = 0;
4251 public String name;
4352 public String author;
4453 public String url;
4554 public String day_frame;
55+ public String short_desc;
4656 public WeakReference<Context> context;
4757
4858 public static final Map<String, String> W_FNAME_MAP = new HashMap<String, String>();
@@ -116,6 +126,7 @@ public class Theme {
116126 }
117127
118128 public String getAuthor() {return author;}
129+ public String getShortDesc() {return short_desc;}
119130 public String getName() {return name;}
120131 public Uri getUri() {
121132 if (this.url == null || this.url.length() < 1)
@@ -128,6 +139,114 @@ public class Theme {
128139 }
129140 }
130141
142+ public static Theme createByContentUrl(Context context, String url) {
143+ return create(context, Uri.parse(url));
144+ }
145+
146+ public static Theme create(Context context, Uri uri) {
147+ try {
148+ Theme theme = createThemeInstance(context, context.getContentResolver().openInputStream(uri));
149+ if (theme == null)
150+ return null;
151+ if (!extractTheme(context, theme.id, context.getContentResolver().openInputStream(uri)))
152+ return null;
153+ return theme;
154+ } catch (FileNotFoundException e) {
155+ e.printStackTrace();
156+ return null;
157+ }
158+ }
159+ public static Theme create(Context context, File file) {
160+ try {
161+ Theme theme = createThemeInstance(context, new FileInputStream(file));
162+ if (theme == null)
163+ return null;
164+ if (!extractTheme(context, theme.id, new FileInputStream(file)))
165+ return null;
166+ return theme;
167+ } catch (FileNotFoundException e) {
168+ e.printStackTrace();
169+ return null;
170+ }
171+ }
172+
173+ private static Theme createThemeInstance(Context context, InputStream is) {
174+ ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));
175+ ZipEntry ze;
176+ Theme theme = null;
177+
178+ // scan theme.json
179+ try {
180+ while((ze = zis.getNextEntry()) != null) {
181+ if (!ze.getName().equals("theme.json"))
182+ continue;
183+ theme = JSON.decode(zis, Theme.class);
184+ if (theme.id == 0) {
185+ Log.e("theme-creation", "theme.json has invalid ID, Skip!");
186+ return null;
187+ }
188+
189+ theme.type = TYPE_IN_STORAGE;
190+ break;
191+ }
192+ zis.close();
193+ } catch (IOException e) {
194+ e.printStackTrace();
195+ return null;
196+ }
197+ if (theme == null) {
198+ Log.e("theme-creation", "theme.json is not found in the archive. Skip!");
199+ return null;
200+ }
201+ return theme;
202+ }
203+
204+ private static boolean extractTheme(Context context, int id, InputStream is) {
205+ if (id == 0)
206+ return false;
207+ ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));
208+ ZipEntry ze;
209+
210+ File themedir = context.getExternalFilesDir(null);
211+ if (themedir == null) {
212+ Log.e("theme-creation", "Cannot get extract directy. You may need to insert SD card. Abort!");
213+ return false;
214+ }
215+ themedir = new File(themedir, STORAGE_DIR + "/" + id);
216+
217+ // Now theme json is OK, extract files...
218+ Log.i("theme-creation", "Extracting theme to " + themedir.getPath());
219+ try {
220+ while((ze = zis.getNextEntry()) != null) {
221+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
222+ byte[] buffer = new byte[16 * 1024];
223+ int count;
224+
225+ String filename = ze.getName();
226+ File outfile = new File(themedir, filename);
227+ (new File(outfile.getParent())).mkdirs();
228+ FileOutputStream fout = new FileOutputStream(outfile);
229+
230+ while((count = zis.read(buffer)) != -1) {
231+ baos.write(buffer, 0, count);
232+ byte[] bytes = baos.toByteArray();
233+ fout.write(bytes);
234+ baos.reset();
235+ }
236+
237+ fout.close();
238+ zis.closeEntry();
239+ }
240+ zis.close();
241+
242+ } catch (IOException e) {
243+ e.printStackTrace();
244+ return false;
245+ }
246+ Log.d("theme-creation", "Theme extract has been completed!");
247+ return true;
248+ }
249+
131250 public static List<Theme> getAll(Context context) {
132251 List<Theme> list = new ArrayList<Theme>();
133252
@@ -139,10 +258,10 @@ public class Theme {
139258 // On storage
140259 File baseDir = context.getExternalFilesDir(null);
141260 if (baseDir != null) {
142- File themesDir = new File(baseDir, "themes");
261+ File themesDir = new File(baseDir, STORAGE_DIR);
143262 if (themesDir.isDirectory()) {
144263 for (String tid: themesDir.list()) {
145- File theme_def = new File(baseDir, "themes/" + tid + "/theme.json");
264+ File theme_def = new File(baseDir, STORAGE_DIR + "/" + tid + "/theme.json");
146265 if (!theme_def.exists())
147266 continue;
148267 try {
@@ -200,7 +319,7 @@ public class Theme {
200319 return getMikuWeatherTheme(context);
201320 case TYPE_BUILTIN:
202321 try {
203- istream = context.getAssets().open("themes/" + themeId + "/theme.json");
322+ istream = context.getAssets().open(STORAGE_DIR + "/" + themeId + "/theme.json");
204323 } catch (IOException e) {
205324 e.printStackTrace();
206325 return null;
@@ -211,11 +330,11 @@ public class Theme {
211330 if (baseDir == null)
212331 throw new LoadFailed("Directory not found: "+ baseDir.getPath());
213332
214- File themesDir = new File(baseDir, "themes");
333+ File themesDir = new File(baseDir, STORAGE_DIR);
215334 if (!themesDir.isDirectory())
216335 throw new LoadFailed("Themes dir is not found:" + themesDir.getPath());
217336
218- File theme_def = new File(baseDir, "themes/" + themeId + "/theme.json");
337+ File theme_def = new File(baseDir, STORAGE_DIR + "/" + themeId + "/theme.json");
219338 if (!theme_def.exists())
220339 throw new LoadFailed("Theme fef file not found:" + theme_def.getPath());
221340
@@ -252,7 +371,7 @@ public class Theme {
252371 return null;
253372 return "mikuweather-icon:tenki_"+icon_name;
254373 } else if (type == TYPE_BUILTIN) {
255- return "assets://themes/" + id + "/" + W_FNAME_MAP.get(iconIdent);
374+ return "assets://" + STORAGE_DIR + "/" + id + "/" + W_FNAME_MAP.get(iconIdent);
256375 } else if (type == TYPE_IN_STORAGE) {
257376 File baseDir = getContext().getExternalFilesDir(null);
258377 if (baseDir == null) {
@@ -260,7 +379,7 @@ public class Theme {
260379 return null;
261380 }
262381
263- File theme_def = new File(baseDir, "themes/" + id + "/" + W_FNAME_MAP.get(iconIdent));
382+ File theme_def = new File(baseDir, STORAGE_DIR + "/" + id + "/" + W_FNAME_MAP.get(iconIdent));
264383 if (!theme_def.exists()) {
265384 Log.d("theme", "Theme fef file not found:" + theme_def.getPath());
266385 return null;
@@ -280,7 +399,7 @@ public class Theme {
280399 return getMikuWeatherBitmap("tenki_"+icon_name);
281400 } else if (type == TYPE_BUILTIN) {
282401 InputStream istream;
283- String asset_path = "themes/" + id + "/" + W_FNAME_MAP.get(iconIdent);
402+ String asset_path = STORAGE_DIR + "/" + id + "/" + W_FNAME_MAP.get(iconIdent);
284403 try {
285404 istream = getContext().getAssets().open(asset_path);
286405 } catch (IOException e) {
@@ -296,7 +415,7 @@ public class Theme {
296415 return null;
297416 }
298417
299- File theme_def = new File(baseDir, "themes/" + id + "/" + W_FNAME_MAP.get(iconIdent));
418+ File theme_def = new File(baseDir, STORAGE_DIR + "/" + id + "/" + W_FNAME_MAP.get(iconIdent));
300419 if (!theme_def.exists()) {
301420 Log.d("theme", "Theme fef file not found:" + theme_def.getPath());
302421 return null;
--- /dev/null
+++ b/app/src/main/java/cloud/hmml/mmw/ThemeDownloadActivity.java
@@ -0,0 +1,342 @@
1+package cloud.hmml.mmw;
2+
3+import android.app.DownloadManager;
4+import android.content.BroadcastReceiver;
5+import android.content.Context;
6+import android.content.Intent;
7+import android.content.IntentFilter;
8+import android.database.Cursor;
9+import android.graphics.Bitmap;
10+import android.net.Uri;
11+import android.os.AsyncTask;
12+import android.os.ParcelFileDescriptor;
13+import android.support.annotation.LayoutRes;
14+import android.support.annotation.NonNull;
15+import android.support.annotation.Nullable;
16+import android.support.v4.view.MenuItemCompat;
17+import android.support.v7.app.AppCompatActivity;
18+import android.os.Bundle;
19+import android.support.v7.widget.SearchView;
20+import android.support.v7.widget.Toolbar;
21+import android.util.Log;
22+import android.view.LayoutInflater;
23+import android.view.Menu;
24+import android.view.MenuInflater;
25+import android.view.MenuItem;
26+import android.view.View;
27+import android.view.ViewGroup;
28+import android.widget.AdapterView;
29+import android.widget.ArrayAdapter;
30+import android.widget.ImageView;
31+import android.widget.ListView;
32+import android.widget.TextView;
33+import android.widget.Toast;
34+
35+import com.koushikdutta.ion.Ion;
36+import com.nostra13.universalimageloader.core.DisplayImageOptions;
37+import com.nostra13.universalimageloader.core.ImageLoader;
38+import com.nostra13.universalimageloader.core.assist.FailReason;
39+import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
40+
41+import net.arnx.jsonic.JSON;
42+
43+import java.io.BufferedReader;
44+import java.io.File;
45+import java.io.FileInputStream;
46+import java.io.FileNotFoundException;
47+import java.io.FileReader;
48+import java.io.IOException;
49+import java.io.InputStream;
50+import java.math.BigDecimal;
51+import java.math.BigInteger;
52+import java.security.MessageDigest;
53+import java.security.NoSuchAlgorithmException;
54+import java.util.ArrayList;
55+import java.util.Arrays;
56+import java.util.HashMap;
57+import java.util.concurrent.ExecutionException;
58+
59+public class ThemeDownloadActivity extends AppCompatActivity {
60+
61+ static class RemoteTheme {
62+ public int id;
63+ public String archive_url;
64+ public String archive_digest;
65+ public String name;
66+ public String author;
67+ public String url;
68+ public String short_desc;
69+ public String preview1;
70+ public String preview2;
71+ public String preview3;
72+ public String preview4;
73+
74+ public boolean checkDigest(File file) {
75+ FileInputStream is = null;
76+ byte[] buffer = new byte[64 * 1024];
77+ int readbytes = 0;
78+ try {
79+ MessageDigest digest = MessageDigest.getInstance("MD5");
80+ is = new FileInputStream(file);
81+ do {
82+ readbytes = is.read(buffer);
83+ if (readbytes > 0)
84+ digest.update(buffer, 0, readbytes);
85+ } while (readbytes > 0);
86+ is.close();
87+ StringBuffer hexdigest = new StringBuffer();
88+ for (byte b: digest.digest()) {
89+ hexdigest.append(String.format("%02x", b & 0xFF));
90+ }
91+ Log.d("theme-downloader", "Target file hex digest: "+hexdigest.toString() + ", expected: "+ archive_digest);
92+ if (!hexdigest.toString().toLowerCase().equals(archive_digest.toLowerCase()))
93+ return false;
94+ Log.d("theme-downloader", "Digest matched.");
95+ return true;
96+ } catch (FileNotFoundException e) {
97+ e.printStackTrace();
98+ return false;
99+ } catch (NoSuchAlgorithmException e) {
100+ e.printStackTrace();
101+ return false;
102+ } catch (IOException e) {
103+ e.printStackTrace();
104+ return false;
105+ } finally {
106+ try {
107+ if (is != null)
108+ is.close();
109+ } catch (IOException e) {
110+ // ignore
111+ }
112+
113+ }
114+
115+ }
116+ }
117+
118+ private SearchView mSearchView;
119+ private ListView listView;
120+ private RemoteThemeAdapter adapter;
121+
122+ static class RemoteThemeLoader extends AsyncTask<RemoteThemeAdapter, Void, ArrayList<Theme>> {
123+ private RemoteThemeAdapter adapter;
124+
125+ @Override
126+ protected ArrayList doInBackground(RemoteThemeAdapter... params) {
127+ this.adapter = params[0];
128+ return adapter.getJson();
129+ }
130+
131+ @Override
132+ protected void onPostExecute(ArrayList arrayList) {
133+ super.onPostExecute(arrayList);
134+ if (arrayList != null)
135+ adapter.addAll(arrayList);
136+ }
137+ }
138+
139+ static class RemoteThemeAdapter extends ArrayAdapter {
140+ static final String BASE_URL = "http://tempest.private.nemui.org:3000/";
141+ String query;
142+ int page = 0;
143+
144+ public RemoteThemeAdapter(@NonNull Context context, @LayoutRes int resource) {
145+ super(context, resource);
146+ }
147+
148+ public int getTotal() {
149+ return total;
150+ }
151+
152+ int total = 0;
153+
154+ public int getPage() {
155+ return page;
156+ }
157+
158+ public String getQuery() {
159+ return query;
160+ }
161+
162+ public void setQuery(String query) {
163+ this.query = query;
164+ }
165+
166+ public void refresh() {
167+ clear();
168+ new RemoteThemeLoader().execute(this);
169+ }
170+
171+ @NonNull
172+ @Override
173+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
174+ if (convertView == null) {
175+ convertView = LayoutInflater.from(getContext()).inflate(R.layout.theme_item, parent, false);
176+ }
177+
178+ RemoteTheme theme = (RemoteTheme) getItem(position);
179+
180+ ImageView image1 = (ImageView) convertView.findViewById(R.id.theme_image1);
181+ ImageView image2 = (ImageView) convertView.findViewById(R.id.theme_image2);
182+ ImageView image3 = (ImageView) convertView.findViewById(R.id.theme_image3);
183+ ImageView image4 = (ImageView) convertView.findViewById(R.id.theme_image4);
184+ TextView name = (TextView) convertView.findViewById(R.id.theme_name);
185+ TextView author = (TextView) convertView.findViewById(R.id.theme_author);
186+ TextView desc = (TextView) convertView.findViewById(R.id.theme_desc);
187+
188+ ImageLoader imageloader = ImageLoader.getInstance();
189+ DisplayImageOptions options = new DisplayImageOptions.Builder()
190+ .cacheOnDisk(true)
191+ .build();
192+ image1.setImageDrawable(null);
193+ image2.setImageDrawable(null);
194+ image3.setImageDrawable(null);
195+ image4.setImageDrawable(null);
196+ imageloader.displayImage(theme.preview1, image1);
197+ imageloader.displayImage(theme.preview2, image2);
198+ imageloader.displayImage(theme.preview3, image3);
199+ imageloader.displayImage(theme.preview4, image4);
200+
201+ name.setText(theme.name);
202+ author.setText(theme.author);
203+ desc.setText(theme.short_desc);
204+
205+ return convertView;
206+ }
207+ private ArrayList<RemoteTheme> getJson() {
208+ try {
209+ InputStream is = Ion.with(this.getContext())
210+ .load(BASE_URL + "themes.json")
211+ .setHeader("X-Requested-With", "Android cloud.hmml.mmw")
212+ .asInputStream()
213+ .get();
214+ return new ArrayList(Arrays.asList(JSON.decode(is, RemoteTheme[].class)));
215+ } catch (InterruptedException e) {
216+ Log.e("download-adapter", "Exception in fetching json: " + e.getMessage());
217+ e.printStackTrace();
218+ return null;
219+ } catch (ExecutionException e) {
220+ Log.e("download-adapter", "ExecutionException in fetching json: "+e.getMessage());
221+ e.printStackTrace();
222+ return null;
223+ } catch (IOException e) {
224+ Log.e("download-adapter", "IOException in fetching json: "+e.getMessage());
225+ e.printStackTrace();
226+ return null;
227+ }
228+ }
229+ }
230+
231+
232+ @Override
233+ protected void onCreate(Bundle savedInstanceState) {
234+ super.onCreate(savedInstanceState);
235+ setContentView(R.layout.activity_theme_download);
236+
237+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
238+ setSupportActionBar(toolbar);
239+
240+ adapter = new RemoteThemeAdapter(this, R.layout.theme_item);
241+
242+ listView = (ListView) findViewById(R.id.download_theme_list);
243+ listView.setAdapter(adapter);
244+
245+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
246+ @Override
247+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
248+ final RemoteTheme rtheme = (RemoteTheme) parent.getAdapter().getItem(position);
249+
250+ if (getApplicationContext().getExternalCacheDir() == null) {
251+ Toast.makeText(getApplicationContext(), "Please insert SD card", Toast.LENGTH_LONG).show();
252+ return;
253+ }
254+ final File file = new File(getApplicationContext().getExternalCacheDir(), "/theme-" + rtheme.id + ".zip");
255+ Log.d("td", "path:" + file.getPath());
256+ (new File(file.getParent())).mkdirs();
257+
258+ DownloadManager.Request request = new DownloadManager.Request(Uri.parse(rtheme.archive_url));
259+ request.setTitle(rtheme.name);
260+ request.setMimeType("application/zip");
261+ request.setVisibleInDownloadsUi(false);
262+ request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
263+ request.setDestinationUri(Uri.fromFile(file));
264+ final DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
265+ final long queueId = downloadManager.enqueue(request);
266+
267+ final BroadcastReceiver reciever = new BroadcastReceiver() {
268+ @Override
269+ public void onReceive(Context context, Intent intent) {
270+ String action = intent.getAction();
271+ if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
272+ long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
273+ DownloadManager.Query query = new DownloadManager.Query();
274+ if (queueId != downloadId)
275+ return;
276+ query.setFilterById(downloadId);
277+ Cursor c = downloadManager.query(query);
278+ if (c.moveToFirst()) {
279+ for (int i = 0; i < c.getColumnCount(); i ++) {
280+ Log.d("dm", "column: "+c.getColumnName(i) + ", value:" + c.getString(i));
281+ }
282+ int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
283+ if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
284+ String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
285+ Uri uri = Uri.parse(uriString);
286+ Log.d("theme-downloader", "download complete:" + uriString);
287+
288+ if (!rtheme.checkDigest(file)) {
289+ Log.e("theme-downloader", "Digest missmatch! Rejecting file: "+file.getPath());
290+ downloadManager.remove(downloadId);
291+ return;
292+ }
293+
294+ Theme.create(getApplicationContext(), downloadManager.getUriForDownloadedFile(downloadId));
295+ }
296+ }
297+ downloadManager.remove(downloadId);
298+ c.close();
299+ context.unregisterReceiver(this);
300+ }
301+ }
302+ };
303+ getApplicationContext().registerReceiver(reciever, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
304+ }
305+ });
306+
307+ adapter.refresh();
308+ }
309+
310+ @Override
311+ public boolean onCreateOptionsMenu(Menu menu) {
312+ // Set Menu
313+ MenuInflater inflater = getMenuInflater();
314+ inflater.inflate(R.menu.menu_search, menu);
315+
316+ MenuItem menuItem = menu.findItem(R.id.toolbar_menu_search);
317+
318+ mSearchView = (SearchView) MenuItemCompat.getActionView(menuItem);
319+
320+ // whether display Magnifying Glass Icon at first
321+ mSearchView.setIconifiedByDefault(true);
322+
323+ // whether display Submit Button
324+ mSearchView.setSubmitButtonEnabled(false);
325+
326+ mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
327+ @Override
328+ public boolean onQueryTextSubmit(String query) {
329+ return true;
330+ }
331+
332+ @Override
333+ public boolean onQueryTextChange(String newText) {
334+ //suggestion_search(newText);
335+ return true;
336+ //return false;
337+ }
338+ });
339+
340+ return super.onCreateOptionsMenu(menu);
341+ }
342+}
--- a/app/src/main/java/cloud/hmml/mmw/ThemePickerActivity.java
+++ b/app/src/main/java/cloud/hmml/mmw/ThemePickerActivity.java
@@ -53,20 +53,26 @@ public class ThemePickerActivity extends AppCompatActivity {
5353 // Lookup view for data population
5454 TextView name = (TextView) convertView.findViewById(R.id.theme_name);
5555 TextView author = (TextView) convertView.findViewById(R.id.theme_author);
56+ TextView desc = (TextView) convertView.findViewById(R.id.theme_desc);
5657 ImageView image1 = (ImageView) convertView.findViewById(R.id.theme_image1);
5758 ImageView image2 = (ImageView) convertView.findViewById(R.id.theme_image2);
5859 ImageView image3 = (ImageView) convertView.findViewById(R.id.theme_image3);
60+ ImageView image4 = (ImageView) convertView.findViewById(R.id.theme_image4);
5961
6062
6163 // Populate the data into the template view using the data object
62- name.setText(theme.name);
63- author.setText(theme.author);
64+ name.setText(theme.getName());
65+ author.setText(theme.getAuthor());
66+ desc.setText(theme.getShortDesc());
67+ image1.setImageDrawable(null);
68+ image2.setImageDrawable(null);
69+ image3.setImageDrawable(null);
70+ image4.setImageDrawable(null);
6471 ImageLoader imageLoader = ImageLoader.getInstance();
65- imageLoader.displayImage(theme.getIconURL(Theme.W_CLEAR_D + Theme.W_RAIN), image1);
66- imageLoader.displayImage(theme.getIconURL(Theme.W_SNOW + Theme.W_CLEAR_N), image2);
67- imageLoader.displayImage(theme.getIconURL(Theme.W_CLOUD + Theme.W_THUNDER), image3);
68- image2.setVisibility(View.VISIBLE);
69- image3.setVisibility(View.VISIBLE);
72+ imageLoader.displayImage(theme.getIconURL(Theme.W_CLEAR_D), image1);
73+ imageLoader.displayImage(theme.getIconURL(Theme.W_CLEAR_D + Theme.W_RAIN), image2);
74+ imageLoader.displayImage(theme.getIconURL(Theme.W_SNOW + Theme.W_CLEAR_N), image3);
75+ imageLoader.displayImage(theme.getIconURL(Theme.W_CLOUD + Theme.W_THUNDER), image4);
7076
7177 Drawable bg;
7278 if (theme.day_frame != null && theme.day_frame.equals("builtin/white")) {
@@ -77,6 +83,7 @@ public class ThemePickerActivity extends AppCompatActivity {
7783 image1.setBackground(bg);
7884 image2.setBackground(bg);
7985 image3.setBackground(bg);
86+ image4.setBackground(bg);
8087
8188 if (theme.getUri() != null) {
8289 author.setTextColor(Color.BLUE);
@@ -95,7 +102,7 @@ public class ThemePickerActivity extends AppCompatActivity {
95102 }
96103
97104 @Override
98- protected void onCreate(Bundle savedInstanceState) {
105+ protected void onCreate(final Bundle savedInstanceState) {
99106 super.onCreate(savedInstanceState);
100107 setContentView(R.layout.activity_theme_picker);
101108 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
@@ -105,9 +112,7 @@ public class ThemePickerActivity extends AppCompatActivity {
105112 to_download.setOnClickListener(new View.OnClickListener() {
106113 @Override
107114 public void onClick(View view) {
108- Snackbar.make(view, "ダウンロード機能は準備中です。暫くお待ちください。", Snackbar.LENGTH_LONG)
109- .setAction("Action", null).show();
110- // TODO: implement download activity
115+ startActivity(new Intent(ThemePickerActivity.this, ThemeDownloadActivity.class));
111116 }
112117 });
113118
--- a/app/src/main/java/cloud/hmml/mmw/WeatherDispWidgetConfigureActivity.java
+++ b/app/src/main/java/cloud/hmml/mmw/WeatherDispWidgetConfigureActivity.java
@@ -41,9 +41,10 @@ public class WeatherDispWidgetConfigureActivity extends Activity {
4141 private Button mBtnFetch;
4242 private TextView mTxtReportedAt;
4343 private Button mBtnThemeChange;
44- private ImageView mThemeImage1;
44+ private ImageView mThemeImage;
4545 private TextView mThemeName;
4646 private TextView mThemeAuthor;
47+ private TextView mThemeDesc;
4748
4849 View.OnClickListener mOnClickListener = new View.OnClickListener() {
4950 public void onClick(View v) {
@@ -107,11 +108,11 @@ public class WeatherDispWidgetConfigureActivity extends Activity {
107108 mTxtReportedAt.setText(DateFormat.getDateTimeInstance().format(upd));
108109
109110 Theme theme = conf.getTheme();
110- mThemeImage1.setImageBitmap(theme.getIcon(Theme.W_CLEAR_D));
111+ mThemeImage.setImageBitmap(theme.getIcon(Theme.W_CLEAR_D));
111112 if (theme.day_frame != null && theme.day_frame.equals("builtin/white")) {
112- mThemeImage1.setBackground(ContextCompat.getDrawable(this, R.drawable.day_frame_white));
113+ mThemeImage.setBackground(ContextCompat.getDrawable(this, R.drawable.day_frame_white));
113114 } else {
114- mThemeImage1.setBackground(ContextCompat.getDrawable(this, R.drawable.day_frame_black));
115+ mThemeImage.setBackground(ContextCompat.getDrawable(this, R.drawable.day_frame_black));
115116 }
116117 mThemeName.setText(theme.getName());
117118 mThemeAuthor.setText(theme.getAuthor());
@@ -128,6 +129,7 @@ public class WeatherDispWidgetConfigureActivity extends Activity {
128129 }
129130 });
130131 }
132+ mThemeDesc.setText(theme.getShortDesc());
131133 }
132134
133135 @Override
@@ -165,9 +167,10 @@ public class WeatherDispWidgetConfigureActivity extends Activity {
165167 mTxtReportedAt = (TextView) findViewById(R.id.txt_last_forecast);
166168 mBtnFetch = (Button) findViewById(R.id.btn_fetch);
167169 mBtnThemeChange = (Button) findViewById(R.id.btn_theme_change);
168- mThemeImage1 = (ImageView) findViewById(R.id.theme_image1);
169- mThemeName = (TextView) findViewById(R.id.theme_name);
170- mThemeAuthor = (TextView) findViewById(R.id.theme_author);
170+ mThemeImage = (ImageView) findViewById(R.id.theme_cur_image);
171+ mThemeName = (TextView) findViewById(R.id.theme_cur_name);
172+ mThemeAuthor = (TextView) findViewById(R.id.theme_cur_author);
173+ mThemeDesc = (TextView) findViewById(R.id.theme_cur_desc);
171174
172175 mChkAutoLocation.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
173176 @Override
--- /dev/null
+++ b/app/src/main/res/layout/activity_theme_download.xml
@@ -0,0 +1,46 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+ xmlns:app="http://schemas.android.com/apk/res-auto"
4+ xmlns:tools="http://schemas.android.com/tools"
5+ android:layout_width="match_parent"
6+ android:layout_height="match_parent"
7+ tools:context="cloud.hmml.mmw.ThemeDownloadActivity">
8+
9+ <android.support.design.widget.AppBarLayout
10+ android:layout_width="match_parent"
11+ android:layout_height="wrap_content"
12+ android:theme="@style/AppTheme.AppBarOverlay"
13+ tools:layout_editor_absoluteX="8dp"
14+ tools:layout_editor_absoluteY="0dp">
15+
16+ <android.support.v7.widget.Toolbar
17+ android:id="@+id/toolbar"
18+ android:layout_width="match_parent"
19+ android:layout_height="?attr/actionBarSize"
20+ android:background="?attr/colorPrimary"
21+ app:popupTheme="@style/AppTheme.PopupOverlay" />
22+
23+ </android.support.design.widget.AppBarLayout>
24+
25+ <LinearLayout
26+ xmlns:tools="http://schemas.android.com/tools"
27+ android:layout_width="match_parent"
28+ android:layout_height="match_parent"
29+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
30+ >
31+
32+ <ListView
33+ android:id="@+id/download_theme_list"
34+ android:layout_width="match_parent"
35+ android:layout_height="match_parent"
36+ android:layout_marginEnd="8dp"
37+ android:layout_marginLeft="8dp"
38+ android:layout_marginRight="8dp"
39+ android:layout_marginStart="8dp"
40+ app:layout_constraintLeft_toLeftOf="parent"
41+ app:layout_constraintRight_toRightOf="parent"
42+ tools:layout_editor_absoluteY="8dp" />
43+ </LinearLayout>
44+
45+
46+</android.support.design.widget.CoordinatorLayout>
--- a/app/src/main/res/layout/activity_theme_picker.xml
+++ b/app/src/main/res/layout/activity_theme_picker.xml
@@ -20,8 +20,22 @@
2020
2121 </android.support.design.widget.AppBarLayout>
2222
23- <include layout="@layout/content_theme_picker"
24- android:layout_height="512dp" />
23+ <LinearLayout
24+ android:layout_width="match_parent"
25+ android:layout_height="match_parent"
26+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
27+ >
28+
29+ <ListView
30+ android:id="@+id/cur_theme_list"
31+ android:layout_width="match_parent"
32+ android:layout_height="match_parent"
33+ android:layout_marginEnd="8dp"
34+ android:layout_marginLeft="8dp"
35+ android:layout_marginRight="8dp"
36+ android:layout_marginStart="8dp"
37+ tools:layout_editor_absoluteY="8dp" />
38+ </LinearLayout>
2539
2640 <android.support.design.widget.FloatingActionButton
2741 android:id="@+id/btn_to_download"
@@ -29,6 +43,7 @@
2943 android:layout_height="wrap_content"
3044 android:layout_gravity="bottom|end"
3145 android:layout_margin="@dimen/fab_margin"
46+ app:elevation="8dp"
3247 app:srcCompat="@android:drawable/stat_sys_download" />
3348
3449 </android.support.design.widget.CoordinatorLayout>
--- a/app/src/main/res/layout/content_theme_picker.xml
+++ /dev/null
@@ -1,20 +0,0 @@
1-<?xml version="1.0" encoding="utf-8"?>
2-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3- xmlns:app="http://schemas.android.com/apk/res-auto"
4- xmlns:tools="http://schemas.android.com/tools"
5- android:layout_width="match_parent"
6- android:layout_height="match_parent"
7- app:layout_behavior="@string/appbar_scrolling_view_behavior"
8- tools:context="cloud.hmml.mmw.ThemePickerActivity"
9- tools:showIn="@layout/activity_theme_picker">
10-
11- <ListView
12- android:id="@+id/cur_theme_list"
13- android:layout_width="match_parent"
14- android:layout_height="match_parent"
15- tools:layout_editor_absoluteY="8dp"
16- android:layout_marginRight="8dp"
17- app:layout_constraintRight_toRightOf="parent"
18- android:layout_marginLeft="8dp"
19- app:layout_constraintLeft_toLeftOf="parent" />
20-</android.support.constraint.ConstraintLayout>
--- a/app/src/main/res/layout/theme_item.xml
+++ b/app/src/main/res/layout/theme_item.xml
@@ -9,8 +9,8 @@
99
1010 <ImageView
1111 android:id="@+id/theme_image1"
12- android:layout_width="72dp"
13- android:layout_height="72dp"
12+ android:layout_width="52dp"
13+ android:layout_height="52dp"
1414 android:layout_weight="1"
1515 android:background="@android:drawable/editbox_dropdown_dark_frame"
1616 android:focusable="false"
@@ -18,33 +18,42 @@
1818
1919 <ImageView
2020 android:id="@+id/theme_image2"
21- android:layout_width="72dp"
22- android:layout_height="72dp"
21+ android:layout_width="52dp"
22+ android:layout_height="52dp"
2323 android:layout_toRightOf="@+id/theme_image1"
2424 android:layout_weight="1"
2525 android:background="@android:drawable/editbox_dropdown_dark_frame"
2626 android:focusable="false"
27- android:visibility="gone"
2827 app:srcCompat="@android:drawable/btn_dialog" />
2928
3029 <ImageView
3130 android:id="@+id/theme_image3"
32- android:layout_width="72dp"
33- android:layout_height="72dp"
34- android:layout_toRightOf="@+id/theme_image2"
31+ android:layout_width="52dp"
32+ android:layout_height="52dp"
33+ android:layout_below="@+id/theme_image1"
34+ android:layout_weight="1"
35+ android:background="@android:drawable/editbox_dropdown_dark_frame"
36+ android:focusable="false"
37+ app:srcCompat="@android:drawable/btn_dialog" />
38+
39+ <ImageView
40+ android:id="@+id/theme_image4"
41+ android:layout_width="52dp"
42+ android:layout_height="52dp"
43+ android:layout_alignTop="@+id/theme_image3"
44+ android:layout_toRightOf="@+id/theme_image3"
3545 android:layout_weight="1"
3646 android:background="@android:drawable/editbox_dropdown_dark_frame"
3747 android:focusable="false"
38- android:visibility="gone"
3948 app:srcCompat="@android:drawable/btn_dialog" />
4049
4150 <TextView
4251 android:id="@+id/theme_author"
43- android:layout_width="wrap_content"
52+ android:layout_width="match_parent"
4453 android:layout_height="wrap_content"
4554 android:layout_below="@+id/theme_name"
4655 android:layout_marginLeft="8sp"
47- android:layout_toRightOf="@id/theme_image3"
56+ android:layout_toRightOf="@+id/theme_image2"
4857 android:layout_weight="1"
4958 android:ems="10"
5059 android:focusable="false"
@@ -57,7 +66,7 @@
5766 android:layout_width="match_parent"
5867 android:layout_height="wrap_content"
5968 android:layout_marginLeft="8sp"
60- android:layout_toRightOf="@+id/theme_image3"
69+ android:layout_toRightOf="@+id/theme_image2"
6170 android:layout_weight="1"
6271 android:ems="10"
6372 android:focusable="false"
@@ -65,4 +74,19 @@
6574 android:text="Name"
6675 android:textAppearance="@style/TextAppearance.AppCompat" />
6776
77+ <LinearLayout
78+ android:id="@+id/theme_info_icons"
79+ android:layout_width="match_parent"
80+ android:layout_height="wrap_content"
81+ android:layout_alignStart="@+id/theme_author"
82+ android:layout_below="@+id/theme_author"
83+ android:orientation="vertical"></LinearLayout>
84+
85+ <TextView
86+ android:id="@+id/theme_desc"
87+ android:layout_width="wrap_content"
88+ android:layout_height="wrap_content"
89+ android:layout_alignLeft="@+id/theme_author"
90+ android:layout_below="@+id/theme_info_icons" />
91+
6892 </RelativeLayout>
--- a/app/src/main/res/layout/weather_disp_widget_configure.xml
+++ b/app/src/main/res/layout/weather_disp_widget_configure.xml
@@ -42,8 +42,6 @@
4242 android:id="@+id/btn_location_change"
4343 android:layout_width="wrap_content"
4444 android:layout_height="wrap_content"
45- android:layout_marginRight="0dp"
46- android:layout_marginTop="0dp"
4745 android:text="Change"
4846 android:visibility="gone"
4947 app:layout_constraintRight_toRightOf="@+id/location_headline"
@@ -80,7 +78,7 @@
8078 app:layout_constraintTop_toBottomOf="@+id/txt_location_name" />
8179
8280 <TextView
83- android:id="@+id/thme_headline"
81+ android:id="@+id/theme_headline"
8482 android:layout_width="0dp"
8583 android:layout_height="wrap_content"
8684 android:layout_marginLeft="16dp"
@@ -98,48 +96,83 @@
9896 android:layout_marginStart="16dp"
9997 android:layout_marginEnd="16dp" />
10098
101- <include
102- layout="@layout/theme_item"
99+ <ImageView
100+ android:id="@+id/theme_cur_image"
101+ android:layout_width="80dp"
102+ android:layout_height="80dp"
103+ android:layout_marginLeft="0dp"
104+ android:layout_marginTop="8dp"
105+ android:layout_weight="1"
106+ android:background="@android:drawable/editbox_dropdown_dark_frame"
107+ android:focusable="false"
108+ app:layout_constraintLeft_toLeftOf="@+id/theme_headline"
109+ app:layout_constraintTop_toBottomOf="@+id/theme_headline"
110+ app:srcCompat="@android:drawable/btn_dialog" />
111+
112+ <TextView
113+ android:id="@+id/theme_cur_author"
103114 android:layout_width="0dp"
104115 android:layout_height="wrap_content"
105- tools:layout_constraintTop_creator="1"
106- app:layout_constraintTop_toBottomOf="@+id/thme_headline"
107- tools:layout_constraintLeft_creator="1"
108- app:layout_constraintLeft_toLeftOf="@+id/thme_headline"
109- android:id="@+id/include"
110- app:layout_constraintHorizontal_bias="0.0"
111- android:layout_marginTop="-1dp"
116+ android:layout_marginLeft="8dp"
112117 android:layout_marginRight="8dp"
113- app:layout_constraintRight_toRightOf="@+id/thme_headline" />
118+ android:layout_marginTop="4dp"
119+ android:layout_weight="1"
120+ android:ems="10"
121+ android:focusable="false"
122+ android:inputType="textPersonName"
123+ android:linksClickable="true"
124+ android:text="Name"
125+ app:layout_constraintLeft_toRightOf="@+id/theme_cur_image"
126+ app:layout_constraintTop_toBottomOf="@+id/theme_cur_name"
127+ app:layout_constraintRight_toLeftOf="@+id/btn_theme_change"
128+ app:layout_constraintHorizontal_bias="0.0" />
129+
130+ <TextView
131+ android:id="@+id/theme_cur_name"
132+ android:layout_width="0dp"
133+ android:layout_height="wrap_content"
134+ android:layout_weight="1"
135+ android:ems="10"
136+ android:focusable="false"
137+ android:inputType="textPersonName"
138+ android:text="Name"
139+ android:textAppearance="@style/TextAppearance.AppCompat"
140+ app:layout_constraintLeft_toRightOf="@+id/theme_cur_image"
141+ android:layout_marginLeft="8dp"
142+ android:layout_marginTop="7dp"
143+ app:layout_constraintTop_toBottomOf="@+id/theme_headline"
144+ android:layout_marginRight="8dp"
145+ app:layout_constraintRight_toLeftOf="@+id/btn_theme_change"
146+ app:layout_constraintHorizontal_bias="0.0" />
114147
115148 <TextView
116149 android:id="@+id/last_updat_headline"
117150 android:layout_width="0dp"
118151 android:layout_height="wrap_content"
152+ android:layout_marginEnd="16dp"
119153 android:layout_marginLeft="16dp"
120154 android:layout_marginRight="16dp"
155+ android:layout_marginStart="16dp"
156+ android:layout_marginTop="16dp"
121157 android:background="@color/colorPrimary"
122158 android:text="@string/last_update"
123159 android:textAppearance="@style/TextAppearance.AppCompat.Inverse"
160+ app:layout_constraintHorizontal_bias="0.0"
124161 app:layout_constraintLeft_toLeftOf="parent"
125162 app:layout_constraintRight_toRightOf="parent"
126- tools:layout_constraintLeft_creator="1"
127- tools:layout_constraintTop_creator="1"
128- app:layout_constraintHorizontal_bias="0.0"
129- android:layout_marginTop="16dp"
130- app:layout_constraintTop_toBottomOf="@+id/include"
131- android:layout_marginStart="16dp"
132- android:layout_marginEnd="16dp" />
163+ app:layout_constraintTop_toBottomOf="@+id/theme_cur_image"
164+ tools:layout_constraintLeft_creator="1" />
133165
134166 <Button
135167 android:id="@+id/btn_theme_change"
136- android:layout_width="wrap_content"
168+ android:layout_width="0dp"
137169 android:layout_height="wrap_content"
138- android:text="@string/change"
139- app:layout_constraintRight_toRightOf="@+id/thme_headline"
140- app:layout_constraintBottom_toBottomOf="@+id/include"
141170 android:layout_marginRight="0dp"
142- android:layout_marginBottom="0dp" />
171+ android:layout_marginTop="8dp"
172+ android:lines="1"
173+ android:text="@string/change"
174+ app:layout_constraintRight_toRightOf="@+id/theme_headline"
175+ app:layout_constraintTop_toBottomOf="@+id/theme_headline" />
143176
144177 <TextView
145178 android:id="@+id/txt_last_update"
@@ -148,12 +181,12 @@
148181 android:text="@android:string/unknownName"
149182 android:textAppearance="@style/TextAppearance.AppCompat.Body1"
150183 tools:layout_constraintLeft_creator="1"
151- tools:layout_constraintRight_creator="1"
152184 tools:layout_constraintTop_creator="1"
153185 app:layout_constraintLeft_toRightOf="@+id/fetch_time"
154186 android:layout_marginLeft="8dp"
155187 android:layout_marginTop="8dp"
156- app:layout_constraintTop_toBottomOf="@+id/forecast_time" />
188+ app:layout_constraintTop_toBottomOf="@+id/forecast_time"
189+ android:layout_marginStart="8dp" />
157190
158191 <Button
159192 android:id="@+id/btn_conf_ok"
@@ -215,14 +248,12 @@
215248 android:layout_marginTop="8dp"
216249 android:text="@string/update"
217250 app:layout_constraintTop_toBottomOf="@+id/last_updat_headline"
218- android:layout_marginRight="0dp"
219251 app:layout_constraintRight_toRightOf="@+id/last_updat_headline" />
220252
221253 <TextView
222254 android:id="@+id/forecast_time"
223255 android:layout_width="wrap_content"
224256 android:layout_height="wrap_content"
225- android:layout_marginLeft="0dp"
226257 android:text="@string/forecast_time"
227258 app:layout_constraintLeft_toLeftOf="@+id/last_updat_headline"
228259 android:layout_marginTop="8dp"
@@ -232,7 +263,6 @@
232263 android:id="@+id/fetch_time"
233264 android:layout_width="wrap_content"
234265 android:layout_height="wrap_content"
235- android:layout_marginLeft="0dp"
236266 android:layout_marginTop="8dp"
237267 android:text="@string/fetch_time"
238268 app:layout_constraintLeft_toLeftOf="@+id/forecast_time"
@@ -247,7 +277,21 @@
247277 android:text="@android:string/unknownName"
248278 android:textAppearance="@style/TextAppearance.AppCompat.Body1"
249279 app:layout_constraintLeft_toRightOf="@+id/forecast_time"
250- app:layout_constraintTop_toBottomOf="@+id/last_updat_headline" />
280+ app:layout_constraintTop_toBottomOf="@+id/last_updat_headline"
281+ android:layout_marginStart="8dp" />
282+
283+ <TextView
284+ android:id="@+id/theme_cur_desc"
285+ android:layout_width="0dp"
286+ android:layout_height="wrap_content"
287+ android:layout_below="@+id/theme_cur_author"
288+ android:layout_marginLeft="8dp"
289+ android:layout_marginRight="8dp"
290+ android:layout_marginTop="4dp"
291+ app:layout_constraintHorizontal_bias="0.0"
292+ app:layout_constraintLeft_toRightOf="@+id/theme_cur_image"
293+ app:layout_constraintRight_toRightOf="parent"
294+ app:layout_constraintTop_toBottomOf="@+id/theme_cur_author" />
251295
252296
253297 </android.support.constraint.ConstraintLayout>
\ No newline at end of file
--- /dev/null
+++ b/app/src/main/res/menu/menu_search.xml
@@ -0,0 +1,12 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<menu xmlns:android="http://schemas.android.com/apk/res/android"
3+ xmlns:app="http://schemas.android.com/apk/res-auto">
4+
5+ <item
6+ android:id="@+id/toolbar_menu_search"
7+ android:icon="@android:drawable/ic_menu_search"
8+ android:title="Search"
9+ app:actionViewClass="android.support.v7.widget.SearchView"
10+ app:showAsAction="always" />
11+
12+</menu>
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -17,4 +17,5 @@
1717 <string name="location_perm_rejected">位置情報の利用が拒否されました。\n自動位置決定は利用できません。</string>
1818 <string name="request_perm_location">予報エリアの自動決定のため、位置情報の利用許可をお願いします。</string>
1919 <string name="title_activity_theme_picker">テーマの選択</string>
20+ <string name="title_activity_theme_download">テーマのダウンロード</string>
2021 </resources>
\ No newline at end of file
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -18,4 +18,5 @@
1818 <string name="location_perm_rejected">Permission request for location has been rejected.\nYou cannot use auto location setting.</string>
1919 <string name="request_perm_location">Please allow to use location info to detect weather forcast area.</string>
2020 <string name="title_activity_theme_picker">Select weather theme</string>
21+ <string name="title_activity_theme_download">Download themes</string>
2122 </resources>