- リモートユーザのプロフィールに警告を表示
- トゥートの公開範囲をTL中に表示する
- アカウント設定にプロフィール編集を追加
This commit is contained in:
tateisu 2017-08-22 03:10:12 +09:00
parent c57df1cacb
commit 16ac5c6880
23 changed files with 785 additions and 40 deletions

View File

@ -9,8 +9,8 @@ android {
applicationId "jp.juggler.subwaytooter"
minSdkVersion 21
targetSdkVersion 25
versionCode 118
versionName "1.1.8"
versionCode 119
versionName "1.1.9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

View File

@ -1,34 +1,58 @@
package jp.juggler.subwaytooter;
import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ClipData;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Base64OutputStream;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.TextView;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import jp.juggler.subwaytooter.api.TootApiClient;
import jp.juggler.subwaytooter.api.TootApiResult;
import jp.juggler.subwaytooter.api.entity.TootAccount;
import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.dialog.DlgAccessToken;
import jp.juggler.subwaytooter.dialog.ActionsDialog;
import jp.juggler.subwaytooter.table.AcctColor;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.Emojione;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
import jp.juggler.subwaytooter.view.MyNetworkImageView;
import okhttp3.Call;
import okhttp3.Request;
import okhttp3.RequestBody;
@ -50,16 +74,22 @@ public class ActAccountSetting extends AppCompatActivity
}
SavedAccount account;
SharedPreferences pref;
@Override
protected void onCreate( @Nullable Bundle savedInstanceState ){
super.onCreate( savedInstanceState );
App1.setActivityTheme( this, false );
this.pref = App1.pref;
initUI();
account = SavedAccount.loadAccount( this, log, getIntent().getLongExtra( KEY_ACCOUNT_DB_ID, - 1L ) );
if( account == null ) finish();
loadUIFromData( account );
initializeProfile();
btnOpenBrowser.setText( getString( R.string.open_instance_website, account.host ) );
}
@ -70,26 +100,89 @@ public class ActAccountSetting extends AppCompatActivity
static final int REQUEST_CODE_ACCT_CUSTOMIZE = 1;
static final int REQUEST_CODE_NOTIFICATION_SOUND = 2;
private static final int REQUEST_CODE_AVATAR_ATTACHMENT = 3;
private static final int REQUEST_CODE_HEADER_ATTACHMENT = 4;
private static final int REQUEST_CODE_AVATAR_CAMERA = 5;
private static final int REQUEST_CODE_HEADER_CAMERA = 6;
@Override protected void onActivityResult( int requestCode, int resultCode, Intent data ){
if( requestCode == REQUEST_CODE_ACCT_CUSTOMIZE && resultCode == RESULT_OK ){
showAcctColor();
}else if( resultCode == RESULT_OK && requestCode == REQUEST_CODE_NOTIFICATION_SOUND ){
// RINGTONE_PICKERからの選択されたデータを取得する
Uri uri = (Uri) data.getExtras().get( RingtoneManager.EXTRA_RINGTONE_PICKED_URI );
if( uri != null ){
notification_sound_uri = uri.toString();
saveUIToData();
// Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), uri);
// TextView ringView = (TextView) findViewById(R.id.ringtone);
// ringView.setText(ringtone.getTitle(getApplicationContext()));
// ringtone.setStreamType(AudioManager.STREAM_ALARM);
// ringtone.play();
// SystemClock.sleep(1000);
// ringtone.stop();
switch( requestCode ){
default:
super.onActivityResult( requestCode, resultCode, data );
break;
case REQUEST_CODE_ACCT_CUSTOMIZE:{
if( resultCode == RESULT_OK ){
showAcctColor();
}
break;
}
case REQUEST_CODE_NOTIFICATION_SOUND:{
if( resultCode == RESULT_OK ){
// RINGTONE_PICKERからの選択されたデータを取得する
Uri uri = (Uri) data.getExtras().get( RingtoneManager.EXTRA_RINGTONE_PICKED_URI );
if( uri != null ){
notification_sound_uri = uri.toString();
saveUIToData();
// Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), uri);
// TextView ringView = (TextView) findViewById(R.id.ringtone);
// ringView.setText(ringtone.getTitle(getApplicationContext()));
// ringtone.setStreamType(AudioManager.STREAM_ALARM);
// ringtone.play();
// SystemClock.sleep(1000);
// ringtone.stop();
}
}
break;
}
case REQUEST_CODE_AVATAR_ATTACHMENT:
case REQUEST_CODE_HEADER_ATTACHMENT:{
if( resultCode == RESULT_OK && data != null ){
Uri uri = data.getData();
if( uri != null ){
// 単一選択
String type = data.getType();
if( TextUtils.isEmpty( type ) ){
type = getContentResolver().getType( uri );
}
addAttachment( requestCode, uri, type );
break;
}
ClipData cd = data.getClipData();
if( cd != null ){
int count = cd.getItemCount();
if( count > 0 ){
ClipData.Item item = cd.getItemAt( 0 );
uri = item.getUri();
String type = getContentResolver().getType( uri );
addAttachment( requestCode, uri, type );
}
}
}
break;
}
case REQUEST_CODE_AVATAR_CAMERA:
case REQUEST_CODE_HEADER_CAMERA:{
if( resultCode != RESULT_OK ){
// 失敗したら DBからデータを削除
if( uriCameraImage != null ){
getContentResolver().delete( uriCameraImage, null, null );
uriCameraImage = null;
}
}else{
// 画像のURL
Uri uri = ( data == null ? null : data.getData() );
if( uri == null ) uri = uriCameraImage;
if( uri != null ){
String type = getContentResolver().getType( uri );
addAttachment( requestCode, uri, type );
}
}
break;
}
}
super.onActivityResult( requestCode, resultCode, data );
}
TextView tvInstance;
@ -120,6 +213,15 @@ public class ActAccountSetting extends AppCompatActivity
String notification_sound_uri;
MyNetworkImageView ivProfileHeader;
MyNetworkImageView ivProfileAvatar;
View btnProfileAvatar;
View btnProfileHeader;
EditText etDisplayName;
View btnDisplayName;
EditText etNote;
View btnNote;
private void initUI(){
setContentView( R.layout.act_account_setting );
@ -147,12 +249,25 @@ public class ActAccountSetting extends AppCompatActivity
tvUserCustom = (TextView) findViewById( R.id.tvUserCustom );
btnUserCustom = findViewById( R.id.btnUserCustom );
ivProfileHeader = (MyNetworkImageView) findViewById( R.id.ivProfileHeader );
ivProfileAvatar = (MyNetworkImageView) findViewById( R.id.ivProfileAvatar );
btnProfileAvatar = findViewById( R.id.btnProfileAvatar );
btnProfileHeader = findViewById( R.id.btnProfileHeader );
etDisplayName = (EditText) findViewById( R.id.etDisplayName );
btnDisplayName = findViewById( R.id.btnDisplayName );
etNote = (EditText) findViewById( R.id.etNote );
btnNote = findViewById( R.id.btnNote );
btnOpenBrowser.setOnClickListener( this );
btnAccessToken.setOnClickListener( this );
btnInputAccessToken.setOnClickListener( this );
btnAccountRemove.setOnClickListener( this );
btnVisibility.setOnClickListener( this );
btnUserCustom.setOnClickListener( this );
btnProfileAvatar.setOnClickListener( this );
btnProfileHeader.setOnClickListener( this );
btnDisplayName.setOnClickListener( this );
btnNote.setOnClickListener( this );
swNSFWOpen.setOnCheckedChangeListener( this );
cbNotificationMention.setOnCheckedChangeListener( this );
@ -290,6 +405,23 @@ public class ActAccountSetting extends AppCompatActivity
notification_sound_uri = "";
saveUIToData();
break;
case R.id.btnProfileAvatar:
pickAvatarImage();
break;
case R.id.btnProfileHeader:
pickHeaderImage();
break;
case R.id.btnDisplayName:
sendDisplayName();
break;
case R.id.btnNote:
sendNote();
break;
}
}
@ -518,5 +650,437 @@ public class ActAccountSetting extends AppCompatActivity
startActivityForResult( chooser, REQUEST_CODE_NOTIFICATION_SOUND );
}
//////////////////////////////////////////////////////////////////////////
private void initializeProfile(){
// 初期状態
ivProfileAvatar.setErrorImageResId( Styler.getAttributeResourceId( this, R.attr.ic_question ) );
ivProfileAvatar.setDefaultImageResId( Styler.getAttributeResourceId( this, R.attr.ic_question ) );
etDisplayName.setText( "(loading...)" );
etNote.setText( "(loading...)" );
// 初期状態では編集不可能
btnProfileAvatar.setEnabled( false );
btnProfileHeader.setEnabled( false );
etDisplayName.setEnabled( false );
btnDisplayName.setEnabled( false );
etNote.setEnabled( false );
btnNote.setEnabled( false );
// 疑似アカウントなら編集不可のまま
if( account.isPseudo() ) return;
loadProfile();
}
void loadProfile(){
// サーバから情報をロードする
final ProgressDialog progress = new ProgressDialog( this );
final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() {
TootAccount data;
@Override protected TootApiResult doInBackground( Void... params ){
TootApiClient client = new TootApiClient( ActAccountSetting.this, new TootApiClient.Callback() {
@Override public boolean isApiCancelled(){
return isCancelled();
}
@Override public void publishApiProgress( final String s ){
}
} );
client.setAccount( account );
TootApiResult result = client.request( "/api/v1/accounts/verify_credentials" );
if( result != null && result.object != null ){
data = TootAccount.parse( ActAccountSetting.this, account, result.object );
if( data == null ) return new TootApiResult( "TootAccount parse failed." );
}
return result;
}
@Override
protected void onCancelled( TootApiResult result ){
super.onPostExecute( result );
}
@Override
protected void onPostExecute( TootApiResult result ){
try{
progress.dismiss();
}catch( Throwable ignored ){
}
if( result == null ){
// cancelled.
}else if( data != null ){
showProfile( data );
}else{
Utils.showToast( ActAccountSetting.this, true, result.error );
}
}
};
task.executeOnExecutor( App1.task_executor );
progress.setIndeterminate( true );
progress.setOnDismissListener( new DialogInterface.OnDismissListener() {
@Override public void onDismiss( DialogInterface dialog ){
task.cancel( true );
}
} );
progress.show();
}
void showProfile( TootAccount src ){
ivProfileAvatar.setImageUrl( App1.pref, 16f, src.avatar_static, src.avatar );
ivProfileHeader.setImageUrl( App1.pref, 0f, src.header_static, src.header );
etDisplayName.setText( Emojione.decodeEmoji( this, src.display_name == null ? "" : src.display_name ) );
String note;
if( src.source != null && src.source.note != null ){
note = src.source.note;
}else{
note = src.note;
}
etNote.setText( Emojione.decodeEmoji( this, note == null ? "" : note ) );
// 編集可能にする
btnProfileAvatar.setEnabled( true );
btnProfileHeader.setEnabled( true );
etDisplayName.setEnabled( true );
btnDisplayName.setEnabled( true );
etNote.setEnabled( true );
btnNote.setEnabled( true );
}
void updateCredential( final String form_data ){
final ProgressDialog progress = new ProgressDialog( this );
final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() {
TootAccount data;
@Override protected TootApiResult doInBackground( Void... params ){
TootApiClient client = new TootApiClient( ActAccountSetting.this, new TootApiClient.Callback() {
@Override public boolean isApiCancelled(){
return isCancelled();
}
@Override public void publishApiProgress( final String s ){
}
} );
client.setAccount( account );
Request.Builder request_builder = new Request.Builder()
.patch( RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED
, form_data
) );
TootApiResult result = client.request( "/api/v1/accounts/update_credentials", request_builder );
if( result != null && result.object != null ){
data = TootAccount.parse( ActAccountSetting.this, account, result.object );
if( data == null ) return new TootApiResult( "TootAccount parse failed." );
}
return result;
}
@Override
protected void onCancelled( TootApiResult result ){
super.onPostExecute( result );
}
@Override
protected void onPostExecute( TootApiResult result ){
try{
progress.dismiss();
}catch( Throwable ignored ){
}
loadProfile();
if( result == null ){
// cancelled.
}else if( data != null ){
showProfile( data );
}else{
Utils.showToast( ActAccountSetting.this, true, result.error );
}
}
};
task.executeOnExecutor( App1.task_executor );
progress.setIndeterminate( true );
progress.setOnDismissListener( new DialogInterface.OnDismissListener() {
@Override public void onDismiss( DialogInterface dialog ){
task.cancel( true );
}
} );
progress.show();
}
private void sendDisplayName(){
updateCredential( "display_name=" + Uri.encode( etDisplayName.getText().toString() ) );
}
private void sendNote(){
updateCredential( "note=" + Uri.encode( etNote.getText().toString() ) );
}
private static final int PERMISSION_REQUEST_AVATAR = 1;
private static final int PERMISSION_REQUEST_HEADER = 2;
private void pickAvatarImage(){
openPicker( PERMISSION_REQUEST_AVATAR );
}
private void pickHeaderImage(){
openPicker( PERMISSION_REQUEST_HEADER );
}
void openPicker( final int permission_request_code ){
int permissionCheck = ContextCompat.checkSelfPermission( this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE );
if( permissionCheck != PackageManager.PERMISSION_GRANTED ){
preparePermission( permission_request_code );
return;
}
ActionsDialog a = new ActionsDialog();
a.addAction( getString( R.string.image_pick ), new Runnable() {
@Override public void run(){
performAttachment( permission_request_code == PERMISSION_REQUEST_AVATAR ? REQUEST_CODE_AVATAR_ATTACHMENT : REQUEST_CODE_HEADER_ATTACHMENT );
}
} );
a.addAction( getString( R.string.image_capture ), new Runnable() {
@Override public void run(){
performCamera( permission_request_code == PERMISSION_REQUEST_AVATAR ? REQUEST_CODE_AVATAR_CAMERA : REQUEST_CODE_HEADER_CAMERA );
}
} );
a.show( this, null );
}
private void preparePermission( int request_code ){
if( Build.VERSION.SDK_INT >= 23 ){
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions( this
, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE }
, request_code
);
return;
}
Utils.showToast( this, true, R.string.missing_storage_permission );
}
@Override public void onRequestPermissionsResult(
int requestCode
, @NonNull String permissions[]
, @NonNull int[] grantResults
){
switch( requestCode ){
case PERMISSION_REQUEST_AVATAR:
case PERMISSION_REQUEST_HEADER:
// If request is cancelled, the result arrays are empty.
if( grantResults.length > 0 &&
grantResults[ 0 ] == PackageManager.PERMISSION_GRANTED
){
openPicker( requestCode );
}else{
Utils.showToast( this, true, R.string.missing_storage_permission );
}
break;
}
}
private void performAttachment( final int request_code ){
// SAFのIntentで開く
try{
Intent intent = new Intent( Intent.ACTION_OPEN_DOCUMENT );
intent.addCategory( Intent.CATEGORY_OPENABLE );
intent.setType( "*/*" );
intent.putExtra( Intent.EXTRA_ALLOW_MULTIPLE, false );
intent.putExtra( Intent.EXTRA_MIME_TYPES, new String[]{ "image/*", "video/*" } );
startActivityForResult( intent
, request_code );
}catch( Throwable ex ){
log.trace( ex );
Utils.showToast( this, ex, "ACTION_OPEN_DOCUMENT failed." );
}
}
Uri uriCameraImage;
private void performCamera( final int request_code ){
try{
// カメラで撮影
String filename = System.currentTimeMillis() + ".jpg";
ContentValues values = new ContentValues();
values.put( MediaStore.Images.Media.TITLE, filename );
values.put( MediaStore.Images.Media.MIME_TYPE, "image/jpeg" );
uriCameraImage = getContentResolver().insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values );
Intent intent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE );
intent.putExtra( MediaStore.EXTRA_OUTPUT, uriCameraImage );
startActivityForResult( intent, request_code );
}catch( Throwable ex ){
log.trace( ex );
Utils.showToast( this, ex, "opening camera app failed." );
}
}
interface InputStreamOpener {
InputStream open() throws IOException;
String getMimeType();
void deleteTempFile();
}
static final String MIME_TYPE_JPEG = "image/jpeg";
static final String MIME_TYPE_PNG = "image/png";
private InputStreamOpener createOpener( final Uri uri, final String mime_type ){
//noinspection LoopStatementThatDoesntLoop
for( ; ; ){
try{
// 画像の種別
boolean is_jpeg = MIME_TYPE_JPEG.equals( mime_type );
boolean is_png = MIME_TYPE_PNG.equals( mime_type );
if( ! is_jpeg && ! is_png ){
log.d( "createOpener: source is not jpeg or png" );
break;
}
// 設定からリサイズ指定を読む
int resize_to = 1280;
Bitmap bitmap = Utils.createResizedBitmap( log, this, uri, false, resize_to );
if( bitmap != null ){
try{
File cache_dir = getExternalCacheDir();
if( cache_dir == null ){
Utils.showToast( this, false, "getExternalCacheDir returns null." );
break;
}
//noinspection ResultOfMethodCallIgnored
cache_dir.mkdir();
final File temp_file = new File( cache_dir, "tmp." + Thread.currentThread().getId() );
FileOutputStream os = new FileOutputStream( temp_file );
try{
if( is_jpeg ){
bitmap.compress( Bitmap.CompressFormat.JPEG, 95, os );
}else{
bitmap.compress( Bitmap.CompressFormat.PNG, 100, os );
}
}finally{
os.close();
}
return new InputStreamOpener() {
@Override public InputStream open() throws IOException{
return new FileInputStream( temp_file );
}
@Override public String getMimeType(){
return mime_type;
}
@Override public void deleteTempFile(){
//noinspection ResultOfMethodCallIgnored
temp_file.delete();
}
};
}finally{
bitmap.recycle();
}
}
}catch( Throwable ex ){
log.trace( ex );
Utils.showToast( this, ex, "Resizing image failed." );
}
break;
}
return new InputStreamOpener() {
@Override public InputStream open() throws IOException{
return getContentResolver().openInputStream( uri );
}
@Override public String getMimeType(){
return mime_type;
}
@Override public void deleteTempFile(){
}
};
}
void addAttachment( final int request_code, final Uri uri, final String mime_type ){
if( mime_type == null ){
Utils.showToast( this, false, "mime type is not provided." );
return;
}
if( ! mime_type.startsWith( "image/" ) ){
Utils.showToast( this, false, "mime type is not image." );
return;
}
new AsyncTask< Void, Void, String >() {
@Override protected String doInBackground( Void... params ){
try{
final InputStreamOpener opener = createOpener( uri, mime_type );
try{
InputStream is = opener.open();
try{
ByteArrayOutputStream bao = new ByteArrayOutputStream();
//
bao.write( Utils.encodeUTF8( "data:" + opener.getMimeType() + ";base64," ) );
//
Base64OutputStream base64 = new Base64OutputStream( bao, Base64.NO_WRAP );
try{
IOUtils.copy( is, base64 );
}finally{
base64.close();
}
String value = Utils.decodeUTF8( bao.toByteArray() );
switch( request_code ){
case REQUEST_CODE_AVATAR_ATTACHMENT:
case REQUEST_CODE_AVATAR_CAMERA:
return "avatar=" + Uri.encode( value );
case REQUEST_CODE_HEADER_ATTACHMENT:
case REQUEST_CODE_HEADER_CAMERA:
return "header=" + Uri.encode( value );
}
}finally{
IOUtils.closeQuietly( is );
}
}finally{
opener.deleteTempFile();
}
}catch( Throwable ex ){
Utils.showToast( ActAccountSetting.this, ex, "image converting failed." );
}
return null;
}
@Override
protected void onPostExecute( String form_data ){
if( form_data != null ){
updateCredential( form_data );
}
}
}.executeOnExecutor( App1.task_executor );
}
}

View File

@ -1323,10 +1323,10 @@ class Column implements StreamReader.Callback {
return getStatuses( client, PATH_FEDERATE );
case TYPE_PROFILE:
if( who_account == null ){
parseAccount1( client, String.format( Locale.JAPAN, PATH_ACCOUNT, profile_id ) );
client.callback.publishApiProgress( "" );
}
parseAccount1( client, String.format( Locale.JAPAN, PATH_ACCOUNT, profile_id ) );
client.callback.publishApiProgress( "" );
switch( profile_tab ){
default:

View File

@ -28,6 +28,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
private final ImageButton btnFollow;
private final ImageView ivFollowedBy;
private final View llProfile;
private final TextView tvRemoteProfileWarning;
private TootAccount who;
@ -49,13 +50,15 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
View btnMore = viewRoot.findViewById( R.id.btnMore );
btnFollow = (ImageButton) viewRoot.findViewById( R.id.btnFollow );
ivFollowedBy = (ImageView) viewRoot.findViewById( R.id.ivFollowedBy );
tvRemoteProfileWarning = (TextView) viewRoot.findViewById( R.id.tvRemoteProfileWarning );
ivBackground.setOnClickListener( this );
btnFollowing.setOnClickListener( this );
btnFollowers.setOnClickListener( this );
btnStatusCount.setOnClickListener( this );
btnMore.setOnClickListener( this );
btnFollow.setOnClickListener( this );
tvRemoteProfileWarning.setOnClickListener( this );
btnFollow.setOnLongClickListener( this );
@ -77,6 +80,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
showColor();
if( who == null ){
tvCreated.setText( "" );
ivBackground.setImageDrawable( null );
@ -89,11 +93,14 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
btnFollowers.setText( activity.getString( R.string.followers ) + "\n" + "?" );
btnFollow.setImageDrawable( null );
tvRemoteProfileWarning.setVisibility( View.GONE );
}else{
tvCreated.setText( TootStatus.formatTime( who.time_created_at ) );
ivBackground.setImageUrl( activity.pref, 0f,access_info.supplyBaseUrl( who.header_static ) );
ivAvatar.setImageUrl( activity.pref, 16f,access_info.supplyBaseUrl( who.avatar_static ) , access_info.supplyBaseUrl( who.avatar ));
tvDisplayName.setText( who.decoded_display_name );
tvRemoteProfileWarning.setVisibility( column.access_info.isRemoteUser(who) ? View.VISIBLE : View.GONE );
String s = "@" + access_info.getFullAcct( who );
if( who.locked ){
@ -117,6 +124,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
switch( v.getId() ){
case R.id.ivBackground:
case R.id.tvRemoteProfileWarning:
if( who != null ){
// 強制的にブラウザで開く
activity.openChromeTab( activity.nextPosition( column ), access_info, who.url, true );
@ -152,6 +160,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
new DlgContextMenu( activity, column, who, null, null ).show();
}
break;
}
}

View File

@ -6,6 +6,8 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.AlertDialog;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@ -27,6 +29,7 @@ import jp.juggler.subwaytooter.table.ContentWarning;
import jp.juggler.subwaytooter.table.MediaShown;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.table.UserRelation;
import jp.juggler.subwaytooter.util.EmojiImageSpan;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.view.MyLinkMovementMethod;
import jp.juggler.subwaytooter.view.MyListView;
@ -377,7 +380,21 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
this.status = status;
llStatus.setVisibility( View.VISIBLE );
tvTime.setText( TootStatus.formatTime( status.time_created_at ) );
if( status instanceof MSPToot ){
tvTime.setText( TootStatus.formatTime( status.time_created_at ) );
}else if( status instanceof TootStatus ){
TootStatus ts = (TootStatus) status;
int icon_id = Styler.getVisibilityIcon( activity, ts.visibility );
SpannableStringBuilder sb = new SpannableStringBuilder( );
int start = sb.length();
sb.append(ts.visibility);
int end = sb.length();
sb.setSpan( new EmojiImageSpan( activity, icon_id ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
sb.append(' ');
sb.append(TootStatus.formatTime( status.time_created_at ));
tvTime.setText(sb);
}
account_thumbnail = status.account;
setAcct( tvAcct, access_info.getFullAcct( status.account ) );

View File

@ -464,6 +464,24 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
return "?@?";
}
public boolean isLocalUser( @NonNull TootAccount who ){
return isLocalUser(who.acct);
}
public boolean isLocalUser( @NonNull String acct ){
int delm = acct.indexOf( '@' );
if( delm == -1 ) return true;
return host.equalsIgnoreCase( acct.substring( delm+1 ) );
}
public boolean isRemoteUser( @NonNull TootAccount who ){
return ! isLocalUser(who);
}
public boolean isRemoteUser( @NonNull String acct ){
return ! isLocalUser(acct);
}
public String getUserUrl( @NonNull String who_acct ){
int p = who_acct.indexOf( '@' );
if( - 1 != p ){
@ -581,4 +599,5 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
return true;
}
}

View File

@ -15,7 +15,7 @@ import android.text.style.ReplacementSpan;
import java.lang.ref.WeakReference;
class EmojiImageSpan extends ReplacementSpan {
public class EmojiImageSpan extends ReplacementSpan {
static DynamicDrawableSpan x = null;
@ -28,7 +28,7 @@ class EmojiImageSpan extends ReplacementSpan {
private final int res_id;
private WeakReference< Drawable > mDrawableRef;
EmojiImageSpan( @NonNull Context context, int res_id ){
public EmojiImageSpan( @NonNull Context context, int res_id ){
super();
this.context = context.getApplicationContext();
this.res_id = res_id;

View File

@ -53,6 +53,7 @@ public class MyNetworkImageView extends AppCompatImageView {
public void setDefaultImageResId( int defaultImage ){
mDefaultImageId = defaultImage;
loadImageIfNecessary();
}
// エラー時に表示するDrawableのリソースID
@ -60,6 +61,7 @@ public class MyNetworkImageView extends AppCompatImageView {
public void setErrorImageResId( int errorImage ){
mErrorImageId = errorImage;
loadImageIfNecessary();
}
// 角丸の半径元画像の短辺に対する割合を指定するらしい

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -2,15 +2,15 @@
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/svContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbarStyle="outsideOverlay"
android:id="@+id/svContent"
android:paddingTop="12dp"
android:paddingBottom="128dp"
android:clipToPadding="false"
android:fillViewport="true"
android:paddingBottom="128dp"
android:paddingTop="12dp"
android:scrollbarStyle="outsideOverlay"
>
@ -60,25 +60,128 @@
style="@style/setting_row_label"
android:text="@string/nickname_label"
/>
<LinearLayout style="@style/setting_row_form">
<TextView
android:id="@+id/tvUserCustom"
style="@style/setting_horizontal_stretch"
android:padding="4dp"
android:layout_gravity="center_vertical"
android:padding="4dp"
/>
<ImageButton
android:layout_marginStart="4dp"
android:id="@+id/btnUserCustom"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="4dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/edit"
android:src="?attr/ic_edit"
android:id="@+id/btnUserCustom"
/>
</LinearLayout>
<View style="@style/setting_divider"/>
<TextView
style="@style/setting_row_label"
android:text="@string/public_profile"
/>
<LinearLayout style="@style/setting_row_form">
<FrameLayout
style="@style/setting_horizontal_stretch"
android:layout_height="64dp"
>
<jp.juggler.subwaytooter.view.MyNetworkImageView
android:id="@+id/ivProfileHeader"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<jp.juggler.subwaytooter.view.MyNetworkImageView
android:id="@+id/ivProfileAvatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:scaleType="fitCenter"
/>
</FrameLayout>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<Button
android:id="@+id/btnProfileAvatar"
style="@style/setting_horizontal_stretch"
android:text="@string/change_avatar"
android:textAllCaps="false"
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<Button
android:id="@+id/btnProfileHeader"
style="@style/setting_horizontal_stretch"
android:text="@string/change_header"
android:textAllCaps="false"
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<TextView
style="@style/setting_row_label"
android:labelFor="@+id/etDisplayName"
android:text="@string/display_name"
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<EditText
android:id="@+id/etDisplayName"
style="@style/setting_horizontal_stretch"
android:inputType="text"
/>
<ImageButton
android:id="@+id/btnDisplayName"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="4dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/post"
android:src="?attr/btn_post"
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<TextView
style="@style/setting_row_label"
android:labelFor="@+id/etNote"
android:text="@string/note"
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<EditText
android:id="@+id/etNote"
style="@style/setting_horizontal_stretch"
android:inputType="textMultiLine"
/>
<ImageButton
android:id="@+id/btnNote"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="4dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/post"
android:src="?attr/btn_post"
/>
</LinearLayout>
@ -112,6 +215,7 @@
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<Button
@ -123,6 +227,7 @@
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<Button

View File

@ -153,5 +153,13 @@
/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="@string/remote_profile_warning"
android:id="@+id/tvRemoteProfileWarning"
android:gravity="center"
android:background="@drawable/btn_bg_transparent"
/>
</LinearLayout>

View File

@ -414,8 +414,14 @@
<string name="enable_gif_animation">Enable GIF animation (It wastes the battery very much)</string>
<string name="acct_sample">(sample)username@instance</string>
<string name="mention_full_acct">Show mention as full acct (app restart required)</string>
<string name="remote_profile_warning">Remote users\' profile may be inadequate information. You can check more accurate information on the web page.</string>
<string name="public_profile">Public profile</string>
<string name="change_avatar">Change avatar icon</string>
<string name="change_header">Change header image</string>
<string name="display_name">Display name</string>
<string name="note">Note</string>
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->
<!--<string name="abc_action_bar_home_subtitle_description_format">%1$s, %2$s, %3$s</string>-->
<!--<string name="abc_action_bar_up_description">Revenir en haut de la page</string>-->

View File

@ -701,5 +701,11 @@
<string name="acct_sample">(見本)username@instance</string>
<string name="enable_gif_animation">GIFアニメーションを有効にする(バッテリーをとても浪費します)</string>
<string name="mention_full_acct">メンションのインスタンス名部分を省略しない(アプリ再起動が必要)</string>
<string name="remote_profile_warning">リモートユーザのプロフィールは情報が不十分な可能性があります。より正確な情報をWebページで確認することができます。</string>
<string name="change_avatar">アバター画像を変更</string>
<string name="change_header">ヘッダー画像を変更</string>
<string name="display_name">表示名</string>
<string name="note">ノート</string>
<string name="public_profile">公開プロフィール</string>
</resources>

View File

@ -117,5 +117,6 @@
<attr name="ic_domain_block" format="reference" />
<attr name="ic_nicoru" format="reference" />
<attr name="ic_question" format="reference" />
</resources>

View File

@ -409,5 +409,11 @@
<string name="enable_gif_animation">Enable GIF animation (It wastes the battery very much)</string>
<string name="acct_sample">(sample)username@instance</string>
<string name="mention_full_acct">Show mention as full acct (app restart required)</string>
<string name="remote_profile_warning">Remote users\' profile may be inadequate information. You can check more accurate information on the web page.</string>
<string name="public_profile">Public profile</string>
<string name="change_avatar">Change avatar icon</string>
<string name="change_header">Change header image</string>
<string name="display_name">Display name</string>
<string name="note">Note</string>
</resources>

View File

@ -87,6 +87,7 @@
<item name="ic_domain_block">@drawable/ic_domain_block</item>
<item name="ic_nicoru">@drawable/ic_nicoru</item>
<item name="ic_question">@drawable/ic_question</item>
</style>
@ -179,6 +180,7 @@
<item name="ic_domain_block">@drawable/ic_domain_block_dark</item>
<item name="ic_nicoru">@drawable/ic_nicoru_dark</item>
<item name="ic_question">@drawable/ic_question_dark</item>
</style>