Skip to content

Commit

Permalink
Keep state in RecyclerView (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
ShortDevelopment authored Jan 26, 2025
1 parent 5f08569 commit a6a318b
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 72 deletions.
2 changes: 1 addition & 1 deletion src/NearShare.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<PackageReference Include="Rive.Android" Version="1.0.5" />
<PackageReference Include="Sentry" Version="5.0.0" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
<PackageReference Include="ShortDev.Android" Version="0.1.0-beta" />
<PackageReference Include="ShortDev.Android" Version="0.3.0-beta" />
<PackageReference Include="ShortDev.Android.FluentIcons" Version="1.1.203" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.6.1.8" />
<PackageReference Include="Xamarin.AndroidX.Browser" Version="1.8.0.1" />
Expand Down
160 changes: 105 additions & 55 deletions src/ReceiveActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ protected override void OnCreate(Bundle? savedInstanceState)
notificationsRecyclerView = FindViewById<RecyclerView>(Resource.Id.notificationsRecyclerView)!;
notificationsRecyclerView.SetLayoutManager(new LinearLayoutManager(this));
notificationsRecyclerView.SetAdapter(
new AdapterDescriptor<TransferToken>(
_notifications.CreateAdapter(
Resource.Layout.item_transfer_notification,
OnInflateNotification
).CreateRecyclerViewAdapter(_notifications)
view => new TransferNotificationViewHolder(view)
{
OnRemove = _notifications.Remove,
RunOnUiThread = RunOnUiThread
}
)
);

FindViewById<Button>(Resource.Id.openFAQButton)!.Click += (s, e) => UIHelper.OpenFAQ(this);
Expand All @@ -64,61 +68,72 @@ protected override void OnCreate(Bundle? savedInstanceState)
UIHelper.RequestReceivePermissions(this);
}

void OnInflateNotification(View view, TransferToken transfer)
sealed class TransferNotificationViewHolder : ViewHolder<TransferToken>
{
var acceptButton = view.FindViewById<Button>(Resource.Id.acceptButton)!;
var openButton = view.FindViewById<Button>(Resource.Id.openButton)!;
var fileNameTextView = view.FindViewById<TextView>(Resource.Id.fileNameTextView)!;
var detailsTextView = view.FindViewById<TextView>(Resource.Id.detailsTextView)!;
var loadingProgressIndicator = view.FindViewById<CircularProgressIndicator>(Resource.Id.loadingProgressIndicator)!;

view.FindViewById<Button>(Resource.Id.cancelButton)!.Click += (s, e) =>
readonly Context _context;
readonly Button _acceptButton, _openButton, _cancelButton;
readonly TextView _fileNameTextView, _detailsTextView;
readonly CircularProgressIndicator _loadingProgressIndicator;
public TransferNotificationViewHolder(View view) : base(view)
{
if (transfer is FileTransferToken fileTransfer)
fileTransfer.Cancel();

_notifications.Remove(transfer);
};
_context = view.Context!;

if (transfer is UriTransferToken uriTranfer)
{
fileNameTextView.Text = uriTranfer.Uri;
detailsTextView.Text = uriTranfer.DeviceName;
_acceptButton = view.FindViewById<Button>(Resource.Id.acceptButton)!;
_acceptButton.Click += AcceptButton_Click;

acceptButton.Visibility = ViewStates.Gone;
openButton.Visibility = ViewStates.Visible;
_openButton = view.FindViewById<Button>(Resource.Id.openButton)!;
_openButton.Click += OpenButton_Click;

openButton.Click += (s, e) => UIHelper.DisplayWebSite(this, uriTranfer.Uri);
_cancelButton = view.FindViewById<Button>(Resource.Id.cancelButton)!;
_cancelButton.Click += CancelButton_Click;

return;
_fileNameTextView = view.FindViewById<TextView>(Resource.Id.fileNameTextView)!;
_detailsTextView = view.FindViewById<TextView>(Resource.Id.detailsTextView)!;
_loadingProgressIndicator = view.FindViewById<CircularProgressIndicator>(Resource.Id.loadingProgressIndicator)!;
}

if (transfer is not FileTransferToken fileTransfer)
throw new UnreachableException();

fileNameTextView.Text = string.Join(", ", fileTransfer.Files.Select(x => x.Name));
detailsTextView.Text = $"{fileTransfer.DeviceName}{FileTransferToken.FormatFileSize(fileTransfer.TotalBytes)}";

acceptButton.Click += OnAccept;
TransferToken? _transfer;
public override void Bind(int index, TransferToken transfer)
{
_transfer = transfer;

fileTransfer.Progress += progress => RunOnUiThread(() => OnProgress(progress));
loadingProgressIndicator.Indeterminate = true;
switch (transfer)
{
case UriTransferToken uriTransfer:
_fileNameTextView.Text = uriTransfer.Uri;
_detailsTextView.Text = uriTransfer.DeviceName;

_acceptButton.Visibility = ViewStates.Gone;
_openButton.Visibility = ViewStates.Visible;
break;

case FileTransferToken fileTransfer:
_fileNameTextView.Text = string.Join(", ", fileTransfer.Files.Select(x => x.Name));
_detailsTextView.Text = $"{fileTransfer.DeviceName}{FileTransferToken.FormatFileSize(fileTransfer.TotalBytes)}";
_loadingProgressIndicator.Indeterminate = true;
UpdateUI(fileTransfer);

fileTransfer.Progress += OnProgress;
break;
}
}

openButton.Click += (_, _) =>
public override void Recycle()
{
this.ViewDownloads();

// ToDo: View single file
// if (fileTransfer.Files.Count == 1)
};
if (_transfer is not FileTransferToken fileTransfer)
return;

UpdateUI();
fileTransfer.Progress -= OnProgress;
}

void OnAccept(object? sender, EventArgs e)
private void AcceptButton_Click(object? sender, EventArgs e)
{
if (_transfer is not FileTransferToken fileTransfer)
return;

try
{
var streams = fileTransfer.Select(file => ContentResolver!.CreateMediaStoreStream(file.Name).stream).ToArray();
var streams = fileTransfer.Select(file => _context.ContentResolver!.CreateMediaStoreStream(file.Name).stream).ToArray();
fileTransfer.Accept(streams);

fileTransfer.Finished += () =>
Expand All @@ -128,33 +143,68 @@ void OnAccept(object? sender, EventArgs e)
}
catch (Exception ex)
{
new MaterialAlertDialogBuilder(this)
new MaterialAlertDialogBuilder(_context)
.SetTitle(ex.GetType().Name)!
.SetMessage(ex.Message)!
.Show();
}

UpdateUI();
UpdateUI(fileTransfer);
}

public required Action<Action> RunOnUiThread { get; init; }
void OnProgress(NearShareProgress progress)
{
loadingProgressIndicator.Indeterminate = false;
if (_transfer is not FileTransferToken fileTransfer)
return;

RunOnUiThread(() =>
{
_loadingProgressIndicator.Indeterminate = false;

int progressInt = progress.TotalBytes == 0 ? 0 : Math.Min((int)(progress.TransferedBytes * 100 / progress.TotalBytes), 100);
if (OperatingSystem.IsAndroidVersionAtLeast(24))
loadingProgressIndicator.SetProgress(progressInt, animate: true);
else
loadingProgressIndicator.Progress = progressInt;
int progressInt = progress.TotalBytes == 0 ? 0 : Math.Min((int)(progress.TransferedBytes * 100 / progress.TotalBytes), 100);
if (OperatingSystem.IsAndroidVersionAtLeast(24))
_loadingProgressIndicator.SetProgress(progressInt, animate: true);
else
_loadingProgressIndicator.Progress = progressInt;

UpdateUI(fileTransfer);
});
}

public required Func<TransferToken, bool> OnRemove { get; init; }
private void CancelButton_Click(object? sender, EventArgs e)
{
if (_transfer is null)
return;

UpdateUI();
if (_transfer is FileTransferToken fileTransfer)
fileTransfer.Cancel();

OnRemove(_transfer);
}

private void OpenButton_Click(object? sender, EventArgs e)
{
switch (_transfer)
{
case UriTransferToken uriTransfer:
UIHelper.DisplayWebSite(_context, uriTransfer.Uri);
break;

case FileTransferToken:
_context.ViewDownloads();
// ToDo: View single file
//if (fileTransfer.Files.Count == 1)
break;
}
}

void UpdateUI()
void UpdateUI(FileTransferToken fileTransfer)
{
acceptButton.Visibility = !fileTransfer.IsTransferComplete && !fileTransfer.IsAccepted ? ViewStates.Visible : ViewStates.Gone;
loadingProgressIndicator.Visibility = !fileTransfer.IsTransferComplete && fileTransfer.IsAccepted ? ViewStates.Visible : ViewStates.Gone;
openButton.Visibility = fileTransfer.IsTransferComplete ? ViewStates.Visible : ViewStates.Gone;
_acceptButton.Visibility = !fileTransfer.IsTransferComplete && !fileTransfer.IsAccepted ? ViewStates.Visible : ViewStates.Gone;
_loadingProgressIndicator.Visibility = !fileTransfer.IsTransferComplete && fileTransfer.IsAccepted ? ViewStates.Visible : ViewStates.Gone;
_openButton.Visibility = fileTransfer.IsTransferComplete ? ViewStates.Visible : ViewStates.Gone;
}
}

Expand Down
49 changes: 37 additions & 12 deletions src/SendActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,51 @@ protected override void OnCreate(Bundle? savedInstanceState)

DeviceDiscoveryListView = _dialog.FindViewById<RecyclerView>(Resource.Id.deviceSelector)!;
DeviceDiscoveryListView.SetLayoutManager(new LinearLayoutManager(this, (int)Orientation.Horizontal, reverseLayout: false));
var adapterDescriptor = new AdapterDescriptor<CdpDevice>(
Resource.Layout.item_device,
(view, device) =>
{
view.FindViewById<ImageView>(Resource.Id.deviceTypeImageView)!.SetImageResource(
device.Type.IsMobile() ? Resource.Drawable.ic_fluent_phone_24_regular : Resource.Drawable.ic_fluent_desktop_24_regular
);
view.FindViewById<ImageView>(Resource.Id.transportTypeImageView)!.SetImageResource(GetTransportIcon(device.Endpoint.TransportType));
view.FindViewById<TextView>(Resource.Id.deviceNameTextView)!.Text = device.Name;
view.Click += (s, e) => SendData(device);
}
DeviceDiscoveryListView.SetAdapter(
RemoteSystems.CreateAdapter(Resource.Layout.item_device, view => new RemoteSystemViewHolder(view) { Click = SendData })
);
DeviceDiscoveryListView.SetAdapter(adapterDescriptor.CreateRecyclerViewAdapter(RemoteSystems));

_loggerFactory = CdpUtils.CreateLoggerFactory(this);
_logger = _loggerFactory.CreateLogger<SendActivity>();

UIHelper.RequestSendPermissions(this);
}

sealed class RemoteSystemViewHolder : ViewHolder<CdpDevice>
{
readonly ImageView _deviceType, _transportType;
readonly TextView _deviceName;
public RemoteSystemViewHolder(View view) : base(view)
{
_deviceType = view.FindViewById<ImageView>(Resource.Id.deviceTypeImageView)!;
_transportType = view.FindViewById<ImageView>(Resource.Id.transportTypeImageView)!;
_deviceName = view.FindViewById<TextView>(Resource.Id.deviceNameTextView)!;

view.Click += OnClick;
}

CdpDevice? _remoteSystem;
public override void Bind(int index, CdpDevice device)
{
_remoteSystem = device;

_deviceType.SetImageResource(
device.Type.IsMobile() ? Resource.Drawable.ic_fluent_phone_24_regular : Resource.Drawable.ic_fluent_desktop_24_regular
);
_transportType.SetImageResource(GetTransportIcon(device.Endpoint.TransportType));
_deviceName.Text = device.Name;
}

public required Action<CdpDevice> Click { get; init; }
private void OnClick(object? sender, EventArgs e)
{
if (_remoteSystem is null)
return;

Click(_remoteSystem);
}
}

public override async void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
_logger.RequestPermissionResult(requestCode, permissions, grantResults);
Expand Down
8 changes: 4 additions & 4 deletions src/Utils/UIHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ public static void OpenCredits(Activity activity)
public static void OpenGitHub(Activity activity)
=> DisplayWebSite(activity, "https://github.com/nearby-sharing/android/");

public static void DisplayWebSite(Activity activity, string url)
public static void DisplayWebSite(Context context, string url)
{
CustomTabsIntent intent = new CustomTabsIntent.Builder()
.Build();
intent.LaunchUrl(activity, AndroidUri.Parse(url) ?? throw new ArgumentException("Invalid url", nameof(url)));
intent.LaunchUrl(context, AndroidUri.Parse(url) ?? throw new ArgumentException("Invalid url", nameof(url)));
}

public static void OpenLocaleSettings(Activity activity)
Expand Down Expand Up @@ -88,10 +88,10 @@ public static void OpenLocaleSettings(Activity activity)
}
}

public static void ViewDownloads(this Activity activity)
public static void ViewDownloads(this Context context)
{
Intent intent = new(DownloadManager.ActionViewDownloads);
activity.StartActivity(intent);
context.StartActivity(intent);
}

public static void SetupToolBar(AppCompatActivity activity, string? title = null)
Expand Down

0 comments on commit a6a318b

Please sign in to comment.