Partial account profile pages now in. Follows/Followers tabs are empty and block/follow does nothing yet.
This commit is contained in:
parent
dbb2663882
commit
60d68b0ae6
22 changed files with 791 additions and 65 deletions
|
@ -35,6 +35,7 @@
|
||||||
<activity android:name=".ViewVideoActivity" />
|
<activity android:name=".ViewVideoActivity" />
|
||||||
<activity android:name=".ViewThreadActivity" />
|
<activity android:name=".ViewThreadActivity" />
|
||||||
<activity android:name=".ViewTagActivity" />
|
<activity android:name=".ViewTagActivity" />
|
||||||
|
<activity android:name=".AccountActivity" />
|
||||||
<service
|
<service
|
||||||
android:name=".NotificationService"
|
android:name=".NotificationService"
|
||||||
android:description="@string/notification_service_description"
|
android:description="@string/notification_service_description"
|
||||||
|
|
267
app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
Normal file
267
app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is part of Tusky.
|
||||||
|
*
|
||||||
|
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.design.widget.TabLayout;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.android.volley.AuthFailureError;
|
||||||
|
import com.android.volley.Request;
|
||||||
|
import com.android.volley.Response;
|
||||||
|
import com.android.volley.VolleyError;
|
||||||
|
import com.android.volley.toolbox.ImageLoader;
|
||||||
|
import com.android.volley.toolbox.JsonArrayRequest;
|
||||||
|
import com.android.volley.toolbox.JsonObjectRequest;
|
||||||
|
import com.android.volley.toolbox.NetworkImageView;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class AccountActivity extends AppCompatActivity {
|
||||||
|
private String domain;
|
||||||
|
private String accessToken;
|
||||||
|
private boolean following = false;
|
||||||
|
private boolean blocking = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_account);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String username = intent.getStringExtra("username");
|
||||||
|
String id = intent.getStringExtra("id");
|
||||||
|
TextView accountName = (TextView) findViewById(R.id.account_username);
|
||||||
|
accountName.setText(username);
|
||||||
|
|
||||||
|
SharedPreferences preferences = getSharedPreferences(
|
||||||
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
domain = preferences.getString("domain", null);
|
||||||
|
accessToken = preferences.getString("accessToken", null);
|
||||||
|
assert(domain != null);
|
||||||
|
assert(accessToken != null);
|
||||||
|
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
NetworkImageView avatar = (NetworkImageView) findViewById(R.id.account_avatar);
|
||||||
|
NetworkImageView header = (NetworkImageView) findViewById(R.id.account_header);
|
||||||
|
avatar.setDefaultImageResId(R.drawable.avatar_default);
|
||||||
|
avatar.setErrorImageResId(R.drawable.avatar_error);
|
||||||
|
header.setDefaultImageResId(R.drawable.account_header_default);
|
||||||
|
|
||||||
|
obtainAccount(id);
|
||||||
|
obtainRelationships(id);
|
||||||
|
|
||||||
|
// Setup the tabs and timeline pager.
|
||||||
|
AccountPagerAdapter adapter = new AccountPagerAdapter(getSupportFragmentManager(), id);
|
||||||
|
String[] pageTitles = {
|
||||||
|
getString(R.string.title_statuses),
|
||||||
|
getString(R.string.title_follows),
|
||||||
|
getString(R.string.title_followers)
|
||||||
|
};
|
||||||
|
adapter.setPageTitles(pageTitles);
|
||||||
|
ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
|
||||||
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void obtainAccount(String id) {
|
||||||
|
String endpoint = String.format(getString(R.string.endpoint_accounts), id);
|
||||||
|
String url = "https://" + domain + endpoint;
|
||||||
|
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null,
|
||||||
|
new Response.Listener<JSONObject>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(JSONObject response) {
|
||||||
|
try {
|
||||||
|
onObtainAccountSuccess(response);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
onObtainAccountFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Response.ErrorListener() {
|
||||||
|
@Override
|
||||||
|
public void onErrorResponse(VolleyError error) {
|
||||||
|
onObtainAccountFailure();
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getHeaders() throws AuthFailureError {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
headers.put("Authorization", "Bearer " + accessToken);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VolleySingleton.getInstance(this).addToRequestQueue(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onObtainAccountSuccess(JSONObject response) throws JSONException {
|
||||||
|
TextView username = (TextView) findViewById(R.id.account_username);
|
||||||
|
TextView displayName = (TextView) findViewById(R.id.account_display_name);
|
||||||
|
TextView note = (TextView) findViewById(R.id.account_note);
|
||||||
|
NetworkImageView avatar = (NetworkImageView) findViewById(R.id.account_avatar);
|
||||||
|
NetworkImageView header = (NetworkImageView) findViewById(R.id.account_header);
|
||||||
|
|
||||||
|
String usernameFormatted = String.format(
|
||||||
|
getString(R.string.status_username_format), response.getString("acct"));
|
||||||
|
username.setText(usernameFormatted);
|
||||||
|
|
||||||
|
String displayNameString = response.getString("display_name");
|
||||||
|
displayName.setText(displayNameString);
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.setTitle(displayNameString);
|
||||||
|
}
|
||||||
|
|
||||||
|
String noteHtml = response.getString("note");
|
||||||
|
Spanned noteSpanned = HtmlUtils.fromHtml(noteHtml);
|
||||||
|
note.setText(noteSpanned);
|
||||||
|
|
||||||
|
ImageLoader imageLoader = VolleySingleton.getInstance(this).getImageLoader();
|
||||||
|
avatar.setImageUrl(response.getString("avatar"), imageLoader);
|
||||||
|
String headerUrl = response.getString("header");
|
||||||
|
if (!headerUrl.isEmpty() && !headerUrl.equals("/headers/original/missing.png")) {
|
||||||
|
header.setImageUrl(headerUrl, imageLoader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onObtainAccountFailure() {
|
||||||
|
//TODO: help
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void obtainRelationships(String id) {
|
||||||
|
String endpoint = getString(R.string.endpoint_relationships);
|
||||||
|
String url = String.format("https://%s%s?id=%s", domain, endpoint, id);
|
||||||
|
JsonArrayRequest request = new JsonArrayRequest(url,
|
||||||
|
new Response.Listener<JSONArray>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(JSONArray response) {
|
||||||
|
boolean following;
|
||||||
|
boolean blocking;
|
||||||
|
try {
|
||||||
|
JSONObject object = response.getJSONObject(0);
|
||||||
|
following = object.getBoolean("following");
|
||||||
|
blocking = object.getBoolean("blocking");
|
||||||
|
} catch (JSONException e) {
|
||||||
|
onObtainRelationshipsFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onObtainRelationshipsSuccess(following, blocking);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Response.ErrorListener() {
|
||||||
|
@Override
|
||||||
|
public void onErrorResponse(VolleyError error) {
|
||||||
|
onObtainRelationshipsFailure();
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getHeaders() throws AuthFailureError {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
headers.put("Authorization", "Bearer " + accessToken);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VolleySingleton.getInstance(this).addToRequestQueue(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onObtainRelationshipsSuccess(boolean following, boolean blocking) {
|
||||||
|
this.following = following;
|
||||||
|
this.blocking = blocking;
|
||||||
|
if (!following || !blocking) {
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onObtainRelationshipsFailure() {
|
||||||
|
//TODO: help
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.account_toolbar, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
|
MenuItem follow = menu.findItem(R.id.action_follow);
|
||||||
|
String title;
|
||||||
|
if (following) {
|
||||||
|
title = getString(R.string.action_unfollow);
|
||||||
|
} else {
|
||||||
|
title = getString(R.string.action_follow);
|
||||||
|
}
|
||||||
|
follow.setTitle(title);
|
||||||
|
MenuItem block = menu.findItem(R.id.action_block);
|
||||||
|
if (blocking) {
|
||||||
|
title = getString(R.string.action_unblock);
|
||||||
|
} else {
|
||||||
|
title = getString(R.string.action_block);
|
||||||
|
}
|
||||||
|
block.setTitle(title);
|
||||||
|
return super.onPrepareOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void follow() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void block() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_back: {
|
||||||
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case R.id.action_follow: {
|
||||||
|
follow();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case R.id.action_block: {
|
||||||
|
block();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is part of Tusky.
|
||||||
|
*
|
||||||
|
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
public class AccountFragment extends Fragment {
|
||||||
|
public enum Type {
|
||||||
|
FOLLOWS,
|
||||||
|
FOLLOWERS,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountFragment newInstance(Type type) {
|
||||||
|
Bundle arguments = new Bundle();
|
||||||
|
AccountFragment fragment = new AccountFragment();
|
||||||
|
arguments.putString("type", type.name());
|
||||||
|
fragment.setArguments(arguments);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
return super.onCreateView(inflater, container, savedInstanceState);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is part of Tusky.
|
||||||
|
*
|
||||||
|
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentPagerAdapter;
|
||||||
|
|
||||||
|
public class AccountPagerAdapter extends FragmentPagerAdapter {
|
||||||
|
private String accountId;
|
||||||
|
private String[] pageTitles;
|
||||||
|
|
||||||
|
public AccountPagerAdapter(FragmentManager manager, String accountId) {
|
||||||
|
super(manager);
|
||||||
|
this.accountId = accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPageTitles(String[] titles) {
|
||||||
|
pageTitles = titles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case 0: {
|
||||||
|
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId);
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWS);
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWERS);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getPageTitle(int position) {
|
||||||
|
return pageTitles[position];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,18 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is part of Tusky.
|
||||||
|
*
|
||||||
|
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
public interface AdapterItemRemover {
|
public interface AdapterItemRemover {
|
||||||
|
|
42
app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java
Normal file
42
app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is part of Tusky.
|
||||||
|
*
|
||||||
|
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.text.Spanned;
|
||||||
|
|
||||||
|
public class HtmlUtils {
|
||||||
|
private static CharSequence trimTrailingWhitespace(CharSequence s) {
|
||||||
|
int i = s.length();
|
||||||
|
do {
|
||||||
|
i--;
|
||||||
|
} while (i >= 0 && Character.isWhitespace(s.charAt(i)));
|
||||||
|
return s.subSequence(0, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Spanned fromHtml(String html) {
|
||||||
|
Spanned result;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
|
||||||
|
} else {
|
||||||
|
result = Html.fromHtml(html);
|
||||||
|
}
|
||||||
|
/* Html.fromHtml returns trailing whitespace if the html ends in a </p> tag, which
|
||||||
|
* all status contents do, so it should be trimmed. */
|
||||||
|
return (Spanned) trimTrailingWhitespace(result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,16 +29,33 @@ import android.support.v7.widget.Toolbar;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import com.android.volley.AuthFailureError;
|
||||||
|
import com.android.volley.Request;
|
||||||
|
import com.android.volley.Response;
|
||||||
|
import com.android.volley.VolleyError;
|
||||||
|
import com.android.volley.toolbox.JsonObjectRequest;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
private AlarmManager alarmManager;
|
private AlarmManager alarmManager;
|
||||||
private PendingIntent serviceAlarmIntent;
|
private PendingIntent serviceAlarmIntent;
|
||||||
private boolean notificationServiceEnabled;
|
private boolean notificationServiceEnabled;
|
||||||
|
private String loggedInAccountId;
|
||||||
|
private String loggedInAccountUsername;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
// Fetch user info while we're doing other things.
|
||||||
|
fetchUserInfo();
|
||||||
|
|
||||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
@ -75,11 +92,78 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fetchUserInfo() {
|
||||||
|
SharedPreferences preferences = getSharedPreferences(
|
||||||
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
String domain = preferences.getString("domain", null);
|
||||||
|
final String accessToken = preferences.getString("accessToken", null);
|
||||||
|
String id = preferences.getString("loggedInAccountId", null);
|
||||||
|
String username = preferences.getString("loggedInAccountUsername", null);
|
||||||
|
if (id != null && username != null) {
|
||||||
|
loggedInAccountId = id;
|
||||||
|
loggedInAccountUsername = username;
|
||||||
|
} else {
|
||||||
|
String endpoint = getString(R.string.endpoint_verify_credentials);
|
||||||
|
String url = "https://" + domain + endpoint;
|
||||||
|
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null,
|
||||||
|
new Response.Listener<JSONObject>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(JSONObject response) {
|
||||||
|
try {
|
||||||
|
String id = response.getString("id");
|
||||||
|
String username = response.getString("acct");
|
||||||
|
onFetchUserInfoSuccess(id, username);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
//TODO: Help
|
||||||
|
assert (false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Response.ErrorListener() {
|
||||||
|
@Override
|
||||||
|
public void onErrorResponse(VolleyError error) {
|
||||||
|
onFetchUserInfoFailure();
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getHeaders() throws AuthFailureError {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
headers.put("Authorization", "Bearer " + accessToken);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VolleySingleton.getInstance(this).addToRequestQueue(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFetchUserInfoSuccess(String id, String username) {
|
||||||
|
loggedInAccountId = id;
|
||||||
|
loggedInAccountUsername = username;
|
||||||
|
SharedPreferences preferences = getSharedPreferences(
|
||||||
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.putString("loggedInAccountId", loggedInAccountId);
|
||||||
|
editor.putString("loggedInAccountUsername", loggedInAccountUsername);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFetchUserInfoFailure() {
|
||||||
|
//TODO: help
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
private void compose() {
|
private void compose() {
|
||||||
Intent intent = new Intent(this, ComposeActivity.class);
|
Intent intent = new Intent(this, ComposeActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void viewProfile() {
|
||||||
|
Intent intent = new Intent(this, AccountActivity.class);
|
||||||
|
intent.putExtra("id", loggedInAccountId);
|
||||||
|
intent.putExtra("username", loggedInAccountUsername);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
private void logOut() {
|
private void logOut() {
|
||||||
if (notificationServiceEnabled) {
|
if (notificationServiceEnabled) {
|
||||||
alarmManager.cancel(serviceAlarmIntent);
|
alarmManager.cancel(serviceAlarmIntent);
|
||||||
|
@ -108,6 +192,10 @@ public class MainActivity extends AppCompatActivity {
|
||||||
compose();
|
compose();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case R.id.action_profile: {
|
||||||
|
viewProfile();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case R.id.action_logout: {
|
case R.id.action_logout: {
|
||||||
logOut();
|
logOut();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -202,4 +202,15 @@ public class NotificationsFragment extends SFragment implements
|
||||||
public void onViewTag(String tag) {
|
public void onViewTag(String tag) {
|
||||||
super.viewTag(tag);
|
super.viewTag(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onViewAccount(String id, String username) {
|
||||||
|
super.viewAccount(id, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onViewAccount(int position) {
|
||||||
|
Status status = adapter.getItem(position).getStatus();
|
||||||
|
String id = status.getAccountId();
|
||||||
|
String username = status.getUsername();
|
||||||
|
super.viewAccount(id, username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,6 @@ import com.android.volley.Response;
|
||||||
import com.android.volley.VolleyError;
|
import com.android.volley.VolleyError;
|
||||||
import com.android.volley.toolbox.JsonObjectRequest;
|
import com.android.volley.toolbox.JsonObjectRequest;
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -61,10 +60,12 @@ public class SFragment extends Fragment {
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
domain = preferences.getString("domain", null);
|
domain = preferences.getString("domain", null);
|
||||||
accessToken = preferences.getString("accessToken", null);
|
accessToken = preferences.getString("accessToken", null);
|
||||||
|
loggedInAccountId = preferences.getString("loggedInAccountId", null);
|
||||||
|
loggedInUsername = preferences.getString("loggedInAccountUsername", null);
|
||||||
assert(domain != null);
|
assert(domain != null);
|
||||||
assert(accessToken != null);
|
assert(accessToken != null);
|
||||||
|
assert(loggedInAccountId != null);
|
||||||
sendUserInfoRequest();
|
assert(loggedInUsername != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendRequest(
|
protected void sendRequest(
|
||||||
|
@ -100,22 +101,6 @@ public class SFragment extends Fragment {
|
||||||
sendRequest(Request.Method.POST, endpoint, null, null);
|
sendRequest(Request.Method.POST, endpoint, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendUserInfoRequest() {
|
|
||||||
sendRequest(Request.Method.GET, getString(R.string.endpoint_verify_credentials), null,
|
|
||||||
new Response.Listener<JSONObject>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(JSONObject response) {
|
|
||||||
try {
|
|
||||||
loggedInAccountId = response.getString("id");
|
|
||||||
loggedInUsername = response.getString("acct");
|
|
||||||
} catch (JSONException e) {
|
|
||||||
//TODO: Help
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void reply(Status status) {
|
protected void reply(Status status) {
|
||||||
String inReplyToId = status.getId();
|
String inReplyToId = status.getId();
|
||||||
Status.Mention[] mentions = status.getMentions();
|
Status.Mention[] mentions = status.getMentions();
|
||||||
|
@ -250,4 +235,11 @@ public class SFragment extends Fragment {
|
||||||
intent.putExtra("hashtag", tag);
|
intent.putExtra("hashtag", tag);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void viewAccount(String id, String username) {
|
||||||
|
Intent intent = new Intent(getContext(), AccountActivity.class);
|
||||||
|
intent.putExtra("id", id);
|
||||||
|
intent.putExtra("username", username);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,13 @@ import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.android.volley.Request;
|
||||||
|
import com.android.volley.Response;
|
||||||
|
import com.android.volley.toolbox.JsonObjectRequest;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
public class SplashActivity extends AppCompatActivity {
|
public class SplashActivity extends AppCompatActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
|
|
@ -182,26 +182,6 @@ public class Status {
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CharSequence trimTrailingWhitespace(CharSequence s) {
|
|
||||||
int i = s.length();
|
|
||||||
do {
|
|
||||||
i--;
|
|
||||||
} while (i >= 0 && Character.isWhitespace(s.charAt(i)));
|
|
||||||
return s.subSequence(0, i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Spanned compatFromHtml(String html) {
|
|
||||||
Spanned result;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
|
|
||||||
} else {
|
|
||||||
result = Html.fromHtml(html);
|
|
||||||
}
|
|
||||||
/* Html.fromHtml returns trailing whitespace if the html ends in a </p> tag, which
|
|
||||||
* all status contents do, so it should be trimmed. */
|
|
||||||
return (Spanned) trimTrailingWhitespace(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Status parse(JSONObject object, boolean isReblog) throws JSONException {
|
public static Status parse(JSONObject object, boolean isReblog) throws JSONException {
|
||||||
String id = object.getString("id");
|
String id = object.getString("id");
|
||||||
String content = object.getString("content");
|
String content = object.getString("content");
|
||||||
|
@ -264,7 +244,7 @@ public class Status {
|
||||||
status = reblog;
|
status = reblog;
|
||||||
status.setRebloggedByUsername(username);
|
status.setRebloggedByUsername(username);
|
||||||
} else {
|
} else {
|
||||||
Spanned contentPlus = compatFromHtml(content);
|
Spanned contentPlus = HtmlUtils.fromHtml(content);
|
||||||
status = new Status(
|
status = new Status(
|
||||||
id, accountId, displayName, username, contentPlus, avatar, createdAt,
|
id, accountId, displayName, username, contentPlus, avatar, createdAt,
|
||||||
reblogged, favourited, visibility);
|
reblogged, favourited, visibility);
|
||||||
|
|
|
@ -25,4 +25,6 @@ public interface StatusActionListener {
|
||||||
void onViewMedia(String url, Status.MediaAttachment.Type type);
|
void onViewMedia(String url, Status.MediaAttachment.Type type);
|
||||||
void onViewThread(int position);
|
void onViewThread(int position);
|
||||||
void onViewTag(String tag);
|
void onViewTag(String tag);
|
||||||
|
void onViewAccount(String id, String username);
|
||||||
|
void onViewAccount(int position);
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,8 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||||
username.setText(usernameText);
|
username.setText(usernameText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setContent(Spanned content, final StatusActionListener listener) {
|
public void setContent(Spanned content, Status.Mention[] mentions,
|
||||||
|
final StatusActionListener listener) {
|
||||||
// Redirect URLSpan's in the status content to the listener for viewing tag pages.
|
// Redirect URLSpan's in the status content to the listener for viewing tag pages.
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder(content);
|
SpannableStringBuilder builder = new SpannableStringBuilder(content);
|
||||||
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
|
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
|
||||||
|
@ -106,17 +107,36 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||||
int start = builder.getSpanStart(span);
|
int start = builder.getSpanStart(span);
|
||||||
int end = builder.getSpanEnd(span);
|
int end = builder.getSpanEnd(span);
|
||||||
int flags = builder.getSpanFlags(span);
|
int flags = builder.getSpanFlags(span);
|
||||||
CharSequence tag = builder.subSequence(start, end);
|
CharSequence text = builder.subSequence(start, end);
|
||||||
if (tag.charAt(0) == '#') {
|
if (text.charAt(0) == '#') {
|
||||||
final String viewTag = tag.subSequence(1, tag.length()).toString();
|
final String tag = text.subSequence(1, text.length()).toString();
|
||||||
ClickableSpan newSpan = new ClickableSpan() {
|
ClickableSpan newSpan = new ClickableSpan() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View widget) {
|
public void onClick(View widget) {
|
||||||
listener.onViewTag(viewTag);
|
listener.onViewTag(tag);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
builder.removeSpan(span);
|
builder.removeSpan(span);
|
||||||
builder.setSpan(newSpan, start, end, flags);
|
builder.setSpan(newSpan, start, end, flags);
|
||||||
|
} else if (text.charAt(0) == '@') {
|
||||||
|
final String accountUsername = text.subSequence(1, text.length()).toString();
|
||||||
|
String id = null;
|
||||||
|
for (Status.Mention mention: mentions) {
|
||||||
|
if (mention.getUsername().equals(accountUsername)) {
|
||||||
|
id = mention.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (id != null) {
|
||||||
|
final String accountId = id;
|
||||||
|
ClickableSpan newSpan = new ClickableSpan() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View widget) {
|
||||||
|
listener.onViewAccount(accountId, accountUsername);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
builder.removeSpan(span);
|
||||||
|
builder.setSpan(newSpan, start, end, flags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Set the contents.
|
// Set the contents.
|
||||||
|
@ -236,7 +256,12 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setupButtons(final StatusActionListener listener, final int position) {
|
public void setupButtons(final StatusActionListener listener, final int position) {
|
||||||
|
avatar.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onViewAccount(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
replyButton.setOnClickListener(new View.OnClickListener() {
|
replyButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
@ -261,19 +286,25 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||||
listener.onMore(v, position);
|
listener.onMore(v, position);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
container.setOnClickListener(new View.OnClickListener() {
|
/* Even though the content TextView is a child of the container, it won't respond to clicks
|
||||||
|
* if it contains URLSpans without also setting its listener. The surrounding spans will
|
||||||
|
* just eat the clicks instead of deferring to the parent listener, but WILL respond to a
|
||||||
|
* listener directly on the TextView, for whatever reason. */
|
||||||
|
View.OnClickListener viewThreadListener = new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
listener.onViewThread(position);
|
listener.onViewThread(position);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
content.setOnClickListener(viewThreadListener);
|
||||||
|
container.setOnClickListener(viewThreadListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setupWithStatus(Status status, StatusActionListener listener, int position) {
|
public void setupWithStatus(Status status, StatusActionListener listener, int position) {
|
||||||
setDisplayName(status.getDisplayName());
|
setDisplayName(status.getDisplayName());
|
||||||
setUsername(status.getUsername());
|
setUsername(status.getUsername());
|
||||||
setCreatedAt(status.getCreatedAt());
|
setCreatedAt(status.getCreatedAt());
|
||||||
setContent(status.getContent(), listener);
|
setContent(status.getContent(), status.getMentions(), listener);
|
||||||
setAvatar(status.getAvatar());
|
setAvatar(status.getAvatar());
|
||||||
setReblogged(status.getReblogged());
|
setReblogged(status.getReblogged());
|
||||||
setFavourited(status.getFavourited());
|
setFavourited(status.getFavourited());
|
||||||
|
|
|
@ -48,13 +48,14 @@ public class TimelineFragment extends SFragment implements
|
||||||
MENTIONS,
|
MENTIONS,
|
||||||
PUBLIC,
|
PUBLIC,
|
||||||
TAG,
|
TAG,
|
||||||
|
USER,
|
||||||
}
|
}
|
||||||
|
|
||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
private RecyclerView recyclerView;
|
private RecyclerView recyclerView;
|
||||||
private TimelineAdapter adapter;
|
private TimelineAdapter adapter;
|
||||||
private Kind kind;
|
private Kind kind;
|
||||||
private String hashtag;
|
private String hashtagOrId;
|
||||||
private LinearLayoutManager layoutManager;
|
private LinearLayoutManager layoutManager;
|
||||||
private EndlessOnScrollListener scrollListener;
|
private EndlessOnScrollListener scrollListener;
|
||||||
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
||||||
|
@ -67,11 +68,11 @@ public class TimelineFragment extends SFragment implements
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TimelineFragment newInstance(Kind kind, String hashtag) {
|
public static TimelineFragment newInstance(Kind kind, String hashtagOrId) {
|
||||||
TimelineFragment fragment = new TimelineFragment();
|
TimelineFragment fragment = new TimelineFragment();
|
||||||
Bundle arguments = new Bundle();
|
Bundle arguments = new Bundle();
|
||||||
arguments.putString("kind", kind.name());
|
arguments.putString("kind", kind.name());
|
||||||
arguments.putString("hashtag", hashtag);
|
arguments.putString("hashtag_or_id", hashtagOrId);
|
||||||
fragment.setArguments(arguments);
|
fragment.setArguments(arguments);
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
@ -82,8 +83,8 @@ public class TimelineFragment extends SFragment implements
|
||||||
|
|
||||||
Bundle arguments = getArguments();
|
Bundle arguments = getArguments();
|
||||||
kind = Kind.valueOf(arguments.getString("kind"));
|
kind = Kind.valueOf(arguments.getString("kind"));
|
||||||
if (kind == Kind.TAG) {
|
if (kind == Kind.TAG || kind == Kind.USER) {
|
||||||
hashtag = arguments.getString("hashtag");
|
hashtagOrId = arguments.getString("hashtag_or_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
View rootView = inflater.inflate(R.layout.fragment_timeline, container, false);
|
View rootView = inflater.inflate(R.layout.fragment_timeline, container, false);
|
||||||
|
@ -118,7 +119,7 @@ public class TimelineFragment extends SFragment implements
|
||||||
adapter = new TimelineAdapter(this, this);
|
adapter = new TimelineAdapter(this, this);
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
if (kind != Kind.TAG) {
|
if (jumpToTopAllowed()) {
|
||||||
TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||||
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
|
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -144,13 +145,17 @@ public class TimelineFragment extends SFragment implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
if (kind != Kind.TAG) {
|
if (jumpToTopAllowed()) {
|
||||||
TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||||
tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
|
tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
|
||||||
}
|
}
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean jumpToTopAllowed() {
|
||||||
|
return kind != Kind.TAG;
|
||||||
|
}
|
||||||
|
|
||||||
private void jumpToTop() {
|
private void jumpToTop() {
|
||||||
layoutManager.scrollToPositionWithOffset(0, 0);
|
layoutManager.scrollToPositionWithOffset(0, 0);
|
||||||
scrollListener.reset();
|
scrollListener.reset();
|
||||||
|
@ -173,8 +178,13 @@ public class TimelineFragment extends SFragment implements
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TAG: {
|
case TAG: {
|
||||||
assert(hashtag != null);
|
assert(hashtagOrId != null);
|
||||||
endpoint = String.format(getString(R.string.endpoint_timelines_tag), hashtag);
|
endpoint = String.format(getString(R.string.endpoint_timelines_tag), hashtagOrId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case USER: {
|
||||||
|
assert(hashtagOrId != null);
|
||||||
|
endpoint = String.format(getString(R.string.endpoint_statuses), hashtagOrId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,4 +290,15 @@ public class TimelineFragment extends SFragment implements
|
||||||
public void onViewTag(String tag) {
|
public void onViewTag(String tag) {
|
||||||
super.viewTag(tag);
|
super.viewTag(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onViewAccount(String id, String username) {
|
||||||
|
super.viewAccount(id, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onViewAccount(int position) {
|
||||||
|
Status status = adapter.getItem(position);
|
||||||
|
String id = status.getAccountId();
|
||||||
|
String username = status.getUsername();
|
||||||
|
super.viewAccount(id, username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,4 +144,15 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene
|
||||||
public void onViewTag(String tag) {
|
public void onViewTag(String tag) {
|
||||||
super.viewTag(tag);
|
super.viewTag(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onViewAccount(String id, String username) {
|
||||||
|
super.viewAccount(id, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onViewAccount(int position) {
|
||||||
|
Status status = adapter.getItem(position);
|
||||||
|
String id = status.getAccountId();
|
||||||
|
String username = status.getUsername();
|
||||||
|
super.viewAccount(id, username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
app/src/main/res/drawable/account_header_default.png
Normal file
BIN
app/src/main/res/drawable/account_header_default.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
122
app/src/main/res/layout/activity_account.xml
Normal file
122
app/src/main/res/layout/activity_account.xml
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.design.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<android.support.design.widget.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<android.support.design.widget.CollapsingToolbarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||||
|
app:titleEnabled="false">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
|
android:background="@color/account_header_background">
|
||||||
|
|
||||||
|
<com.android.volley.toolbox.NetworkImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/account_header"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:adjustViewBounds="true" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.android.volley.toolbox.NetworkImageView
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:id="@+id/account_avatar"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingRight="8dp"
|
||||||
|
android:paddingTop="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_toRightOf="@id/account_avatar"
|
||||||
|
android:layout_centerVertical="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/account_display_name"
|
||||||
|
android:textStyle="normal|bold"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/account_username" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/account_note"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
app:layout_collapseMode="pin" />
|
||||||
|
|
||||||
|
</android.support.design.widget.CollapsingToolbarLayout>
|
||||||
|
|
||||||
|
</android.support.design.widget.AppBarLayout>
|
||||||
|
|
||||||
|
<android.support.v4.view.ViewPager
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/pager"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<android.support.design.widget.TabLayout
|
||||||
|
android:id="@+id/tab_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<android.support.design.widget.TabItem
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<android.support.design.widget.TabItem
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<android.support.design.widget.TabItem
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</android.support.design.widget.TabLayout>
|
||||||
|
|
||||||
|
</android.support.v4.view.ViewPager>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/overlay_fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</android.support.design.widget.CoordinatorLayout>
|
|
@ -66,6 +66,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="@dimen/status_since_created_left_margin" />
|
android:layout_marginLeft="@dimen/status_since_created_left_margin" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|
18
app/src/main/res/menu/account_toolbar.xml
Normal file
18
app/src/main/res/menu/account_toolbar.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item android:id="@+id/action_back"
|
||||||
|
android:title="@string/action_back"
|
||||||
|
android:icon="@drawable/ic_back"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
<item android:id="@+id/action_follow"
|
||||||
|
android:title="@string/action_follow"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item android:id="@+id/action_block"
|
||||||
|
android:title="@string/action_block"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
</menu>
|
|
@ -9,6 +9,11 @@
|
||||||
android:icon="@drawable/ic_compose"
|
android:icon="@drawable/ic_compose"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_profile"
|
||||||
|
android:title="@string/action_profile"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_logout"
|
android:id="@+id/action_logout"
|
||||||
android:title="@string/action_logout"
|
android:title="@string/action_logout"
|
||||||
|
|
|
@ -9,4 +9,5 @@
|
||||||
<color name="media_preview_unloaded_background">#DFDFDF</color>
|
<color name="media_preview_unloaded_background">#DFDFDF</color>
|
||||||
<color name="compose_mention">#4F5F6F</color>
|
<color name="compose_mention">#4F5F6F</color>
|
||||||
<color name="notification_content_faded">#9F9F9F</color>
|
<color name="notification_content_faded">#9F9F9F</color>
|
||||||
|
<color name="account_header_background">#FFFFFF</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
<string name="oauth_redirect_host">oauth2redirect</string>
|
<string name="oauth_redirect_host">oauth2redirect</string>
|
||||||
<string name="preferences_file_key">com.keylesspalace.tusky.PREFERENCES</string>
|
<string name="preferences_file_key">com.keylesspalace.tusky.PREFERENCES</string>
|
||||||
|
|
||||||
<string name="content_uri_format_tag">content://com.keylesspalace.tusky.viewtagactivity/%s</string>
|
|
||||||
|
|
||||||
<string name="endpoint_status">/api/v1/statuses</string>
|
<string name="endpoint_status">/api/v1/statuses</string>
|
||||||
<string name="endpoint_media">/api/v1/media</string>
|
<string name="endpoint_media">/api/v1/media</string>
|
||||||
<string name="endpoint_timelines_home">/api/v1/timelines/home</string>
|
<string name="endpoint_timelines_home">/api/v1/timelines/home</string>
|
||||||
|
@ -42,13 +40,13 @@
|
||||||
|
|
||||||
<string name="error_fetching_timeline">Tusky failed to fetch the timeline.</string>
|
<string name="error_fetching_timeline">Tusky failed to fetch the timeline.</string>
|
||||||
<string name="error_fetching_notifications">Notifications could not be fetched.</string>
|
<string name="error_fetching_notifications">Notifications could not be fetched.</string>
|
||||||
<string name="error_compose_character_limit">The toot is too long!</string>
|
<string name="error_compose_character_limit">The status is too long!</string>
|
||||||
<string name="error_sending_status">The toot failed to be sent.</string>
|
<string name="error_sending_status">The status failed to be sent.</string>
|
||||||
<string name="error_media_upload_size">The file must be less than 4MB.</string>
|
<string name="error_media_upload_size">The file must be less than 4MB.</string>
|
||||||
<string name="error_media_upload_type">That type of file is not able to be uploaded.</string>
|
<string name="error_media_upload_type">That type of file is not able to be uploaded.</string>
|
||||||
<string name="error_media_upload_opening">That file could not be opened.</string>
|
<string name="error_media_upload_opening">That file could not be opened.</string>
|
||||||
<string name="error_media_upload_permission">Permission to read media is required to upload it.</string>
|
<string name="error_media_upload_permission">Permission to read media is required to upload it.</string>
|
||||||
<string name="error_media_upload_image_or_video">Images and videos cannot both be attached to the same toot.</string>
|
<string name="error_media_upload_image_or_video">Images and videos cannot both be attached to the same status.</string>
|
||||||
<string name="error_media_upload_sending">The media could not be uploaded.</string>
|
<string name="error_media_upload_sending">The media could not be uploaded.</string>
|
||||||
|
|
||||||
<string name="title_home">Home</string>
|
<string name="title_home">Home</string>
|
||||||
|
@ -56,6 +54,9 @@
|
||||||
<string name="title_public">Public</string>
|
<string name="title_public">Public</string>
|
||||||
<string name="title_thread">Thread</string>
|
<string name="title_thread">Thread</string>
|
||||||
<string name="title_tag">#%s</string>
|
<string name="title_tag">#%s</string>
|
||||||
|
<string name="title_statuses">Posts</string>
|
||||||
|
<string name="title_follows">Follows</string>
|
||||||
|
<string name="title_followers">Followers</string>
|
||||||
|
|
||||||
<string name="status_username_format">\@%s</string>
|
<string name="status_username_format">\@%s</string>
|
||||||
<string name="status_boosted_format">%s boosted</string>
|
<string name="status_boosted_format">%s boosted</string>
|
||||||
|
@ -64,21 +65,24 @@
|
||||||
|
|
||||||
<string name="footer_text">Could not load the rest of the toots.</string>
|
<string name="footer_text">Could not load the rest of the toots.</string>
|
||||||
|
|
||||||
<string name="notification_reblog_format">%s boosted your toot</string>
|
<string name="notification_reblog_format">%s boosted your status</string>
|
||||||
<string name="notification_favourite_format">%s favourited your toot</string>
|
<string name="notification_favourite_format">%s favourited your status</string>
|
||||||
<string name="notification_follow_format">%s followed you</string>
|
<string name="notification_follow_format">%s followed you</string>
|
||||||
|
|
||||||
<string name="action_compose">Compose</string>
|
<string name="action_compose">Compose</string>
|
||||||
<string name="action_login">Log In</string>
|
<string name="action_login">Log In</string>
|
||||||
<string name="action_logout">Log Out</string>
|
<string name="action_logout">Log Out</string>
|
||||||
<string name="action_follow">Follow</string>
|
<string name="action_follow">Follow</string>
|
||||||
|
<string name="action_unfollow">Unfollow</string>
|
||||||
<string name="action_block">Block</string>
|
<string name="action_block">Block</string>
|
||||||
|
<string name="action_unblock">Unblock</string>
|
||||||
<string name="action_delete">Delete</string>
|
<string name="action_delete">Delete</string>
|
||||||
<string name="action_send">TOOT</string>
|
<string name="action_send">TOOT</string>
|
||||||
<string name="action_retry">Retry</string>
|
<string name="action_retry">Retry</string>
|
||||||
<string name="action_mark_sensitive">Mark Sensitive</string>
|
<string name="action_mark_sensitive">Mark Sensitive</string>
|
||||||
<string name="action_cancel">Cancel</string>
|
<string name="action_cancel">Cancel</string>
|
||||||
<string name="action_back">Back</string>
|
<string name="action_back">Back</string>
|
||||||
|
<string name="action_profile">Profile</string>
|
||||||
|
|
||||||
<string name="confirmation_send">Toot!</string>
|
<string name="confirmation_send">Toot!</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue